@alienplatform/core 1.7.1 → 1.9.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.
Files changed (88) hide show
  1. package/.turbo/turbo-build.log +12 -11
  2. package/dist/index.d.ts +783 -100
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +199 -33
  5. package/dist/index.js.map +1 -1
  6. package/dist/stack.js +579 -44
  7. package/dist/stack.js.map +1 -1
  8. package/dist/tests/index.js +1 -1
  9. package/package.json +1 -1
  10. package/src/__tests__/__snapshots__/stack.test.ts.snap +10 -4
  11. package/src/__tests__/error.test.ts +1 -1
  12. package/src/__tests__/stack.test.ts +184 -2
  13. package/src/compute-cluster.ts +211 -0
  14. package/src/container.ts +38 -26
  15. package/src/daemon.ts +79 -0
  16. package/src/generated/index.ts +42 -2
  17. package/src/generated/schemas/architecture.json +1 -0
  18. package/src/generated/schemas/capacityGroup.json +1 -0
  19. package/src/generated/schemas/capacityGroupScalePolicy.json +1 -0
  20. package/src/generated/schemas/computeChoiceRange.json +1 -0
  21. package/src/generated/schemas/computeCluster.json +1 -0
  22. package/src/generated/schemas/computePoolSelection.json +1 -0
  23. package/src/generated/schemas/computeSettings.json +1 -0
  24. package/src/generated/schemas/container.json +1 -1
  25. package/src/generated/schemas/containerOutputs.json +1 -1
  26. package/src/generated/schemas/containerPort.json +1 -1
  27. package/src/generated/schemas/daemon.json +1 -1
  28. package/src/generated/schemas/daemonOutputs.json +1 -1
  29. package/src/generated/schemas/daemonRuntime.json +1 -0
  30. package/src/generated/schemas/daemonRuntimeMount.json +1 -0
  31. package/src/generated/schemas/exposeProtocol.json +1 -1
  32. package/src/generated/schemas/gpuSpec.json +1 -0
  33. package/src/generated/schemas/machineProfile.json +1 -0
  34. package/src/generated/schemas/publicEndpoint.json +1 -0
  35. package/src/generated/schemas/publicEndpointOutput.json +1 -0
  36. package/src/generated/schemas/stack.json +1 -1
  37. package/src/generated/schemas/stackImportRequest.json +1 -1
  38. package/src/generated/schemas/stackImportResponse.json +1 -1
  39. package/src/generated/schemas/stackInputDefaultValue.json +1 -0
  40. package/src/generated/schemas/stackInputDefinition.json +1 -0
  41. package/src/generated/schemas/stackInputEnvironmentMapping.json +1 -0
  42. package/src/generated/schemas/stackInputEnvironmentVariableType.json +1 -0
  43. package/src/generated/schemas/stackInputKind.json +1 -0
  44. package/src/generated/schemas/stackInputProvider.json +1 -0
  45. package/src/generated/schemas/stackInputValidation.json +1 -0
  46. package/src/generated/schemas/stackSettings.json +1 -1
  47. package/src/generated/schemas/worker.json +1 -1
  48. package/src/generated/schemas/workerOutputs.json +1 -1
  49. package/src/generated/schemas/workerPublicEndpoint.json +1 -0
  50. package/src/generated/zod/architecture-schema.ts +13 -0
  51. package/src/generated/zod/capacity-group-scale-policy-schema.ts +27 -0
  52. package/src/generated/zod/capacity-group-schema.ts +27 -0
  53. package/src/generated/zod/compute-choice-range-schema.ts +17 -0
  54. package/src/generated/zod/compute-cluster-schema.ts +20 -0
  55. package/src/generated/zod/compute-pool-selection-schema.ts +22 -0
  56. package/src/generated/zod/compute-settings-schema.ts +18 -0
  57. package/src/generated/zod/container-outputs-schema.ts +5 -6
  58. package/src/generated/zod/container-port-schema.ts +1 -5
  59. package/src/generated/zod/container-schema.ts +7 -3
  60. package/src/generated/zod/daemon-outputs-schema.ts +4 -0
  61. package/src/generated/zod/daemon-runtime-mount-schema.ts +14 -0
  62. package/src/generated/zod/daemon-runtime-schema.ts +19 -0
  63. package/src/generated/zod/daemon-schema.ts +14 -2
  64. package/src/generated/zod/expose-protocol-schema.ts +2 -2
  65. package/src/generated/zod/gpu-spec-schema.ts +16 -0
  66. package/src/generated/zod/index.ts +42 -2
  67. package/src/generated/zod/machine-profile-schema.ts +25 -0
  68. package/src/generated/zod/public-endpoint-output-schema.ts +21 -0
  69. package/src/generated/zod/public-endpoint-schema.ts +22 -0
  70. package/src/generated/zod/stack-import-request-schema.ts +3 -0
  71. package/src/generated/zod/stack-input-default-value-schema.ts +25 -0
  72. package/src/generated/zod/stack-input-definition-schema.ts +43 -0
  73. package/src/generated/zod/stack-input-environment-mapping-schema.ts +20 -0
  74. package/src/generated/zod/stack-input-environment-variable-type-schema.ts +13 -0
  75. package/src/generated/zod/stack-input-kind-schema.ts +13 -0
  76. package/src/generated/zod/stack-input-provider-schema.ts +13 -0
  77. package/src/generated/zod/stack-input-validation-schema.ts +23 -0
  78. package/src/generated/zod/stack-schema.ts +4 -0
  79. package/src/generated/zod/stack-settings-schema.ts +5 -1
  80. package/src/generated/zod/worker-outputs-schema.ts +4 -5
  81. package/src/generated/zod/worker-public-endpoint-schema.ts +17 -0
  82. package/src/generated/zod/worker-schema.ts +4 -4
  83. package/src/index.ts +9 -0
  84. package/src/input.ts +380 -0
  85. package/src/stack.ts +19 -0
  86. package/src/worker.ts +24 -14
  87. package/src/generated/schemas/ingress.json +0 -1
  88. package/src/generated/zod/ingress-schema.ts +0 -13
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Generated by Kubb (https://kubb.dev/).
3
+ * Do not edit manually.
4
+ */
5
+
6
+ import * as z from "zod";
7
+ import { PlatformSchema } from "./platform-schema.js";
8
+ import { StackInputDefaultValueSchema } from "./stack-input-default-value-schema.js";
9
+ import { StackInputEnvironmentMappingSchema } from "./stack-input-environment-mapping-schema.js";
10
+ import { StackInputKindSchema } from "./stack-input-kind-schema.js";
11
+ import { StackInputProviderSchema } from "./stack-input-provider-schema.js";
12
+ import { StackInputValidationSchema } from "./stack-input-validation-schema.js";
13
+
14
+ /**
15
+ * @description Stack input definition serialized into a release stack.
16
+ */
17
+ export const StackInputDefinitionSchema = z.object({
18
+ get "default"(){
19
+ return z.union([StackInputDefaultValueSchema, z.null()]).optional()
20
+ },
21
+ "description": z.string().describe("Human-facing helper text."),
22
+ get "env"(){
23
+ return z.array(StackInputEnvironmentMappingSchema.describe("How a resolved stack input is injected into runtime environment variables.")).describe("Runtime env-var mappings for v1 input resolution.").optional()
24
+ },
25
+ "id": z.string().describe("Stable input ID used by CLI/API calls."),
26
+ get "kind"(){
27
+ return StackInputKindSchema.describe("Primitive stack input kind.")
28
+ },
29
+ "label": z.string().describe("Human-facing field label."),
30
+ "placeholder": z.string().describe("Example placeholder shown in UI.").nullish(),
31
+ get "platforms"(){
32
+ return z.array(PlatformSchema.describe("Represents the target cloud platform.")).describe("Platforms where this input applies.").nullish()
33
+ },
34
+ get "providedBy"(){
35
+ return z.array(StackInputProviderSchema.describe("Who can provide a stack input value.")).describe("Who can provide this value.")
36
+ },
37
+ "required": z.boolean().describe("Whether a resolved value is required before deployment can proceed."),
38
+ get "validation"(){
39
+ return z.union([StackInputValidationSchema, z.null()]).optional()
40
+ }
41
+ }).describe("Stack input definition serialized into a release stack.")
42
+
43
+ export type StackInputDefinition = z.infer<typeof StackInputDefinitionSchema>
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Generated by Kubb (https://kubb.dev/).
3
+ * Do not edit manually.
4
+ */
5
+
6
+ import * as z from "zod";
7
+ import { StackInputEnvironmentVariableTypeSchema } from "./stack-input-environment-variable-type-schema.js";
8
+
9
+ /**
10
+ * @description How a resolved stack input is injected into runtime environment variables.
11
+ */
12
+ export const StackInputEnvironmentMappingSchema = z.object({
13
+ "name": z.string().describe("Environment variable name."),
14
+ "targetResources": z.array(z.string()).describe("Target resource IDs or patterns. None means every env-capable resource.").nullish(),
15
+ get "type"(){
16
+ return z.union([StackInputEnvironmentVariableTypeSchema, z.null()]).optional()
17
+ }
18
+ }).describe("How a resolved stack input is injected into runtime environment variables.")
19
+
20
+ export type StackInputEnvironmentMapping = z.infer<typeof StackInputEnvironmentMappingSchema>
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Generated by Kubb (https://kubb.dev/).
3
+ * Do not edit manually.
4
+ */
5
+
6
+ import * as z from "zod";
7
+
8
+ /**
9
+ * @description Environment variable handling for a stack input mapping.
10
+ */
11
+ export const StackInputEnvironmentVariableTypeSchema = z.enum(["plain", "secret"]).describe("Environment variable handling for a stack input mapping.")
12
+
13
+ export type StackInputEnvironmentVariableType = z.infer<typeof StackInputEnvironmentVariableTypeSchema>
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Generated by Kubb (https://kubb.dev/).
3
+ * Do not edit manually.
4
+ */
5
+
6
+ import * as z from "zod";
7
+
8
+ /**
9
+ * @description Primitive stack input kind.
10
+ */
11
+ export const StackInputKindSchema = z.enum(["string", "secret", "number", "integer", "boolean", "enum", "stringList"]).describe("Primitive stack input kind.")
12
+
13
+ export type StackInputKind = z.infer<typeof StackInputKindSchema>
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Generated by Kubb (https://kubb.dev/).
3
+ * Do not edit manually.
4
+ */
5
+
6
+ import * as z from "zod";
7
+
8
+ /**
9
+ * @description Who can provide a stack input value.
10
+ */
11
+ export const StackInputProviderSchema = z.enum(["developer", "deployer"]).describe("Who can provide a stack input value.")
12
+
13
+ export type StackInputProvider = z.infer<typeof StackInputProviderSchema>
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Generated by Kubb (https://kubb.dev/).
3
+ * Do not edit manually.
4
+ */
5
+
6
+ import * as z from "zod";
7
+
8
+ /**
9
+ * @description Portable stack input validation constraints.
10
+ */
11
+ export const StackInputValidationSchema = z.object({
12
+ "format": z.string().describe("Semantic format hint such as url.").nullish(),
13
+ "max": z.string().describe("Maximum number.").nullish(),
14
+ "maxItems": z.int().min(0).describe("Maximum string-list items.").nullish(),
15
+ "maxLength": z.int().min(0).describe("Maximum string length.").nullish(),
16
+ "min": z.string().describe("Minimum number.").nullish(),
17
+ "minItems": z.int().min(0).describe("Minimum string-list items.").nullish(),
18
+ "minLength": z.int().min(0).describe("Minimum string length.").nullish(),
19
+ "pattern": z.string().describe("Portable whole-value regex pattern.").nullish(),
20
+ "values": z.array(z.string()).describe("Allowed string enum values.").nullish()
21
+ }).describe("Portable stack input validation constraints.")
22
+
23
+ export type StackInputValidation = z.infer<typeof StackInputValidationSchema>
@@ -7,12 +7,16 @@ import * as z from "zod";
7
7
  import { PermissionsConfigSchema } from "./permissions-config-schema.js";
8
8
  import { PlatformSchema } from "./platform-schema.js";
9
9
  import { ResourceEntrySchema } from "./resource-entry-schema.js";
10
+ import { StackInputDefinitionSchema } from "./stack-input-definition-schema.js";
10
11
 
11
12
  /**
12
13
  * @description A bag of resources, unaware of any cloud.
13
14
  */
14
15
  export const StackSchema = z.object({
15
16
  "id": z.string().describe("Unique identifier for the stack"),
17
+ get "inputs"(){
18
+ return z.array(StackInputDefinitionSchema.describe("Stack input definition serialized into a release stack.")).describe("Input definitions required before setup or deployment can proceed.").optional()
19
+ },
16
20
  get "permissions"(){
17
21
  return PermissionsConfigSchema.describe("Combined permissions configuration that contains both profiles and management").optional()
18
22
  },
@@ -4,6 +4,7 @@
4
4
  */
5
5
 
6
6
  import * as z from "zod";
7
+ import { ComputeSettingsSchema } from "./compute-settings-schema.js";
7
8
  import { DeploymentModelSchema } from "./deployment-model-schema.js";
8
9
  import { DomainSettingsSchema } from "./domain-settings-schema.js";
9
10
  import { HeartbeatsModeSchema } from "./heartbeats-mode-schema.js";
@@ -16,7 +17,10 @@ import { UpdatesModeSchema } from "./updates-mode-schema.js";
16
17
  * @description User-customizable deployment settings specified at deploy time.\n\nThese settings are provided by the customer via CloudFormation parameters,\nTerraform attributes, CLI flags, or Helm values. They customize how the\ndeployment runs and what capabilities are enabled.\n\n**Key distinction**: StackSettings is user-customizable, while ManagementConfig\nis platform-derived (from the Manager\'s ServiceAccount).
17
18
  */
18
19
  export const StackSettingsSchema = z.object({
19
- get "deploymentModel"(){
20
+ get "compute"(){
21
+ return z.union([ComputeSettingsSchema, z.null()]).optional()
22
+ },
23
+ get "deploymentModel"(){
20
24
  return DeploymentModelSchema.describe("Deployment model: how updates are delivered to the remote environment.").optional()
21
25
  },
22
26
  get "domains"(){
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  import * as z from "zod";
7
- import { LoadBalancerEndpointSchema } from "./load-balancer-endpoint-schema.js";
7
+ import { PublicEndpointOutputSchema } from "./public-endpoint-output-schema.js";
8
8
 
9
9
  /**
10
10
  * @description Outputs generated by a successfully provisioned Worker.
@@ -12,10 +12,9 @@ import { LoadBalancerEndpointSchema } from "./load-balancer-endpoint-schema.js";
12
12
  export const WorkerOutputsSchema = z.object({
13
13
  "commandsPushTarget": z.string().describe("Push target for commands delivery. Platform-specific:\n- AWS: Lambda function name or ARN\n- GCP: Full Pub/Sub topic path (projects/{project}/topics/{topic})\n- Azure: Service Bus \"{namespace}/{queue}\"").nullish(),
14
14
  "identifier": z.string().describe("The ARN or platform-specific identifier.").nullish(),
15
- get "loadBalancerEndpoint"(){
16
- return z.union([LoadBalancerEndpointSchema, z.null()]).optional()
17
- },
18
- "url": z.string().describe("The invocation URL (if applicable, e.g., for public ingress or specific platforms).").nullish(),
15
+ "publicEndpoints": z.optional(z.object({
16
+
17
+ }).catchall(z.lazy(() => PublicEndpointOutputSchema).describe("Runtime-resolved public endpoint metadata.")).describe("Public endpoints resolved for this worker.")),
19
18
  "workerName": z.string().describe("The platform-specific worker name.")
20
19
  }).describe("Outputs generated by a successfully provisioned Worker.")
21
20
 
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Generated by Kubb (https://kubb.dev/).
3
+ * Do not edit manually.
4
+ */
5
+
6
+ import * as z from "zod";
7
+
8
+ /**
9
+ * @description Public endpoint configuration for Worker resources.
10
+ */
11
+ export const WorkerPublicEndpointSchema = z.object({
12
+ "hostLabel": z.string().describe("Optional DNS label override for generated endpoint hostnames.").nullish(),
13
+ "name": z.string().describe("Endpoint name within the resource."),
14
+ "wildcardSubdomains": z.optional(z.boolean().describe("Whether to route wildcard subdomains to this endpoint."))
15
+ }).describe("Public endpoint configuration for Worker resources.")
16
+
17
+ export type WorkerPublicEndpoint = z.infer<typeof WorkerPublicEndpointSchema>
@@ -4,10 +4,10 @@
4
4
  */
5
5
 
6
6
  import * as z from "zod";
7
- import { IngressSchema } from "./ingress-schema.js";
8
7
  import { ReadinessProbeSchema } from "./readiness-probe-schema.js";
9
8
  import { ResourceRefSchema } from "./resource-ref-schema.js";
10
9
  import { WorkerCodeSchema } from "./worker-code-schema.js";
10
+ import { WorkerPublicEndpointSchema } from "./worker-public-endpoint-schema.js";
11
11
  import { WorkerTriggerSchema } from "./worker-trigger-schema.js";
12
12
 
13
13
  /**
@@ -23,14 +23,14 @@ export const WorkerSchema = z.object({
23
23
 
24
24
  }).catchall(z.string()).describe("Key-value pairs to set as environment variables for the worker.")),
25
25
  "id": z.string().describe("Identifier for the worker. Must contain only alphanumeric characters, hyphens, and underscores ([A-Za-z0-9-_]).\nMaximum 64 characters."),
26
- get "ingress"(){
27
- return IngressSchema.default("private").optional()
28
- },
29
26
  get "links"(){
30
27
  return z.array(ResourceRefSchema.describe("New ResourceRef that works with any resource type.\nThis can eventually replace the enum-based ResourceRef for full extensibility.")).describe("List of resource references this worker depends on.")
31
28
  },
32
29
  "memoryMb": z.optional(z.int().min(0).default(256).describe("Memory allocated to the worker in megabytes (MB).\nDefault: 256\n\nPlatform-specific constraints:\n- **AWS Lambda**: 128–10240 MB in 1 MB increments\n- **GCP Cloud Run**: 128–32768 MB\n- **Azure Container Apps**: fixed CPU/memory pairs — 512, 1024, 1536, 2048, 2560,\n 3072, 3584, 4096 MB. Values below 512 are automatically rounded up at deploy time.")),
33
30
  "permissions": z.string().describe("Permission profile name that defines the permissions granted to this worker.\nThis references a profile defined in the stack's permission definitions."),
31
+ get "publicEndpoints"(){
32
+ return z.array(WorkerPublicEndpointSchema.describe("Public endpoint configuration for Worker resources.")).describe("Public endpoints exposed by this worker.")
33
+ },
34
34
  get "readinessProbe"(){
35
35
  return z.union([ReadinessProbeSchema, z.null()]).optional()
36
36
  },
package/src/index.ts CHANGED
@@ -2,6 +2,7 @@ export * from "./resource.js"
2
2
  export * from "./storage.js"
3
3
  export * from "./worker.js"
4
4
  export * from "./container.js"
5
+ export * from "./compute-cluster.js"
5
6
  export * from "./daemon.js"
6
7
  export * from "./build.js"
7
8
  export * from "./artifact-registry.js"
@@ -10,6 +11,7 @@ export * from "./kv.js"
10
11
  export * from "./queue.js"
11
12
  export * from "./service-account.js"
12
13
  export * from "./stack.js"
14
+ export * from "./input.js"
13
15
  export * from "./get-resource-outputs.js"
14
16
  export * from "./error.js"
15
17
  export * from "./common-errors.js"
@@ -177,6 +179,13 @@ export {
177
179
  ResourceLifecycleSchema,
178
180
  ResourceRefSchema,
179
181
  StackStateSchema,
182
+ StackInputDefaultValueSchema,
183
+ StackInputDefinitionSchema,
184
+ StackInputEnvironmentMappingSchema,
185
+ StackInputEnvironmentVariableTypeSchema,
186
+ StackInputKindSchema,
187
+ StackInputProviderSchema,
188
+ StackInputValidationSchema,
180
189
  AwsArtifactRegistryImportDataSchema,
181
190
  AwsBuildImportDataSchema,
182
191
  AwsComputeClusterImportDataSchema,
package/src/input.ts ADDED
@@ -0,0 +1,380 @@
1
+ import type {
2
+ Platform,
3
+ StackInputDefaultValue,
4
+ StackInputDefinition,
5
+ StackInputEnvironmentMapping,
6
+ StackInputEnvironmentVariableType,
7
+ StackInputKind,
8
+ StackInputProvider,
9
+ StackInputValidation,
10
+ } from "./generated/index.js"
11
+
12
+ const stackInputDraftSymbol = Symbol("alien.stackInputDraft")
13
+ const stackInputDefinitionsSymbol = Symbol("alien.stackInputDefinitions")
14
+
15
+ export type StackInputValue = string | number | boolean | string[]
16
+
17
+ type OneOrMany<T> = T | readonly T[]
18
+
19
+ export type StackInputEnvMapping =
20
+ | string
21
+ | {
22
+ name: string
23
+ targetResources?: readonly string[]
24
+ type?: StackInputEnvironmentVariableType
25
+ }
26
+
27
+ interface CommonInputOptions<TDefault extends StackInputValue> {
28
+ providedBy: OneOrMany<StackInputProvider>
29
+ required: boolean
30
+ label: string
31
+ description: string
32
+ placeholder?: string
33
+ default?: TDefault
34
+ platforms?: readonly Platform[]
35
+ env?: OneOrMany<StackInputEnvMapping>
36
+ }
37
+
38
+ export interface StringInputOptions extends CommonInputOptions<string> {
39
+ minLength?: number
40
+ maxLength?: number
41
+ pattern?: string
42
+ format?: string
43
+ }
44
+
45
+ export interface SecretInputOptions extends Omit<CommonInputOptions<string>, "default"> {
46
+ minLength?: number
47
+ maxLength?: number
48
+ pattern?: string
49
+ format?: string
50
+ }
51
+
52
+ export interface NumberInputOptions extends CommonInputOptions<number> {
53
+ min?: number
54
+ max?: number
55
+ }
56
+
57
+ export interface IntegerInputOptions extends CommonInputOptions<number> {
58
+ min?: number
59
+ max?: number
60
+ }
61
+
62
+ export interface BooleanInputOptions extends CommonInputOptions<boolean> {}
63
+
64
+ export interface EnumInputOptions extends CommonInputOptions<string> {
65
+ placeholder?: string
66
+ }
67
+
68
+ export interface StringListInputOptions extends CommonInputOptions<string[]> {
69
+ minItems?: number
70
+ maxItems?: number
71
+ }
72
+
73
+ export interface StackInputRef<TValue extends StackInputValue = StackInputValue> {
74
+ readonly id: string
75
+ readonly kind: StackInputKind
76
+ readonly __value?: TValue
77
+ }
78
+
79
+ export type StackInputCollection = {
80
+ readonly [stackInputDefinitionsSymbol]: readonly StackInputDefinition[]
81
+ }
82
+
83
+ interface StackInputDraft<TValue extends StackInputValue = StackInputValue> {
84
+ readonly [stackInputDraftSymbol]: true
85
+ readonly kind: StackInputKind
86
+ readonly options: CommonInputOptions<TValue>
87
+ readonly validation: StackInputValidation
88
+ }
89
+
90
+ export type StackInputSet<T extends Record<string, StackInputDraft>> = {
91
+ readonly [K in keyof T]: T[K] extends StackInputDraft<infer TValue>
92
+ ? StackInputRef<TValue>
93
+ : never
94
+ } & StackInputCollection
95
+
96
+ export function inputs<const T extends Record<string, StackInputDraft>>(
97
+ drafts: T,
98
+ ): StackInputSet<T> {
99
+ const result = {} as Record<string, StackInputRef>
100
+ const definitions: StackInputDefinition[] = []
101
+
102
+ for (const [id, draft] of Object.entries(drafts)) {
103
+ validateInputId(id)
104
+ validateDraft(id, draft)
105
+
106
+ result[id] = { id, kind: draft.kind }
107
+ definitions.push({
108
+ id,
109
+ kind: draft.kind,
110
+ providedBy: normalizeArray(draft.options.providedBy),
111
+ required: draft.options.required,
112
+ label: draft.options.label,
113
+ description: draft.options.description,
114
+ placeholder: draft.options.placeholder,
115
+ default: toDefaultValue(draft.kind, draft.options.default),
116
+ platforms: draft.options.platforms ? [...draft.options.platforms] : undefined,
117
+ validation: Object.keys(draft.validation).length > 0 ? draft.validation : undefined,
118
+ env: normalizeEnv(draft.options.env),
119
+ })
120
+ }
121
+
122
+ Object.defineProperty(result, stackInputDefinitionsSymbol, {
123
+ value: Object.freeze(definitions),
124
+ enumerable: false,
125
+ })
126
+
127
+ return result as StackInputSet<T>
128
+ }
129
+
130
+ export function getStackInputDefinitions(
131
+ value: StackInputCollection | readonly StackInputDefinition[],
132
+ ): StackInputDefinition[] {
133
+ if (Array.isArray(value)) {
134
+ return [...(value as readonly StackInputDefinition[])]
135
+ }
136
+
137
+ return [...(value as StackInputCollection)[stackInputDefinitionsSymbol]]
138
+ }
139
+
140
+ function defineInput<TValue extends StackInputValue>(
141
+ kind: StackInputKind,
142
+ options: CommonInputOptions<TValue>,
143
+ validation: StackInputValidation = {},
144
+ ): StackInputDraft<TValue> {
145
+ return {
146
+ [stackInputDraftSymbol]: true,
147
+ kind,
148
+ options,
149
+ validation,
150
+ }
151
+ }
152
+
153
+ function defineStringInput(options: StringInputOptions): StackInputDraft<string> {
154
+ return defineInput("string", options, {
155
+ minLength: options.minLength,
156
+ maxLength: options.maxLength,
157
+ pattern: options.pattern,
158
+ format: options.format,
159
+ })
160
+ }
161
+
162
+ function defineSecretInput(options: SecretInputOptions): StackInputDraft<string> {
163
+ return defineInput("secret", options, {
164
+ minLength: options.minLength,
165
+ maxLength: options.maxLength,
166
+ pattern: options.pattern,
167
+ format: options.format,
168
+ })
169
+ }
170
+
171
+ function defineNumberInput(options: NumberInputOptions): StackInputDraft<number> {
172
+ return defineInput("number", options, {
173
+ min: numberToString(options.min),
174
+ max: numberToString(options.max),
175
+ })
176
+ }
177
+
178
+ function defineIntegerInput(options: IntegerInputOptions): StackInputDraft<number> {
179
+ return defineInput("integer", options, {
180
+ min: integerToString(options.min, "min"),
181
+ max: integerToString(options.max, "max"),
182
+ })
183
+ }
184
+
185
+ function defineBooleanInput(options: BooleanInputOptions): StackInputDraft<boolean> {
186
+ return defineInput("boolean", options)
187
+ }
188
+
189
+ function defineEnumInput<const TValues extends readonly [string, ...string[]]>(
190
+ values: TValues,
191
+ options: EnumInputOptions,
192
+ ): StackInputDraft<TValues[number]> {
193
+ return defineInput("enum", options, {
194
+ values: [...values],
195
+ }) as StackInputDraft<TValues[number]>
196
+ }
197
+
198
+ function defineStringListInput(options: StringListInputOptions): StackInputDraft<string[]> {
199
+ return defineInput("stringList", options, {
200
+ minItems: options.minItems,
201
+ maxItems: options.maxItems,
202
+ })
203
+ }
204
+
205
+ function normalizeArray<T>(value: OneOrMany<T>): T[] {
206
+ return Array.isArray(value) ? [...(value as readonly T[])] : [value as T]
207
+ }
208
+
209
+ function normalizeEnv(
210
+ value: OneOrMany<StackInputEnvMapping> | undefined,
211
+ ): StackInputEnvironmentMapping[] {
212
+ if (!value) {
213
+ return []
214
+ }
215
+
216
+ return normalizeArray(value).map(mapping => {
217
+ if (typeof mapping === "string") {
218
+ return { name: mapping }
219
+ }
220
+
221
+ return {
222
+ name: mapping.name,
223
+ targetResources: mapping.targetResources ? [...mapping.targetResources] : undefined,
224
+ type: mapping.type,
225
+ }
226
+ })
227
+ }
228
+
229
+ function toDefaultValue(
230
+ kind: StackInputKind,
231
+ value: StackInputValue | undefined,
232
+ ): StackInputDefaultValue | undefined {
233
+ if (value === undefined) {
234
+ return undefined
235
+ }
236
+
237
+ switch (kind) {
238
+ case "string":
239
+ case "enum":
240
+ return { type: "string", value: String(value) }
241
+ case "number":
242
+ return { type: "number", value: numberToString(value as number) ?? "" }
243
+ case "integer":
244
+ return { type: "number", value: integerToString(value as number, "default") ?? "" }
245
+ case "boolean":
246
+ return { type: "boolean", value: Boolean(value) }
247
+ case "stringList":
248
+ return { type: "stringList", value: [...(value as string[])] }
249
+ case "secret":
250
+ throw new Error("Secret stack inputs cannot declare a default value")
251
+ }
252
+ }
253
+
254
+ function validateDraft(id: string, draft: StackInputDraft): void {
255
+ if (!draft || draft[stackInputDraftSymbol] !== true) {
256
+ throw new Error(
257
+ `Stack input '${id}' must be created with alien.string(), alien.secret(), or another input helper`,
258
+ )
259
+ }
260
+
261
+ if (normalizeArray(draft.options.providedBy).length === 0) {
262
+ throw new Error(`Stack input '${id}' must include at least one providedBy value`)
263
+ }
264
+
265
+ if (!draft.options.label.trim()) {
266
+ throw new Error(`Stack input '${id}' must include a label`)
267
+ }
268
+
269
+ if (draft.options.required && !draft.options.description.trim()) {
270
+ throw new Error(`Stack input '${id}' must include a description when required is true`)
271
+ }
272
+
273
+ validateValidation(id, draft.kind, draft.validation)
274
+
275
+ for (const mapping of normalizeEnv(draft.options.env)) {
276
+ validateEnvName(id, mapping.name)
277
+ if (mapping.targetResources?.length === 0) {
278
+ throw new Error(
279
+ `Stack input '${id}' env mapping '${mapping.name}' cannot use an empty targetResources list`,
280
+ )
281
+ }
282
+ }
283
+ }
284
+
285
+ function validateInputId(id: string): void {
286
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(id)) {
287
+ throw new Error(
288
+ `Stack input '${id}' id must start with a letter or underscore and contain only letters, digits, and underscores`,
289
+ )
290
+ }
291
+
292
+ if (!/[a-z]/.test(id)) {
293
+ throw new Error(`Stack input '${id}' id must not be all-caps env-var style`)
294
+ }
295
+ }
296
+
297
+ function validateEnvName(id: string, name: string): void {
298
+ if (!/^[A-Z_][A-Z0-9_]*$/.test(name)) {
299
+ throw new Error(
300
+ `Stack input '${id}' env name '${name}' must use uppercase letters, digits, and underscores`,
301
+ )
302
+ }
303
+ }
304
+
305
+ function validateValidation(
306
+ id: string,
307
+ kind: StackInputKind,
308
+ validation: StackInputValidation,
309
+ ): void {
310
+ const minLength = validation.minLength ?? undefined
311
+ const maxLength = validation.maxLength ?? undefined
312
+ const minItems = validation.minItems ?? undefined
313
+ const maxItems = validation.maxItems ?? undefined
314
+
315
+ if (minLength !== undefined && maxLength !== undefined && minLength > maxLength) {
316
+ throw new Error(`Stack input '${id}' minLength must be less than or equal to maxLength`)
317
+ }
318
+
319
+ if (minItems !== undefined && maxItems !== undefined && minItems > maxItems) {
320
+ throw new Error(`Stack input '${id}' minItems must be less than or equal to maxItems`)
321
+ }
322
+
323
+ if (kind === "enum" && (!validation.values || validation.values.length === 0)) {
324
+ throw new Error(`Stack input '${id}' enum values must not be empty`)
325
+ }
326
+
327
+ if (validation.pattern) {
328
+ validatePortablePattern(id, validation.pattern)
329
+ }
330
+ }
331
+
332
+ function validatePortablePattern(id: string, pattern: string): void {
333
+ if (pattern.length === 0) {
334
+ throw new Error(`Stack input '${id}' pattern must not be empty`)
335
+ }
336
+
337
+ if (pattern.startsWith("/") && pattern.endsWith("/") && pattern.length > 1) {
338
+ throw new Error(`Stack input '${id}' pattern must not use regex delimiters`)
339
+ }
340
+
341
+ if (/\\[1-9pPk]/.test(pattern) || /\(\?/.test(pattern) || /\[[^\]]*\[/.test(pattern)) {
342
+ throw new Error(
343
+ `Stack input '${id}' pattern is not portable across TypeScript, CloudFormation, and Terraform`,
344
+ )
345
+ }
346
+ }
347
+
348
+ function numberToString(value: number | undefined): string | undefined {
349
+ if (value === undefined) {
350
+ return undefined
351
+ }
352
+
353
+ if (!Number.isFinite(value)) {
354
+ throw new Error("Stack input number constraints must be finite")
355
+ }
356
+
357
+ return String(value)
358
+ }
359
+
360
+ function integerToString(value: number | undefined, field: string): string | undefined {
361
+ if (value === undefined) {
362
+ return undefined
363
+ }
364
+
365
+ if (!Number.isInteger(value)) {
366
+ throw new Error(`Stack input integer ${field} must be an integer`)
367
+ }
368
+
369
+ return String(value)
370
+ }
371
+
372
+ export {
373
+ defineBooleanInput as boolean,
374
+ defineEnumInput as enum,
375
+ defineIntegerInput as integer,
376
+ defineNumberInput as number,
377
+ defineSecretInput as secret,
378
+ defineStringInput as string,
379
+ defineStringListInput as stringList,
380
+ }