alchemy 2.0.0-beta.62 → 2.0.0-beta.63
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 +12 -12
- package/bin/exec.js +1959 -70
- package/bin/exec.js.map +1 -1
- package/lib/AWS/AuthProvider.js +1 -1
- package/lib/AWS/AuthProvider.js.map +1 -1
- package/lib/AWS/EC2/hosted.d.ts.map +1 -1
- package/lib/AWS/EC2/hosted.js +2 -1
- package/lib/AWS/EC2/hosted.js.map +1 -1
- package/lib/AWS/ECS/Task.d.ts.map +1 -1
- package/lib/AWS/ECS/Task.js +2 -1
- package/lib/AWS/ECS/Task.js.map +1 -1
- package/lib/AWS/Lambda/Function.d.ts.map +1 -1
- package/lib/AWS/Lambda/Function.js +13 -40
- package/lib/AWS/Lambda/Function.js.map +1 -1
- package/lib/AWS/Lambda/MicrovmRuntimeContext.d.ts.map +1 -1
- package/lib/AWS/Lambda/MicrovmRuntimeContext.js +9 -21
- package/lib/AWS/Lambda/MicrovmRuntimeContext.js.map +1 -1
- package/lib/Action.d.ts +8 -0
- package/lib/Action.d.ts.map +1 -1
- package/lib/Action.js +16 -5
- package/lib/Action.js.map +1 -1
- package/lib/ActionRuntimeContext.d.ts +14 -0
- package/lib/ActionRuntimeContext.d.ts.map +1 -0
- package/lib/ActionRuntimeContext.js +56 -0
- package/lib/ActionRuntimeContext.js.map +1 -0
- package/lib/Apply.d.ts.map +1 -1
- package/lib/Apply.js +40 -6
- package/lib/Apply.js.map +1 -1
- package/lib/Auth/AuthProvider.d.ts +3 -3
- package/lib/Auth/AuthProvider.d.ts.map +1 -1
- package/lib/Auth/AuthProvider.js +1 -1
- package/lib/Auth/AuthProvider.js.map +1 -1
- package/lib/Auth/Lock.d.ts.map +1 -1
- package/lib/Auth/Lock.js +5 -2
- package/lib/Auth/Lock.js.map +1 -1
- package/lib/Bundle/PurePlugin.d.ts.map +1 -1
- package/lib/Bundle/PurePlugin.js +26 -1
- package/lib/Bundle/PurePlugin.js.map +1 -1
- package/lib/Bundle/Vite.d.ts +2 -0
- package/lib/Bundle/Vite.d.ts.map +1 -1
- package/lib/Bundle/Vite.js +24 -1
- package/lib/Bundle/Vite.js.map +1 -1
- package/lib/Cli/LoggingCli.d.ts.map +1 -1
- package/lib/Cli/LoggingCli.js +7 -4
- package/lib/Cli/LoggingCli.js.map +1 -1
- package/lib/Cli/commands/_shared.d.ts +19 -0
- package/lib/Cli/commands/_shared.d.ts.map +1 -1
- package/lib/Cli/commands/_shared.js +22 -0
- package/lib/Cli/commands/_shared.js.map +1 -1
- package/lib/Cli/commands/login.d.ts +7 -3
- package/lib/Cli/commands/login.d.ts.map +1 -1
- package/lib/Cli/commands/login.js +70 -12
- package/lib/Cli/commands/login.js.map +1 -1
- package/lib/Cli/commands/profile.d.ts.map +1 -1
- package/lib/Cli/commands/profile.js +7 -20
- package/lib/Cli/commands/profile.js.map +1 -1
- package/lib/Cloudflare/AI/LanguageModel.js +21 -4
- package/lib/Cloudflare/AI/LanguageModel.js.map +1 -1
- package/lib/Cloudflare/AI/QuerySearchLocal.d.ts +37 -0
- package/lib/Cloudflare/AI/QuerySearchLocal.d.ts.map +1 -0
- package/lib/Cloudflare/AI/QuerySearchLocal.js +53 -0
- package/lib/Cloudflare/AI/QuerySearchLocal.js.map +1 -0
- package/lib/Cloudflare/AI/QuerySearchNamespaceLocal.d.ts +35 -0
- package/lib/Cloudflare/AI/QuerySearchNamespaceLocal.d.ts.map +1 -0
- package/lib/Cloudflare/AI/QuerySearchNamespaceLocal.js +47 -0
- package/lib/Cloudflare/AI/QuerySearchNamespaceLocal.js.map +1 -0
- package/lib/Cloudflare/AI/SearchHttpClient.d.ts +34 -0
- package/lib/Cloudflare/AI/SearchHttpClient.d.ts.map +1 -0
- package/lib/Cloudflare/AI/SearchHttpClient.js +262 -0
- package/lib/Cloudflare/AI/SearchHttpClient.js.map +1 -0
- package/lib/Cloudflare/AI/index.d.ts +2 -0
- package/lib/Cloudflare/AI/index.d.ts.map +1 -1
- package/lib/Cloudflare/AI/index.js +2 -0
- package/lib/Cloudflare/AI/index.js.map +1 -1
- package/lib/Cloudflare/Auth/AuthProvider.js +1 -1
- package/lib/Cloudflare/Auth/AuthProvider.js.map +1 -1
- package/lib/Cloudflare/Containers/ContainerBundle.d.ts.map +1 -1
- package/lib/Cloudflare/Containers/ContainerBundle.js +11 -1
- package/lib/Cloudflare/Containers/ContainerBundle.js.map +1 -1
- package/lib/Cloudflare/Containers/ContainerPlatform.d.ts.map +1 -1
- package/lib/Cloudflare/Containers/ContainerPlatform.js +12 -33
- package/lib/Cloudflare/Containers/ContainerPlatform.js.map +1 -1
- package/lib/Cloudflare/D1/QueryDatabaseHttpClient.d.ts +24 -0
- package/lib/Cloudflare/D1/QueryDatabaseHttpClient.d.ts.map +1 -0
- package/lib/Cloudflare/D1/QueryDatabaseHttpClient.js +106 -0
- package/lib/Cloudflare/D1/QueryDatabaseHttpClient.js.map +1 -0
- package/lib/Cloudflare/D1/QueryDatabaseLocal.d.ts +38 -0
- package/lib/Cloudflare/D1/QueryDatabaseLocal.d.ts.map +1 -0
- package/lib/Cloudflare/D1/QueryDatabaseLocal.js +54 -0
- package/lib/Cloudflare/D1/QueryDatabaseLocal.js.map +1 -0
- package/lib/Cloudflare/D1/index.d.ts +1 -0
- package/lib/Cloudflare/D1/index.d.ts.map +1 -1
- package/lib/Cloudflare/D1/index.js +1 -0
- package/lib/Cloudflare/D1/index.js.map +1 -1
- package/lib/Cloudflare/DNS/DnsHttp.d.ts +16 -1
- package/lib/Cloudflare/DNS/DnsHttp.d.ts.map +1 -1
- package/lib/Cloudflare/DNS/DnsHttp.js +6 -1
- package/lib/Cloudflare/DNS/DnsHttp.js.map +1 -1
- package/lib/Cloudflare/DNS/DnsLocal.d.ts +22 -0
- package/lib/Cloudflare/DNS/DnsLocal.d.ts.map +1 -0
- package/lib/Cloudflare/DNS/DnsLocal.js +31 -0
- package/lib/Cloudflare/DNS/DnsLocal.js.map +1 -0
- package/lib/Cloudflare/DNS/ReadDnsHttp.d.ts +3 -3
- package/lib/Cloudflare/DNS/ReadDnsHttp.d.ts.map +1 -1
- package/lib/Cloudflare/DNS/ReadDnsHttp.js +3 -4
- package/lib/Cloudflare/DNS/ReadDnsHttp.js.map +1 -1
- package/lib/Cloudflare/DNS/ReadDnsLocal.d.ts +32 -0
- package/lib/Cloudflare/DNS/ReadDnsLocal.d.ts.map +1 -0
- package/lib/Cloudflare/DNS/ReadDnsLocal.js +35 -0
- package/lib/Cloudflare/DNS/ReadDnsLocal.js.map +1 -0
- package/lib/Cloudflare/DNS/ReadWriteDnsHttp.d.ts +3 -3
- package/lib/Cloudflare/DNS/ReadWriteDnsHttp.d.ts.map +1 -1
- package/lib/Cloudflare/DNS/ReadWriteDnsHttp.js +4 -4
- package/lib/Cloudflare/DNS/ReadWriteDnsHttp.js.map +1 -1
- package/lib/Cloudflare/DNS/ReadWriteDnsLocal.d.ts +40 -0
- package/lib/Cloudflare/DNS/ReadWriteDnsLocal.d.ts.map +1 -0
- package/lib/Cloudflare/DNS/ReadWriteDnsLocal.js +43 -0
- package/lib/Cloudflare/DNS/ReadWriteDnsLocal.js.map +1 -0
- package/lib/Cloudflare/DNS/WriteDnsHttp.d.ts +3 -3
- package/lib/Cloudflare/DNS/WriteDnsHttp.d.ts.map +1 -1
- package/lib/Cloudflare/DNS/WriteDnsHttp.js +3 -4
- package/lib/Cloudflare/DNS/WriteDnsHttp.js.map +1 -1
- package/lib/Cloudflare/DNS/WriteDnsLocal.d.ts +38 -0
- package/lib/Cloudflare/DNS/WriteDnsLocal.d.ts.map +1 -0
- package/lib/Cloudflare/DNS/WriteDnsLocal.js +41 -0
- package/lib/Cloudflare/DNS/WriteDnsLocal.js.map +1 -0
- package/lib/Cloudflare/DNS/index.d.ts +3 -0
- package/lib/Cloudflare/DNS/index.d.ts.map +1 -1
- package/lib/Cloudflare/DNS/index.js +3 -0
- package/lib/Cloudflare/DNS/index.js.map +1 -1
- package/lib/Cloudflare/Flagship/ReadFlagsHttpClient.d.ts +27 -0
- package/lib/Cloudflare/Flagship/ReadFlagsHttpClient.d.ts.map +1 -0
- package/lib/Cloudflare/Flagship/ReadFlagsHttpClient.js +86 -0
- package/lib/Cloudflare/Flagship/ReadFlagsHttpClient.js.map +1 -0
- package/lib/Cloudflare/Flagship/ReadFlagsLocal.d.ts +41 -0
- package/lib/Cloudflare/Flagship/ReadFlagsLocal.d.ts.map +1 -0
- package/lib/Cloudflare/Flagship/ReadFlagsLocal.js +57 -0
- package/lib/Cloudflare/Flagship/ReadFlagsLocal.js.map +1 -0
- package/lib/Cloudflare/Flagship/index.d.ts +1 -0
- package/lib/Cloudflare/Flagship/index.d.ts.map +1 -1
- package/lib/Cloudflare/Flagship/index.js +1 -0
- package/lib/Cloudflare/Flagship/index.js.map +1 -1
- package/lib/Cloudflare/KV/NamespaceHttp.d.ts +16 -1
- package/lib/Cloudflare/KV/NamespaceHttp.d.ts.map +1 -1
- package/lib/Cloudflare/KV/NamespaceHttp.js +8 -2
- package/lib/Cloudflare/KV/NamespaceHttp.js.map +1 -1
- package/lib/Cloudflare/KV/NamespaceLocal.d.ts +22 -0
- package/lib/Cloudflare/KV/NamespaceLocal.d.ts.map +1 -0
- package/lib/Cloudflare/KV/NamespaceLocal.js +32 -0
- package/lib/Cloudflare/KV/NamespaceLocal.js.map +1 -0
- package/lib/Cloudflare/KV/ReadNamespaceHttp.d.ts +2 -2
- package/lib/Cloudflare/KV/ReadNamespaceHttp.d.ts.map +1 -1
- package/lib/Cloudflare/KV/ReadNamespaceHttp.js +5 -6
- package/lib/Cloudflare/KV/ReadNamespaceHttp.js.map +1 -1
- package/lib/Cloudflare/KV/ReadNamespaceLocal.d.ts +27 -0
- package/lib/Cloudflare/KV/ReadNamespaceLocal.d.ts.map +1 -0
- package/lib/Cloudflare/KV/ReadNamespaceLocal.js +30 -0
- package/lib/Cloudflare/KV/ReadNamespaceLocal.js.map +1 -0
- package/lib/Cloudflare/KV/ReadWriteNamespaceHttp.d.ts +3 -3
- package/lib/Cloudflare/KV/ReadWriteNamespaceHttp.d.ts.map +1 -1
- package/lib/Cloudflare/KV/ReadWriteNamespaceHttp.js +6 -6
- package/lib/Cloudflare/KV/ReadWriteNamespaceHttp.js.map +1 -1
- package/lib/Cloudflare/KV/ReadWriteNamespaceLocal.d.ts +34 -0
- package/lib/Cloudflare/KV/ReadWriteNamespaceLocal.d.ts.map +1 -0
- package/lib/Cloudflare/KV/ReadWriteNamespaceLocal.js +37 -0
- package/lib/Cloudflare/KV/ReadWriteNamespaceLocal.js.map +1 -0
- package/lib/Cloudflare/KV/WriteNamespaceHttp.d.ts +2 -2
- package/lib/Cloudflare/KV/WriteNamespaceHttp.d.ts.map +1 -1
- package/lib/Cloudflare/KV/WriteNamespaceHttp.js +5 -6
- package/lib/Cloudflare/KV/WriteNamespaceHttp.js.map +1 -1
- package/lib/Cloudflare/KV/WriteNamespaceLocal.d.ts +27 -0
- package/lib/Cloudflare/KV/WriteNamespaceLocal.d.ts.map +1 -0
- package/lib/Cloudflare/KV/WriteNamespaceLocal.js +30 -0
- package/lib/Cloudflare/KV/WriteNamespaceLocal.js.map +1 -0
- package/lib/Cloudflare/KV/index.d.ts +3 -0
- package/lib/Cloudflare/KV/index.d.ts.map +1 -1
- package/lib/Cloudflare/KV/index.js +3 -0
- package/lib/Cloudflare/KV/index.js.map +1 -1
- package/lib/Cloudflare/Queues/QueueHttp.d.ts +20 -1
- package/lib/Cloudflare/Queues/QueueHttp.d.ts.map +1 -1
- package/lib/Cloudflare/Queues/QueueHttp.js +2 -2
- package/lib/Cloudflare/Queues/QueueHttp.js.map +1 -1
- package/lib/Cloudflare/Queues/WriteQueueHttp.d.ts +9 -3
- package/lib/Cloudflare/Queues/WriteQueueHttp.d.ts.map +1 -1
- package/lib/Cloudflare/Queues/WriteQueueHttp.js +11 -6
- package/lib/Cloudflare/Queues/WriteQueueHttp.js.map +1 -1
- package/lib/Cloudflare/Queues/WriteQueueLocal.d.ts +36 -0
- package/lib/Cloudflare/Queues/WriteQueueLocal.d.ts.map +1 -0
- package/lib/Cloudflare/Queues/WriteQueueLocal.js +51 -0
- package/lib/Cloudflare/Queues/WriteQueueLocal.js.map +1 -0
- package/lib/Cloudflare/Queues/index.d.ts +1 -0
- package/lib/Cloudflare/Queues/index.d.ts.map +1 -1
- package/lib/Cloudflare/Queues/index.js +1 -0
- package/lib/Cloudflare/Queues/index.js.map +1 -1
- package/lib/Cloudflare/R2/BucketHttp.d.ts +15 -1
- package/lib/Cloudflare/R2/BucketHttp.d.ts.map +1 -1
- package/lib/Cloudflare/R2/BucketHttp.js +3 -3
- package/lib/Cloudflare/R2/BucketHttp.js.map +1 -1
- package/lib/Cloudflare/R2/BucketLocal.d.ts +21 -0
- package/lib/Cloudflare/R2/BucketLocal.d.ts.map +1 -0
- package/lib/Cloudflare/R2/BucketLocal.js +32 -0
- package/lib/Cloudflare/R2/BucketLocal.js.map +1 -0
- package/lib/Cloudflare/R2/ReadBucketHttp.d.ts +2 -2
- package/lib/Cloudflare/R2/ReadBucketHttp.d.ts.map +1 -1
- package/lib/Cloudflare/R2/ReadBucketHttp.js +4 -4
- package/lib/Cloudflare/R2/ReadBucketHttp.js.map +1 -1
- package/lib/Cloudflare/R2/ReadBucketLocal.d.ts +27 -0
- package/lib/Cloudflare/R2/ReadBucketLocal.d.ts.map +1 -0
- package/lib/Cloudflare/R2/ReadBucketLocal.js +30 -0
- package/lib/Cloudflare/R2/ReadBucketLocal.js.map +1 -0
- package/lib/Cloudflare/R2/ReadWriteBucketHttp.d.ts +2 -2
- package/lib/Cloudflare/R2/ReadWriteBucketHttp.d.ts.map +1 -1
- package/lib/Cloudflare/R2/ReadWriteBucketHttp.js +5 -4
- package/lib/Cloudflare/R2/ReadWriteBucketHttp.js.map +1 -1
- package/lib/Cloudflare/R2/ReadWriteBucketLocal.d.ts +30 -0
- package/lib/Cloudflare/R2/ReadWriteBucketLocal.d.ts.map +1 -0
- package/lib/Cloudflare/R2/ReadWriteBucketLocal.js +33 -0
- package/lib/Cloudflare/R2/ReadWriteBucketLocal.js.map +1 -0
- package/lib/Cloudflare/R2/WriteBucketHttp.d.ts +2 -2
- package/lib/Cloudflare/R2/WriteBucketHttp.d.ts.map +1 -1
- package/lib/Cloudflare/R2/WriteBucketHttp.js +4 -4
- package/lib/Cloudflare/R2/WriteBucketHttp.js.map +1 -1
- package/lib/Cloudflare/R2/WriteBucketLocal.d.ts +27 -0
- package/lib/Cloudflare/R2/WriteBucketLocal.d.ts.map +1 -0
- package/lib/Cloudflare/R2/WriteBucketLocal.js +30 -0
- package/lib/Cloudflare/R2/WriteBucketLocal.js.map +1 -0
- package/lib/Cloudflare/R2/index.d.ts +3 -0
- package/lib/Cloudflare/R2/index.d.ts.map +1 -1
- package/lib/Cloudflare/R2/index.js +3 -0
- package/lib/Cloudflare/R2/index.js.map +1 -1
- package/lib/Cloudflare/StateStore/Api.d.ts +2 -0
- package/lib/Cloudflare/Tunnel/ReadTunnel.d.ts +3 -3
- package/lib/Cloudflare/Tunnel/ReadTunnel.d.ts.map +1 -1
- package/lib/Cloudflare/Tunnel/ReadTunnel.js +7 -8
- package/lib/Cloudflare/Tunnel/ReadTunnel.js.map +1 -1
- package/lib/Cloudflare/Tunnel/ReadTunnelLocal.d.ts +29 -0
- package/lib/Cloudflare/Tunnel/ReadTunnelLocal.d.ts.map +1 -0
- package/lib/Cloudflare/Tunnel/ReadTunnelLocal.js +30 -0
- package/lib/Cloudflare/Tunnel/ReadTunnelLocal.js.map +1 -0
- package/lib/Cloudflare/Tunnel/ReadWriteTunnel.d.ts +3 -3
- package/lib/Cloudflare/Tunnel/ReadWriteTunnel.d.ts.map +1 -1
- package/lib/Cloudflare/Tunnel/ReadWriteTunnel.js +4 -4
- package/lib/Cloudflare/Tunnel/ReadWriteTunnel.js.map +1 -1
- package/lib/Cloudflare/Tunnel/ReadWriteTunnelLocal.d.ts +35 -0
- package/lib/Cloudflare/Tunnel/ReadWriteTunnelLocal.d.ts.map +1 -0
- package/lib/Cloudflare/Tunnel/ReadWriteTunnelLocal.js +36 -0
- package/lib/Cloudflare/Tunnel/ReadWriteTunnelLocal.js.map +1 -0
- package/lib/Cloudflare/Tunnel/TunnelBinding.d.ts +17 -1
- package/lib/Cloudflare/Tunnel/TunnelBinding.d.ts.map +1 -1
- package/lib/Cloudflare/Tunnel/TunnelBinding.js +7 -1
- package/lib/Cloudflare/Tunnel/TunnelBinding.js.map +1 -1
- package/lib/Cloudflare/Tunnel/TunnelLocalBinding.d.ts +19 -0
- package/lib/Cloudflare/Tunnel/TunnelLocalBinding.d.ts.map +1 -0
- package/lib/Cloudflare/Tunnel/TunnelLocalBinding.js +29 -0
- package/lib/Cloudflare/Tunnel/TunnelLocalBinding.js.map +1 -0
- package/lib/Cloudflare/Tunnel/WarpConnector.d.ts +3 -3
- package/lib/Cloudflare/Tunnel/WarpConnector.d.ts.map +1 -1
- package/lib/Cloudflare/Tunnel/WarpConnector.js +7 -4
- package/lib/Cloudflare/Tunnel/WarpConnector.js.map +1 -1
- package/lib/Cloudflare/Tunnel/WriteTunnel.d.ts +3 -3
- package/lib/Cloudflare/Tunnel/WriteTunnel.d.ts.map +1 -1
- package/lib/Cloudflare/Tunnel/WriteTunnel.js +7 -8
- package/lib/Cloudflare/Tunnel/WriteTunnel.js.map +1 -1
- package/lib/Cloudflare/Tunnel/WriteTunnelLocal.d.ts +29 -0
- package/lib/Cloudflare/Tunnel/WriteTunnelLocal.d.ts.map +1 -0
- package/lib/Cloudflare/Tunnel/WriteTunnelLocal.js +30 -0
- package/lib/Cloudflare/Tunnel/WriteTunnelLocal.js.map +1 -0
- package/lib/Cloudflare/Tunnel/index.d.ts +3 -0
- package/lib/Cloudflare/Tunnel/index.d.ts.map +1 -1
- package/lib/Cloudflare/Tunnel/index.js +3 -0
- package/lib/Cloudflare/Tunnel/index.js.map +1 -1
- package/lib/Cloudflare/Vectorize/SearchIndexHttpClient.d.ts +30 -0
- package/lib/Cloudflare/Vectorize/SearchIndexHttpClient.d.ts.map +1 -0
- package/lib/Cloudflare/Vectorize/SearchIndexHttpClient.js +100 -0
- package/lib/Cloudflare/Vectorize/SearchIndexHttpClient.js.map +1 -0
- package/lib/Cloudflare/Vectorize/SearchIndexLocal.d.ts +42 -0
- package/lib/Cloudflare/Vectorize/SearchIndexLocal.d.ts.map +1 -0
- package/lib/Cloudflare/Vectorize/SearchIndexLocal.js +58 -0
- package/lib/Cloudflare/Vectorize/SearchIndexLocal.js.map +1 -0
- package/lib/Cloudflare/Vectorize/index.d.ts +1 -0
- package/lib/Cloudflare/Vectorize/index.d.ts.map +1 -1
- package/lib/Cloudflare/Vectorize/index.js +1 -0
- package/lib/Cloudflare/Vectorize/index.js.map +1 -1
- package/lib/Cloudflare/Workers/BrowserHttpClient.d.ts +32 -0
- package/lib/Cloudflare/Workers/BrowserHttpClient.d.ts.map +1 -0
- package/lib/Cloudflare/Workers/BrowserHttpClient.js +100 -0
- package/lib/Cloudflare/Workers/BrowserHttpClient.js.map +1 -0
- package/lib/Cloudflare/Workers/BrowserLocal.d.ts +37 -0
- package/lib/Cloudflare/Workers/BrowserLocal.d.ts.map +1 -0
- package/lib/Cloudflare/Workers/BrowserLocal.js +53 -0
- package/lib/Cloudflare/Workers/BrowserLocal.js.map +1 -0
- package/lib/Cloudflare/Workers/ConfigProvider.d.ts +8 -0
- package/lib/Cloudflare/Workers/ConfigProvider.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/ConfigProvider.js +10 -1
- package/lib/Cloudflare/Workers/ConfigProvider.js.map +1 -1
- package/lib/Cloudflare/Workers/DurableObject.d.ts +137 -3
- package/lib/Cloudflare/Workers/DurableObject.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/DurableObject.js +141 -6
- package/lib/Cloudflare/Workers/DurableObject.js.map +1 -1
- package/lib/Cloudflare/Workers/LocalWorkerProvider.d.ts +2 -185
- package/lib/Cloudflare/Workers/LocalWorkerProvider.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/Vite.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/Vite.js +81 -2
- package/lib/Cloudflare/Workers/Vite.js.map +1 -1
- package/lib/Cloudflare/Workers/Worker.d.ts +15 -1
- package/lib/Cloudflare/Workers/Worker.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/Worker.js.map +1 -1
- package/lib/Cloudflare/Workers/WorkerAsyncBindings.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/WorkerAsyncBindings.js +2 -1
- package/lib/Cloudflare/Workers/WorkerAsyncBindings.js.map +1 -1
- package/lib/Cloudflare/Workers/WorkerBinding.d.ts +22 -1
- package/lib/Cloudflare/Workers/WorkerBinding.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/WorkerBinding.js.map +1 -1
- package/lib/Cloudflare/Workers/WorkerBridge.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/WorkerBridge.js +7 -2
- package/lib/Cloudflare/Workers/WorkerBridge.js.map +1 -1
- package/lib/Cloudflare/Workers/WorkerProvider.d.ts +81 -0
- package/lib/Cloudflare/Workers/WorkerProvider.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/WorkerProvider.js +450 -61
- package/lib/Cloudflare/Workers/WorkerProvider.js.map +1 -1
- package/lib/Cloudflare/Workers/WorkerRuntimeContext.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/WorkerRuntimeContext.js +11 -37
- package/lib/Cloudflare/Workers/WorkerRuntimeContext.js.map +1 -1
- package/lib/Cloudflare/Workers/index.d.ts +1 -0
- package/lib/Cloudflare/Workers/index.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/index.js +1 -0
- package/lib/Cloudflare/Workers/index.js.map +1 -1
- package/lib/Diff.d.ts +16 -0
- package/lib/Diff.d.ts.map +1 -1
- package/lib/Diff.js +33 -0
- package/lib/Diff.js.map +1 -1
- package/lib/GitHub/Secrets.d.ts +1 -1
- package/lib/GitHub/Secrets.js +1 -1
- package/lib/GitHub/Variables.d.ts +1 -1
- package/lib/GitHub/Variables.js +1 -1
- package/lib/Local/RpcSerialization.d.ts.map +1 -1
- package/lib/Local/RpcSerialization.js +6 -2
- package/lib/Local/RpcSerialization.js.map +1 -1
- package/lib/Local/RpcSpawner.d.ts.map +1 -1
- package/lib/Local/RpcSpawner.js +14 -8
- package/lib/Local/RpcSpawner.js.map +1 -1
- package/lib/Neon/AuthProvider.js +1 -1
- package/lib/Neon/AuthProvider.js.map +1 -1
- package/lib/Plan.d.ts.map +1 -1
- package/lib/Plan.js +11 -4
- package/lib/Plan.js.map +1 -1
- package/lib/Planetscale/Database.d.ts +2 -1
- package/lib/Planetscale/Database.d.ts.map +1 -1
- package/lib/Planetscale/MySQL/MySQLClusterSize.d.ts +41 -3
- package/lib/Planetscale/MySQL/MySQLClusterSize.d.ts.map +1 -1
- package/lib/Planetscale/MySQL/MySQLClusterSize.js +88 -11
- package/lib/Planetscale/MySQL/MySQLClusterSize.js.map +1 -1
- package/lib/Planetscale/MySQL/MySQLDatabase.d.ts +14 -0
- package/lib/Planetscale/MySQL/MySQLDatabase.d.ts.map +1 -1
- package/lib/Planetscale/MySQL/MySQLDatabase.js +24 -11
- package/lib/Planetscale/MySQL/MySQLDatabase.js.map +1 -1
- package/lib/Platform.d.ts.map +1 -1
- package/lib/Platform.js +11 -1
- package/lib/Platform.js.map +1 -1
- package/lib/Runtime.d.ts +23 -0
- package/lib/Runtime.d.ts.map +1 -1
- package/lib/Runtime.js +83 -0
- package/lib/Runtime.js.map +1 -1
- package/lib/RuntimeContext.d.ts +46 -0
- package/lib/RuntimeContext.d.ts.map +1 -1
- package/lib/RuntimeContext.js +60 -0
- package/lib/RuntimeContext.js.map +1 -1
- package/lib/Server/Process.d.ts +1 -1
- package/lib/Server/Process.d.ts.map +1 -1
- package/lib/Server/Process.js +8 -8
- package/lib/Server/Process.js.map +1 -1
- package/lib/Stack.js +1 -1
- package/lib/Stack.js.map +1 -1
- package/lib/State/HttpStateApi.d.ts +10 -10
- package/lib/Test/Alchemy.d.ts +74 -0
- package/lib/Test/Alchemy.d.ts.map +1 -0
- package/lib/Test/Alchemy.js +143 -0
- package/lib/Test/Alchemy.js.map +1 -0
- package/lib/Util/cached-function.d.ts +1 -1
- package/lib/Util/cached-function.d.ts.map +1 -1
- package/lib/Util/cached-function.js +2 -2
- package/lib/Util/cached-function.js.map +1 -1
- package/lib/Util/proxy-chain.js +3 -0
- package/lib/Util/proxy-chain.js.map +1 -1
- package/package.json +10 -10
- package/src/AWS/AuthProvider.ts +1 -1
- package/src/AWS/EC2/hosted.ts +2 -1
- package/src/AWS/ECS/Task.ts +2 -1
- package/src/AWS/Lambda/Function.ts +13 -47
- package/src/AWS/Lambda/MicrovmRuntimeContext.ts +11 -38
- package/src/Action.ts +39 -6
- package/src/ActionRuntimeContext.ts +68 -0
- package/src/Apply.ts +46 -8
- package/src/Auth/AuthProvider.ts +1 -1
- package/src/Auth/Lock.ts +5 -2
- package/src/Bundle/PurePlugin.ts +34 -1
- package/src/Bundle/Vite.ts +51 -1
- package/src/Cli/LoggingCli.ts +7 -4
- package/src/Cli/commands/_shared.ts +52 -0
- package/src/Cli/commands/login.ts +90 -14
- package/src/Cli/commands/profile.ts +7 -41
- package/src/Cloudflare/AI/LanguageModel.ts +25 -5
- package/src/Cloudflare/AI/QuerySearchLocal.ts +66 -0
- package/src/Cloudflare/AI/QuerySearchNamespaceLocal.ts +60 -0
- package/src/Cloudflare/AI/SearchHttpClient.ts +423 -0
- package/src/Cloudflare/AI/index.ts +2 -0
- package/src/Cloudflare/Auth/AuthProvider.ts +1 -1
- package/src/Cloudflare/Containers/ContainerBundle.ts +11 -1
- package/src/Cloudflare/Containers/ContainerPlatform.ts +14 -52
- package/src/Cloudflare/D1/QueryDatabaseHttpClient.ts +176 -0
- package/src/Cloudflare/D1/QueryDatabaseLocal.ts +66 -0
- package/src/Cloudflare/D1/index.ts +1 -0
- package/src/Cloudflare/DNS/DnsHttp.ts +24 -2
- package/src/Cloudflare/DNS/DnsLocal.ts +41 -0
- package/src/Cloudflare/DNS/ReadDnsHttp.ts +4 -5
- package/src/Cloudflare/DNS/ReadDnsLocal.ts +38 -0
- package/src/Cloudflare/DNS/ReadWriteDnsHttp.ts +5 -5
- package/src/Cloudflare/DNS/ReadWriteDnsLocal.ts +46 -0
- package/src/Cloudflare/DNS/WriteDnsHttp.ts +4 -5
- package/src/Cloudflare/DNS/WriteDnsLocal.ts +44 -0
- package/src/Cloudflare/DNS/index.ts +3 -0
- package/src/Cloudflare/Flagship/ReadFlagsHttpClient.ts +183 -0
- package/src/Cloudflare/Flagship/ReadFlagsLocal.ts +69 -0
- package/src/Cloudflare/Flagship/index.ts +1 -0
- package/src/Cloudflare/KV/NamespaceHttp.ts +25 -2
- package/src/Cloudflare/KV/NamespaceLocal.ts +42 -0
- package/src/Cloudflare/KV/ReadNamespaceHttp.ts +7 -6
- package/src/Cloudflare/KV/ReadNamespaceLocal.ts +35 -0
- package/src/Cloudflare/KV/ReadWriteNamespaceHttp.ts +11 -6
- package/src/Cloudflare/KV/ReadWriteNamespaceLocal.ts +42 -0
- package/src/Cloudflare/KV/WriteNamespaceHttp.ts +7 -6
- package/src/Cloudflare/KV/WriteNamespaceLocal.ts +35 -0
- package/src/Cloudflare/KV/index.ts +3 -0
- package/src/Cloudflare/Queues/QueueHttp.ts +24 -2
- package/src/Cloudflare/Queues/WriteQueueHttp.ts +16 -7
- package/src/Cloudflare/Queues/WriteQueueLocal.ts +64 -0
- package/src/Cloudflare/Queues/index.ts +1 -0
- package/src/Cloudflare/R2/BucketHttp.ts +20 -3
- package/src/Cloudflare/R2/BucketLocal.ts +47 -0
- package/src/Cloudflare/R2/ReadBucketHttp.ts +10 -5
- package/src/Cloudflare/R2/ReadBucketLocal.ts +33 -0
- package/src/Cloudflare/R2/ReadWriteBucketHttp.ts +11 -5
- package/src/Cloudflare/R2/ReadWriteBucketLocal.ts +36 -0
- package/src/Cloudflare/R2/WriteBucketHttp.ts +10 -5
- package/src/Cloudflare/R2/WriteBucketLocal.ts +33 -0
- package/src/Cloudflare/R2/index.ts +3 -0
- package/src/Cloudflare/Tunnel/ReadTunnel.ts +8 -9
- package/src/Cloudflare/Tunnel/ReadTunnelLocal.ts +33 -0
- package/src/Cloudflare/Tunnel/ReadWriteTunnel.ts +5 -5
- package/src/Cloudflare/Tunnel/ReadWriteTunnelLocal.ts +39 -0
- package/src/Cloudflare/Tunnel/TunnelBinding.ts +26 -2
- package/src/Cloudflare/Tunnel/TunnelLocalBinding.ts +38 -0
- package/src/Cloudflare/Tunnel/WarpConnector.ts +8 -5
- package/src/Cloudflare/Tunnel/WriteTunnel.ts +8 -9
- package/src/Cloudflare/Tunnel/WriteTunnelLocal.ts +33 -0
- package/src/Cloudflare/Tunnel/index.ts +3 -0
- package/src/Cloudflare/Vectorize/SearchIndexHttpClient.ts +183 -0
- package/src/Cloudflare/Vectorize/SearchIndexLocal.ts +70 -0
- package/src/Cloudflare/Vectorize/index.ts +1 -0
- package/src/Cloudflare/Workers/BrowserHttpClient.ts +201 -0
- package/src/Cloudflare/Workers/BrowserLocal.ts +65 -0
- package/src/Cloudflare/Workers/ConfigProvider.ts +15 -1
- package/src/Cloudflare/Workers/DurableObject.ts +227 -6
- package/src/Cloudflare/Workers/Vite.ts +93 -11
- package/src/Cloudflare/Workers/Worker.ts +19 -1
- package/src/Cloudflare/Workers/WorkerAsyncBindings.ts +6 -5
- package/src/Cloudflare/Workers/WorkerBinding.ts +20 -1
- package/src/Cloudflare/Workers/WorkerBridge.ts +12 -2
- package/src/Cloudflare/Workers/WorkerProvider.ts +664 -117
- package/src/Cloudflare/Workers/WorkerRuntimeContext.ts +14 -45
- package/src/Cloudflare/Workers/index.ts +1 -0
- package/src/Diff.ts +33 -0
- package/src/GitHub/Secrets.ts +1 -1
- package/src/GitHub/Variables.ts +1 -1
- package/src/Local/RpcSerialization.ts +6 -2
- package/src/Local/RpcSpawner.ts +21 -8
- package/src/Neon/AuthProvider.ts +1 -1
- package/src/Plan.ts +17 -4
- package/src/Planetscale/Database.ts +2 -1
- package/src/Planetscale/MySQL/MySQLClusterSize.ts +120 -21
- package/src/Planetscale/MySQL/MySQLDatabase.ts +84 -44
- package/src/Platform.ts +12 -3
- package/src/Runtime.ts +92 -0
- package/src/RuntimeContext.ts +79 -0
- package/src/Server/Process.ts +11 -18
- package/src/Stack.ts +1 -1
- package/src/Test/Alchemy.ts +282 -0
- package/src/Util/cached-function.ts +5 -5
- package/src/Util/proxy-chain.ts +3 -0
package/bin/exec.js
CHANGED
|
@@ -8,10 +8,14 @@ import * as FileSystem from "effect/FileSystem";
|
|
|
8
8
|
import * as Path$1 from "effect/Path";
|
|
9
9
|
import { Path } from "effect/Path";
|
|
10
10
|
import * as Redacted from "effect/Redacted";
|
|
11
|
+
import * as path$1 from "pathe";
|
|
11
12
|
import path from "pathe";
|
|
12
13
|
import * as Config from "effect/Config";
|
|
14
|
+
import * as NodeOs from "node:os";
|
|
13
15
|
import os from "node:os";
|
|
14
16
|
import * as Semaphore from "effect/Semaphore";
|
|
17
|
+
import { lock } from "@alchemy.run/node-utils/lockfile";
|
|
18
|
+
import * as fs from "node:fs/promises";
|
|
15
19
|
import { newWebSocketRpcSession } from "capnweb";
|
|
16
20
|
import * as Cache from "effect/Cache";
|
|
17
21
|
import * as HttpBody from "effect/unstable/http/HttpBody";
|
|
@@ -27,6 +31,7 @@ import { SingleShotGen } from "effect/Utils";
|
|
|
27
31
|
import * as Effectable from "effect/Effectable";
|
|
28
32
|
import "effect/Predicate";
|
|
29
33
|
import "effect/Record";
|
|
34
|
+
import http from "node:http";
|
|
30
35
|
import * as Cause from "effect/Cause";
|
|
31
36
|
import * as Exit from "effect/Exit";
|
|
32
37
|
import * as Stream from "effect/Stream";
|
|
@@ -38,6 +43,22 @@ import * as Metric from "effect/Metric";
|
|
|
38
43
|
import * as Argument from "effect/unstable/cli/Argument";
|
|
39
44
|
import * as CliError from "effect/unstable/cli/CliError";
|
|
40
45
|
import { pathToFileURL } from "node:url";
|
|
46
|
+
import * as DistilledAuth from "@distilled.cloud/aws/Auth";
|
|
47
|
+
import { Credentials } from "@distilled.cloud/aws/Credentials";
|
|
48
|
+
import * as STS from "@distilled.cloud/aws/sts";
|
|
49
|
+
import * as Match from "effect/Match";
|
|
50
|
+
import { ChildProcess } from "effect/unstable/process";
|
|
51
|
+
import * as NodeCrypto from "node:crypto";
|
|
52
|
+
import crypto$1 from "node:crypto";
|
|
53
|
+
import * as p from "@clack/prompts";
|
|
54
|
+
import * as Region from "@distilled.cloud/aws/Region";
|
|
55
|
+
import { DEFAULT_API_BASE_URL } from "@distilled.cloud/axiom/Credentials";
|
|
56
|
+
import * as cfAccounts from "@distilled.cloud/cloudflare/accounts";
|
|
57
|
+
import * as CfCredentialsModule from "@distilled.cloud/cloudflare/Credentials";
|
|
58
|
+
import * as ChildProcess$1 from "effect/unstable/process/ChildProcess";
|
|
59
|
+
import { ChildProcessSpawner } from "effect/unstable/process/ChildProcessSpawner";
|
|
60
|
+
import * as PsCredentialsModule from "@distilled.cloud/planetscale/Credentials";
|
|
61
|
+
import { listOrganizations } from "@distilled.cloud/planetscale/Operations";
|
|
41
62
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
42
63
|
import { Box, Text, render, useApp, useInput } from "ink";
|
|
43
64
|
import { useEffect, useMemo, useRef, useState } from "react";
|
|
@@ -63,9 +84,9 @@ var AlchemyContext, AlchemyContextLive;
|
|
|
63
84
|
var init_AlchemyContext = __esm({ "lib/AlchemyContext.js": (() => {
|
|
64
85
|
AlchemyContext = class extends EffectContext.Service()("alchemy/Context") {};
|
|
65
86
|
AlchemyContextLive = Layer.effect(AlchemyContext, Effect.gen(function* () {
|
|
66
|
-
const fs = yield* FileSystem.FileSystem;
|
|
87
|
+
const fs$1 = yield* FileSystem.FileSystem;
|
|
67
88
|
const dir = (yield* Path$1.Path).join(process.cwd(), ".alchemy");
|
|
68
|
-
yield* fs.makeDirectory(dir, { recursive: true });
|
|
89
|
+
yield* fs$1.makeDirectory(dir, { recursive: true });
|
|
69
90
|
return {
|
|
70
91
|
dotAlchemy: dir,
|
|
71
92
|
updateStateStore: false,
|
|
@@ -90,19 +111,90 @@ var init_interactive = __esm({ "lib/Util/interactive.js": (() => {
|
|
|
90
111
|
};
|
|
91
112
|
}) });
|
|
92
113
|
|
|
114
|
+
//#endregion
|
|
115
|
+
//#region lib/Auth/Lock.js
|
|
116
|
+
var sanitizeLockKey, UNLOCKABLE_FS_CODES, withLock;
|
|
117
|
+
var init_Lock = __esm({ "lib/Auth/Lock.js": (() => {
|
|
118
|
+
init_Profile();
|
|
119
|
+
sanitizeLockKey = (key) => key.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
120
|
+
UNLOCKABLE_FS_CODES = new Set([
|
|
121
|
+
"EROFS",
|
|
122
|
+
"EACCES",
|
|
123
|
+
"EPERM",
|
|
124
|
+
"ENOSPC"
|
|
125
|
+
]);
|
|
126
|
+
withLock = (key, effect) => {
|
|
127
|
+
const lockDir = path$1.join(rootDir, "lock");
|
|
128
|
+
const lockPath = path$1.join(lockDir, `${sanitizeLockKey(key)}.lock`);
|
|
129
|
+
return Effect.acquireUseRelease(Effect.promise(async () => {
|
|
130
|
+
try {
|
|
131
|
+
await fs.mkdir(lockDir, { recursive: true });
|
|
132
|
+
return await lock(lockPath, {
|
|
133
|
+
retries: {
|
|
134
|
+
retries: 2400,
|
|
135
|
+
minTimeout: 50,
|
|
136
|
+
maxTimeout: 50
|
|
137
|
+
},
|
|
138
|
+
stale: 3e4,
|
|
139
|
+
realpath: false,
|
|
140
|
+
onCompromised: (err) => {
|
|
141
|
+
console.warn(`auth lock compromised (continuing): ${err.message}`);
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
} catch (err) {
|
|
145
|
+
const code = err.code;
|
|
146
|
+
if (code !== void 0 && UNLOCKABLE_FS_CODES.has(code)) {
|
|
147
|
+
console.warn(`auth lock unavailable (${code} at '${lockPath}') — continuing without cross-process locking`);
|
|
148
|
+
return async () => {};
|
|
149
|
+
}
|
|
150
|
+
if (err instanceof Error && err.message.includes("already being held")) throw new Error(`Timed out waiting for the alchemy auth lock '${lockPath}' — another alchemy process has held it for over 120s. If no other alchemy process is running, delete the lock file and retry.`, { cause: err });
|
|
151
|
+
throw err;
|
|
152
|
+
}
|
|
153
|
+
}), () => effect, (release) => Effect.promise(() => release().catch(() => {})));
|
|
154
|
+
};
|
|
155
|
+
}) });
|
|
156
|
+
|
|
93
157
|
//#endregion
|
|
94
158
|
//#region lib/Auth/AuthProvider.js
|
|
95
|
-
var AUTH_LANDING_HOST, AUTH_SUCCESS_URL, AUTH_ERROR_URL, interactiveMutex, AuthError, AuthProviders;
|
|
159
|
+
var AUTH_LANDING_HOST, AUTH_SUCCESS_URL, AUTH_ERROR_URL, LOCKED_METHODS, INTERACTIVE_METHODS, interactiveMutex, AuthError, AuthProviders, AuthProvider, AuthProviderLayer, getAuthProvider;
|
|
96
160
|
var init_AuthProvider = __esm({ "lib/Auth/AuthProvider.js": (() => {
|
|
97
|
-
|
|
161
|
+
init_Lock();
|
|
162
|
+
AUTH_LANDING_HOST = "https://alchemy.run";
|
|
98
163
|
AUTH_SUCCESS_URL = `${AUTH_LANDING_HOST}/auth/success`;
|
|
99
164
|
AUTH_ERROR_URL = `${AUTH_LANDING_HOST}/auth/error`;
|
|
165
|
+
LOCKED_METHODS = new Set([
|
|
166
|
+
"read",
|
|
167
|
+
"login",
|
|
168
|
+
"logout",
|
|
169
|
+
"configure"
|
|
170
|
+
]);
|
|
171
|
+
INTERACTIVE_METHODS = new Set(["login", "configure"]);
|
|
100
172
|
interactiveMutex = Semaphore.makeUnsafe(1);
|
|
101
173
|
AuthError = class extends Schema.TaggedErrorClass()("AuthError", {
|
|
102
174
|
message: Schema.String,
|
|
103
175
|
cause: Schema.optional(Schema.Defect())
|
|
104
176
|
}) {};
|
|
105
177
|
AuthProviders = class extends EffectContext.Service()("AuthProviders") {};
|
|
178
|
+
AuthProvider = () => (name, impl) => Effect.gen(function* () {
|
|
179
|
+
const ctx = yield* Effect.context();
|
|
180
|
+
const providers = yield* AuthProviders;
|
|
181
|
+
const service = yield* Effect.isEffect(impl) ? impl : Effect.succeed(impl);
|
|
182
|
+
return yield* Effect.sync(() => providers[name] = {
|
|
183
|
+
kind: "AuthProvider",
|
|
184
|
+
name,
|
|
185
|
+
...Object.fromEntries(Object.entries(service).map(([methodName, fn]) => [methodName, (...args) => {
|
|
186
|
+
let eff = fn(...args).pipe(Effect.provideContext(ctx));
|
|
187
|
+
if (LOCKED_METHODS.has(methodName)) {
|
|
188
|
+
const profileName = args[0];
|
|
189
|
+
eff = withLock(`${profileName}-${name}`, eff);
|
|
190
|
+
}
|
|
191
|
+
if (INTERACTIVE_METHODS.has(methodName)) eff = Semaphore.withPermits(interactiveMutex, 1)(eff);
|
|
192
|
+
return eff;
|
|
193
|
+
}]))
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
AuthProviderLayer = () => (name, impl) => Layer.effectDiscard(AuthProvider()(name, impl));
|
|
197
|
+
getAuthProvider = (name) => AuthProviders.use((registry) => registry[name] == null ? Effect.fail(new AuthError({ message: `AuthProvider '${name}' is not registered. Make sure its layer has been provided.` })) : Effect.succeed(registry[name]));
|
|
106
198
|
}) });
|
|
107
199
|
|
|
108
200
|
//#endregion
|
|
@@ -121,8 +213,8 @@ var init_Profile = __esm({ "lib/Auth/Profile.js": (() => {
|
|
|
121
213
|
});
|
|
122
214
|
AlchemyProfile = class extends EffectContext.Service()("Alchemy::Profile") {};
|
|
123
215
|
ProfileLive = Layer.effect(AlchemyProfile, Effect.gen(function* () {
|
|
124
|
-
const fs = yield* FileSystem.FileSystem;
|
|
125
|
-
const readConfig = fs.readFileString(configFilePath).pipe(Effect.flatMap((data) => Effect.try({
|
|
216
|
+
const fs$1 = yield* FileSystem.FileSystem;
|
|
217
|
+
const readConfig = fs$1.readFileString(configFilePath).pipe(Effect.flatMap((data) => Effect.try({
|
|
126
218
|
try: () => {
|
|
127
219
|
const parsed = JSON.parse(data);
|
|
128
220
|
if (parsed?.version !== CONFIG_VERSION) return emptyConfig();
|
|
@@ -130,7 +222,7 @@ var init_Profile = __esm({ "lib/Auth/Profile.js": (() => {
|
|
|
130
222
|
},
|
|
131
223
|
catch: emptyConfig
|
|
132
224
|
})), Effect.orElseSucceed(emptyConfig));
|
|
133
|
-
const writeConfig = (config) => fs.makeDirectory(path.dirname(configFilePath), { recursive: true }).pipe(Effect.flatMap(() => fs.writeFileString(configFilePath, JSON.stringify(config, null, 2))));
|
|
225
|
+
const writeConfig = (config) => fs$1.makeDirectory(path.dirname(configFilePath), { recursive: true }).pipe(Effect.flatMap(() => fs$1.writeFileString(configFilePath, JSON.stringify(config, null, 2))));
|
|
134
226
|
const getProfile = (name) => readConfig.pipe(Effect.map((config) => config.profiles[name]));
|
|
135
227
|
const setProfile = (name, profile$1) => readConfig.pipe(Effect.tap((config) => Effect.sync(() => config.profiles[name] = profile$1)), Effect.flatMap(writeConfig));
|
|
136
228
|
const deleteProfile = (name) => readConfig.pipe(Effect.flatMap((config) => {
|
|
@@ -156,16 +248,21 @@ var init_Profile = __esm({ "lib/Auth/Profile.js": (() => {
|
|
|
156
248
|
loadOrConfigure
|
|
157
249
|
};
|
|
158
250
|
}));
|
|
159
|
-
withProfileOverride = (base, profile$1) => {
|
|
160
|
-
if (profile$1 === void 0) return base;
|
|
251
|
+
withProfileOverride = (base$1, profile$1) => {
|
|
252
|
+
if (profile$1 === void 0) return base$1;
|
|
161
253
|
const overrides = { ALCHEMY_PROFILE: profile$1 };
|
|
162
|
-
const overrideProvider = ConfigProvider.make((path$
|
|
163
|
-
return ConfigProvider.orElse(base)(overrideProvider);
|
|
254
|
+
const overrideProvider = ConfigProvider.make((path$2) => Effect.succeed(path$2.length === 1 && typeof path$2[0] === "string" && path$2[0] in overrides ? ConfigProvider.makeValue(overrides[path$2[0]]) : void 0));
|
|
255
|
+
return ConfigProvider.orElse(base$1)(overrideProvider);
|
|
164
256
|
};
|
|
165
257
|
}) });
|
|
166
258
|
|
|
167
259
|
//#endregion
|
|
168
260
|
//#region lib/Auth/Credentials.js
|
|
261
|
+
function displayRedacted(r, visibleChars = 4) {
|
|
262
|
+
const raw = Redacted.value(r);
|
|
263
|
+
if (raw.length <= visibleChars) return "****";
|
|
264
|
+
return `${raw.slice(0, visibleChars)}****`;
|
|
265
|
+
}
|
|
169
266
|
var credentialsDirPath, profileCredentialsDirPath, credentialsFilePath, CredentialsStore, CredentialsStoreLive;
|
|
170
267
|
var init_Credentials = __esm({ "lib/Auth/Credentials.js": (() => {
|
|
171
268
|
init_Profile();
|
|
@@ -174,8 +271,8 @@ var init_Credentials = __esm({ "lib/Auth/Credentials.js": (() => {
|
|
|
174
271
|
credentialsFilePath = (profile$1, provider) => path.join(profileCredentialsDirPath(profile$1), `${provider}.json`);
|
|
175
272
|
CredentialsStore = class extends EffectContext.Service()("Alchemy::CredentialsStore") {};
|
|
176
273
|
CredentialsStoreLive = Layer.effect(CredentialsStore, Effect.gen(function* () {
|
|
177
|
-
const fs = yield* FileSystem.FileSystem;
|
|
178
|
-
const read = (profile$1, provider) => fs.readFileString(credentialsFilePath(profile$1, provider)).pipe(Effect.catch(() => Effect.succeed(void 0)), Effect.map((data) => {
|
|
274
|
+
const fs$1 = yield* FileSystem.FileSystem;
|
|
275
|
+
const read = (profile$1, provider) => fs$1.readFileString(credentialsFilePath(profile$1, provider)).pipe(Effect.catch(() => Effect.succeed(void 0)), Effect.map((data) => {
|
|
179
276
|
if (data === void 0) return void 0;
|
|
180
277
|
try {
|
|
181
278
|
return JSON.parse(data);
|
|
@@ -185,12 +282,12 @@ var init_Credentials = __esm({ "lib/Auth/Credentials.js": (() => {
|
|
|
185
282
|
}));
|
|
186
283
|
const write = (profile$1, provider, credentials) => {
|
|
187
284
|
const filePath = credentialsFilePath(profile$1, provider);
|
|
188
|
-
return fs.makeDirectory(path.dirname(filePath), { recursive: true }).pipe(Effect.flatMap(() => {
|
|
189
|
-
return fs.writeFileString(filePath, JSON.stringify(credentials, null, 2));
|
|
285
|
+
return fs$1.makeDirectory(path.dirname(filePath), { recursive: true }).pipe(Effect.flatMap(() => {
|
|
286
|
+
return fs$1.writeFileString(filePath, JSON.stringify(credentials, null, 2));
|
|
190
287
|
}));
|
|
191
288
|
};
|
|
192
|
-
const remove_ = (profile$1, provider) => fs.remove(credentialsFilePath(profile$1, provider)).pipe(Effect.catch(() => Effect.void));
|
|
193
|
-
const deleteProfile = (profile$1) => fs.remove(profileCredentialsDirPath(profile$1), { recursive: true }).pipe(Effect.catch(() => Effect.void));
|
|
289
|
+
const remove_ = (profile$1, provider) => fs$1.remove(credentialsFilePath(profile$1, provider)).pipe(Effect.catch(() => Effect.void));
|
|
290
|
+
const deleteProfile = (profile$1) => fs$1.remove(profileCredentialsDirPath(profile$1), { recursive: true }).pipe(Effect.catch(() => Effect.void));
|
|
194
291
|
return {
|
|
195
292
|
read,
|
|
196
293
|
write,
|
|
@@ -276,13 +373,13 @@ var init_FQN = __esm({ "lib/FQN.js": (() => {
|
|
|
276
373
|
FQN_SEPARATOR = "/";
|
|
277
374
|
toPath = (ns) => {
|
|
278
375
|
if (!ns) return [];
|
|
279
|
-
const path$
|
|
376
|
+
const path$2 = [];
|
|
280
377
|
let current = ns;
|
|
281
378
|
while (current) {
|
|
282
|
-
path$
|
|
379
|
+
path$2.unshift(current.Id);
|
|
283
380
|
current = current.Parent;
|
|
284
381
|
}
|
|
285
|
-
return path$
|
|
382
|
+
return path$2;
|
|
286
383
|
};
|
|
287
384
|
parseFqn = (fqn) => {
|
|
288
385
|
const parts = fqn.split(FQN_SEPARATOR);
|
|
@@ -387,7 +484,7 @@ function proxy(self) {
|
|
|
387
484
|
});
|
|
388
485
|
return proxy$1;
|
|
389
486
|
}
|
|
390
|
-
var inspect, of, asOutput, isOutput, ExprSymbol, exprKind, isExpr, BaseExpr, isResourceExpr, ResourceExpr, isPropExpr, PropExpr, isLiteralExpr, LiteralExpr, VoidExpr, isApplyExpr, ApplyExpr, isFlatMapExpr, FlatMapExpr, isEffectExpr, EffectExpr, isNamedExpr, NamedExpr, isAllExpr, AllExpr, isRefExpr, RefExpr, isStackRefExpr, StackRefExpr, stackRef, MissingSourceError, InvalidReferenceError, evaluate, hasOutputs, upstreamAny, upstream, resolveUpstream, toObject;
|
|
487
|
+
var inspect, of$1, asOutput, isOutput, ExprSymbol, exprKind, isExpr, BaseExpr, isResourceExpr, ResourceExpr, isPropExpr, PropExpr, isLiteralExpr, LiteralExpr, VoidExpr, isApplyExpr, ApplyExpr, isFlatMapExpr, FlatMapExpr, isEffectExpr, EffectExpr, isNamedExpr, NamedExpr, isAllExpr, AllExpr, isRefExpr, RefExpr, isStackRefExpr, StackRefExpr, stackRef, MissingSourceError, InvalidReferenceError, evaluate, hasOutputs, upstreamAny, upstream, resolveUpstream, toObject;
|
|
391
488
|
var init_Output = __esm({ "lib/Output.js": (() => {
|
|
392
489
|
init_Ref();
|
|
393
490
|
init_Resource();
|
|
@@ -397,7 +494,7 @@ var init_Output = __esm({ "lib/Output.js": (() => {
|
|
|
397
494
|
init_State();
|
|
398
495
|
init_data();
|
|
399
496
|
inspect = Symbol.for("nodejs.util.inspect.custom");
|
|
400
|
-
of = (resource) => {
|
|
497
|
+
of$1 = (resource) => {
|
|
401
498
|
if (isRef(resource)) {
|
|
402
499
|
const metadata = getRefMetadata(resource);
|
|
403
500
|
return new RefExpr(metadata.stack, metadata.stage, metadata.id, metadata.type !== void 0 ? { Type: metadata.type } : void 0);
|
|
@@ -582,7 +679,7 @@ var init_Output = __esm({ "lib/Output.js": (() => {
|
|
|
582
679
|
return `stackRef(${this.stack}${this.stage ? `, { stage: ${this.stage} }` : ""})`;
|
|
583
680
|
}
|
|
584
681
|
};
|
|
585
|
-
stackRef = (stack, options = {}) => Effect.succeed(new StackRefExpr(stack, options.stage));
|
|
682
|
+
stackRef = (stack, options$6 = {}) => Effect.succeed(new StackRefExpr(stack, options$6.stage));
|
|
586
683
|
MissingSourceError = class extends Data.TaggedError("MissingSourceError") {};
|
|
587
684
|
InvalidReferenceError = class extends Data.TaggedError("InvalidReferenceError") {};
|
|
588
685
|
evaluate = (expr, upstream$1) => Effect.gen(function* () {
|
|
@@ -720,10 +817,10 @@ var init_FileLogger = __esm({ "lib/Util/FileLogger.js": (() => {
|
|
|
720
817
|
init_AlchemyContext();
|
|
721
818
|
fileLogger = Effect.fn(function* (...segments) {
|
|
722
819
|
const { dotAlchemy } = yield* AlchemyContext;
|
|
723
|
-
const fs = yield* FileSystem.FileSystem;
|
|
724
|
-
const path$
|
|
725
|
-
const logFile = path$
|
|
726
|
-
yield* fs.makeDirectory(path$
|
|
820
|
+
const fs$1 = yield* FileSystem.FileSystem;
|
|
821
|
+
const path$2 = yield* Path;
|
|
822
|
+
const logFile = path$2.join(dotAlchemy, "log", ...segments);
|
|
823
|
+
yield* fs$1.makeDirectory(path$2.dirname(logFile), { recursive: true });
|
|
727
824
|
return yield* Logger.formatLogFmt.pipe(Logger.toFile(logFile, { flag: "a" }));
|
|
728
825
|
});
|
|
729
826
|
}) });
|
|
@@ -747,9 +844,9 @@ var init_PlatformServices = __esm({ "lib/Util/PlatformServices.js": (() => {
|
|
|
747
844
|
return Layer.merge(NodeServices.layer, NodeSocket.layerWebSocketConstructor);
|
|
748
845
|
}
|
|
749
846
|
});
|
|
750
|
-
runMain = (effect, options) => {
|
|
751
|
-
if (isBun) import("@effect/platform-bun/BunRuntime").then((BunRuntime) => BunRuntime.runMain(effect, options));
|
|
752
|
-
else import("@effect/platform-node/NodeRuntime").then((NodeRuntime) => NodeRuntime.runMain(effect, options));
|
|
847
|
+
runMain = (effect, options$6) => {
|
|
848
|
+
if (isBun) import("@effect/platform-bun/BunRuntime").then((BunRuntime) => BunRuntime.runMain(effect, options$6));
|
|
849
|
+
else import("@effect/platform-node/NodeRuntime").then((NodeRuntime) => NodeRuntime.runMain(effect, options$6));
|
|
753
850
|
};
|
|
754
851
|
}) });
|
|
755
852
|
|
|
@@ -764,31 +861,31 @@ var init_Stack = __esm({ "lib/Stack.js": (() => {
|
|
|
764
861
|
init_effect();
|
|
765
862
|
init_FileLogger();
|
|
766
863
|
init_PlatformServices();
|
|
767
|
-
Stack = Object.assign(taggedFunction(EffectContext.Service()("Stack"), (stackName, options, eff) => {
|
|
864
|
+
Stack = Object.assign(taggedFunction(EffectContext.Service()("Stack"), (stackName, options$6, eff) => {
|
|
768
865
|
if (!stackName) return (stackName$1) => Object.assign(stackRef(stackName$1).pipe(effectClass), {
|
|
769
866
|
stackName: stackName$1,
|
|
770
867
|
stage: createStageProxy(stackName$1),
|
|
771
|
-
state: options?.state,
|
|
772
|
-
providers: options?.providers,
|
|
773
|
-
make: (options$
|
|
868
|
+
state: options$6?.state,
|
|
869
|
+
providers: options$6?.providers,
|
|
870
|
+
make: (options$7, eff$1) => Stack(stackName$1, options$7, eff$1)
|
|
774
871
|
});
|
|
775
872
|
return eff.pipe(make$2({
|
|
776
873
|
name: stackName,
|
|
777
|
-
...options
|
|
874
|
+
...options$6
|
|
778
875
|
}), (eff$1) => Object.assign(eff$1, {
|
|
779
876
|
stackName,
|
|
780
877
|
stage: createStageProxy(stackName),
|
|
781
|
-
state: options?.state,
|
|
782
|
-
providers: options?.providers
|
|
878
|
+
state: options$6?.state,
|
|
879
|
+
providers: options$6?.providers
|
|
783
880
|
}));
|
|
784
881
|
}));
|
|
785
882
|
createStageProxy = (stackName) => new Proxy({}, { get: (_, stage$1) => stackRef(stackName, { stage: stage$1 }) });
|
|
786
883
|
StackName = Stack.use((stack) => Effect.succeed(stack.name));
|
|
787
|
-
make$2 = (options) => (effect) => Effect.scope.pipe(Effect.flatMap((scope) => {
|
|
788
|
-
if (options.state == null) return Effect.die(/* @__PURE__ */ new Error(`Stack "${options.name}" is missing a state store. Add a \`state\` layer to the stack options, e.g.:\n Alchemy.Stack("${options.name}", {\n providers: Cloudflare.providers(),\n state: Cloudflare.state(), // <-- required\n }, ...)\nSee https://
|
|
789
|
-
if (options.providers == null) return Effect.die(/* @__PURE__ */ new Error(`Stack "${options.name}" is missing a providers layer. Add a \`providers\` layer to the stack options, e.g.:\n Alchemy.Stack("${options.name}", {\n providers: Cloudflare.providers(), // <-- required\n state: Cloudflare.state(),\n }, ...)`));
|
|
790
|
-
return options.providers.pipe(Layer.provideMerge(options.state), Layer.provideMerge(Layer.effect(Stack, Stage.pipe(Effect.map((stage$1) => options.stack ?? {
|
|
791
|
-
name: options.name,
|
|
884
|
+
make$2 = (options$6) => (effect) => Effect.scope.pipe(Effect.flatMap((scope) => {
|
|
885
|
+
if (options$6.state == null) return Effect.die(/* @__PURE__ */ new Error(`Stack "${options$6.name}" is missing a state store. Add a \`state\` layer to the stack options, e.g.:\n Alchemy.Stack("${options$6.name}", {\n providers: Cloudflare.providers(),\n state: Cloudflare.state(), // <-- required\n }, ...)\nSee https://alchemy.run/state-store for available state stores.`));
|
|
886
|
+
if (options$6.providers == null) return Effect.die(/* @__PURE__ */ new Error(`Stack "${options$6.name}" is missing a providers layer. Add a \`providers\` layer to the stack options, e.g.:\n Alchemy.Stack("${options$6.name}", {\n providers: Cloudflare.providers(), // <-- required\n state: Cloudflare.state(),\n }, ...)`));
|
|
887
|
+
return options$6.providers.pipe(Layer.provideMerge(options$6.state), Layer.provideMerge(Layer.effect(Stack, Stage.pipe(Effect.map((stage$1) => options$6.stack ?? {
|
|
888
|
+
name: options$6.name,
|
|
792
889
|
stage: stage$1,
|
|
793
890
|
resources: {},
|
|
794
891
|
bindings: {},
|
|
@@ -876,6 +973,44 @@ const make$1 = Effect.fn(function* (spawnerUrl) {
|
|
|
876
973
|
});
|
|
877
974
|
const fromEnv = () => Layer.effect(RpcProviderProxy, Config.string(SPAWNER_URL_ENV_KEY).pipe(Effect.flatMap(make$1)));
|
|
878
975
|
|
|
976
|
+
//#endregion
|
|
977
|
+
//#region lib/ActionRuntimeContext.js
|
|
978
|
+
/**
|
|
979
|
+
* Runtime context bridge that lets an {@link Action} read Resource Outputs.
|
|
980
|
+
*
|
|
981
|
+
* Actions are peculiar: their init Effect runs at plan/stack-eval time (before
|
|
982
|
+
* any resource exists) while their body runs at apply time (after upstreams are
|
|
983
|
+
* materialized). To make `yield* db.databaseId` work inside an Action we split
|
|
984
|
+
* the {@link RuntimeContext} into two cooperating halves that share nothing but
|
|
985
|
+
* a sanitized key:
|
|
986
|
+
*
|
|
987
|
+
* - {@link makeCaptureContext} is provided while the init Effect runs. Its
|
|
988
|
+
* `set(key, output)` records the referenced {@link Output} (so the engine can
|
|
989
|
+
* add a dependency edge and later resolve it) and its `get(key)` hands back a
|
|
990
|
+
* *deferred* accessor — an Effect that re-reads whatever `RuntimeContext` is
|
|
991
|
+
* ambient when it actually runs.
|
|
992
|
+
* - {@link makeResolveContext} is provided around the Action body at apply
|
|
993
|
+
* time. Its `get(key)` returns the already-resolved value for that key.
|
|
994
|
+
*
|
|
995
|
+
* Because the accessor produced at init re-reads `RuntimeContext`, yielding it
|
|
996
|
+
* inside the body resolves against the apply-time context — no shared mutable
|
|
997
|
+
* state, no infinite recursion (the resolve context's `get` is terminal).
|
|
998
|
+
*/
|
|
999
|
+
const base = (id) => ({
|
|
1000
|
+
Type: "Action",
|
|
1001
|
+
id,
|
|
1002
|
+
env: {}
|
|
1003
|
+
});
|
|
1004
|
+
/**
|
|
1005
|
+
* Resolve context — provided around an Action body at apply time. `resolved`
|
|
1006
|
+
* maps each captured key to its value (already evaluated against the tracker).
|
|
1007
|
+
*/
|
|
1008
|
+
const makeResolveContext = (resolved) => ({
|
|
1009
|
+
...base("resolve"),
|
|
1010
|
+
set: (key) => Effect.succeed(key),
|
|
1011
|
+
get: (key) => Effect.succeed(resolved[key])
|
|
1012
|
+
});
|
|
1013
|
+
|
|
879
1014
|
//#endregion
|
|
880
1015
|
//#region lib/Diff.js
|
|
881
1016
|
init_Output();
|
|
@@ -888,6 +1023,30 @@ const _hasUnresolved = (value) => {
|
|
|
888
1023
|
if (typeof value === "object") return Object.values(value).some(_hasUnresolved);
|
|
889
1024
|
return false;
|
|
890
1025
|
};
|
|
1026
|
+
/**
|
|
1027
|
+
* Deeply replace every unresolved plan-time expression (an `Output`/`Expr`
|
|
1028
|
+
* or an un-evaluated `Effect`) with `undefined`, leaving resolved values
|
|
1029
|
+
* (including opaque `Redacted`/`Duration` instances) intact.
|
|
1030
|
+
*
|
|
1031
|
+
* Persisted resource state must only ever hold plain data. Durable (JSON)
|
|
1032
|
+
* state stores already enforce this implicitly — Output proxies are
|
|
1033
|
+
* function-typed, so `JSON.stringify` silently drops them — but the
|
|
1034
|
+
* in-memory store used by tests retains live proxies, which would later be
|
|
1035
|
+
* fed back into provider lifecycle operations as `olds` after an
|
|
1036
|
+
* interrupted apply (e.g. `read` during a destroy plan) and blow up on
|
|
1037
|
+
* first string coercion. Sanitizing at the commit boundary keeps both
|
|
1038
|
+
* store kinds consistent with the provider contract that `olds` is
|
|
1039
|
+
* resolved `Props`.
|
|
1040
|
+
*/
|
|
1041
|
+
const stripUnresolved = (value) => _stripUnresolved(value);
|
|
1042
|
+
const _stripUnresolved = (value) => {
|
|
1043
|
+
if (value == null || isPrimitive(value)) return value;
|
|
1044
|
+
if (isExpr(value) || Effect.isEffect(value)) return void 0;
|
|
1045
|
+
if (Redacted.isRedacted(value) || Duration.isDuration(value)) return value;
|
|
1046
|
+
if (Array.isArray(value)) return value.map(_stripUnresolved);
|
|
1047
|
+
if (typeof value === "object") return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, _stripUnresolved(item)]));
|
|
1048
|
+
return value;
|
|
1049
|
+
};
|
|
891
1050
|
const havePropsChanged = (oldProps, newProps) => hasOutputs(newProps) || JSON.stringify(canonicalize(oldProps ?? {}, false)) !== JSON.stringify(canonicalize(newProps ?? {}, false));
|
|
892
1051
|
const canonicalize = (value, stripNullish) => {
|
|
893
1052
|
if (stripNullish && value == null) return void 0;
|
|
@@ -1133,7 +1292,7 @@ init_Output();
|
|
|
1133
1292
|
init_Provider();
|
|
1134
1293
|
init_Resource();
|
|
1135
1294
|
init_sha256();
|
|
1136
|
-
const make = (stack, options = {}) => Effect.gen(function* () {
|
|
1295
|
+
const make = (stack, options$6 = {}) => Effect.gen(function* () {
|
|
1137
1296
|
const state = yield* yield* State;
|
|
1138
1297
|
const resources = Object.values(stack.resources);
|
|
1139
1298
|
const actions = Object.values(stack.actions ?? {});
|
|
@@ -1201,7 +1360,7 @@ const make = (stack, options = {}) => Effect.gen(function* () {
|
|
|
1201
1360
|
else if (Duration.isDuration(input) || Redacted.isRedacted(input)) return input;
|
|
1202
1361
|
else if (Array.isArray(input)) return yield* Effect.all(input.map(resolveInput), { concurrency: "unbounded" });
|
|
1203
1362
|
else if (isResource(input)) {
|
|
1204
|
-
const resolved = yield* resolveOutput(of(input));
|
|
1363
|
+
const resolved = yield* resolveOutput(of$1(input));
|
|
1205
1364
|
if (isResourceExpr(resolved) && resolved.stables) return yield* resolveInput(resolved.stables);
|
|
1206
1365
|
return yield* resolveInput(resolved);
|
|
1207
1366
|
} else if (typeof input === "object") return Object.fromEntries(yield* Effect.all(Object.entries(input).map(([key, value]) => resolveInput(value).pipe(Effect.map((value$1) => [key, value$1]))), { concurrency: "unbounded" }));
|
|
@@ -1257,7 +1416,7 @@ const make = (stack, options = {}) => Effect.gen(function* () {
|
|
|
1257
1416
|
const newResourceFqns = new Set(resources.map((r) => r.FQN));
|
|
1258
1417
|
const newActionFqns = new Set(actions.map((t) => t.FQN));
|
|
1259
1418
|
const newNodeFqns = new Set([...newResourceFqns, ...newActionFqns]);
|
|
1260
|
-
const newUpstreamDependencies = Object.fromEntries([...resources.map((resource) => [resource.FQN, Object.values(upstreamAny(resource.Props)).map((r) => r.FQN)]), ...actions.map((action) => [action.FQN, Object.values(upstreamAny(action.Input)).map((r) => r.FQN)])]);
|
|
1419
|
+
const newUpstreamDependencies = Object.fromEntries([...resources.map((resource) => [resource.FQN, Object.values(upstreamAny(resource.Props)).map((r) => r.FQN)]), ...actions.map((action) => [action.FQN, Array.from(new Set([...Object.values(upstreamAny(action.Input)).map((r) => r.FQN), ...Object.values(upstreamAny(action.Captures)).map((r) => r.FQN)]))])]);
|
|
1261
1420
|
const bindingUpstreamDependencies = Object.fromEntries(resources.map((resource) => [resource.FQN, Object.values(upstreamAny(stack.bindings[resource.FQN] ?? [])).map((r) => r.FQN)]));
|
|
1262
1421
|
const rawUpstreamDependencies = Object.fromEntries([...resources.map((resource) => {
|
|
1263
1422
|
const fqn = resource.FQN;
|
|
@@ -1382,7 +1541,7 @@ const make = (stack, options = {}) => Effect.gen(function* () {
|
|
|
1382
1541
|
news,
|
|
1383
1542
|
oldBindings,
|
|
1384
1543
|
newBindings
|
|
1385
|
-
}).pipe(providePlanScope(fqn, oldState.instanceId))).pipe(Effect.map((diff$1) => diff$1 ?? { action: havePropsChanged(oldProps, news) || bindingDiffs.some((b) => b.action !== "noop") ? "update" : "noop" }), Effect.map((diff$1) => options.force && diff$1.action === "noop" ? { action: "update" } : diff$1), Effect.map((diff$1) => forceUpdateAfterAdoption && diff$1.action === "noop" ? { action: "update" } : diff$1));
|
|
1544
|
+
}).pipe(providePlanScope(fqn, oldState.instanceId))).pipe(Effect.map((diff$1) => diff$1 ?? { action: havePropsChanged(oldProps, news) || bindingDiffs.some((b) => b.action !== "noop") ? "update" : "noop" }), Effect.map((diff$1) => options$6.force && diff$1.action === "noop" ? { action: "update" } : diff$1), Effect.map((diff$1) => forceUpdateAfterAdoption && diff$1.action === "noop" ? { action: "update" } : diff$1));
|
|
1386
1545
|
if (oldState.status === "creating") if (diff.action === "noop") return Node({
|
|
1387
1546
|
action: "create",
|
|
1388
1547
|
props: news,
|
|
@@ -1491,7 +1650,7 @@ const make = (stack, options = {}) => Effect.gen(function* () {
|
|
|
1491
1650
|
forced: false
|
|
1492
1651
|
}];
|
|
1493
1652
|
const prior = oldState;
|
|
1494
|
-
if (prior?.status === "ran" && prior.inputHash === inputHash && !options.force) return [fqn, {
|
|
1653
|
+
if (prior?.status === "ran" && prior.inputHash === inputHash && !options$6.force) return [fqn, {
|
|
1495
1654
|
kind: "action",
|
|
1496
1655
|
action: "noop",
|
|
1497
1656
|
def: action,
|
|
@@ -1505,7 +1664,7 @@ const make = (stack, options = {}) => Effect.gen(function* () {
|
|
|
1505
1664
|
input: action.Input,
|
|
1506
1665
|
state: prior,
|
|
1507
1666
|
downstream,
|
|
1508
|
-
forced: !!options.force
|
|
1667
|
+
forced: !!options$6.force
|
|
1509
1668
|
}];
|
|
1510
1669
|
})), { concurrency: "unbounded" }));
|
|
1511
1670
|
const cycleMembers = combinedCycleMembers;
|
|
@@ -1560,6 +1719,7 @@ const make = (stack, options = {}) => Effect.gen(function* () {
|
|
|
1560
1719
|
LogicalId: logicalId,
|
|
1561
1720
|
Type: persisted.actionType,
|
|
1562
1721
|
Input: persisted.input,
|
|
1722
|
+
Captures: {},
|
|
1563
1723
|
Run: () => void 0,
|
|
1564
1724
|
Output: void 0
|
|
1565
1725
|
}
|
|
@@ -1641,7 +1801,7 @@ const make = (stack, options = {}) => Effect.gen(function* () {
|
|
|
1641
1801
|
"alchemy.stack": stack.name,
|
|
1642
1802
|
"alchemy.stage": stack.stage,
|
|
1643
1803
|
"alchemy.resources.count": Object.keys(stack.resources).length,
|
|
1644
|
-
"alchemy.force": !!options.force
|
|
1804
|
+
"alchemy.force": !!options$6.force
|
|
1645
1805
|
} }));
|
|
1646
1806
|
const providePlanScope = (fqn, instanceId) => (effect) => Effect.serviceOption(ArtifactStore).pipe(Effect.map(Option.getOrElse(createArtifactStore)), Effect.flatMap((store) => effect.pipe(Effect.provideService(Artifacts, makeScopedArtifacts(store, fqn)), Effect.provideService(InstanceId, instanceId))));
|
|
1647
1807
|
var DeleteResourceHasDownstreamDependencies = class extends Data.TaggedError("DeleteResourceHasDownstreamDependencies") {};
|
|
@@ -1649,6 +1809,7 @@ var UnsatisfiedResourceCycle = class extends Data.TaggedError("UnsatisfiedResour
|
|
|
1649
1809
|
|
|
1650
1810
|
//#endregion
|
|
1651
1811
|
//#region lib/Apply.js
|
|
1812
|
+
init_RuntimeContext();
|
|
1652
1813
|
init_Artifacts();
|
|
1653
1814
|
init_Cli();
|
|
1654
1815
|
init_Output();
|
|
@@ -1728,6 +1889,7 @@ const executeNode = (fqn, node, tracker, ready, readyStable, terminalStatuses, s
|
|
|
1728
1889
|
fqn,
|
|
1729
1890
|
value: {
|
|
1730
1891
|
...value,
|
|
1892
|
+
props: stripUnresolved(value.props),
|
|
1731
1893
|
namespace
|
|
1732
1894
|
}
|
|
1733
1895
|
});
|
|
@@ -2148,6 +2310,16 @@ const executeNode = (fqn, node, tracker, ready, readyStable, terminalStatuses, s
|
|
|
2148
2310
|
"alchemy.resource.logical_id": node.resource.LogicalId,
|
|
2149
2311
|
"alchemy.resource.action": node.action
|
|
2150
2312
|
} }));
|
|
2313
|
+
/**
|
|
2314
|
+
* Run an Action body, resolving any Outputs it captured via `yield* output`
|
|
2315
|
+
* during init against the current tracker and exposing them to the body through
|
|
2316
|
+
* the resolve {@link RuntimeContext}. See {@link makeCaptureContext}.
|
|
2317
|
+
*/
|
|
2318
|
+
const runAction = Effect.fn("apply.runAction")(function* (task, input, outputs) {
|
|
2319
|
+
const resolved = {};
|
|
2320
|
+
for (const [key, output] of Object.entries(task.Captures)) resolved[key] = yield* evaluate(output, outputs);
|
|
2321
|
+
return yield* task.Run(input).pipe(Effect.provideService(RuntimeContext, makeResolveContext(resolved)));
|
|
2322
|
+
});
|
|
2151
2323
|
const executeActionNode = (fqn, node, tracker, ready, readyStable, terminalStatuses, session, state, stackName, stage$1, getOutputs, waitForDeps, failures) => Effect.gen(function* () {
|
|
2152
2324
|
const task = node.def;
|
|
2153
2325
|
const logicalId = task.LogicalId;
|
|
@@ -2187,7 +2359,7 @@ const executeActionNode = (fqn, node, tracker, ready, readyStable, terminalStatu
|
|
|
2187
2359
|
return;
|
|
2188
2360
|
}
|
|
2189
2361
|
yield* report("pending");
|
|
2190
|
-
yield* waitForDeps(Object.keys(resolveUpstream(node.input)).filter((f) => f in readyStable));
|
|
2362
|
+
yield* waitForDeps([...new Set([...Object.keys(resolveUpstream(node.input)), ...Object.keys(upstreamAny(task.Captures))])].filter((f) => f in readyStable));
|
|
2191
2363
|
const outputs = getOutputs();
|
|
2192
2364
|
const resolvedInput = yield* evaluate(node.input, outputs);
|
|
2193
2365
|
const inputHashValue = yield* hashInput(resolvedInput);
|
|
@@ -2202,7 +2374,7 @@ const executeActionNode = (fqn, node, tracker, ready, readyStable, terminalStatu
|
|
|
2202
2374
|
downstream: node.downstream
|
|
2203
2375
|
});
|
|
2204
2376
|
yield* report("running");
|
|
2205
|
-
const result = yield* task
|
|
2377
|
+
const result = yield* runAction(task, resolvedInput, outputs);
|
|
2206
2378
|
yield* commit({
|
|
2207
2379
|
kind: "action",
|
|
2208
2380
|
status: "ran",
|
|
@@ -2341,7 +2513,7 @@ const converge = Effect.fn(function* (plan, tracker, terminalStatuses, session,
|
|
|
2341
2513
|
downstream: node.downstream
|
|
2342
2514
|
}
|
|
2343
2515
|
});
|
|
2344
|
-
const result = yield* node.def
|
|
2516
|
+
const result = yield* runAction(node.def, newInput, outputs);
|
|
2345
2517
|
yield* state.set({
|
|
2346
2518
|
stack: stackName,
|
|
2347
2519
|
stage: stage$1,
|
|
@@ -2429,6 +2601,7 @@ const collectGarbage = Effect.fn(function* (plan, session) {
|
|
|
2429
2601
|
fqn,
|
|
2430
2602
|
value: {
|
|
2431
2603
|
...value,
|
|
2604
|
+
props: stripUnresolved(value.props),
|
|
2432
2605
|
namespace
|
|
2433
2606
|
}
|
|
2434
2607
|
});
|
|
@@ -2546,6 +2719,1709 @@ const excludeDeletedBindings = (bindings) => bindings.flatMap(({ action, sid, da
|
|
|
2546
2719
|
data
|
|
2547
2720
|
}]);
|
|
2548
2721
|
|
|
2722
|
+
//#endregion
|
|
2723
|
+
//#region lib/Util/Clank.js
|
|
2724
|
+
var PromptCancelled = class extends Data.TaggedError("PromptCancelled") {};
|
|
2725
|
+
const retryIfCancelled = Effect.retry({ while: (e) => e instanceof PromptCancelled });
|
|
2726
|
+
/**
|
|
2727
|
+
* Wraps a clack prompt (which returns a `Promise<T | symbol>` where the
|
|
2728
|
+
* symbol indicates user cancellation) in an Effect.
|
|
2729
|
+
*
|
|
2730
|
+
* Returns `undefined` if the user cancels (Ctrl+C / Escape).
|
|
2731
|
+
*
|
|
2732
|
+
* Uses `Effect.callback` so fiber interruption propagates via the abort
|
|
2733
|
+
* signal to any async resources we own; the clack prompt itself is left
|
|
2734
|
+
* to resolve — its result is ignored after interruption.
|
|
2735
|
+
*/
|
|
2736
|
+
const prompt = (fn) => Effect.callback((resume, signal) => {
|
|
2737
|
+
let settled = false;
|
|
2738
|
+
fn().then((result) => {
|
|
2739
|
+
if (settled || signal.aborted) return;
|
|
2740
|
+
settled = true;
|
|
2741
|
+
if (p.isCancel(result)) resume(Effect.fail(new PromptCancelled()));
|
|
2742
|
+
else resume(Effect.succeed(result));
|
|
2743
|
+
}, (err) => {
|
|
2744
|
+
if (settled || signal.aborted) return;
|
|
2745
|
+
settled = true;
|
|
2746
|
+
resume(Effect.die(err));
|
|
2747
|
+
});
|
|
2748
|
+
});
|
|
2749
|
+
const success = (str) => Effect.sync(() => p.log.success(str));
|
|
2750
|
+
const warn = (str) => Effect.sync(() => p.log.warn(str));
|
|
2751
|
+
const info = (str) => Effect.sync(() => p.log.info(str));
|
|
2752
|
+
const text = (opts) => prompt(() => p.text(opts));
|
|
2753
|
+
const password = (opts) => prompt(() => p.password(opts));
|
|
2754
|
+
const select = (opts) => prompt(() => p.select(opts));
|
|
2755
|
+
const confirm = (opts) => prompt(() => p.confirm(opts));
|
|
2756
|
+
const multiselect = (opts) => prompt(() => p.multiselect(opts));
|
|
2757
|
+
/**
|
|
2758
|
+
* Open a URL in the user's default browser.
|
|
2759
|
+
*
|
|
2760
|
+
* On Windows, uses rundll32's FileProtocolHandler — a built-in shim that
|
|
2761
|
+
* opens URLs in the default browser. It accepts the URL as a direct
|
|
2762
|
+
* argument (no shell, no quoting of `&`). cmd.exe `start` would treat
|
|
2763
|
+
* `&` in OAuth URLs as a command separator, and `explorer.exe` treats
|
|
2764
|
+
* its arg as a path.
|
|
2765
|
+
*/
|
|
2766
|
+
const openUrl = (url) => Effect.gen(function* () {
|
|
2767
|
+
const [cmd, args] = process.platform === "win32" ? ["rundll32.exe", ["url.dll,FileProtocolHandler", url]] : process.platform === "darwin" ? ["open", [url]] : ["xdg-open", [url]];
|
|
2768
|
+
yield* (yield* ChildProcess.make(cmd, [...args], { shell: false })).exitCode;
|
|
2769
|
+
}).pipe(Effect.scoped);
|
|
2770
|
+
|
|
2771
|
+
//#endregion
|
|
2772
|
+
//#region lib/Auth/Env.js
|
|
2773
|
+
init_AuthProvider();
|
|
2774
|
+
const getEnv = (key) => Config.string(key).pipe(Effect.orElseSucceed(() => void 0));
|
|
2775
|
+
const getEnvRequired = (key) => Config.string(key).pipe(Effect.catch(() => Effect.fail(new AuthError({ message: `Missing required env: ${key}` }))));
|
|
2776
|
+
const getEnvRedacted = (key) => Config.redacted(key).pipe(Effect.orElseSucceed(() => void 0));
|
|
2777
|
+
const getEnvRedactedRequired = (key) => Config.redacted(key).pipe(Effect.catch(() => Effect.fail(new AuthError({ message: `Missing required env: ${key}` }))));
|
|
2778
|
+
const retryOnce = (self) => self.pipe(Effect.retry({
|
|
2779
|
+
times: 1,
|
|
2780
|
+
while: (e) => e instanceof PromptCancelled
|
|
2781
|
+
}), Effect.mapError((e) => new AuthError({
|
|
2782
|
+
message: "User cancelled prompt",
|
|
2783
|
+
cause: e
|
|
2784
|
+
})));
|
|
2785
|
+
|
|
2786
|
+
//#endregion
|
|
2787
|
+
//#region lib/AWS/Environment.js
|
|
2788
|
+
init_AuthProvider();
|
|
2789
|
+
init_Profile();
|
|
2790
|
+
const AWS_PROFILE = Config.string("AWS_PROFILE").pipe(Config.withDefault("default"));
|
|
2791
|
+
const AWS_REGION = Config.string("AWS_REGION");
|
|
2792
|
+
const AWS_ACCOUNT_ID = Config.string("AWS_ACCOUNT_ID");
|
|
2793
|
+
const AWS_ACCESS_KEY_ID = Config.string("AWS_ACCESS_KEY_ID");
|
|
2794
|
+
const AWS_SECRET_ACCESS_KEY = Config.redacted("AWS_SECRET_ACCESS_KEY");
|
|
2795
|
+
const AWS_SESSION_TOKEN = Config.redacted("AWS_SESSION_TOKEN");
|
|
2796
|
+
var AWSEnvironment = class AWSEnvironment extends EffectContext.Service()("AWS::Environment") {
|
|
2797
|
+
static current = AWSEnvironment.use((env) => env);
|
|
2798
|
+
kind = "Environment";
|
|
2799
|
+
};
|
|
2800
|
+
const Default = Layer.effect(AWSEnvironment, Effect.gen(function* () {
|
|
2801
|
+
const profile$1 = yield* AlchemyProfile;
|
|
2802
|
+
const auth = yield* getAuthProvider(AWS_AUTH_PROVIDER_NAME);
|
|
2803
|
+
const profileName = yield* ALCHEMY_PROFILE;
|
|
2804
|
+
const ci = yield* Config.boolean("CI").pipe(Config.withDefault(false));
|
|
2805
|
+
return yield* profile$1.loadOrConfigure(auth, profileName, { ci }).pipe(Effect.flatMap((config) => auth.read(profileName, config)), Effect.orDie, Effect.cached);
|
|
2806
|
+
})).pipe(Layer.orDie);
|
|
2807
|
+
|
|
2808
|
+
//#endregion
|
|
2809
|
+
//#region lib/AWS/Region.js
|
|
2810
|
+
const of = (region) => Layer.succeed(Region.Region, Effect.succeed(region));
|
|
2811
|
+
const CurrentRegion = Effect.suspend(() => AWSEnvironment.use((env) => Effect.flatMap(env, ({ region }) => Effect.succeed(region))));
|
|
2812
|
+
/**
|
|
2813
|
+
* Derive the AWS region from the surrounding {@link AWSEnvironment}.
|
|
2814
|
+
*/
|
|
2815
|
+
const fromEnvironment = Layer.effect(Region.Region, Effect.gen(function* () {
|
|
2816
|
+
const env = yield* AWSEnvironment;
|
|
2817
|
+
return Effect.map(env, (env$1) => env$1.region);
|
|
2818
|
+
}));
|
|
2819
|
+
|
|
2820
|
+
//#endregion
|
|
2821
|
+
//#region lib/AWS/AuthProvider.js
|
|
2822
|
+
init_AuthProvider();
|
|
2823
|
+
init_Credentials();
|
|
2824
|
+
const AWS_AUTH_PROVIDER_NAME = "AWS";
|
|
2825
|
+
const options$5 = [
|
|
2826
|
+
{
|
|
2827
|
+
value: "sso",
|
|
2828
|
+
label: "SSO",
|
|
2829
|
+
hint: "aws sso login — credentials loaded from AWS SSO cache"
|
|
2830
|
+
},
|
|
2831
|
+
{
|
|
2832
|
+
value: "env",
|
|
2833
|
+
label: "Environment Variables",
|
|
2834
|
+
hint: "AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY"
|
|
2835
|
+
},
|
|
2836
|
+
{
|
|
2837
|
+
value: "stored",
|
|
2838
|
+
label: "Stored",
|
|
2839
|
+
hint: "stored in ~/.alchemy/credentials"
|
|
2840
|
+
}
|
|
2841
|
+
];
|
|
2842
|
+
/**
|
|
2843
|
+
* Layer that registers the AWS {@link AuthProvider} into the
|
|
2844
|
+
* {@link AuthProviders} registry when built. Include this in the AWS
|
|
2845
|
+
* `providers()` layer so `alchemy login` can discover it.
|
|
2846
|
+
*/
|
|
2847
|
+
const AwsAuth = AuthProviderLayer()(AWS_AUTH_PROVIDER_NAME, Effect.gen(function* () {
|
|
2848
|
+
const store = yield* CredentialsStore;
|
|
2849
|
+
const getAccountId = ({ accessKeyId, secretAccessKey, sessionToken, region }) => STS.getCallerIdentity({}).pipe(Effect.provide(Layer.mergeAll(Layer.succeed(Credentials, Effect.succeed({
|
|
2850
|
+
accessKeyId,
|
|
2851
|
+
secretAccessKey,
|
|
2852
|
+
sessionToken
|
|
2853
|
+
})), of(region))), Effect.flatMap((self) => self.Account ? Effect.succeed(self.Account) : Effect.die(/* @__PURE__ */ new Error("No account ID found"))));
|
|
2854
|
+
const loginStored = Effect.fn(function* (profileName) {
|
|
2855
|
+
const accessKeyId = yield* text({
|
|
2856
|
+
message: "AWS Access Key ID",
|
|
2857
|
+
validate: (v) => v.length === 0 ? "Required" : void 0
|
|
2858
|
+
}).pipe(retryOnce);
|
|
2859
|
+
const secretAccessKey = yield* password({
|
|
2860
|
+
message: "AWS Secret Access Key",
|
|
2861
|
+
validate: (v) => v.length === 0 ? "Required" : void 0
|
|
2862
|
+
}).pipe(retryOnce);
|
|
2863
|
+
const sessionToken = yield* text({
|
|
2864
|
+
message: "AWS Session Token (optional — press Enter or Esc to skip)",
|
|
2865
|
+
placeholder: "(none)"
|
|
2866
|
+
}).pipe(Effect.catch(() => Effect.succeed("")));
|
|
2867
|
+
const region = yield* text({
|
|
2868
|
+
message: "AWS Region",
|
|
2869
|
+
placeholder: "us-east-1",
|
|
2870
|
+
defaultValue: "us-east-1"
|
|
2871
|
+
}).pipe(retryOnce);
|
|
2872
|
+
const accountId = yield* getAccountId({
|
|
2873
|
+
accessKeyId: Redacted.make(accessKeyId),
|
|
2874
|
+
secretAccessKey: Redacted.make(secretAccessKey),
|
|
2875
|
+
sessionToken: sessionToken ? Redacted.make(sessionToken) : void 0,
|
|
2876
|
+
region
|
|
2877
|
+
});
|
|
2878
|
+
yield* store.write(profileName, "aws", {
|
|
2879
|
+
accountId,
|
|
2880
|
+
accessKeyId,
|
|
2881
|
+
secretAccessKey,
|
|
2882
|
+
sessionToken,
|
|
2883
|
+
region
|
|
2884
|
+
});
|
|
2885
|
+
yield* success("AWS credentials saved.");
|
|
2886
|
+
return { method: "stored" };
|
|
2887
|
+
});
|
|
2888
|
+
const configureInteractive = (profileName) => select({
|
|
2889
|
+
message: "AWS authentication method",
|
|
2890
|
+
options: options$5
|
|
2891
|
+
}).pipe(Effect.flatMap((method) => Match.value(method).pipe(Match.when("env", () => Effect.succeed({ method: "env" })), Match.when("sso", () => Effect.gen(function* () {
|
|
2892
|
+
const config = {
|
|
2893
|
+
method: "sso",
|
|
2894
|
+
ssoProfile: (yield* text({
|
|
2895
|
+
message: "AWS profile name (from ~/.aws/config)",
|
|
2896
|
+
placeholder: "default",
|
|
2897
|
+
defaultValue: "default"
|
|
2898
|
+
})) ?? "default"
|
|
2899
|
+
};
|
|
2900
|
+
yield* loginSSO(config);
|
|
2901
|
+
return config;
|
|
2902
|
+
})), Match.when("stored", () => loginStored(profileName)), Match.exhaustive)));
|
|
2903
|
+
const configureCredentials = (profileName, ctx) => Effect.gen(function* () {
|
|
2904
|
+
if (ctx.ci) return { method: "env" };
|
|
2905
|
+
return yield* configureInteractive(profileName);
|
|
2906
|
+
}).pipe(Effect.mapError((e) => new AuthError({
|
|
2907
|
+
message: "failed to configure credentials",
|
|
2908
|
+
cause: e
|
|
2909
|
+
})));
|
|
2910
|
+
const resolveCredentials = (profileName, config) => Match.value(config).pipe(Match.when({ method: "env" }, Effect.fn(function* () {
|
|
2911
|
+
const accessKeyId = yield* getEnvRedactedRequired("AWS_ACCESS_KEY_ID");
|
|
2912
|
+
const secretAccessKey = yield* getEnvRedactedRequired("AWS_SECRET_ACCESS_KEY");
|
|
2913
|
+
const sessionToken = yield* getEnvRedacted("AWS_SESSION_TOKEN");
|
|
2914
|
+
const region = yield* getEnv("AWS_REGION").pipe(Effect.flatMap((region$1) => region$1 ? Effect.succeed(region$1) : getEnv("AWS_DEFAULT_REGION")));
|
|
2915
|
+
if (!region) return yield* Effect.fail(new AuthError({ message: "AWS region not found. Set AWS_REGION or AWS_DEFAULT_REGION." }));
|
|
2916
|
+
return {
|
|
2917
|
+
accountId: yield* getEnvRequired("AWS_ACCOUNT_ID").pipe(Effect.catch(() => getAccountId({
|
|
2918
|
+
accessKeyId,
|
|
2919
|
+
secretAccessKey,
|
|
2920
|
+
sessionToken,
|
|
2921
|
+
region
|
|
2922
|
+
}))),
|
|
2923
|
+
credentials: Effect.succeed({
|
|
2924
|
+
accessKeyId,
|
|
2925
|
+
secretAccessKey,
|
|
2926
|
+
sessionToken
|
|
2927
|
+
}),
|
|
2928
|
+
region,
|
|
2929
|
+
source: { type: "env" }
|
|
2930
|
+
};
|
|
2931
|
+
})), Match.when({ method: "stored" }, () => store.read(profileName, "aws").pipe(Effect.flatMap((creds) => creds == null ? Effect.fail(new AuthError({ message: "AWS stored credentials not found. Run: alchemy login --configure" })) : Effect.succeed({
|
|
2932
|
+
accountId: creds.accountId,
|
|
2933
|
+
credentials: Effect.succeed({
|
|
2934
|
+
accessKeyId: Redacted.make(creds.accessKeyId),
|
|
2935
|
+
secretAccessKey: Redacted.make(creds.secretAccessKey),
|
|
2936
|
+
sessionToken: creds.sessionToken ? Redacted.make(creds.sessionToken) : void 0
|
|
2937
|
+
}),
|
|
2938
|
+
region: creds.region,
|
|
2939
|
+
source: { type: "stored" }
|
|
2940
|
+
})), Effect.flatMap((creds) => creds.accountId ? Effect.succeed(creds) : creds.credentials.pipe(Effect.flatMap((resolved) => getAccountId({
|
|
2941
|
+
accessKeyId: resolved.accessKeyId,
|
|
2942
|
+
secretAccessKey: resolved.secretAccessKey,
|
|
2943
|
+
sessionToken: resolved.sessionToken,
|
|
2944
|
+
region: creds.region
|
|
2945
|
+
})), Effect.map((accountId) => ({
|
|
2946
|
+
...creds,
|
|
2947
|
+
accountId
|
|
2948
|
+
})), Effect.tap((creds$1) => creds$1.credentials.pipe(Effect.tap(({ accessKeyId, secretAccessKey, sessionToken }) => store.write(profileName, "aws", {
|
|
2949
|
+
accessKeyId: Redacted.value(accessKeyId),
|
|
2950
|
+
secretAccessKey: Redacted.value(secretAccessKey),
|
|
2951
|
+
sessionToken: sessionToken ? Redacted.value(sessionToken) : void 0,
|
|
2952
|
+
region: creds$1.region,
|
|
2953
|
+
accountId: creds$1.accountId
|
|
2954
|
+
})))))))), Match.when({ method: "sso" }, (config$1) => Effect.gen(function* () {
|
|
2955
|
+
const auth = yield* DistilledAuth.Default;
|
|
2956
|
+
const profile$1 = yield* auth.loadProfile(config$1.ssoProfile).pipe(Effect.catch(() => Effect.succeed(void 0)));
|
|
2957
|
+
return {
|
|
2958
|
+
accountId: profile$1?.sso_account_id,
|
|
2959
|
+
credentials: auth.loadProfileCredentials(config$1.ssoProfile).pipe(Effect.mapError((e) => new AuthError({
|
|
2960
|
+
message: "failed to load credentials",
|
|
2961
|
+
cause: e
|
|
2962
|
+
})), Effect.orDie),
|
|
2963
|
+
region: profile$1?.region,
|
|
2964
|
+
source: {
|
|
2965
|
+
type: "sso",
|
|
2966
|
+
details: config$1.ssoProfile
|
|
2967
|
+
}
|
|
2968
|
+
};
|
|
2969
|
+
})), Match.exhaustive).pipe(Effect.mapError((e) => new AuthError({
|
|
2970
|
+
message: "login failed",
|
|
2971
|
+
cause: e
|
|
2972
|
+
})));
|
|
2973
|
+
const prettyPrint = (profileName, config) => resolveCredentials(profileName, config).pipe(Effect.tap(Effect.fn(function* (creds) {
|
|
2974
|
+
const { accessKeyId, secretAccessKey, sessionToken } = yield* creds.credentials;
|
|
2975
|
+
yield* Console.log(` accessKeyId: ${displayRedacted(accessKeyId)}`);
|
|
2976
|
+
yield* Console.log(` secretAccessKey: ${displayRedacted(secretAccessKey)}`);
|
|
2977
|
+
if (sessionToken) yield* Console.log(` sessionToken: ${displayRedacted(sessionToken)}`);
|
|
2978
|
+
if (creds.region) yield* Console.log(` region: ${creds.region}`);
|
|
2979
|
+
yield* Console.log(` source: ${creds.source.details ? `${creds.source.type} - ${creds.source.details}` : creds.source.type}`);
|
|
2980
|
+
})));
|
|
2981
|
+
const logout = (profileName, config) => Match.value(config).pipe(Match.when({ method: "env" }, () => Effect.void), Match.when({ method: "sso" }, (config$1) => info(`AWS: running 'aws sso logout --profile ${config$1.ssoProfile}'...`).pipe(Effect.zip(runSsoCommand("logout", config$1.ssoProfile)), Effect.zip(clearDistilledSsoCache(config$1.ssoProfile)), Effect.match({
|
|
2982
|
+
onSuccess: () => success("AWS: SSO logout complete"),
|
|
2983
|
+
onFailure: (e) => warn(`AWS: SSO logout failed: \`${e.message}\``)
|
|
2984
|
+
}))), Match.when({ method: "stored" }, () => store.delete(profileName, "aws").pipe(Effect.andThen(success("AWS: stored credentials removed")))), Match.exhaustive);
|
|
2985
|
+
const login = (profileName, config) => Match.value(config).pipe(Match.when({ method: "env" }, () => Effect.void), Match.when({ method: "sso" }, loginSSO), Match.when({ method: "stored" }, () => store.read(profileName, "aws").pipe(Effect.flatMap((creds) => creds == null ? loginStored(profileName) : Effect.void))), Match.exhaustive).pipe(Effect.mapError((e) => new AuthError({
|
|
2986
|
+
message: "login failed",
|
|
2987
|
+
cause: e
|
|
2988
|
+
})));
|
|
2989
|
+
return {
|
|
2990
|
+
configure: configureCredentials,
|
|
2991
|
+
login,
|
|
2992
|
+
logout,
|
|
2993
|
+
prettyPrint,
|
|
2994
|
+
read: resolveCredentials
|
|
2995
|
+
};
|
|
2996
|
+
}));
|
|
2997
|
+
const runSsoCommand = (command, ssoProfile) => Effect.gen(function* () {
|
|
2998
|
+
const exit = yield* (yield* ChildProcess.make("aws", [
|
|
2999
|
+
"sso",
|
|
3000
|
+
command,
|
|
3001
|
+
"--profile",
|
|
3002
|
+
ssoProfile
|
|
3003
|
+
], {
|
|
3004
|
+
shell: false,
|
|
3005
|
+
stdin: "inherit",
|
|
3006
|
+
stdout: "inherit",
|
|
3007
|
+
stderr: "inherit"
|
|
3008
|
+
})).exitCode;
|
|
3009
|
+
if (exit !== 0) return yield* new AuthError({ message: `aws sso ${command} exited with code ${exit}` });
|
|
3010
|
+
}).pipe(Effect.scoped);
|
|
3011
|
+
const loginSSO = (config) => info(`AWS SSO: running 'aws sso login --profile ${config.ssoProfile}'...`).pipe(Effect.andThen(runSsoCommand("login", config.ssoProfile)), Effect.matchEffect({
|
|
3012
|
+
onSuccess: () => success("AWS SSO: login complete"),
|
|
3013
|
+
onFailure: (e) => warn(`AWS SSO: login faield: \`${e.message}\``)
|
|
3014
|
+
}));
|
|
3015
|
+
/**
|
|
3016
|
+
* `aws sso logout` only clears AWS CLI's own caches — it does not know about the
|
|
3017
|
+
* `<sha1(sso_session)>.credentials.json` file that `@distilled.cloud/aws`
|
|
3018
|
+
* writes alongside the SSO token. Without this cleanup, `loadProfileCredentials`
|
|
3019
|
+
* short-circuits on the stale distilled cache file after logout and appears to
|
|
3020
|
+
* stay logged in until the role creds hit their TTL.
|
|
3021
|
+
*/
|
|
3022
|
+
const clearDistilledSsoCache = (ssoProfile) => Effect.gen(function* () {
|
|
3023
|
+
const ssoSession = (yield* (yield* DistilledAuth.Default).loadProfile(ssoProfile)).sso_session;
|
|
3024
|
+
if (!ssoSession) return;
|
|
3025
|
+
const fs$1 = yield* FileSystem.FileSystem;
|
|
3026
|
+
const path$2 = yield* Path$1.Path;
|
|
3027
|
+
const hash = NodeCrypto.createHash("sha1").update(ssoSession).digest("hex");
|
|
3028
|
+
const cacheFile = path$2.join(NodeOs.homedir(), ".aws", "sso", "cache", `${hash}.credentials.json`);
|
|
3029
|
+
yield* fs$1.remove(cacheFile).pipe(Effect.catch(() => Effect.void));
|
|
3030
|
+
}).pipe(Effect.catch(() => Effect.void));
|
|
3031
|
+
|
|
3032
|
+
//#endregion
|
|
3033
|
+
//#region lib/Axiom/AuthProvider.js
|
|
3034
|
+
init_AuthProvider();
|
|
3035
|
+
init_Credentials();
|
|
3036
|
+
const STORAGE_KEY = "axiom-stored";
|
|
3037
|
+
const options$4 = [{
|
|
3038
|
+
value: "env",
|
|
3039
|
+
label: "Environment Variables",
|
|
3040
|
+
hint: "AXIOM_TOKEN (or AXIOM_API_KEY) + optional AXIOM_ORG_ID, AXIOM_URL"
|
|
3041
|
+
}, {
|
|
3042
|
+
value: "stored",
|
|
3043
|
+
label: "API Token or Personal Access Token",
|
|
3044
|
+
hint: "enter credentials interactively, stored in ~/.alchemy/credentials"
|
|
3045
|
+
}];
|
|
3046
|
+
const AXIOM_AUTH_PROVIDER_NAME = "Axiom";
|
|
3047
|
+
const promptOrgId = (required) => getEnv("AXIOM_ORG_ID").pipe(Effect.flatMap((envOrgId) => text({
|
|
3048
|
+
message: required ? "Axiom Org ID (required for PAT)" : "Axiom Org ID (Enter to skip)",
|
|
3049
|
+
placeholder: envOrgId ?? "",
|
|
3050
|
+
defaultValue: envOrgId ?? "",
|
|
3051
|
+
validate: required ? (v) => v.length === 0 ? "Required" : void 0 : void 0
|
|
3052
|
+
}).pipe(retryOnce)));
|
|
3053
|
+
/**
|
|
3054
|
+
* Layer that registers the Axiom {@link AuthProvider} into the
|
|
3055
|
+
* {@link AuthProviders} registry when built. Include this in the Axiom
|
|
3056
|
+
* `providers()` layer so `alchemy login` can discover it.
|
|
3057
|
+
*/
|
|
3058
|
+
const AxiomAuth = AuthProviderLayer()(AXIOM_AUTH_PROVIDER_NAME, Effect.gen(function* () {
|
|
3059
|
+
const store = yield* CredentialsStore;
|
|
3060
|
+
const loginStored = Effect.fn(function* (profileName) {
|
|
3061
|
+
const credentialType = yield* select({
|
|
3062
|
+
message: "Axiom credential type",
|
|
3063
|
+
options: [{
|
|
3064
|
+
value: "apiToken",
|
|
3065
|
+
label: "API Token",
|
|
3066
|
+
hint: "recommended — scoped to specific datasets/actions"
|
|
3067
|
+
}, {
|
|
3068
|
+
value: "pat",
|
|
3069
|
+
label: "Personal Access Token",
|
|
3070
|
+
hint: "full account control, requires Org ID"
|
|
3071
|
+
}]
|
|
3072
|
+
}).pipe(retryOnce);
|
|
3073
|
+
const apiBaseUrlPrompt = yield* text({
|
|
3074
|
+
message: "Axiom API Base URL (Enter for default)",
|
|
3075
|
+
placeholder: DEFAULT_API_BASE_URL,
|
|
3076
|
+
defaultValue: DEFAULT_API_BASE_URL
|
|
3077
|
+
}).pipe(retryOnce);
|
|
3078
|
+
const apiBaseUrl = apiBaseUrlPrompt && apiBaseUrlPrompt.length > 0 ? apiBaseUrlPrompt : void 0;
|
|
3079
|
+
return yield* Match.value(credentialType).pipe(Match.when("apiToken", () => Effect.gen(function* () {
|
|
3080
|
+
const apiToken = yield* password({
|
|
3081
|
+
message: "Axiom API Token",
|
|
3082
|
+
validate: (v) => v.length === 0 ? "Required" : void 0
|
|
3083
|
+
}).pipe(retryOnce);
|
|
3084
|
+
const orgId = yield* promptOrgId(false);
|
|
3085
|
+
yield* store.write(profileName, STORAGE_KEY, {
|
|
3086
|
+
type: "apiToken",
|
|
3087
|
+
apiToken,
|
|
3088
|
+
apiBaseUrl,
|
|
3089
|
+
orgId: orgId && orgId.length > 0 ? orgId : void 0
|
|
3090
|
+
});
|
|
3091
|
+
yield* success("Axiom: credentials saved.");
|
|
3092
|
+
return {
|
|
3093
|
+
method: "stored",
|
|
3094
|
+
credentialType: "apiToken"
|
|
3095
|
+
};
|
|
3096
|
+
})), Match.when("pat", () => Effect.gen(function* () {
|
|
3097
|
+
const apiToken = yield* password({
|
|
3098
|
+
message: "Axiom Personal Access Token",
|
|
3099
|
+
validate: (v) => v.length === 0 ? "Required" : void 0
|
|
3100
|
+
}).pipe(retryOnce);
|
|
3101
|
+
const orgId = yield* promptOrgId(true);
|
|
3102
|
+
yield* store.write(profileName, STORAGE_KEY, {
|
|
3103
|
+
type: "pat",
|
|
3104
|
+
apiToken,
|
|
3105
|
+
apiBaseUrl,
|
|
3106
|
+
orgId
|
|
3107
|
+
});
|
|
3108
|
+
yield* success("Axiom: credentials saved.");
|
|
3109
|
+
return {
|
|
3110
|
+
method: "stored",
|
|
3111
|
+
credentialType: "pat"
|
|
3112
|
+
};
|
|
3113
|
+
})), Match.exhaustive);
|
|
3114
|
+
});
|
|
3115
|
+
const configureInteractive = (profileName) => select({
|
|
3116
|
+
message: "Axiom authentication method",
|
|
3117
|
+
options: options$4
|
|
3118
|
+
}).pipe(Effect.flatMap((method) => Match.value(method).pipe(Match.when("env", () => Effect.succeed({ method: "env" })), Match.when("stored", () => loginStored(profileName)), Match.exhaustive)));
|
|
3119
|
+
const configureCredentials = (profileName, ctx) => Effect.gen(function* () {
|
|
3120
|
+
if (ctx.ci) return { method: "env" };
|
|
3121
|
+
return yield* configureInteractive(profileName);
|
|
3122
|
+
}).pipe(Effect.mapError((e) => new AuthError({
|
|
3123
|
+
message: "failed to configure credentials",
|
|
3124
|
+
cause: e
|
|
3125
|
+
})));
|
|
3126
|
+
const resolveCredentials = (profileName, config) => Match.value(config).pipe(Match.when({ method: "env" }, Effect.fn(function* () {
|
|
3127
|
+
const apiToken = (yield* getEnvRedacted("AXIOM_TOKEN")) ?? (yield* getEnvRedacted("AXIOM_API_KEY"));
|
|
3128
|
+
if (!apiToken) return yield* new AuthError({ message: "Axiom env credentials not found. Set AXIOM_TOKEN (or AXIOM_API_KEY)." });
|
|
3129
|
+
const apiBaseUrl = (yield* getEnv("AXIOM_URL")) ?? DEFAULT_API_BASE_URL;
|
|
3130
|
+
const orgId = yield* getEnv("AXIOM_ORG_ID");
|
|
3131
|
+
if (orgId) return {
|
|
3132
|
+
type: "pat",
|
|
3133
|
+
apiToken,
|
|
3134
|
+
apiBaseUrl,
|
|
3135
|
+
orgId,
|
|
3136
|
+
source: { type: "env" }
|
|
3137
|
+
};
|
|
3138
|
+
return {
|
|
3139
|
+
type: "apiToken",
|
|
3140
|
+
apiToken,
|
|
3141
|
+
apiBaseUrl,
|
|
3142
|
+
source: { type: "env" }
|
|
3143
|
+
};
|
|
3144
|
+
})), Match.when({ method: "stored" }, () => store.read(profileName, STORAGE_KEY).pipe(Effect.flatMap((creds) => creds == null ? Effect.fail(new AuthError({ message: "Axiom stored credentials not found. Run: alchemy login --configure" })) : Effect.succeed(Match.value(creds).pipe(Match.when({ type: "apiToken" }, (c$1) => ({
|
|
3145
|
+
type: "apiToken",
|
|
3146
|
+
apiToken: Redacted.make(c$1.apiToken),
|
|
3147
|
+
apiBaseUrl: c$1.apiBaseUrl ?? DEFAULT_API_BASE_URL,
|
|
3148
|
+
orgId: c$1.orgId,
|
|
3149
|
+
source: { type: "stored" }
|
|
3150
|
+
})), Match.when({ type: "pat" }, (c$1) => ({
|
|
3151
|
+
type: "pat",
|
|
3152
|
+
apiToken: Redacted.make(c$1.apiToken),
|
|
3153
|
+
apiBaseUrl: c$1.apiBaseUrl ?? DEFAULT_API_BASE_URL,
|
|
3154
|
+
orgId: c$1.orgId,
|
|
3155
|
+
source: { type: "stored" }
|
|
3156
|
+
})), Match.exhaustive))))), Match.exhaustive);
|
|
3157
|
+
const logout = (profileName, config) => Match.value(config).pipe(Match.when({ method: "env" }, () => Effect.void), Match.when({ method: "stored" }, () => store.delete(profileName, STORAGE_KEY).pipe(Effect.andThen(success("Axiom: stored credentials removed")))), Match.exhaustive);
|
|
3158
|
+
const login = (profileName, config) => Match.value(config).pipe(Match.when({ method: "env" }, () => Effect.void), Match.when({ method: "stored" }, () => store.read(profileName, STORAGE_KEY).pipe(Effect.flatMap((creds) => creds == null ? loginStored(profileName) : Effect.void))), Match.exhaustive).pipe(Effect.mapError((e) => new AuthError({
|
|
3159
|
+
message: "login failed",
|
|
3160
|
+
cause: e
|
|
3161
|
+
})));
|
|
3162
|
+
const prettyPrint = (profileName, config) => resolveCredentials(profileName, config).pipe(Effect.tap((creds) => {
|
|
3163
|
+
const sourceStr = creds.source.details ? `${creds.source.type} - ${creds.source.details}` : creds.source.type;
|
|
3164
|
+
return Match.value(creds).pipe(Match.when({ type: "apiToken" }, (c$1) => Effect.all([
|
|
3165
|
+
Console.log(` apiToken: ${displayRedacted(c$1.apiToken, 9)}`),
|
|
3166
|
+
Console.log(` apiBaseUrl: ${c$1.apiBaseUrl}`),
|
|
3167
|
+
Console.log(` orgId: ${c$1.orgId ?? "(none)"}`),
|
|
3168
|
+
Console.log(` source: ${sourceStr}`)
|
|
3169
|
+
])), Match.when({ type: "pat" }, (c$1) => Effect.all([
|
|
3170
|
+
Console.log(` apiToken: ${displayRedacted(c$1.apiToken, 9)}`),
|
|
3171
|
+
Console.log(` apiBaseUrl: ${c$1.apiBaseUrl}`),
|
|
3172
|
+
Console.log(` orgId: ${c$1.orgId}`),
|
|
3173
|
+
Console.log(` source: ${sourceStr}`)
|
|
3174
|
+
])), Match.exhaustive);
|
|
3175
|
+
}));
|
|
3176
|
+
return {
|
|
3177
|
+
configure: configureCredentials,
|
|
3178
|
+
logout,
|
|
3179
|
+
login,
|
|
3180
|
+
prettyPrint,
|
|
3181
|
+
read: resolveCredentials
|
|
3182
|
+
};
|
|
3183
|
+
}));
|
|
3184
|
+
|
|
3185
|
+
//#endregion
|
|
3186
|
+
//#region lib/Cloudflare/StateStore/CredentialsFile.js
|
|
3187
|
+
/**
|
|
3188
|
+
* Filename (under `~/.alchemy/credentials/{profile}/`) used to cache the
|
|
3189
|
+
* Cloudflare-deployed HTTP state store's endpoint + bearer token.
|
|
3190
|
+
*
|
|
3191
|
+
* Kept in this leaf module (rather than `StateStore/State.ts`) so the
|
|
3192
|
+
* Cloudflare auth provider can invalidate the cache on
|
|
3193
|
+
* `login --configure` / `logout` without importing the heavy state-store
|
|
3194
|
+
* module and creating an import cycle
|
|
3195
|
+
* (`StateStore/State.ts` -> `Providers.ts` -> `Auth/AuthProvider.ts`).
|
|
3196
|
+
*/
|
|
3197
|
+
const CREDENTIALS_FILE = "cloudflare-state-store";
|
|
3198
|
+
|
|
3199
|
+
//#endregion
|
|
3200
|
+
//#region lib/Cloudflare/Auth/OAuthClient.js
|
|
3201
|
+
init_AuthProvider();
|
|
3202
|
+
var OAuthError$1 = class extends Data.TaggedError("OAuthError") {};
|
|
3203
|
+
function generateState$1(length = 32) {
|
|
3204
|
+
return crypto$1.randomBytes(length).toString("base64url");
|
|
3205
|
+
}
|
|
3206
|
+
function generatePKCE(length = 96) {
|
|
3207
|
+
const verifier = crypto$1.randomBytes(length).toString("base64url");
|
|
3208
|
+
return {
|
|
3209
|
+
verifier,
|
|
3210
|
+
challenge: crypto$1.createHash("sha256").update(verifier).digest("base64url")
|
|
3211
|
+
};
|
|
3212
|
+
}
|
|
3213
|
+
function extractCredentials$1(json) {
|
|
3214
|
+
return {
|
|
3215
|
+
type: "oauth",
|
|
3216
|
+
access: json.access_token,
|
|
3217
|
+
refresh: json.refresh_token,
|
|
3218
|
+
expires: Date.now() + json.expires_in * 1e3,
|
|
3219
|
+
scopes: json.scope.split(" ")
|
|
3220
|
+
};
|
|
3221
|
+
}
|
|
3222
|
+
const tokenRequest$1 = (body) => Effect.gen(function* () {
|
|
3223
|
+
const res = yield* Effect.tryPromise({
|
|
3224
|
+
try: () => fetch(OAUTH_ENDPOINTS$1.token, {
|
|
3225
|
+
method: "POST",
|
|
3226
|
+
headers: {
|
|
3227
|
+
Accept: "application/json",
|
|
3228
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
3229
|
+
},
|
|
3230
|
+
body: new URLSearchParams(body).toString()
|
|
3231
|
+
}),
|
|
3232
|
+
catch: (err) => new OAuthError$1({
|
|
3233
|
+
error: "network_error",
|
|
3234
|
+
errorDescription: `Token request failed: ${err}`
|
|
3235
|
+
})
|
|
3236
|
+
});
|
|
3237
|
+
if (!res.ok) {
|
|
3238
|
+
const json = yield* Effect.tryPromise({
|
|
3239
|
+
try: () => res.json(),
|
|
3240
|
+
catch: () => new OAuthError$1({
|
|
3241
|
+
error: "parse_error",
|
|
3242
|
+
errorDescription: `Token endpoint returned ${res.status}`
|
|
3243
|
+
})
|
|
3244
|
+
});
|
|
3245
|
+
return yield* new OAuthError$1({
|
|
3246
|
+
error: json.error,
|
|
3247
|
+
errorDescription: json.error_description
|
|
3248
|
+
});
|
|
3249
|
+
}
|
|
3250
|
+
return extractCredentials$1(yield* Effect.tryPromise({
|
|
3251
|
+
try: () => res.json(),
|
|
3252
|
+
catch: () => new OAuthError$1({
|
|
3253
|
+
error: "parse_error",
|
|
3254
|
+
errorDescription: "Failed to parse token response"
|
|
3255
|
+
})
|
|
3256
|
+
}));
|
|
3257
|
+
});
|
|
3258
|
+
/**
|
|
3259
|
+
* Generate an authorization URL with PKCE for the given scopes.
|
|
3260
|
+
*/
|
|
3261
|
+
function authorize$1(scopes) {
|
|
3262
|
+
const state = generateState$1();
|
|
3263
|
+
const pkce = generatePKCE();
|
|
3264
|
+
const url = new URL(OAUTH_ENDPOINTS$1.authorize);
|
|
3265
|
+
url.searchParams.set("client_id", OAUTH_CLIENT_ID$1);
|
|
3266
|
+
url.searchParams.set("redirect_uri", OAUTH_REDIRECT_URI$1);
|
|
3267
|
+
url.searchParams.set("response_type", "code");
|
|
3268
|
+
url.searchParams.set("scope", scopes.join(" "));
|
|
3269
|
+
url.searchParams.set("state", state);
|
|
3270
|
+
url.searchParams.set("code_challenge", pkce.challenge);
|
|
3271
|
+
url.searchParams.set("code_challenge_method", "S256");
|
|
3272
|
+
return {
|
|
3273
|
+
url: url.toString(),
|
|
3274
|
+
state,
|
|
3275
|
+
verifier: pkce.verifier
|
|
3276
|
+
};
|
|
3277
|
+
}
|
|
3278
|
+
/**
|
|
3279
|
+
* Exchange an authorization code for OAuth credentials.
|
|
3280
|
+
*/
|
|
3281
|
+
const exchange$1 = (code, verifier) => tokenRequest$1({
|
|
3282
|
+
grant_type: "authorization_code",
|
|
3283
|
+
code,
|
|
3284
|
+
code_verifier: verifier,
|
|
3285
|
+
client_id: OAUTH_CLIENT_ID$1,
|
|
3286
|
+
redirect_uri: OAUTH_REDIRECT_URI$1
|
|
3287
|
+
});
|
|
3288
|
+
/**
|
|
3289
|
+
* Refresh expired OAuth credentials.
|
|
3290
|
+
*/
|
|
3291
|
+
const refresh$1 = (credentials) => tokenRequest$1({
|
|
3292
|
+
grant_type: "refresh_token",
|
|
3293
|
+
refresh_token: credentials.refresh,
|
|
3294
|
+
client_id: OAUTH_CLIENT_ID$1,
|
|
3295
|
+
redirect_uri: OAUTH_REDIRECT_URI$1
|
|
3296
|
+
});
|
|
3297
|
+
/**
|
|
3298
|
+
* Revoke OAuth credentials.
|
|
3299
|
+
*/
|
|
3300
|
+
const revoke = (credentials) => Effect.gen(function* () {
|
|
3301
|
+
yield* Effect.tryPromise({
|
|
3302
|
+
try: () => fetch(OAUTH_ENDPOINTS$1.revoke, {
|
|
3303
|
+
method: "POST",
|
|
3304
|
+
headers: {
|
|
3305
|
+
Accept: "application/json",
|
|
3306
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
3307
|
+
},
|
|
3308
|
+
body: new URLSearchParams({
|
|
3309
|
+
refresh_token: credentials.refresh,
|
|
3310
|
+
client_id: OAUTH_CLIENT_ID$1,
|
|
3311
|
+
redirect_uri: OAUTH_REDIRECT_URI$1
|
|
3312
|
+
}).toString()
|
|
3313
|
+
}),
|
|
3314
|
+
catch: (err) => new OAuthError$1({
|
|
3315
|
+
error: "network_error",
|
|
3316
|
+
errorDescription: `Revoke request failed: ${err}`
|
|
3317
|
+
})
|
|
3318
|
+
});
|
|
3319
|
+
});
|
|
3320
|
+
/**
|
|
3321
|
+
* Start a local HTTP server to listen for the OAuth callback, exchange
|
|
3322
|
+
* the authorization code, and return the credentials.
|
|
3323
|
+
*
|
|
3324
|
+
* Times out after 5 minutes.
|
|
3325
|
+
*/
|
|
3326
|
+
const callback$1 = (authorization) => Effect.tryPromise({
|
|
3327
|
+
try: () => callbackPromise$1(authorization),
|
|
3328
|
+
catch: (err) => {
|
|
3329
|
+
if (err instanceof OAuthError$1) return err;
|
|
3330
|
+
return new OAuthError$1({
|
|
3331
|
+
error: "callback_error",
|
|
3332
|
+
errorDescription: `OAuth callback failed: ${err}`
|
|
3333
|
+
});
|
|
3334
|
+
}
|
|
3335
|
+
});
|
|
3336
|
+
function callbackPromise$1(authorization) {
|
|
3337
|
+
const { pathname, port } = new URL(OAUTH_REDIRECT_URI$1);
|
|
3338
|
+
return new Promise((resolve, reject) => {
|
|
3339
|
+
const server = http.createServer(async (req, res) => {
|
|
3340
|
+
const url = new URL(req.url ?? "/", `http://${req.headers.host}`);
|
|
3341
|
+
if (url.pathname !== pathname) {
|
|
3342
|
+
res.statusCode = 404;
|
|
3343
|
+
res.end("Not Found");
|
|
3344
|
+
return;
|
|
3345
|
+
}
|
|
3346
|
+
const error = url.searchParams.get("error");
|
|
3347
|
+
const errorDescription = url.searchParams.get("error_description");
|
|
3348
|
+
if (error) {
|
|
3349
|
+
res.writeHead(302, { Location: AUTH_ERROR_URL });
|
|
3350
|
+
res.end();
|
|
3351
|
+
cleanup();
|
|
3352
|
+
reject(new OAuthError$1({
|
|
3353
|
+
error,
|
|
3354
|
+
errorDescription: errorDescription ?? "An unknown error occurred."
|
|
3355
|
+
}));
|
|
3356
|
+
return;
|
|
3357
|
+
}
|
|
3358
|
+
const code = url.searchParams.get("code");
|
|
3359
|
+
const state = url.searchParams.get("state");
|
|
3360
|
+
if (!code || !state) {
|
|
3361
|
+
res.writeHead(302, { Location: AUTH_ERROR_URL });
|
|
3362
|
+
res.end();
|
|
3363
|
+
cleanup();
|
|
3364
|
+
reject(new OAuthError$1({
|
|
3365
|
+
error: "invalid_request",
|
|
3366
|
+
errorDescription: "Missing code or state"
|
|
3367
|
+
}));
|
|
3368
|
+
return;
|
|
3369
|
+
}
|
|
3370
|
+
if (state !== authorization.state) {
|
|
3371
|
+
res.writeHead(302, { Location: AUTH_ERROR_URL });
|
|
3372
|
+
res.end();
|
|
3373
|
+
cleanup();
|
|
3374
|
+
reject(new OAuthError$1({
|
|
3375
|
+
error: "invalid_request",
|
|
3376
|
+
errorDescription: "Invalid state"
|
|
3377
|
+
}));
|
|
3378
|
+
return;
|
|
3379
|
+
}
|
|
3380
|
+
try {
|
|
3381
|
+
const credentials = await Effect.runPromise(exchange$1(code, authorization.verifier));
|
|
3382
|
+
res.writeHead(302, { Location: AUTH_SUCCESS_URL });
|
|
3383
|
+
res.end();
|
|
3384
|
+
cleanup();
|
|
3385
|
+
resolve(credentials);
|
|
3386
|
+
} catch (err) {
|
|
3387
|
+
res.writeHead(302, { Location: AUTH_ERROR_URL });
|
|
3388
|
+
res.end();
|
|
3389
|
+
cleanup();
|
|
3390
|
+
reject(err);
|
|
3391
|
+
}
|
|
3392
|
+
});
|
|
3393
|
+
const timeout = setTimeout(() => {
|
|
3394
|
+
cleanup();
|
|
3395
|
+
reject(new OAuthError$1({
|
|
3396
|
+
error: "timeout",
|
|
3397
|
+
errorDescription: "The authorization process timed out."
|
|
3398
|
+
}));
|
|
3399
|
+
}, 300 * 1e3);
|
|
3400
|
+
function cleanup() {
|
|
3401
|
+
clearTimeout(timeout);
|
|
3402
|
+
server.close();
|
|
3403
|
+
}
|
|
3404
|
+
server.on("error", (err) => {
|
|
3405
|
+
cleanup();
|
|
3406
|
+
reject(new OAuthError$1({
|
|
3407
|
+
error: "server_error",
|
|
3408
|
+
errorDescription: `Failed to start callback server: ${err.message}`
|
|
3409
|
+
}));
|
|
3410
|
+
});
|
|
3411
|
+
server.listen(Number(port));
|
|
3412
|
+
});
|
|
3413
|
+
}
|
|
3414
|
+
|
|
3415
|
+
//#endregion
|
|
3416
|
+
//#region lib/Cloudflare/Auth/AuthProvider.js
|
|
3417
|
+
init_AuthProvider();
|
|
3418
|
+
init_Credentials();
|
|
3419
|
+
const options$3 = [
|
|
3420
|
+
{
|
|
3421
|
+
value: "oauth",
|
|
3422
|
+
label: "OAuth",
|
|
3423
|
+
hint: "recommended — browser-based login with automatic token refresh"
|
|
3424
|
+
},
|
|
3425
|
+
{
|
|
3426
|
+
value: "env",
|
|
3427
|
+
label: "Environment Variables",
|
|
3428
|
+
hint: "CLOUDFLARE_API_TOKEN or CLOUDFLARE_API_KEY + CLOUDFLARE_EMAIL"
|
|
3429
|
+
},
|
|
3430
|
+
{
|
|
3431
|
+
value: "stored",
|
|
3432
|
+
label: "API Token or API Key",
|
|
3433
|
+
hint: "enter credentials interactively, stored in ~/.alchemy/credentials"
|
|
3434
|
+
}
|
|
3435
|
+
];
|
|
3436
|
+
const CLOUDFLARE_AUTH_PROVIDER_NAME = "Cloudflare";
|
|
3437
|
+
const withOAuthCredentials$1 = (accessToken, effect) => Effect.provide(effect, Layer.mergeAll(CfCredentialsModule.fromOAuth({
|
|
3438
|
+
load: Effect.succeed({ accessToken }),
|
|
3439
|
+
refresh: () => Effect.die("refresh not expected during account selection")
|
|
3440
|
+
}), FetchHttpClient.layer));
|
|
3441
|
+
const selectAccount = (accessToken) => Effect.gen(function* () {
|
|
3442
|
+
const accounts = (yield* (yield* cfAccounts.listAccounts)({})).result;
|
|
3443
|
+
if (accounts.length === 0) return yield* new AuthError({ message: "Cloudflare: no accounts found for this credential." });
|
|
3444
|
+
if (accounts.length === 1) {
|
|
3445
|
+
const account = accounts[0];
|
|
3446
|
+
yield* info(`Cloudflare: using account: ${account.name} (${account.id})`);
|
|
3447
|
+
return account.id;
|
|
3448
|
+
}
|
|
3449
|
+
return yield* select({
|
|
3450
|
+
message: "Select a Cloudflare account",
|
|
3451
|
+
options: accounts.map((a) => ({
|
|
3452
|
+
value: a.id,
|
|
3453
|
+
label: a.name,
|
|
3454
|
+
hint: a.id
|
|
3455
|
+
}))
|
|
3456
|
+
}).pipe(retryOnce);
|
|
3457
|
+
}).pipe((e) => withOAuthCredentials$1(accessToken, e));
|
|
3458
|
+
/**
|
|
3459
|
+
* Cloudflare account IDs are 32 lowercase hex characters. Placeholder
|
|
3460
|
+
* values ("", "-", "dummy", …) end up interpolated into API paths and
|
|
3461
|
+
* surface as baffling `InvalidRoute: Could not route to
|
|
3462
|
+
* /accounts/<value>/...` errors, so reject them up front with an
|
|
3463
|
+
* actionable message instead.
|
|
3464
|
+
*/
|
|
3465
|
+
const ACCOUNT_ID_PATTERN = /^[0-9a-f]{32}$/i;
|
|
3466
|
+
const validateAccountId = (accountId, source) => {
|
|
3467
|
+
const trimmed = accountId?.trim() ?? "";
|
|
3468
|
+
if (trimmed.length === 0) return Effect.fail(new AuthError({ message: `Cloudflare account ID is missing (${source}). Set CLOUDFLARE_ACCOUNT_ID or re-run 'alchemy login' and provide your account ID (found in the Cloudflare dashboard under Workers & Pages → Account details).` }));
|
|
3469
|
+
if (!ACCOUNT_ID_PATTERN.test(trimmed)) return Effect.fail(new AuthError({ message: `'${trimmed}' is not a valid Cloudflare account ID (${source}) — expected 32 hex characters. Copy the account ID from the Cloudflare dashboard (Workers & Pages → Account details) into CLOUDFLARE_ACCOUNT_ID or re-run 'alchemy login'.` }));
|
|
3470
|
+
return Effect.succeed(trimmed.toLowerCase());
|
|
3471
|
+
};
|
|
3472
|
+
const promptAccountId = () => getEnv("CLOUDFLARE_ACCOUNT_ID").pipe(Effect.flatMap((envAccountId) => text({
|
|
3473
|
+
message: "Cloudflare Account ID (Enter to skip)",
|
|
3474
|
+
placeholder: envAccountId ?? "",
|
|
3475
|
+
defaultValue: envAccountId ?? "",
|
|
3476
|
+
validate: (v) => v.trim().length === 0 || ACCOUNT_ID_PATTERN.test(v.trim()) ? void 0 : "Expected a 32-character hex account ID (Workers & Pages → Account details)"
|
|
3477
|
+
}).pipe(retryOnce)));
|
|
3478
|
+
const promptOAuthScopes = () => confirm({
|
|
3479
|
+
message: "Customize OAuth scopes? (default covers typical use cases)",
|
|
3480
|
+
initialValue: false
|
|
3481
|
+
}).pipe(retryOnce, Effect.flatMap((customize) => {
|
|
3482
|
+
if (!customize) return Effect.succeed([...DEFAULT_SCOPES]);
|
|
3483
|
+
return multiselect({
|
|
3484
|
+
message: "Select OAuth scopes",
|
|
3485
|
+
initialValues: DEFAULT_SCOPES,
|
|
3486
|
+
options: Object.entries(ALL_SCOPES).map(([value, hint]) => ({
|
|
3487
|
+
value,
|
|
3488
|
+
label: value,
|
|
3489
|
+
hint
|
|
3490
|
+
})),
|
|
3491
|
+
required: true
|
|
3492
|
+
}).pipe(Effect.map((s) => s), retryOnce);
|
|
3493
|
+
}));
|
|
3494
|
+
/**
|
|
3495
|
+
* Layer that registers the Cloudflare {@link AuthProvider} into the
|
|
3496
|
+
* {@link AuthProviders} registry when built. Include this in the Cloudflare
|
|
3497
|
+
* `providers()` layer so `alchemy login` can discover it.
|
|
3498
|
+
*/
|
|
3499
|
+
const CloudflareAuth = AuthProviderLayer()(CLOUDFLARE_AUTH_PROVIDER_NAME, Effect.gen(function* () {
|
|
3500
|
+
const store = yield* CredentialsStore;
|
|
3501
|
+
const oauthLogin = (profileName, scopes) => Effect.gen(function* () {
|
|
3502
|
+
const authorization = authorize$1([...scopes, "offline_access"]);
|
|
3503
|
+
yield* info("Cloudflare: opening browser for OAuth login...");
|
|
3504
|
+
yield* info(authorization.url);
|
|
3505
|
+
yield* openUrl(authorization.url).pipe(Effect.catch(() => warn("Cloudflare: could not open browser automatically. Please open the URL above manually.")));
|
|
3506
|
+
yield* info("Cloudflare: waiting for authorization (up to 5 minutes)...");
|
|
3507
|
+
const credentials = yield* callback$1(authorization);
|
|
3508
|
+
yield* store.write(profileName, "cf-oauth", credentials);
|
|
3509
|
+
yield* success("Cloudflare: OAuth credentials saved.");
|
|
3510
|
+
return credentials;
|
|
3511
|
+
});
|
|
3512
|
+
const loginStored = Effect.fn(function* (profileName) {
|
|
3513
|
+
const credentialType = yield* select({
|
|
3514
|
+
message: "Cloudflare credential type",
|
|
3515
|
+
options: [{
|
|
3516
|
+
value: "apiToken",
|
|
3517
|
+
label: "API Token",
|
|
3518
|
+
hint: "recommended"
|
|
3519
|
+
}, {
|
|
3520
|
+
value: "apiKey",
|
|
3521
|
+
label: "API Key + Email"
|
|
3522
|
+
}]
|
|
3523
|
+
}).pipe(retryOnce);
|
|
3524
|
+
return yield* Match.value(credentialType).pipe(Match.when("apiToken", () => Effect.gen(function* () {
|
|
3525
|
+
const apiToken = yield* password({
|
|
3526
|
+
message: "Cloudflare API Token",
|
|
3527
|
+
validate: (v) => v.length === 0 ? "Required" : void 0
|
|
3528
|
+
}).pipe(retryOnce);
|
|
3529
|
+
const accountId = yield* promptAccountId();
|
|
3530
|
+
yield* store.write(profileName, "cf-stored", {
|
|
3531
|
+
type: "apiToken",
|
|
3532
|
+
apiToken,
|
|
3533
|
+
accountId
|
|
3534
|
+
});
|
|
3535
|
+
yield* success("Cloudflare: credentials saved.");
|
|
3536
|
+
return {
|
|
3537
|
+
method: "stored",
|
|
3538
|
+
credentialType: "apiToken"
|
|
3539
|
+
};
|
|
3540
|
+
})), Match.when("apiKey", () => Effect.gen(function* () {
|
|
3541
|
+
const apiKey = yield* text({
|
|
3542
|
+
message: "Cloudflare API Key",
|
|
3543
|
+
validate: (v) => v.length === 0 ? "Required" : void 0
|
|
3544
|
+
}).pipe(retryOnce);
|
|
3545
|
+
const email = yield* text({
|
|
3546
|
+
message: "Cloudflare Email",
|
|
3547
|
+
validate: (v) => v.length === 0 ? "Required" : void 0
|
|
3548
|
+
}).pipe(retryOnce);
|
|
3549
|
+
const accountId = yield* promptAccountId();
|
|
3550
|
+
yield* store.write(profileName, "cf-stored", {
|
|
3551
|
+
type: "apiKey",
|
|
3552
|
+
apiKey,
|
|
3553
|
+
email,
|
|
3554
|
+
accountId
|
|
3555
|
+
});
|
|
3556
|
+
yield* success("Cloudflare: credentials saved.");
|
|
3557
|
+
return {
|
|
3558
|
+
method: "stored",
|
|
3559
|
+
credentialType: "apiKey"
|
|
3560
|
+
};
|
|
3561
|
+
})), Match.exhaustive);
|
|
3562
|
+
});
|
|
3563
|
+
const configureOAuth = Effect.fn(function* (profileName) {
|
|
3564
|
+
const scopes = yield* promptOAuthScopes();
|
|
3565
|
+
return {
|
|
3566
|
+
method: "oauth",
|
|
3567
|
+
scopes,
|
|
3568
|
+
accountId: yield* selectAccount((yield* oauthLogin(profileName, scopes)).access).pipe(Effect.mapError((e) => new AuthError({
|
|
3569
|
+
message: "Cloudflare: could not list accounts",
|
|
3570
|
+
cause: e
|
|
3571
|
+
})))
|
|
3572
|
+
};
|
|
3573
|
+
});
|
|
3574
|
+
const configureInteractive = (profileName) => select({
|
|
3575
|
+
message: "Cloudflare authentication method",
|
|
3576
|
+
options: options$3
|
|
3577
|
+
}).pipe(Effect.flatMap((method) => Match.value(method).pipe(Match.when("env", () => Effect.succeed({ method: "env" })), Match.when("oauth", () => configureOAuth(profileName)), Match.when("stored", () => loginStored(profileName)), Match.exhaustive)));
|
|
3578
|
+
const configureCredentials = (profileName, ctx) => Effect.gen(function* () {
|
|
3579
|
+
const config = ctx.ci ? { method: "env" } : yield* configureInteractive(profileName);
|
|
3580
|
+
yield* store.delete(profileName, CREDENTIALS_FILE).pipe(Effect.ignore);
|
|
3581
|
+
return config;
|
|
3582
|
+
}).pipe(Effect.mapError((e) => new AuthError({
|
|
3583
|
+
message: "failed to configure credentials",
|
|
3584
|
+
cause: e
|
|
3585
|
+
})));
|
|
3586
|
+
const resolveCredentials = (profileName, config) => Match.value(config).pipe(Match.when({ method: "env" }, Effect.fn(function* () {
|
|
3587
|
+
const accountId = yield* getEnvRequired("CLOUDFLARE_ACCOUNT_ID").pipe(Effect.flatMap((id) => validateAccountId(id, "from the CLOUDFLARE_ACCOUNT_ID env var")));
|
|
3588
|
+
const apiToken = yield* getEnvRedacted("CLOUDFLARE_API_TOKEN");
|
|
3589
|
+
if (apiToken) return {
|
|
3590
|
+
type: "apiToken",
|
|
3591
|
+
apiToken,
|
|
3592
|
+
accountId,
|
|
3593
|
+
source: { type: "env" }
|
|
3594
|
+
};
|
|
3595
|
+
const apiKey = yield* getEnvRedacted("CLOUDFLARE_API_KEY");
|
|
3596
|
+
const email = yield* getEnvRedacted("CLOUDFLARE_EMAIL");
|
|
3597
|
+
if (apiKey && email) return {
|
|
3598
|
+
type: "apiKey",
|
|
3599
|
+
apiKey,
|
|
3600
|
+
email,
|
|
3601
|
+
accountId,
|
|
3602
|
+
source: { type: "env" }
|
|
3603
|
+
};
|
|
3604
|
+
return yield* new AuthError({ message: "Cloudflare env credentials not found. Set CLOUDFLARE_API_TOKEN or CLOUDFLARE_API_KEY + CLOUDFLARE_EMAIL." });
|
|
3605
|
+
})), Match.when({ method: "stored" }, () => store.read(profileName, "cf-stored").pipe(Effect.flatMap((creds) => creds == null ? Effect.fail(new AuthError({ message: "Cloudflare stored credentials not found. Run: alchemy login --configure" })) : Effect.gen(function* () {
|
|
3606
|
+
const envAccountId = yield* getEnv("CLOUDFLARE_ACCOUNT_ID");
|
|
3607
|
+
const accountId = yield* validateAccountId(creds.accountId?.trim() || envAccountId, `stored for profile '${profileName}'`);
|
|
3608
|
+
return Match.value(creds).pipe(Match.when({ type: "apiToken" }, (c$1) => ({
|
|
3609
|
+
type: "apiToken",
|
|
3610
|
+
apiToken: Redacted.make(c$1.apiToken),
|
|
3611
|
+
accountId,
|
|
3612
|
+
source: { type: "stored" }
|
|
3613
|
+
})), Match.when({ type: "apiKey" }, (c$1) => ({
|
|
3614
|
+
type: "apiKey",
|
|
3615
|
+
apiKey: Redacted.make(c$1.apiKey),
|
|
3616
|
+
email: Redacted.make(c$1.email),
|
|
3617
|
+
accountId,
|
|
3618
|
+
source: { type: "stored" }
|
|
3619
|
+
})), Match.exhaustive);
|
|
3620
|
+
})))), Match.when({ method: "oauth" }, (cfg) => Effect.gen(function* () {
|
|
3621
|
+
const accountId = yield* validateAccountId(cfg.accountId, `configured for profile '${profileName}'`);
|
|
3622
|
+
const creds = yield* store.read(profileName, "cf-oauth");
|
|
3623
|
+
if (creds == null || creds.type !== "oauth") return yield* Effect.fail(new AuthError({ message: "Cloudflare OAuth credentials not found. Run: alchemy login" }));
|
|
3624
|
+
const fresh = creds.expires > Date.now() + 1e4 ? creds : yield* refresh$1(creds).pipe(Effect.tap((refreshed) => store.write(profileName, "cf-oauth", refreshed)), Effect.mapError((e) => new AuthError({
|
|
3625
|
+
message: "Cloudflare OAuth refresh failed. Run: alchemy login",
|
|
3626
|
+
cause: e
|
|
3627
|
+
})));
|
|
3628
|
+
return {
|
|
3629
|
+
type: "oauth",
|
|
3630
|
+
accessToken: Redacted.make(fresh.access),
|
|
3631
|
+
expires: fresh.expires,
|
|
3632
|
+
accountId,
|
|
3633
|
+
source: { type: "oauth" }
|
|
3634
|
+
};
|
|
3635
|
+
})), Match.exhaustive);
|
|
3636
|
+
const logout = (profileName, config) => Match.value(config).pipe(Match.when({ method: "env" }, () => Effect.void), Match.when({ method: "stored" }, () => store.delete(profileName, "cf-stored").pipe(Effect.andThen(success("Cloudflare: stored credentials removed")))), Match.when({ method: "oauth" }, () => store.read(profileName, "cf-oauth").pipe(Effect.tap((creds) => creds?.type === "oauth" ? revoke(creds).pipe(Effect.catchTag("OAuthError", (err) => warn(`Cloudflare: could not revoke OAuth token: ${err.errorDescription}`))) : Effect.void), Effect.andThen(store.delete(profileName, "cf-oauth")), Effect.andThen(success("Cloudflare: OAuth credentials removed.")))), Match.exhaustive).pipe(Effect.andThen(store.delete(profileName, CREDENTIALS_FILE).pipe(Effect.ignore)));
|
|
3637
|
+
const login = (profileName, config) => Match.value(config).pipe(Match.when({ method: "env" }, () => Effect.void), Match.when({ method: "stored" }, () => store.read(profileName, "cf-stored").pipe(Effect.flatMap((creds) => creds == null ? loginStored(profileName) : Effect.void))), Match.when({ method: "oauth" }, (c$1) => Effect.gen(function* () {
|
|
3638
|
+
const creds = yield* store.read(profileName, "cf-oauth");
|
|
3639
|
+
if (creds?.type === "oauth") {
|
|
3640
|
+
yield* info("Cloudflare: refreshing OAuth credentials...");
|
|
3641
|
+
yield* refresh$1(creds).pipe(Effect.flatMap((refreshed) => store.write(profileName, "cf-oauth", refreshed).pipe(Effect.andThen(success("Cloudflare: OAuth credentials refreshed.")))), Effect.catchTag("OAuthError", () => oauthLogin(profileName, c$1.scopes).pipe(Effect.asVoid)));
|
|
3642
|
+
return;
|
|
3643
|
+
}
|
|
3644
|
+
yield* oauthLogin(profileName, c$1.scopes);
|
|
3645
|
+
})), Match.exhaustive).pipe(Effect.mapError((e) => new AuthError({
|
|
3646
|
+
message: "login failed",
|
|
3647
|
+
cause: e
|
|
3648
|
+
})));
|
|
3649
|
+
const prettyPrint = (profileName, config) => resolveCredentials(profileName, config).pipe(Effect.tap((creds) => {
|
|
3650
|
+
const sourceStr = creds.source.details ? `${creds.source.type} - ${creds.source.details}` : creds.source.type;
|
|
3651
|
+
return Match.value(creds).pipe(Match.when({ type: "apiToken" }, (c$1) => Effect.all([
|
|
3652
|
+
Console.log(` apiToken: ${displayRedacted(c$1.apiToken, 9)}`),
|
|
3653
|
+
Console.log(` accountId: ${c$1.accountId}`),
|
|
3654
|
+
Console.log(` source: ${sourceStr}`)
|
|
3655
|
+
])), Match.when({ type: "apiKey" }, (c$1) => Effect.all([
|
|
3656
|
+
Console.log(` apiKey: ${displayRedacted(c$1.apiKey)}`),
|
|
3657
|
+
Console.log(` email: ${displayRedacted(c$1.email)}`),
|
|
3658
|
+
Console.log(` accountId: ${c$1.accountId}`),
|
|
3659
|
+
Console.log(` source: ${sourceStr}`)
|
|
3660
|
+
])), Match.when({ type: "oauth" }, (c$1) => {
|
|
3661
|
+
const remainingMs = c$1.expires - Date.now();
|
|
3662
|
+
const expiresAt = new Date(c$1.expires).toISOString();
|
|
3663
|
+
const expiresStr = remainingMs <= 0 ? `expired (${expiresAt})` : `in ${Duration.format(Duration.millis(remainingMs))} (${expiresAt})`;
|
|
3664
|
+
return Effect.all([
|
|
3665
|
+
Console.log(` accessToken: ${displayRedacted(c$1.accessToken)}`),
|
|
3666
|
+
Console.log(` expires: ${expiresStr}`),
|
|
3667
|
+
Console.log(` accountId: ${c$1.accountId}`),
|
|
3668
|
+
Console.log(` source: ${sourceStr}`)
|
|
3669
|
+
]);
|
|
3670
|
+
}), Match.exhaustive);
|
|
3671
|
+
}));
|
|
3672
|
+
return {
|
|
3673
|
+
configure: configureCredentials,
|
|
3674
|
+
logout,
|
|
3675
|
+
login,
|
|
3676
|
+
prettyPrint,
|
|
3677
|
+
read: resolveCredentials
|
|
3678
|
+
};
|
|
3679
|
+
}));
|
|
3680
|
+
const ALL_SCOPES = {
|
|
3681
|
+
"access:read": "See Cloudflare Access data such as zones, applications, certificates, device postures, groups, identity providers, login counts, organizations, policies, service tokens, and users",
|
|
3682
|
+
"access:write": "See and change Cloudflare Access data such as zones, applications, certificates, device postures, groups, identity providers, login counts, organizations, policies, service tokens, and users",
|
|
3683
|
+
"account:read": "See your account info such as account details, analytics, and memberships",
|
|
3684
|
+
"agw:read": "Grants read level access to Agents Gateway",
|
|
3685
|
+
"agw:run": "Grants run level access to Agents Gateway",
|
|
3686
|
+
"agw:write": "Grants read and write level access to Agents Gateway",
|
|
3687
|
+
"ai:read": "Grants read level access to Workers AI",
|
|
3688
|
+
"ai:write": "Grants write level access to Workers AI",
|
|
3689
|
+
"aiaudit:read": "Grants read level access to AI Audit",
|
|
3690
|
+
"aiaudit:write": "Grants write level access to AI Audit",
|
|
3691
|
+
"aig:read": "Grants read level access to AI Gateway",
|
|
3692
|
+
"aig:write": "Grants write level access to AI Gateway",
|
|
3693
|
+
"auditlogs:read": "View Cloudflare Account Audit Logs",
|
|
3694
|
+
"browser:read": "Grants read level access to Browser Rendering",
|
|
3695
|
+
"browser:write": "Grants write level access to Browser Rendering",
|
|
3696
|
+
"cfone:read": "Grants read level access to Cloudforce One data",
|
|
3697
|
+
"cfone:write": "Grants write level access to Cloudforce One data",
|
|
3698
|
+
"cloudchamber:write": "See and make changes to Cloudchamber",
|
|
3699
|
+
"connectivity:admin": "See, change, and bind to Connectivity Directory services, including creating services targeting Cloudflare Tunnel",
|
|
3700
|
+
"connectivity:bind": "read, list, and bind to Connectivity Directory services, as well as read and list Cloudflare Tunnels",
|
|
3701
|
+
"connectivity:read": "See Connectivity Directory services and Cloudflare Tunnels",
|
|
3702
|
+
"constellation:write": "Grants write access to Constellation configuration and models",
|
|
3703
|
+
"containers:write": "See and make changes to Workers Containers",
|
|
3704
|
+
"d1:write": "See and make changes to D1",
|
|
3705
|
+
"dex:read": "Grants read level access to Cloudflare DEX",
|
|
3706
|
+
"dex:write": "Grants write level access to Cloudflare DEX",
|
|
3707
|
+
"dns_analytics:read": "Grants read level access to Cloudflare DNS Analytics",
|
|
3708
|
+
"dns_records:edit": "Grants edit level access to dns records",
|
|
3709
|
+
"dns_records:read": "Grants read level access to dns records",
|
|
3710
|
+
"dns_settings:read": "Grants read level access to Cloudflare DNS Settings",
|
|
3711
|
+
"firstpartytags:write": "Can see, edit and publish Google tag gateway configuration.",
|
|
3712
|
+
"lb:edit": "Grants edit level access to lb and lb pools",
|
|
3713
|
+
"lb:read": "Grants read level access to lb and lb pools",
|
|
3714
|
+
"logpush:read": "See Cloudflare Logpush data",
|
|
3715
|
+
"logpush:write": "See and change Cloudflare Logpush data",
|
|
3716
|
+
"notification:read": "View Cloudflare Notifications",
|
|
3717
|
+
"notification:write": "View and Modify Cloudflare Notifications",
|
|
3718
|
+
"pages:read": "See Cloudflare Pages projects, settings and deployments",
|
|
3719
|
+
"pages:write": "See and change Cloudflare Pages projects, settings and deployments",
|
|
3720
|
+
"pipelines:read": "Grants read level access to Cloudflare Pipelines",
|
|
3721
|
+
"pipelines:setup": "Grants permission to generate R2 tokens for Workers Pipelines",
|
|
3722
|
+
"pipelines:write": "Grants write level access to Cloudflare Pipelines",
|
|
3723
|
+
"query_cache:write": "See and make changes to Hyperdrive",
|
|
3724
|
+
"queues:write": "See and change Cloudflare Queues settings and data",
|
|
3725
|
+
"r2_catalog:write": "Grants write level access to R2 Data Catalog",
|
|
3726
|
+
"radar:read": "Grants access to read Cloudflare Radar data",
|
|
3727
|
+
"ai-search:read": "Grants read level access to AI Search",
|
|
3728
|
+
"ai-search:write": "Grants write level access to AI Search",
|
|
3729
|
+
"ai-search:run": "Grants run level access to AI Search",
|
|
3730
|
+
"secrets_store:read": "Grants read level access to Secrets Store",
|
|
3731
|
+
"secrets_store:write": "Grants write level access to Secrets Store",
|
|
3732
|
+
"ssl_certs:write": "Grants read and write access to SSL MTLS certificates or Certificate Store",
|
|
3733
|
+
"sso-connector:read": "See Cloudflare SSO connectors",
|
|
3734
|
+
"sso-connector:write": "See Cloudflare SSO connectors to toggle activation and deactivation of SSO",
|
|
3735
|
+
"teams:pii": "See personally identifiable Cloudflare Teams data",
|
|
3736
|
+
"teams:read": "See Cloudflare Teams data such as zones, gateway, and argo tunnel details",
|
|
3737
|
+
"teams:secure_location": "See all DNS Location data but can only change secure DNS Locations",
|
|
3738
|
+
"teams:write": "See and change Cloudflare Teams data such as zones, gateway, and argo tunnel details",
|
|
3739
|
+
"url_scanner:read": "Grants read level access to URL Scanner",
|
|
3740
|
+
"url_scanner:write": "Grants write level access to URL Scanner",
|
|
3741
|
+
"user:read": "See your user info such as name, email address, and account memberships",
|
|
3742
|
+
"vectorize:write": "See and make changes to Vectorize",
|
|
3743
|
+
"workers:read": "See Cloudflare Workers data such as zones, KV storage, R2 storage, scripts, and routes",
|
|
3744
|
+
"workers:write": "See and change Cloudflare Workers data such as zones, KV storage, R2 storage, scripts, and routes",
|
|
3745
|
+
"workers_builds:read": "See Cloudflare Workers Builds data such as builds, build configuration, and build logs",
|
|
3746
|
+
"workers_builds:write": "See and change Cloudflare Workers Builds data such as builds, build configuration, and build logs",
|
|
3747
|
+
"workers_kv:write": "See and change Cloudflare Workers KV Storage data such as keys and namespaces",
|
|
3748
|
+
"workers_observability:read": "Grants read access to Cloudflare Workers Observability",
|
|
3749
|
+
"workers_observability:write": "Grants read and write access to Cloudflare Workers Observability",
|
|
3750
|
+
"workers_observability_telemetry:write": "Grants write access to Cloudflare Workers Observability Telemetry API",
|
|
3751
|
+
"workers_routes:write": "See and change Cloudflare Workers data such as filters and routes",
|
|
3752
|
+
"workers_scripts:write": "See and change Cloudflare Workers scripts, durable objects, subdomains, triggers, and tail data",
|
|
3753
|
+
"workers_tail:read": "See Cloudflare Workers tail and script data",
|
|
3754
|
+
"zone:read": "Grants read level access to account zone"
|
|
3755
|
+
};
|
|
3756
|
+
const DEFAULT_SCOPES = [
|
|
3757
|
+
"account:read",
|
|
3758
|
+
"ai-search:write",
|
|
3759
|
+
"ai-search:run",
|
|
3760
|
+
"ai:write",
|
|
3761
|
+
"aig:read",
|
|
3762
|
+
"aig:write",
|
|
3763
|
+
"cloudchamber:write",
|
|
3764
|
+
"connectivity:admin",
|
|
3765
|
+
"containers:write",
|
|
3766
|
+
"d1:write",
|
|
3767
|
+
"pages:write",
|
|
3768
|
+
"pipelines:write",
|
|
3769
|
+
"queues:write",
|
|
3770
|
+
"secrets_store:write",
|
|
3771
|
+
"ssl_certs:write",
|
|
3772
|
+
"user:read",
|
|
3773
|
+
"vectorize:write",
|
|
3774
|
+
"workers_kv:write",
|
|
3775
|
+
"workers_observability:read",
|
|
3776
|
+
"workers_observability:write",
|
|
3777
|
+
"workers_observability_telemetry:write",
|
|
3778
|
+
"workers_routes:write",
|
|
3779
|
+
"workers_scripts:write",
|
|
3780
|
+
"workers_tail:read",
|
|
3781
|
+
"workers:write",
|
|
3782
|
+
"zone:read"
|
|
3783
|
+
];
|
|
3784
|
+
const OAUTH_CLIENT_ID$1 = "6d8c2255-0773-45f6-b376-2914632e6f91";
|
|
3785
|
+
const OAUTH_REDIRECT_URI$1 = "http://localhost:9976/auth/callback";
|
|
3786
|
+
const OAUTH_ENDPOINTS$1 = {
|
|
3787
|
+
authorize: "https://dash.cloudflare.com/oauth2/authorize",
|
|
3788
|
+
token: "https://dash.cloudflare.com/oauth2/token",
|
|
3789
|
+
revoke: "https://dash.cloudflare.com/oauth2/revoke"
|
|
3790
|
+
};
|
|
3791
|
+
|
|
3792
|
+
//#endregion
|
|
3793
|
+
//#region lib/GitHub/AuthProvider.js
|
|
3794
|
+
init_AuthProvider();
|
|
3795
|
+
init_Credentials();
|
|
3796
|
+
const options$2 = [
|
|
3797
|
+
{
|
|
3798
|
+
value: "gh-cli",
|
|
3799
|
+
label: "GitHub CLI",
|
|
3800
|
+
hint: "delegate to `gh auth token` (run `gh auth login` first)"
|
|
3801
|
+
},
|
|
3802
|
+
{
|
|
3803
|
+
value: "env",
|
|
3804
|
+
label: "Environment Variables",
|
|
3805
|
+
hint: "GITHUB_ACCESS_TOKEN or GITHUB_TOKEN"
|
|
3806
|
+
},
|
|
3807
|
+
{
|
|
3808
|
+
value: "stored",
|
|
3809
|
+
label: "Personal Access Token",
|
|
3810
|
+
hint: "enter PAT interactively, stored in ~/.alchemy/credentials"
|
|
3811
|
+
}
|
|
3812
|
+
];
|
|
3813
|
+
const GITHUB_AUTH_PROVIDER_NAME = "GitHub";
|
|
3814
|
+
var GhCliError = class extends Error {
|
|
3815
|
+
_tag = "GhCliError";
|
|
3816
|
+
};
|
|
3817
|
+
/**
|
|
3818
|
+
* Layer that registers the GitHub {@link AuthProvider} into the
|
|
3819
|
+
* {@link AuthProviders} registry when built. Include this in the GitHub
|
|
3820
|
+
* `providers()` layer so `alchemy login` can discover it.
|
|
3821
|
+
*
|
|
3822
|
+
* Supported methods:
|
|
3823
|
+
* - `gh-cli`: shells out to `gh auth token` (recommended).
|
|
3824
|
+
* - `env`: reads `GITHUB_ACCESS_TOKEN` or `GITHUB_TOKEN`.
|
|
3825
|
+
* - `stored`: prompts for a PAT and writes it to `~/.alchemy/credentials`.
|
|
3826
|
+
*
|
|
3827
|
+
* Browser/device OAuth is intentionally not implemented: GitHub's
|
|
3828
|
+
* OAuth App flow requires a `client_secret` we cannot ship, and
|
|
3829
|
+
* device flow is exactly what `gh auth login` already does.
|
|
3830
|
+
*/
|
|
3831
|
+
const GitHubAuth = AuthProviderLayer()(GITHUB_AUTH_PROVIDER_NAME, Effect.gen(function* () {
|
|
3832
|
+
const store = yield* CredentialsStore;
|
|
3833
|
+
const cp = yield* ChildProcessSpawner;
|
|
3834
|
+
const ghCliToken = () => Effect.gen(function* () {
|
|
3835
|
+
const handle = yield* cp.spawn(ChildProcess$1.make("gh", ["auth", "token"], { shell: false }));
|
|
3836
|
+
const [exitCode, stdout, stderr] = yield* Effect.all([
|
|
3837
|
+
handle.exitCode,
|
|
3838
|
+
Stream.mkString(Stream.decodeText(handle.stdout)),
|
|
3839
|
+
Stream.mkString(Stream.decodeText(handle.stderr))
|
|
3840
|
+
], { concurrency: 3 });
|
|
3841
|
+
if (exitCode !== 0) return yield* Effect.fail(new GhCliError(`gh auth token exited with ${exitCode}: ${stderr.trim() || stdout.trim()}`));
|
|
3842
|
+
const token = stdout.trim();
|
|
3843
|
+
if (!token) return yield* Effect.fail(new GhCliError("gh auth token returned empty output"));
|
|
3844
|
+
return token;
|
|
3845
|
+
}).pipe(Effect.scoped, Effect.mapError((e) => e instanceof GhCliError ? new AuthError({
|
|
3846
|
+
message: e.message,
|
|
3847
|
+
cause: e
|
|
3848
|
+
}) : new AuthError({
|
|
3849
|
+
message: "Could not invoke `gh`. Install GitHub CLI from https://cli.github.com/ and run `gh auth login`.",
|
|
3850
|
+
cause: e
|
|
3851
|
+
})));
|
|
3852
|
+
const loginStored = Effect.fn(function* (profileName) {
|
|
3853
|
+
const token = yield* password({
|
|
3854
|
+
message: "GitHub Personal Access Token (needs `repo` scope; `workflow` for Actions)",
|
|
3855
|
+
validate: (v) => v.length === 0 ? "Required" : void 0
|
|
3856
|
+
}).pipe(retryOnce);
|
|
3857
|
+
yield* store.write(profileName, "gh-stored", {
|
|
3858
|
+
type: "pat",
|
|
3859
|
+
token
|
|
3860
|
+
});
|
|
3861
|
+
yield* success("GitHub: credentials saved.");
|
|
3862
|
+
return { method: "stored" };
|
|
3863
|
+
});
|
|
3864
|
+
const configureInteractive = (profileName) => select({
|
|
3865
|
+
message: "GitHub authentication method",
|
|
3866
|
+
options: options$2
|
|
3867
|
+
}).pipe(Effect.flatMap((method) => Match.value(method).pipe(Match.when("env", () => Effect.succeed({ method: "env" })), Match.when("gh-cli", () => ghCliToken().pipe(Effect.as({ method: "gh-cli" }), Effect.mapError((e) => new AuthError({
|
|
3868
|
+
message: `gh CLI not available: ${e.message}`,
|
|
3869
|
+
cause: e
|
|
3870
|
+
})))), Match.when("stored", () => loginStored(profileName)), Match.exhaustive)));
|
|
3871
|
+
const configureCredentials = (profileName, ctx) => Effect.gen(function* () {
|
|
3872
|
+
if (ctx.ci) return { method: "env" };
|
|
3873
|
+
return yield* configureInteractive(profileName);
|
|
3874
|
+
}).pipe(Effect.mapError((e) => new AuthError({
|
|
3875
|
+
message: "failed to configure credentials",
|
|
3876
|
+
cause: e
|
|
3877
|
+
})));
|
|
3878
|
+
const resolveCredentials = (profileName, config) => Match.value(config).pipe(Match.when({ method: "env" }, Effect.fn(function* () {
|
|
3879
|
+
const access = yield* getEnvRedacted("GITHUB_ACCESS_TOKEN");
|
|
3880
|
+
if (access) return {
|
|
3881
|
+
type: "token",
|
|
3882
|
+
token: access,
|
|
3883
|
+
source: {
|
|
3884
|
+
type: "env",
|
|
3885
|
+
details: "GITHUB_ACCESS_TOKEN"
|
|
3886
|
+
}
|
|
3887
|
+
};
|
|
3888
|
+
const token = yield* getEnvRedacted("GITHUB_TOKEN");
|
|
3889
|
+
if (token) return {
|
|
3890
|
+
type: "token",
|
|
3891
|
+
token,
|
|
3892
|
+
source: {
|
|
3893
|
+
type: "env",
|
|
3894
|
+
details: "GITHUB_TOKEN"
|
|
3895
|
+
}
|
|
3896
|
+
};
|
|
3897
|
+
return yield* new AuthError({ message: "GitHub env credentials not found. Set GITHUB_ACCESS_TOKEN or GITHUB_TOKEN." });
|
|
3898
|
+
})), Match.when({ method: "stored" }, () => store.read(profileName, "gh-stored").pipe(Effect.flatMap((creds) => creds == null ? Effect.fail(new AuthError({ message: "GitHub stored credentials not found. Run: alchemy login --configure" })) : Effect.succeed({
|
|
3899
|
+
type: "token",
|
|
3900
|
+
token: Redacted.make(creds.token),
|
|
3901
|
+
source: { type: "stored" }
|
|
3902
|
+
})))), Match.when({ method: "gh-cli" }, () => ghCliToken().pipe(Effect.map((token) => ({
|
|
3903
|
+
type: "token",
|
|
3904
|
+
token: Redacted.make(token),
|
|
3905
|
+
source: { type: "gh-cli" }
|
|
3906
|
+
})))), Match.exhaustive);
|
|
3907
|
+
const logout = (profileName, config) => Match.value(config).pipe(Match.when({ method: "env" }, () => Effect.void), Match.when({ method: "gh-cli" }, () => Effect.void), Match.when({ method: "stored" }, () => store.delete(profileName, "gh-stored").pipe(Effect.andThen(success("GitHub: stored credentials removed")))), Match.exhaustive);
|
|
3908
|
+
const login = (profileName, config) => Match.value(config).pipe(Match.when({ method: "env" }, () => Effect.void), Match.when({ method: "gh-cli" }, () => ghCliToken().pipe(Effect.tap(() => success("GitHub: gh CLI authentication available.")), Effect.asVoid)), Match.when({ method: "stored" }, () => store.read(profileName, "gh-stored").pipe(Effect.flatMap((creds) => creds == null ? loginStored(profileName) : Effect.void))), Match.exhaustive).pipe(Effect.mapError((e) => new AuthError({
|
|
3909
|
+
message: "login failed",
|
|
3910
|
+
cause: e
|
|
3911
|
+
})));
|
|
3912
|
+
const prettyPrint = (profileName, config) => resolveCredentials(profileName, config).pipe(Effect.tap((creds) => {
|
|
3913
|
+
const sourceStr = creds.source.details ? `${creds.source.type} - ${creds.source.details}` : creds.source.type;
|
|
3914
|
+
return Effect.all([Console.log(` token: ${displayRedacted(creds.token, 6)}`), Console.log(` source: ${sourceStr}`)]);
|
|
3915
|
+
}));
|
|
3916
|
+
return {
|
|
3917
|
+
configure: configureCredentials,
|
|
3918
|
+
logout,
|
|
3919
|
+
login,
|
|
3920
|
+
prettyPrint,
|
|
3921
|
+
read: resolveCredentials
|
|
3922
|
+
};
|
|
3923
|
+
}));
|
|
3924
|
+
|
|
3925
|
+
//#endregion
|
|
3926
|
+
//#region lib/Neon/AuthProvider.js
|
|
3927
|
+
init_AuthProvider();
|
|
3928
|
+
init_Credentials();
|
|
3929
|
+
init_Profile();
|
|
3930
|
+
const NEON_AUTH_PROVIDER_NAME = "Neon";
|
|
3931
|
+
const options$1 = [{
|
|
3932
|
+
value: "env",
|
|
3933
|
+
label: "Environment Variable",
|
|
3934
|
+
hint: "NEON_API_KEY"
|
|
3935
|
+
}, {
|
|
3936
|
+
value: "stored",
|
|
3937
|
+
label: "API Key",
|
|
3938
|
+
hint: "enter interactively, stored in ~/.alchemy/credentials"
|
|
3939
|
+
}];
|
|
3940
|
+
/**
|
|
3941
|
+
* Layer that registers the Neon {@link AuthProvider} into the
|
|
3942
|
+
* {@link AuthProviders} registry.
|
|
3943
|
+
*/
|
|
3944
|
+
const NeonAuth = AuthProviderLayer()(NEON_AUTH_PROVIDER_NAME, Effect.gen(function* () {
|
|
3945
|
+
const profiles = yield* AlchemyProfile;
|
|
3946
|
+
const store = yield* CredentialsStore;
|
|
3947
|
+
const loginStored = Effect.fn(function* (profileName) {
|
|
3948
|
+
const apiKey = yield* password({
|
|
3949
|
+
message: "Neon API Key",
|
|
3950
|
+
validate: (v) => v.length === 0 ? "Required" : void 0
|
|
3951
|
+
}).pipe(retryOnce);
|
|
3952
|
+
yield* store.write(profileName, "neon-stored", {
|
|
3953
|
+
type: "apiKey",
|
|
3954
|
+
apiKey
|
|
3955
|
+
});
|
|
3956
|
+
yield* success("Neon: credentials saved.");
|
|
3957
|
+
return { method: "stored" };
|
|
3958
|
+
});
|
|
3959
|
+
const configureInteractive = (profileName) => select({
|
|
3960
|
+
message: "Neon authentication method",
|
|
3961
|
+
options: options$1
|
|
3962
|
+
}).pipe(Effect.flatMap((method) => Match.value(method).pipe(Match.when("env", () => Effect.succeed({ method: "env" })), Match.when("stored", () => loginStored(profileName)), Match.exhaustive)));
|
|
3963
|
+
const configureCredentials = (profileName, ctx) => Effect.gen(function* () {
|
|
3964
|
+
if (ctx.ci) return { method: "env" };
|
|
3965
|
+
return yield* configureInteractive(profileName);
|
|
3966
|
+
}).pipe(Effect.mapError((e) => new AuthError({
|
|
3967
|
+
message: "failed to configure credentials",
|
|
3968
|
+
cause: e
|
|
3969
|
+
})));
|
|
3970
|
+
const resolveCredentials = (profileName, config) => Match.value(config).pipe(Match.when({ method: "env" }, Effect.fn(function* () {
|
|
3971
|
+
const apiKey = yield* getEnvRedacted("NEON_API_KEY");
|
|
3972
|
+
if (!apiKey) return yield* new AuthError({ message: "Neon env credentials not found. Set NEON_API_KEY." });
|
|
3973
|
+
return {
|
|
3974
|
+
type: "apiKey",
|
|
3975
|
+
apiKey,
|
|
3976
|
+
source: { type: "env" }
|
|
3977
|
+
};
|
|
3978
|
+
})), Match.when({ method: "stored" }, () => store.read(profileName, "neon-stored").pipe(Effect.flatMap((creds) => creds == null ? Effect.fail(new AuthError({ message: "Neon stored credentials not found. Run: alchemy login --configure" })) : Effect.succeed({
|
|
3979
|
+
type: "apiKey",
|
|
3980
|
+
apiKey: Redacted.make(creds.apiKey),
|
|
3981
|
+
source: { type: "stored" }
|
|
3982
|
+
})))), Match.exhaustive);
|
|
3983
|
+
const logout = (profileName, config) => Match.value(config).pipe(Match.when({ method: "env" }, () => Effect.void), Match.when({ method: "stored" }, () => store.delete(profileName, "neon-stored").pipe(Effect.andThen(success("Neon: stored credentials removed")))), Match.exhaustive);
|
|
3984
|
+
const login = (profileName, config) => Match.value(config).pipe(Match.when({ method: "env" }, () => getEnvRedacted("NEON_API_KEY").pipe(Effect.flatMap((apiKey) => apiKey ? Effect.void : Effect.gen(function* () {
|
|
3985
|
+
const next = yield* configureInteractive(profileName);
|
|
3986
|
+
const existing = yield* profiles.getProfile(profileName);
|
|
3987
|
+
yield* profiles.setProfile(profileName, {
|
|
3988
|
+
...existing,
|
|
3989
|
+
[NEON_AUTH_PROVIDER_NAME]: next
|
|
3990
|
+
});
|
|
3991
|
+
})))), Match.when({ method: "stored" }, () => store.read(profileName, "neon-stored").pipe(Effect.flatMap((creds) => creds == null ? loginStored(profileName) : Effect.void))), Match.exhaustive).pipe(Effect.mapError((e) => new AuthError({
|
|
3992
|
+
message: "login failed",
|
|
3993
|
+
cause: e
|
|
3994
|
+
})));
|
|
3995
|
+
const prettyPrint = (profileName, config) => resolveCredentials(profileName, config).pipe(Effect.tap((creds) => {
|
|
3996
|
+
const sourceStr = creds.source.details ? `${creds.source.type} - ${creds.source.details}` : creds.source.type;
|
|
3997
|
+
return Effect.all([Console.log(` apiKey: ${displayRedacted(creds.apiKey, 9)}`), Console.log(` source: ${sourceStr}`)]);
|
|
3998
|
+
}));
|
|
3999
|
+
return {
|
|
4000
|
+
configure: configureCredentials,
|
|
4001
|
+
logout,
|
|
4002
|
+
login,
|
|
4003
|
+
prettyPrint,
|
|
4004
|
+
read: resolveCredentials
|
|
4005
|
+
};
|
|
4006
|
+
}));
|
|
4007
|
+
|
|
4008
|
+
//#endregion
|
|
4009
|
+
//#region lib/Planetscale/OAuthClient.js
|
|
4010
|
+
init_AuthProvider();
|
|
4011
|
+
var OAuthError = class extends Data.TaggedError("OAuthError") {};
|
|
4012
|
+
/**
|
|
4013
|
+
* Registered PlanetScale OAuth application credentials.
|
|
4014
|
+
*
|
|
4015
|
+
* Unlike Cloudflare, PlanetScale OAuth has **no public-client flow** — the
|
|
4016
|
+
* token endpoint requires client authentication for every grant (PKCE is
|
|
4017
|
+
* advertised in their discovery doc but does not lift that requirement), so
|
|
4018
|
+
* exchanging the authorization code (and refreshing the token) requires the
|
|
4019
|
+
* application's `client_secret`. There is no way to keep that secret out of
|
|
4020
|
+
* a distributed CLI, so it ships here: the exposure is the same posture as a
|
|
4021
|
+
* public `client_id` (a stolen refresh token is usable, exactly like
|
|
4022
|
+
* Cloudflare's secret-less refresh), and it can be rotated by cutting a new
|
|
4023
|
+
* release. PlanetScale's own CLI ships its OAuth `client_secret` in source
|
|
4024
|
+
* the same way:
|
|
4025
|
+
* https://github.com/planetscale/cli/blob/main/internal/auth/authenticator.go
|
|
4026
|
+
*
|
|
4027
|
+
* Registered at https://app.planetscale.com with redirect URI
|
|
4028
|
+
* {@link OAUTH_REDIRECT_URI}. Scopes are configured on the application
|
|
4029
|
+
* itself, not requested per-authorization. Rotate by registering a new
|
|
4030
|
+
* secret and cutting a release.
|
|
4031
|
+
*/
|
|
4032
|
+
const OAUTH_CLIENT_ID = "pscale_app_aa12e3938baebb788aac443f66e422da";
|
|
4033
|
+
const OAUTH_CLIENT_SECRET = "pscale_app_secret_yyZ3Q8oe99GP9_yA5wrA5er6RuN6Lz9dC66Bj1OJzpg";
|
|
4034
|
+
const OAUTH_REDIRECT_URI = "http://localhost:9976/auth/callback";
|
|
4035
|
+
const OAUTH_ENDPOINTS = {
|
|
4036
|
+
authorize: "https://app.planetscale.com/oauth/authorize",
|
|
4037
|
+
token: "https://auth.planetscale.com/oauth/token"
|
|
4038
|
+
};
|
|
4039
|
+
function generateState(length = 32) {
|
|
4040
|
+
return crypto$1.randomBytes(length).toString("base64url");
|
|
4041
|
+
}
|
|
4042
|
+
function extractCredentials(json) {
|
|
4043
|
+
return {
|
|
4044
|
+
type: "oauth",
|
|
4045
|
+
access: json.access_token,
|
|
4046
|
+
refresh: json.refresh_token,
|
|
4047
|
+
expires: Date.now() + json.expires_in * 1e3,
|
|
4048
|
+
scopes: json.scope ? json.scope.split(" ") : []
|
|
4049
|
+
};
|
|
4050
|
+
}
|
|
4051
|
+
const tokenRequest = (params) => Effect.gen(function* () {
|
|
4052
|
+
const url = new URL(OAUTH_ENDPOINTS.token);
|
|
4053
|
+
for (const [k, v] of Object.entries(params)) url.searchParams.set(k, v);
|
|
4054
|
+
const res = yield* Effect.tryPromise({
|
|
4055
|
+
try: () => fetch(url.toString(), {
|
|
4056
|
+
method: "POST",
|
|
4057
|
+
headers: { Accept: "application/json" }
|
|
4058
|
+
}),
|
|
4059
|
+
catch: (err) => new OAuthError({
|
|
4060
|
+
error: "network_error",
|
|
4061
|
+
errorDescription: `Token request failed: ${err}`
|
|
4062
|
+
})
|
|
4063
|
+
});
|
|
4064
|
+
if (!res.ok) {
|
|
4065
|
+
const json = yield* Effect.tryPromise({
|
|
4066
|
+
try: () => res.json(),
|
|
4067
|
+
catch: () => new OAuthError({
|
|
4068
|
+
error: "parse_error",
|
|
4069
|
+
errorDescription: `Token endpoint returned ${res.status}`
|
|
4070
|
+
})
|
|
4071
|
+
});
|
|
4072
|
+
return yield* new OAuthError({
|
|
4073
|
+
error: json.error,
|
|
4074
|
+
errorDescription: json.error_description
|
|
4075
|
+
});
|
|
4076
|
+
}
|
|
4077
|
+
return extractCredentials(yield* Effect.tryPromise({
|
|
4078
|
+
try: () => res.json(),
|
|
4079
|
+
catch: () => new OAuthError({
|
|
4080
|
+
error: "parse_error",
|
|
4081
|
+
errorDescription: "Failed to parse token response"
|
|
4082
|
+
})
|
|
4083
|
+
}));
|
|
4084
|
+
});
|
|
4085
|
+
/**
|
|
4086
|
+
* Generate a PlanetScale authorization URL.
|
|
4087
|
+
*
|
|
4088
|
+
* No `scope` parameter is sent: PlanetScale scopes are configured on the
|
|
4089
|
+
* OAuth application itself, not requested per-authorization, so the
|
|
4090
|
+
* consent screen shows whatever the app is registered with.
|
|
4091
|
+
*/
|
|
4092
|
+
function authorize() {
|
|
4093
|
+
const state = generateState();
|
|
4094
|
+
const url = new URL(OAUTH_ENDPOINTS.authorize);
|
|
4095
|
+
url.searchParams.set("client_id", OAUTH_CLIENT_ID);
|
|
4096
|
+
url.searchParams.set("redirect_uri", OAUTH_REDIRECT_URI);
|
|
4097
|
+
url.searchParams.set("response_type", "code");
|
|
4098
|
+
url.searchParams.set("state", state);
|
|
4099
|
+
return {
|
|
4100
|
+
url: url.toString(),
|
|
4101
|
+
state
|
|
4102
|
+
};
|
|
4103
|
+
}
|
|
4104
|
+
/**
|
|
4105
|
+
* Exchange an authorization code for OAuth credentials.
|
|
4106
|
+
*/
|
|
4107
|
+
const exchange = (code) => tokenRequest({
|
|
4108
|
+
grant_type: "authorization_code",
|
|
4109
|
+
code,
|
|
4110
|
+
client_id: OAUTH_CLIENT_ID,
|
|
4111
|
+
client_secret: OAUTH_CLIENT_SECRET,
|
|
4112
|
+
redirect_uri: OAUTH_REDIRECT_URI
|
|
4113
|
+
});
|
|
4114
|
+
/**
|
|
4115
|
+
* Refresh expired OAuth credentials.
|
|
4116
|
+
*/
|
|
4117
|
+
const refresh = (credentials) => tokenRequest({
|
|
4118
|
+
grant_type: "refresh_token",
|
|
4119
|
+
refresh_token: credentials.refresh,
|
|
4120
|
+
client_id: OAUTH_CLIENT_ID,
|
|
4121
|
+
client_secret: OAUTH_CLIENT_SECRET
|
|
4122
|
+
});
|
|
4123
|
+
/**
|
|
4124
|
+
* Start a local HTTP server to listen for the OAuth callback, exchange
|
|
4125
|
+
* the authorization code, and return the credentials.
|
|
4126
|
+
*
|
|
4127
|
+
* Times out after 5 minutes.
|
|
4128
|
+
*/
|
|
4129
|
+
const callback = (authorization) => Effect.tryPromise({
|
|
4130
|
+
try: () => callbackPromise(authorization),
|
|
4131
|
+
catch: (err) => {
|
|
4132
|
+
if (err instanceof OAuthError) return err;
|
|
4133
|
+
return new OAuthError({
|
|
4134
|
+
error: "callback_error",
|
|
4135
|
+
errorDescription: `OAuth callback failed: ${err}`
|
|
4136
|
+
});
|
|
4137
|
+
}
|
|
4138
|
+
});
|
|
4139
|
+
function callbackPromise(authorization) {
|
|
4140
|
+
const { pathname, port } = new URL(OAUTH_REDIRECT_URI);
|
|
4141
|
+
return new Promise((resolve, reject) => {
|
|
4142
|
+
const server = http.createServer(async (req, res) => {
|
|
4143
|
+
const url = new URL(req.url ?? "/", `http://${req.headers.host}`);
|
|
4144
|
+
if (url.pathname !== pathname) {
|
|
4145
|
+
res.statusCode = 404;
|
|
4146
|
+
res.end("Not Found");
|
|
4147
|
+
return;
|
|
4148
|
+
}
|
|
4149
|
+
const error = url.searchParams.get("error");
|
|
4150
|
+
const errorDescription = url.searchParams.get("error_description");
|
|
4151
|
+
if (error) {
|
|
4152
|
+
res.writeHead(302, { Location: AUTH_ERROR_URL });
|
|
4153
|
+
res.end();
|
|
4154
|
+
cleanup();
|
|
4155
|
+
reject(new OAuthError({
|
|
4156
|
+
error,
|
|
4157
|
+
errorDescription: errorDescription ?? "An unknown error occurred."
|
|
4158
|
+
}));
|
|
4159
|
+
return;
|
|
4160
|
+
}
|
|
4161
|
+
const code = url.searchParams.get("code");
|
|
4162
|
+
const state = url.searchParams.get("state");
|
|
4163
|
+
if (!code || !state) {
|
|
4164
|
+
res.writeHead(302, { Location: AUTH_ERROR_URL });
|
|
4165
|
+
res.end();
|
|
4166
|
+
cleanup();
|
|
4167
|
+
reject(new OAuthError({
|
|
4168
|
+
error: "invalid_request",
|
|
4169
|
+
errorDescription: "Missing code or state"
|
|
4170
|
+
}));
|
|
4171
|
+
return;
|
|
4172
|
+
}
|
|
4173
|
+
if (state !== authorization.state) {
|
|
4174
|
+
res.writeHead(302, { Location: AUTH_ERROR_URL });
|
|
4175
|
+
res.end();
|
|
4176
|
+
cleanup();
|
|
4177
|
+
reject(new OAuthError({
|
|
4178
|
+
error: "invalid_request",
|
|
4179
|
+
errorDescription: "Invalid state"
|
|
4180
|
+
}));
|
|
4181
|
+
return;
|
|
4182
|
+
}
|
|
4183
|
+
try {
|
|
4184
|
+
const credentials = await Effect.runPromise(exchange(code));
|
|
4185
|
+
res.writeHead(302, { Location: AUTH_SUCCESS_URL });
|
|
4186
|
+
res.end();
|
|
4187
|
+
cleanup();
|
|
4188
|
+
resolve(credentials);
|
|
4189
|
+
} catch (err) {
|
|
4190
|
+
res.writeHead(302, { Location: AUTH_ERROR_URL });
|
|
4191
|
+
res.end();
|
|
4192
|
+
cleanup();
|
|
4193
|
+
reject(err);
|
|
4194
|
+
}
|
|
4195
|
+
});
|
|
4196
|
+
const timeout = setTimeout(() => {
|
|
4197
|
+
cleanup();
|
|
4198
|
+
reject(new OAuthError({
|
|
4199
|
+
error: "timeout",
|
|
4200
|
+
errorDescription: "The authorization process timed out."
|
|
4201
|
+
}));
|
|
4202
|
+
}, 300 * 1e3);
|
|
4203
|
+
function cleanup() {
|
|
4204
|
+
clearTimeout(timeout);
|
|
4205
|
+
server.close();
|
|
4206
|
+
}
|
|
4207
|
+
server.on("error", (err) => {
|
|
4208
|
+
cleanup();
|
|
4209
|
+
reject(new OAuthError({
|
|
4210
|
+
error: "server_error",
|
|
4211
|
+
errorDescription: `Failed to start callback server: ${err.message}`
|
|
4212
|
+
}));
|
|
4213
|
+
});
|
|
4214
|
+
server.listen(Number(port));
|
|
4215
|
+
});
|
|
4216
|
+
}
|
|
4217
|
+
|
|
4218
|
+
//#endregion
|
|
4219
|
+
//#region lib/Planetscale/AuthProvider.js
|
|
4220
|
+
init_AuthProvider();
|
|
4221
|
+
init_Credentials();
|
|
4222
|
+
/**
|
|
4223
|
+
* Canonical name registered in {@link AuthProviders}. Use this key to look
|
|
4224
|
+
* up the PlanetScale {@link AuthProvider} from inside provider Layers.
|
|
4225
|
+
*/
|
|
4226
|
+
const PLANETSCALE_AUTH_PROVIDER_NAME = "Planetscale";
|
|
4227
|
+
/**
|
|
4228
|
+
* Provide PlanetScale `Credentials` + `HttpClient` to an Effect using a
|
|
4229
|
+
* just-obtained OAuth access token. Used during configure to call
|
|
4230
|
+
* org-discovery endpoints before the user has chosen an org.
|
|
4231
|
+
*
|
|
4232
|
+
* `organization` is required by the credential type but isn't consulted by
|
|
4233
|
+
* `listOrganizations` (it's a user-scoped endpoint), so an empty string is
|
|
4234
|
+
* fine here.
|
|
4235
|
+
*/
|
|
4236
|
+
const withOAuthCredentials = (accessToken, effect) => Effect.provide(effect, Layer.mergeAll(PsCredentialsModule.fromOAuth({
|
|
4237
|
+
accessToken,
|
|
4238
|
+
organization: ""
|
|
4239
|
+
}), FetchHttpClient.layer));
|
|
4240
|
+
/**
|
|
4241
|
+
* List the organizations the OAuth user belongs to and either auto-pick
|
|
4242
|
+
* (one org) or prompt the user to choose. Returns the org's URL slug
|
|
4243
|
+
* (`name` field, used as `{organization}` in API paths).
|
|
4244
|
+
*/
|
|
4245
|
+
const selectOrganization = (accessToken) => Effect.gen(function* () {
|
|
4246
|
+
const orgs = (yield* (yield* listOrganizations)({})).data;
|
|
4247
|
+
if (orgs.length === 0) return yield* new AuthError({ message: "Planetscale: no organizations found for this credential." });
|
|
4248
|
+
if (orgs.length === 1) {
|
|
4249
|
+
const org = orgs[0];
|
|
4250
|
+
yield* info(`Planetscale: using organization: ${org.name} (${org.id})`);
|
|
4251
|
+
return org.name;
|
|
4252
|
+
}
|
|
4253
|
+
return yield* select({
|
|
4254
|
+
message: "Select a Planetscale organization",
|
|
4255
|
+
options: orgs.map((o) => ({
|
|
4256
|
+
value: o.name,
|
|
4257
|
+
label: o.name,
|
|
4258
|
+
hint: o.id
|
|
4259
|
+
}))
|
|
4260
|
+
}).pipe(retryOnce);
|
|
4261
|
+
}).pipe((e) => withOAuthCredentials(accessToken, e));
|
|
4262
|
+
const options = [
|
|
4263
|
+
{
|
|
4264
|
+
value: "env",
|
|
4265
|
+
label: "Environment Variables",
|
|
4266
|
+
hint: "PLANETSCALE_API_TOKEN_ID + PLANETSCALE_API_TOKEN + PLANETSCALE_ORGANIZATION"
|
|
4267
|
+
},
|
|
4268
|
+
{
|
|
4269
|
+
value: "oauth",
|
|
4270
|
+
label: "OAuth",
|
|
4271
|
+
hint: "recommended — browser-based login with automatic token refresh"
|
|
4272
|
+
},
|
|
4273
|
+
{
|
|
4274
|
+
value: "stored",
|
|
4275
|
+
label: "Service Token",
|
|
4276
|
+
hint: "enter service token interactively, stored in ~/.alchemy/credentials"
|
|
4277
|
+
}
|
|
4278
|
+
];
|
|
4279
|
+
/**
|
|
4280
|
+
* Layer that registers the PlanetScale {@link AuthProvider} into the
|
|
4281
|
+
* {@link AuthProviders} registry when built. Include this in the
|
|
4282
|
+
* PlanetScale `providers()` layer so `alchemy login` can discover it.
|
|
4283
|
+
*
|
|
4284
|
+
* Supported methods:
|
|
4285
|
+
* - `env`: reads `PLANETSCALE_API_TOKEN_ID`/`PLANETSCALE_API_TOKEN`/`PLANETSCALE_ORGANIZATION`.
|
|
4286
|
+
* - `stored`: prompts for a service token interactively and writes it to
|
|
4287
|
+
* `~/.alchemy/credentials/<profile>/planetscale-stored.json`.
|
|
4288
|
+
* - `oauth`: browser-based login storing access/refresh tokens at
|
|
4289
|
+
* `~/.alchemy/credentials/<profile>/planetscale-oauth.json`.
|
|
4290
|
+
*/
|
|
4291
|
+
const PlanetscaleAuth = AuthProviderLayer()(PLANETSCALE_AUTH_PROVIDER_NAME, Effect.gen(function* () {
|
|
4292
|
+
const store = yield* CredentialsStore;
|
|
4293
|
+
const oauthLogin = (profileName) => Effect.gen(function* () {
|
|
4294
|
+
const authorization = authorize();
|
|
4295
|
+
yield* info("Planetscale: opening browser for OAuth login...");
|
|
4296
|
+
yield* info(authorization.url);
|
|
4297
|
+
yield* openUrl(authorization.url).pipe(Effect.catch(() => warn("Planetscale: could not open browser automatically. Please open the URL above manually.")));
|
|
4298
|
+
yield* info("Planetscale: waiting for authorization (up to 5 minutes)...");
|
|
4299
|
+
const credentials = yield* callback(authorization);
|
|
4300
|
+
yield* store.write(profileName, "planetscale-oauth", credentials);
|
|
4301
|
+
yield* success("Planetscale: OAuth credentials saved.");
|
|
4302
|
+
return credentials;
|
|
4303
|
+
});
|
|
4304
|
+
const configureOAuth = Effect.fn(function* (profileName) {
|
|
4305
|
+
return {
|
|
4306
|
+
method: "oauth",
|
|
4307
|
+
organization: yield* selectOrganization((yield* oauthLogin(profileName)).access).pipe(Effect.catch((e) => Effect.gen(function* () {
|
|
4308
|
+
yield* warn(`Planetscale: could not auto-list organizations (${String(e)}). Falling back to manual entry.`);
|
|
4309
|
+
return yield* text({
|
|
4310
|
+
message: "Planetscale Organization (URL slug)",
|
|
4311
|
+
validate: (v) => v.length === 0 ? "Required" : void 0
|
|
4312
|
+
}).pipe(retryOnce);
|
|
4313
|
+
})))
|
|
4314
|
+
};
|
|
4315
|
+
});
|
|
4316
|
+
const loginStored = Effect.fn(function* (profileName) {
|
|
4317
|
+
const tokenId = yield* text({
|
|
4318
|
+
message: "Planetscale Service Token ID",
|
|
4319
|
+
validate: (v) => v.length === 0 ? "Required" : void 0
|
|
4320
|
+
}).pipe(retryOnce);
|
|
4321
|
+
const token = yield* password({
|
|
4322
|
+
message: "Planetscale Service Token",
|
|
4323
|
+
validate: (v) => v.length === 0 ? "Required" : void 0
|
|
4324
|
+
}).pipe(retryOnce);
|
|
4325
|
+
const organization = yield* text({
|
|
4326
|
+
message: "Planetscale Organization (URL slug)",
|
|
4327
|
+
validate: (v) => v.length === 0 ? "Required" : void 0
|
|
4328
|
+
}).pipe(retryOnce);
|
|
4329
|
+
yield* store.write(profileName, "planetscale-stored", {
|
|
4330
|
+
type: "apiToken",
|
|
4331
|
+
tokenId,
|
|
4332
|
+
token,
|
|
4333
|
+
organization
|
|
4334
|
+
});
|
|
4335
|
+
yield* success("Planetscale: credentials saved.");
|
|
4336
|
+
return { method: "stored" };
|
|
4337
|
+
});
|
|
4338
|
+
const configureInteractive = (profileName) => select({
|
|
4339
|
+
message: "Planetscale authentication method",
|
|
4340
|
+
options
|
|
4341
|
+
}).pipe(Effect.flatMap((method) => Match.value(method).pipe(Match.when("env", () => Effect.succeed({ method: "env" })), Match.when("oauth", () => configureOAuth(profileName)), Match.when("stored", () => loginStored(profileName)), Match.exhaustive)));
|
|
4342
|
+
const configureCredentials = (profileName, ctx) => Effect.gen(function* () {
|
|
4343
|
+
if (ctx.ci) return { method: "env" };
|
|
4344
|
+
return yield* configureInteractive(profileName);
|
|
4345
|
+
}).pipe(Effect.mapError((e) => new AuthError({
|
|
4346
|
+
message: "failed to configure credentials",
|
|
4347
|
+
cause: e
|
|
4348
|
+
})));
|
|
4349
|
+
const resolveCredentials = (profileName, config) => Match.value(config).pipe(Match.when({ method: "env" }, Effect.fn(function* () {
|
|
4350
|
+
return {
|
|
4351
|
+
type: "apiToken",
|
|
4352
|
+
tokenId: yield* getEnvRedactedRequired("PLANETSCALE_API_TOKEN_ID"),
|
|
4353
|
+
token: yield* getEnvRedactedRequired("PLANETSCALE_API_TOKEN"),
|
|
4354
|
+
organization: yield* getEnvRequired("PLANETSCALE_ORGANIZATION"),
|
|
4355
|
+
source: {
|
|
4356
|
+
type: "env",
|
|
4357
|
+
details: "PLANETSCALE_API_TOKEN_ID/PLANETSCALE_API_TOKEN"
|
|
4358
|
+
}
|
|
4359
|
+
};
|
|
4360
|
+
})), Match.when({ method: "stored" }, () => store.read(profileName, "planetscale-stored").pipe(Effect.flatMap((creds) => creds == null ? Effect.fail(new AuthError({ message: "Planetscale stored credentials not found. Run: alchemy login --configure" })) : Effect.succeed({
|
|
4361
|
+
type: "apiToken",
|
|
4362
|
+
tokenId: Redacted.make(creds.tokenId),
|
|
4363
|
+
token: Redacted.make(creds.token),
|
|
4364
|
+
organization: creds.organization,
|
|
4365
|
+
source: {
|
|
4366
|
+
type: "stored",
|
|
4367
|
+
details: void 0
|
|
4368
|
+
}
|
|
4369
|
+
})))), Match.when({ method: "oauth" }, (cfg) => Effect.gen(function* () {
|
|
4370
|
+
const creds = yield* store.read(profileName, "planetscale-oauth");
|
|
4371
|
+
if (creds == null || creds.type !== "oauth") return yield* Effect.fail(new AuthError({ message: "Planetscale OAuth credentials not found. Run: alchemy login" }));
|
|
4372
|
+
const fresh = creds.expires > Date.now() + 1e4 ? creds : yield* refresh(creds).pipe(Effect.tap((refreshed) => store.write(profileName, "planetscale-oauth", refreshed)), Effect.mapError((e) => new AuthError({
|
|
4373
|
+
message: "Planetscale OAuth refresh failed. Run: alchemy login",
|
|
4374
|
+
cause: e
|
|
4375
|
+
})));
|
|
4376
|
+
return {
|
|
4377
|
+
type: "oauth",
|
|
4378
|
+
accessToken: Redacted.make(fresh.access),
|
|
4379
|
+
expires: fresh.expires,
|
|
4380
|
+
organization: cfg.organization,
|
|
4381
|
+
source: { type: "oauth" }
|
|
4382
|
+
};
|
|
4383
|
+
})), Match.exhaustive);
|
|
4384
|
+
const logout = (profileName, config) => Match.value(config).pipe(Match.when({ method: "env" }, () => Effect.void), Match.when({ method: "stored" }, () => store.delete(profileName, "planetscale-stored").pipe(Effect.andThen(success("Planetscale: stored credentials removed")))), Match.when({ method: "oauth" }, () => store.delete(profileName, "planetscale-oauth").pipe(Effect.andThen(success("Planetscale: OAuth credentials removed.")))), Match.exhaustive);
|
|
4385
|
+
const login = (profileName, config) => Match.value(config).pipe(Match.when({ method: "env" }, () => Effect.void), Match.when({ method: "stored" }, () => store.read(profileName, "planetscale-stored").pipe(Effect.flatMap((creds) => creds == null ? loginStored(profileName) : Effect.void))), Match.when({ method: "oauth" }, (c$1) => Effect.gen(function* () {
|
|
4386
|
+
const creds = yield* store.read(profileName, "planetscale-oauth");
|
|
4387
|
+
if (creds?.type === "oauth") {
|
|
4388
|
+
yield* info("Planetscale: refreshing OAuth credentials...");
|
|
4389
|
+
yield* refresh(creds).pipe(Effect.flatMap((refreshed) => store.write(profileName, "planetscale-oauth", refreshed).pipe(Effect.andThen(success("Planetscale: OAuth credentials refreshed.")))), Effect.catchTag("OAuthError", () => oauthLogin(profileName).pipe(Effect.asVoid)));
|
|
4390
|
+
return;
|
|
4391
|
+
}
|
|
4392
|
+
yield* oauthLogin(profileName);
|
|
4393
|
+
})), Match.exhaustive).pipe(Effect.mapError((e) => new AuthError({
|
|
4394
|
+
message: "login failed",
|
|
4395
|
+
cause: e
|
|
4396
|
+
})));
|
|
4397
|
+
const prettyPrint = (profileName, config) => resolveCredentials(profileName, config).pipe(Effect.tap((creds) => {
|
|
4398
|
+
const sourceStr = creds.source.details ? `${creds.source.type} - ${creds.source.details}` : creds.source.type;
|
|
4399
|
+
return Match.value(creds).pipe(Match.when({ type: "apiToken" }, (c$1) => Effect.all([
|
|
4400
|
+
Console.log(` tokenId: ${displayRedacted(c$1.tokenId, 3)}`),
|
|
4401
|
+
Console.log(` token: ${displayRedacted(c$1.token, 6)}`),
|
|
4402
|
+
Console.log(` organization: ${c$1.organization}`),
|
|
4403
|
+
Console.log(` source: ${sourceStr}`)
|
|
4404
|
+
])), Match.when({ type: "oauth" }, (c$1) => {
|
|
4405
|
+
const remainingMs = c$1.expires - Date.now();
|
|
4406
|
+
const expiresAt = new Date(c$1.expires).toISOString();
|
|
4407
|
+
const expiresStr = remainingMs <= 0 ? `expired (${expiresAt})` : `in ${Duration.format(Duration.millis(remainingMs))} (${expiresAt})`;
|
|
4408
|
+
return Effect.all([
|
|
4409
|
+
Console.log(` accessToken: ${displayRedacted(c$1.accessToken)}`),
|
|
4410
|
+
Console.log(` expires: ${expiresStr}`),
|
|
4411
|
+
Console.log(` organization: ${c$1.organization}`),
|
|
4412
|
+
Console.log(` source: ${sourceStr}`)
|
|
4413
|
+
]);
|
|
4414
|
+
}), Match.exhaustive);
|
|
4415
|
+
}));
|
|
4416
|
+
return {
|
|
4417
|
+
configure: configureCredentials,
|
|
4418
|
+
logout,
|
|
4419
|
+
login,
|
|
4420
|
+
prettyPrint,
|
|
4421
|
+
read: resolveCredentials
|
|
4422
|
+
};
|
|
4423
|
+
}));
|
|
4424
|
+
|
|
2549
4425
|
//#endregion
|
|
2550
4426
|
//#region lib/Cli/commands/_shared.js
|
|
2551
4427
|
init_AuthProvider();
|
|
@@ -2554,11 +4430,11 @@ init_Stack();
|
|
|
2554
4430
|
init_Stage();
|
|
2555
4431
|
init_ConfigProvider();
|
|
2556
4432
|
init_FileLogger();
|
|
2557
|
-
var __rewriteRelativeImportExtension = void 0 && (void 0).__rewriteRelativeImportExtension || function(path$
|
|
2558
|
-
if (typeof path$
|
|
4433
|
+
var __rewriteRelativeImportExtension = void 0 && (void 0).__rewriteRelativeImportExtension || function(path$2, preserveJsx) {
|
|
4434
|
+
if (typeof path$2 === "string" && /^\.\.?\//.test(path$2)) return path$2.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function(m, tsx, d, ext, cm) {
|
|
2559
4435
|
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : d + ext + "." + cm.toLowerCase() + "js";
|
|
2560
4436
|
});
|
|
2561
|
-
return path$
|
|
4437
|
+
return path$2;
|
|
2562
4438
|
};
|
|
2563
4439
|
const USER = Config.string("USER").pipe(Config.orElse(() => Config.string("USERNAME")), Config.withDefault("unknown"));
|
|
2564
4440
|
const STAGE = Config.string("stage").pipe(Config.option, (a) => a, Effect.map(Option.getOrUndefined));
|
|
@@ -2661,16 +4537,32 @@ const placeholderStack = (name) => ({
|
|
|
2661
4537
|
* entrypoint is fatal (login / nuke let it propagate) or best-effort
|
|
2662
4538
|
* (profile show wraps the call in `Effect.catchCause`).
|
|
2663
4539
|
*/
|
|
2664
|
-
const buildStackProviders = Effect.fn("buildStackProviders")(function* (options) {
|
|
2665
|
-
const authProviders = options.registry ?? {};
|
|
2666
|
-
const stackEffect = yield* importStack(options.main);
|
|
2667
|
-
const configProvider = withProfileOverride(yield* loadConfigProvider(options.envFile), options.profile);
|
|
4540
|
+
const buildStackProviders = Effect.fn("buildStackProviders")(function* (options$6) {
|
|
4541
|
+
const authProviders = options$6.registry ?? {};
|
|
4542
|
+
const stackEffect = yield* importStack(options$6.main);
|
|
4543
|
+
const configProvider = withProfileOverride(yield* loadConfigProvider(options$6.envFile), options$6.profile);
|
|
2668
4544
|
return {
|
|
2669
4545
|
authProviders,
|
|
2670
|
-
context: yield* Layer.build((stackEffect.providers ?? Layer.empty).pipe(Layer.provideMerge(stackEffect.state ?? Layer.empty), Layer.provideMerge(Layer.mergeAll(Layer.succeed(AuthProviders, authProviders), ConfigProvider.layer(configProvider), options.logger ?? Logger.layer([fileLogger("out")], { mergeWithExisting: true }), Layer.succeed(Stage, "placeholder"), Layer.succeed(Stack, placeholderStack(stackEffect.stackName)), options.extra ?? Layer.empty)))),
|
|
4546
|
+
context: yield* Layer.build((stackEffect.providers ?? Layer.empty).pipe(Layer.provideMerge(stackEffect.state ?? Layer.empty), Layer.provideMerge(Layer.mergeAll(Layer.succeed(AuthProviders, authProviders), ConfigProvider.layer(configProvider), options$6.logger ?? Logger.layer([fileLogger("out")], { mergeWithExisting: true }), Layer.succeed(Stage, "placeholder"), Layer.succeed(Stack, placeholderStack(stackEffect.stackName)), options$6.extra ?? Layer.empty)))),
|
|
2671
4547
|
stackEffect
|
|
2672
4548
|
};
|
|
2673
4549
|
});
|
|
4550
|
+
/**
|
|
4551
|
+
* The auth providers Alchemy ships with. Used as the baseline registry so
|
|
4552
|
+
* `alchemy login` works from any folder (no `alchemy.run.ts` required) and
|
|
4553
|
+
* `alchemy profile show` can pretty-print any provider a profile mentions,
|
|
4554
|
+
* even one the current stack doesn't wire up.
|
|
4555
|
+
*/
|
|
4556
|
+
const builtinAuth = Layer.mergeAll(AwsAuth, AxiomAuth, CloudflareAuth, GitHubAuth, NeonAuth, PlanetscaleAuth);
|
|
4557
|
+
/**
|
|
4558
|
+
* Build {@link builtinAuth} against `registry` so every built-in auth
|
|
4559
|
+
* provider registers itself, without importing any stack entrypoint.
|
|
4560
|
+
*/
|
|
4561
|
+
const buildBuiltinAuthProviders = Effect.fn("buildBuiltinAuthProviders")(function* (options$6) {
|
|
4562
|
+
const authProviders = options$6.registry ?? {};
|
|
4563
|
+
yield* Layer.build(Layer.provide(builtinAuth, Layer.mergeAll(Layer.succeed(AuthProviders, authProviders), ConfigProvider.layer(withProfileOverride(yield* loadConfigProvider(options$6.envFile), options$6.profile)), Logger.layer([fileLogger("out")], { mergeWithExisting: true }))));
|
|
4564
|
+
return authProviders;
|
|
4565
|
+
});
|
|
2674
4566
|
|
|
2675
4567
|
//#endregion
|
|
2676
4568
|
//#region lib/Cli/commands/deploy.js
|
|
@@ -2847,17 +4739,14 @@ const LoggingCli = Layer.succeed(Cli, Cli.of({
|
|
|
2847
4739
|
fail: 0
|
|
2848
4740
|
};
|
|
2849
4741
|
return {
|
|
2850
|
-
emit: (event) => Effect.
|
|
2851
|
-
if (event.kind === "annotate") {
|
|
2852
|
-
console.log(`${tag(event.id)} ${blue(event.message)}`);
|
|
2853
|
-
return;
|
|
2854
|
-
}
|
|
4742
|
+
emit: (event) => Effect.suspend(() => {
|
|
4743
|
+
if (event.kind === "annotate") return Console.log(`${tag(event.id)} ${blue(event.message)}`);
|
|
2855
4744
|
const id = event.bindingId ? `${event.id}/${event.bindingId}` : event.id;
|
|
2856
4745
|
const status = statusColor$1(event.status)(event.status);
|
|
2857
4746
|
const msg = event.message ? ` ${dim("—")} ${event.message}` : "";
|
|
2858
|
-
console.log(`${tag(id)} ${status}${msg}`);
|
|
2859
4747
|
if (isTerminal(event.status)) if (event.status === "fail") counts.fail++;
|
|
2860
4748
|
else counts.ok++;
|
|
4749
|
+
return Console.log(`${tag(id)} ${status}${msg}`);
|
|
2861
4750
|
}),
|
|
2862
4751
|
done: () => Console.log(`\n${bold("Done:")} ${green(`${counts.ok} succeeded`)}${counts.fail ? dim(", ") + red(`${counts.fail} failed`) : ""}`)
|
|
2863
4752
|
};
|
|
@@ -2874,15 +4763,15 @@ function buildNamespaceTree(items, actions = []) {
|
|
|
2874
4763
|
resources: [],
|
|
2875
4764
|
actions: []
|
|
2876
4765
|
};
|
|
2877
|
-
const getNode = (path$
|
|
4766
|
+
const getNode = (path$2) => {
|
|
2878
4767
|
let current = root;
|
|
2879
|
-
for (let i = 0; i < path$
|
|
2880
|
-
const segment = path$
|
|
4768
|
+
for (let i = 0; i < path$2.length; i++) {
|
|
4769
|
+
const segment = path$2[i];
|
|
2881
4770
|
let child = current.children.get(segment);
|
|
2882
4771
|
if (!child) {
|
|
2883
4772
|
child = {
|
|
2884
4773
|
id: segment,
|
|
2885
|
-
path: path$
|
|
4774
|
+
path: path$2.slice(0, i + 1),
|
|
2886
4775
|
children: /* @__PURE__ */ new Map(),
|
|
2887
4776
|
resources: [],
|
|
2888
4777
|
actions: []
|