@ts-cloud/core 0.9.4 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,130 @@
1
+ import type { CloudConfig, EnvironmentType, MailServiceConfig, MailServiceMode } from './types';
2
+ /**
3
+ * The one place that decides what "mail" means for an environment.
4
+ *
5
+ * `services.mail` is deliberately allowed to be `true`, because the useful
6
+ * declaration is "this project sends email" and not "this project runs an MTA
7
+ * on 25 with DKIM keys for two domains and a smarthost". Everything after that
8
+ * follows from the environment: production wants a real server, a preview box
9
+ * wants a trap. Both are the same binary, which is the point - see
10
+ * {@link MailServiceMode}.
11
+ *
12
+ * The provisioner, the firewall, the DNS planner and the `.env` writer all
13
+ * resolve through here so they cannot disagree about which ports are open,
14
+ * which hostname is announced, or where the webmail UI is. That mattered
15
+ * immediately: a catcher whose SMTP port the `.env` said was 1025 and whose
16
+ * firewall rule said 25 is a box where mail silently goes nowhere.
17
+ */
18
+ /** Everything about the mail service, with nothing left to infer. */
19
+ export interface ResolvedMailService {
20
+ /** Whether mail is provisioned at all. */
21
+ enabled: boolean;
22
+ /** Real MTA or local trap. */
23
+ mode: MailServiceMode;
24
+ /** Pinned version, or `undefined` for the latest release. */
25
+ version?: string;
26
+ /** The FQDN the server announces and signs as. */
27
+ hostname: string;
28
+ /** Every domain delivered to mailboxes here, `hostname`'s parent included. */
29
+ domains: string[];
30
+ /** Resolved listening ports; a port that is off is absent. */
31
+ ports: ResolvedMailPorts;
32
+ /** TLS, resolved. `acme` implies the server obtains its own certificate. */
33
+ tls: {
34
+ enabled: boolean;
35
+ acme: boolean;
36
+ certPath?: string;
37
+ keyPath?: string;
38
+ requireForAuth: boolean;
39
+ acmeEmail?: string;
40
+ };
41
+ /** DKIM, resolved. Off means the server signs nothing. */
42
+ dkim: {
43
+ enabled: boolean;
44
+ selector: string;
45
+ rotate: boolean;
46
+ rotateIntervalDays: number;
47
+ };
48
+ /** Webmail UI, resolved. */
49
+ webmail: {
50
+ enabled: boolean;
51
+ port: number;
52
+ domain?: string;
53
+ };
54
+ /** How outbound mail leaves. */
55
+ delivery: 'direct' | 'ses' | 'none';
56
+ /** SES region for `delivery: 'ses'`. */
57
+ sesRegion: string;
58
+ /** Mailboxes created on provision. */
59
+ accounts: Array<{
60
+ address: string;
61
+ password: string;
62
+ }>;
63
+ /** Where mailboxes, the database and the DKIM keys live. */
64
+ storagePath: string;
65
+ /** Largest accepted message, in bytes. */
66
+ maxMessageSize: number;
67
+ /** Inbound spam handling, resolved. */
68
+ spam: {
69
+ enabled: boolean;
70
+ enforce: boolean;
71
+ junkScore: number;
72
+ rejectScore: number;
73
+ dnsbl: boolean;
74
+ greylist: boolean;
75
+ };
76
+ /** Where received mail is POSTed, when configured. */
77
+ webhookUrl?: string;
78
+ /** Whether the mail ports face the internet. */
79
+ expose: boolean;
80
+ /** The address the listeners bind to, which follows from {@link expose}. */
81
+ bindAddress: string;
82
+ }
83
+ /** Resolved ports. A protocol this mode does not serve is absent, not zero. */
84
+ export interface ResolvedMailPorts {
85
+ smtp: number;
86
+ submission?: number;
87
+ submissions?: number;
88
+ imap?: number;
89
+ imaps?: number;
90
+ webmail: number;
91
+ managesieve?: number;
92
+ }
93
+ /** Whether a `services.mail` declaration asks for anything at all. */
94
+ export declare function mailEnabled(value: boolean | MailServiceConfig | undefined): boolean;
95
+ export interface ResolveMailOptions {
96
+ /** Which environment is being provisioned, e.g. `'production'`. */
97
+ environment?: string;
98
+ /**
99
+ * Its type, when the caller already knows it. Otherwise it is read from
100
+ * `config.environments[environment].type`, and an unknown environment is
101
+ * treated as non-production - which is the safe direction: the mistake that
102
+ * costs something is provisioning an open relay, not provisioning a trap.
103
+ */
104
+ environmentType?: EnvironmentType;
105
+ }
106
+ /**
107
+ * Resolve `infrastructure.compute.managedServices.mail` for an environment.
108
+ *
109
+ * Returns `enabled: false` (and defaults everywhere else) when mail is not
110
+ * declared, so callers can read the shape unconditionally instead of guarding
111
+ * every field.
112
+ */
113
+ export declare function resolveMailService(config: Pick<CloudConfig, 'project' | 'sites' | 'environments' | 'infrastructure'>, options?: ResolveMailOptions): ResolvedMailService;
114
+ /**
115
+ * The ports the host firewall has to open for this mail service.
116
+ *
117
+ * Empty when mail is not exposed, which is the whole reason this is a function
118
+ * rather than a list somebody maintains beside the resolver: a catcher's ports
119
+ * must never end up in an allow list.
120
+ */
121
+ export declare function mailFirewallPorts(mail: ResolvedMailService): number[];
122
+ /**
123
+ * `.env` pairs pointing an application at this mail service, in the shape the
124
+ * Stacks/Laravel families of framework read.
125
+ *
126
+ * Merge into a site's `env` so `MAIL_*` is set from the same resolution the
127
+ * provisioner used. The alternative - a hand-copied `MAIL_PORT` - is the
128
+ * mistake this whole module exists to make impossible.
129
+ */
130
+ export declare function buildMailEnv(mail: ResolvedMailService): Record<string, string>;
@@ -119,6 +119,35 @@ export interface CloudDriver {
119
119
  findComputeTargets(options: FindComputeTargetsOptions): Promise<ComputeTarget[]>;
120
120
  /** Run a shell script on every target (SSM, SSH, etc.) */
121
121
  runRemoteDeploy(options: RunRemoteDeployOptions): Promise<RemoteDeployResult>;
122
+ /**
123
+ * Enumerate every resource this driver's credential can see — and therefore,
124
+ * on providers without per-resource scoping, write to and delete.
125
+ *
126
+ * Exists so nothing above the driver has to assume "one all-powerful token"
127
+ * is the only credential shape. Attaching to another project's box works by
128
+ * LISTING the provider with the attaching project's own credential, so the
129
+ * radius is a property of that credential rather than of the config, and only
130
+ * the driver holding it can report it. A driver whose credential IS narrowly
131
+ * scoped simply enumerates less, and the same reporting comes out right
132
+ * without a special case.
133
+ *
134
+ * Optional: a driver that cannot enumerate omits it, and callers report no
135
+ * radius rather than a wrong one.
136
+ *
137
+ * @see https://github.com/stacksjs/ts-cloud/issues/169
138
+ */
139
+ listReachableResources?(): Promise<ReachableResource[]>;
140
+ }
141
+ /**
142
+ * One resource a provider credential can reach, reduced to what attribution
143
+ * needs: a name to print and the labels that say who owns it.
144
+ *
145
+ * Deliberately structural and provider-agnostic — a Hetzner server satisfies it
146
+ * as-is, and another driver can satisfy it without importing anything.
147
+ */
148
+ export interface ReachableResource {
149
+ name: string;
150
+ labels?: Record<string, string>;
122
151
  }
123
152
  export interface DeploySiteReleaseOptions {
124
153
  config: CloudConfig;
package/dist/index.d.ts CHANGED
@@ -5,6 +5,7 @@ export * from './types';
5
5
  export * from './state-dir';
6
6
  export * from './deployment-mode';
7
7
  export * from './app-database';
8
+ export * from './app-mail';
8
9
  export * from './drivers';
9
10
  export * from './template-builder';
10
11
  export { validateTemplate, validateTemplateSize, validateResourceLimits, type ValidationResult, } from './template-validator';