@alienplatform/testing 0.1.0 → 1.3.3

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/LICENSE.md ADDED
@@ -0,0 +1,105 @@
1
+ # Functional Source License, Version 1.1, Apache 2.0 Future License
2
+
3
+ ## Abbreviation
4
+
5
+ FSL-1.1-Apache-2.0
6
+
7
+ ## Notice
8
+
9
+ Copyright 2026 Alien Software, Inc.
10
+
11
+ ## Terms and Conditions
12
+
13
+ ### Licensor ("We")
14
+
15
+ The party offering the Software under these Terms and Conditions.
16
+
17
+ ### The Software
18
+
19
+ The "Software" is each version of the software that we make available under
20
+ these Terms and Conditions, as indicated by our inclusion of these Terms and
21
+ Conditions with the Software.
22
+
23
+ ### License Grant
24
+
25
+ Subject to your compliance with this License Grant and the Patents,
26
+ Redistribution and Trademark clauses below, we hereby grant you the right to
27
+ use, copy, modify, create derivative works, publicly perform, publicly display
28
+ and redistribute the Software for any Permitted Purpose identified below.
29
+
30
+ ### Permitted Purpose
31
+
32
+ A Permitted Purpose is any purpose other than a Competing Use. A Competing Use
33
+ means making the Software available to others in a commercial product or
34
+ service that:
35
+
36
+ 1. substitutes for the Software;
37
+
38
+ 2. substitutes for any other product or service we offer using the Software
39
+ that exists as of the date we make the Software available; or
40
+
41
+ 3. offers the same or substantially similar functionality as the Software.
42
+
43
+ Permitted Purposes specifically include using the Software:
44
+
45
+ 1. for your internal use and access;
46
+
47
+ 2. for non-commercial education;
48
+
49
+ 3. for non-commercial research; and
50
+
51
+ 4. in connection with professional services that you provide to a licensee
52
+ using the Software in accordance with these Terms and Conditions.
53
+
54
+ ### Patents
55
+
56
+ To the extent your use for a Permitted Purpose would necessarily infringe our
57
+ patents, the license grant above includes a license under our patents. If you
58
+ make a claim against any party that the Software infringes or contributes to
59
+ the infringement of any patent, then your patent license to the Software ends
60
+ immediately.
61
+
62
+ ### Redistribution
63
+
64
+ The Terms and Conditions apply to all copies, modifications and derivatives of
65
+ the Software.
66
+
67
+ If you redistribute any copies, modifications or derivatives of the Software,
68
+ you must include a copy of or a link to these Terms and Conditions and not
69
+ remove any copyright notices provided in or with the Software.
70
+
71
+ ### Disclaimer
72
+
73
+ THE SOFTWARE IS PROVIDED "AS IS" AND WITHOUT WARRANTIES OF ANY KIND, EXPRESS OR
74
+ IMPLIED, INCLUDING WITHOUT LIMITATION WARRANTIES OF FITNESS FOR A PARTICULAR
75
+ PURPOSE, MERCHANTABILITY, TITLE OR NON-INFRINGEMENT.
76
+
77
+ IN NO EVENT WILL WE HAVE ANY LIABILITY TO YOU ARISING OUT OF OR RELATED TO THE
78
+ SOFTWARE, INCLUDING INDIRECT, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES,
79
+ EVEN IF WE HAVE BEEN INFORMED OF THEIR POSSIBILITY IN ADVANCE.
80
+
81
+ ### Trademarks
82
+
83
+ Except for displaying the License Details and identifying us as the origin of
84
+ the Software, you have no right under these Terms and Conditions to use our
85
+ trademarks, trade names, service marks or product names.
86
+
87
+ ## Grant of Future License
88
+
89
+ We hereby irrevocably grant you an additional license to use the Software under
90
+ the Apache License, Version 2.0 that is effective on the second anniversary of
91
+ the date we make the Software available. On or after that date, you may use the
92
+ Software under the Apache License, Version 2.0, in which case the following
93
+ will apply:
94
+
95
+ Licensed under the Apache License, Version 2.0 (the "License"); you may not use
96
+ this file except in compliance with the License.
97
+
98
+ You may obtain a copy of the License at
99
+
100
+ http://www.apache.org/licenses/LICENSE-2.0
101
+
102
+ Unless required by applicable law or agreed to in writing, software distributed
103
+ under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
104
+ CONDITIONS OF ANY KIND, either express or implied. See the License for the
105
+ specific language governing permissions and limitations under the License.
package/README.md CHANGED
@@ -1,320 +1,107 @@
1
1
  # @alienplatform/testing
2
2
 
3
- Testing framework for Alien applications. Deploy, test, and tear down Alien apps in real environments.
3
+ Testing framework for Alien applications.
4
4
 
5
- ## Installation
5
+ ## Install
6
6
 
7
7
  ```bash
8
- npm install @alienplatform/testing
8
+ npm install --save-dev @alienplatform/testing
9
9
  ```
10
10
 
11
- ## Quick Start
11
+ ## Local vs Cloud
12
12
 
13
- ```typescript
14
- import { deploy } from "@alienplatform/testing"
15
-
16
- const deployment = await deploy({
17
- app: "./my-app",
18
- platform: "aws",
19
- workspace: "my-workspace",
20
- project: "my-project",
21
- })
22
-
23
- // Test your deployment
24
- const response = await fetch(`${deployment.url}/api/test`)
25
- expect(response.status).toBe(200)
13
+ This package has two deployment modes:
26
14
 
27
- // Cleanup
28
- await deployment.destroy()
29
- ```
30
-
31
- ## Authentication
32
-
33
- ### API Key
34
-
35
- Set your Alien API key:
36
-
37
- ```bash
38
- export ALIEN_API_KEY="your-key"
39
- # or
40
- alien login
41
- ```
15
+ - local mode uses `alien dev`
16
+ - cloud mode uses the platform API
42
17
 
43
- ### Platform Credentials
18
+ That split is intentional. Local mode stays centered on the shipped `alien` CLI binary instead of asking users to discover lower-level manager binaries.
44
19
 
45
- Credentials are **optional**. When not provided, deployers use standard environment variables.
20
+ ## Local Mode
46
21
 
47
- #### AWS
48
- ```bash
49
- export AWS_ACCESS_KEY_ID="..."
50
- export AWS_SECRET_ACCESS_KEY="..."
51
- export AWS_REGION="us-east-1"
52
- ```
22
+ Local mode is the default:
53
23
 
54
- Or pass explicitly:
55
24
  ```typescript
56
- credentials: {
57
- platform: "aws",
58
- accessKeyId: "...",
59
- secretAccessKey: "...",
60
- region: "us-east-1",
61
- }
62
- ```
25
+ import { afterAll, beforeAll, describe, expect, it } from "vitest"
26
+ import { deploy, type Deployment } from "@alienplatform/testing"
63
27
 
64
- #### GCP
65
- ```bash
66
- export GOOGLE_APPLICATION_CREDENTIALS="/path/to/key.json"
67
- export GCP_PROJECT_ID="my-project"
68
- ```
28
+ describe("My App", () => {
29
+ let deployment: Deployment
69
30
 
70
- Or pass explicitly:
71
- ```typescript
72
- credentials: {
73
- platform: "gcp",
74
- projectId: "my-project",
75
- serviceAccountKeyPath: "/path/to/key.json",
76
- // or
77
- serviceAccountKeyJson: "{ ... }",
78
- }
79
- ```
31
+ beforeAll(async () => {
32
+ deployment = await deploy({ app: "./my-app" })
33
+ }, 300_000)
80
34
 
81
- #### Azure
82
- ```bash
83
- export AZURE_SUBSCRIPTION_ID="..."
84
- export AZURE_TENANT_ID="..."
85
- export AZURE_CLIENT_ID="..."
86
- export AZURE_CLIENT_SECRET="..."
87
- ```
35
+ afterAll(async () => {
36
+ await deployment?.destroy()
37
+ })
88
38
 
89
- Or pass explicitly:
90
- ```typescript
91
- credentials: {
92
- platform: "azure",
93
- subscriptionId: "...",
94
- tenantId: "...",
95
- clientId: "...",
96
- clientSecret: "...",
97
- }
98
- ```
99
-
100
- #### Kubernetes
101
- ```bash
102
- export KUBECONFIG="~/.kube/config"
103
- ```
104
-
105
- Or pass explicitly:
106
- ```typescript
107
- credentials: {
108
- platform: "kubernetes",
109
- kubeconfigPath: "~/.kube/config",
110
- }
111
- ```
112
-
113
- ## Deployment Methods
114
-
115
- ### API (Default)
116
-
117
- Fastest method. Agent Manager deploys directly:
118
-
119
- ```typescript
120
- const deployment = await deploy({
121
- app: "./my-app",
122
- platform: "aws",
123
- workspace: "my-workspace",
124
- project: "my-project",
125
- method: "api", // default
39
+ it("responds to requests", async () => {
40
+ const response = await fetch(`${deployment.url}/api/hello`)
41
+ expect(response.status).toBe(200)
42
+ })
126
43
  })
127
44
  ```
128
45
 
129
- ### CLI
46
+ Under the hood the package:
130
47
 
131
- Tests the actual CLI deployment flow:
48
+ 1. finds the `alien` binary
49
+ 2. spawns `alien dev --status-file ...`
50
+ 3. waits for the status file to report readiness
51
+ 4. reads the public URL and commands URL from that file
132
52
 
133
- ```typescript
134
- const deployment = await deploy({
135
- app: "./my-app",
136
- platform: "aws",
137
- workspace: "my-workspace",
138
- project: "my-project",
139
- method: "cli",
140
- })
141
- ```
53
+ The local machine contract is the `DevStatus` JSON written by `alien dev`, not terminal output.
142
54
 
143
- ### Terraform
55
+ ## Cloud Mode
144
56
 
145
- Tests Terraform provider:
57
+ Cloud mode targets real infrastructure:
146
58
 
147
59
  ```typescript
148
60
  const deployment = await deploy({
149
61
  app: "./my-app",
150
62
  platform: "aws",
151
- workspace: "my-workspace",
152
- project: "my-project",
153
- method: "terraform",
154
63
  })
155
64
  ```
156
65
 
157
- ### CloudFormation
66
+ Supported cloud platforms:
158
67
 
159
- Tests CloudFormation deployment (AWS only):
68
+ - `aws`
69
+ - `gcp`
70
+ - `azure`
160
71
 
161
- ```typescript
162
- const deployment = await deploy({
163
- app: "./my-app",
164
- platform: "aws",
165
- workspace: "my-workspace",
166
- project: "my-project",
167
- method: "cloudformation",
168
- })
169
- ```
170
-
171
- ### Helm
172
-
173
- Tests Helm chart deployment (Kubernetes only):
174
-
175
- ```typescript
176
- const deployment = await deploy({
177
- app: "./my-app",
178
- platform: "kubernetes",
179
- workspace: "my-workspace",
180
- project: "my-project",
181
- method: "helm",
182
- valuesYaml: "./values.yaml",
183
- namespace: "default",
184
- })
185
- ```
72
+ Cloud mode requires `ALIEN_API_KEY`.
186
73
 
187
- ### Operator Image
188
-
189
- Tests pull-mode deployment via Docker operator:
74
+ ## API
190
75
 
191
76
  ```typescript
192
- const deployment = await deploy({
193
- app: "./my-app",
194
- platform: "aws",
195
- workspace: "my-workspace",
196
- project: "my-project",
197
- method: "operator-image",
198
- })
199
- ```
200
-
201
- ## Query Logs
202
-
203
- Query deployment logs using DeepStore:
204
-
205
- ```typescript
206
- const deployment = await deploy({
207
- app: "./my-app",
208
- platform: "test",
209
- workspace: "my-workspace",
210
- project: "my-project",
211
- })
212
-
213
- // Configure log querying
214
- const logsConfig = {
215
- managerUrl: "http://localhost:3000",
216
- deepstoreServerUrl: "http://localhost:8080",
217
- databaseId: "db_123",
218
- agentToken: "token_123",
77
+ interface DeployOptions {
78
+ app: string
79
+ platform?: "local" | "aws" | "gcp" | "azure"
80
+ config?: string
81
+ environmentVariables?: EnvironmentVariable[]
82
+ verbose?: boolean
219
83
  }
220
-
221
- const logs = await deployment.queryLogs({
222
- query: "level:ERROR",
223
- startTime: new Date(Date.now() - 3600_000), // 1 hour ago
224
- endTime: new Date(),
225
- maxHits: 100,
226
- })
227
-
228
- console.log(`Found ${logs.num_hits} logs`)
229
- ```
230
-
231
- ## Environment Variables
232
-
233
- Pass environment variables to your deployment:
234
-
235
- ```typescript
236
- const deployment = await deploy({
237
- app: "./my-app",
238
- platform: "aws",
239
- workspace: "my-workspace",
240
- project: "my-project",
241
- environmentVariables: [
242
- { name: "DATABASE_URL", value: "postgres://...", type: "plaintext" },
243
- { name: "API_KEY", value: "secret", type: "secret" },
244
- ],
245
- })
246
84
  ```
247
85
 
248
- ## Stack Settings
249
-
250
- Customize deployment behavior:
251
-
252
- ```typescript
253
- const deployment = await deploy({
254
- app: "./my-app",
255
- platform: "aws",
256
- workspace: "my-workspace",
257
- project: "my-project",
258
- stackSettings: {
259
- deploymentModel: "push",
260
- heartbeats: "on",
261
- telemetry: "auto",
262
- updates: "auto",
263
- },
264
- })
265
- ```
266
-
267
- ## Example Test
268
-
269
- ```typescript
270
- import { describe, it, expect } from "vitest"
271
- import { deploy } from "@alienplatform/testing"
272
-
273
- describe("my app", () => {
274
- it("should deploy and respond", async () => {
275
- const deployment = await deploy({
276
- app: "./fixtures/my-app",
277
- platform: "test",
278
- workspace: "test-workspace",
279
- project: "test-project",
280
- })
281
-
282
- try {
283
- const response = await fetch(`${deployment.url}/api/hello`)
284
- const data = await response.json()
285
-
286
- expect(response.status).toBe(200)
287
- expect(data.message).toBe("Hello, World!")
288
- } finally {
289
- await deployment.destroy()
290
- }
291
- }, 180_000) // 3 min timeout
292
- })
293
- ```
294
-
295
- ## API Reference
296
-
297
- ### `deploy(options: DeployOptions): Promise<Deployment>`
298
-
299
- Deploy an Alien application for testing.
300
-
301
- ### `Deployment`
86
+ The returned `Deployment` supports:
302
87
 
303
- Handle to a deployed application.
88
+ - `deployment.url`
89
+ - `deployment.invokeCommand(name, params)`
90
+ - `deployment.setExternalSecret(vault, key, value)`
91
+ - `deployment.upgrade(options)` for cloud mode
92
+ - `deployment.destroy()`
304
93
 
305
- **Properties:**
306
- - `id: string` - Agent ID
307
- - `name: string` - Agent name
308
- - `url: string` - Deployment URL
309
- - `platform: Platform` - Target platform
310
- - `status: AgentStatus` - Current status
94
+ ## Configuration
311
95
 
312
- **Methods:**
313
- - `refresh(): Promise<void>` - Refresh deployment info from API
314
- - `waitForStatus(status: AgentStatus, options?: WaitOptions): Promise<void>` - Wait for specific status
315
- - `queryLogs(query: LogQuery): Promise<LogQueryResult>` - Query deployment logs
316
- - `destroy(): Promise<void>` - Tear down the deployment
96
+ | Variable | Description | Default |
97
+ |---|---|---|
98
+ | `ALIEN_API_KEY` | Platform API key for cloud mode | — |
99
+ | `ALIEN_API_URL` | Platform API base URL | `https://api.alien.dev` |
100
+ | `ALIEN_CLI_PATH` | Path to the `alien` binary | auto-detected |
101
+ | `VERBOSE` | Show detailed child-process logs | `false` |
317
102
 
318
- ## License
103
+ ## Notes
319
104
 
320
- ISC
105
+ - local mode needs only the `alien` binary
106
+ - cloud mode needs `ALIEN_API_KEY`
107
+ - repo-internal low-level tests can still use standalone manager helpers directly; this package is the higher-level product-facing layer
package/dist/errors.js ADDED
@@ -0,0 +1,37 @@
1
+ import { AlienError, defineError } from "@alienplatform/core";
2
+ import * as z from "zod/v4";
3
+ //#region src/errors.ts
4
+ const TestingOperationFailedError = defineError({
5
+ code: "TESTING_OPERATION_FAILED",
6
+ context: z.object({
7
+ operation: z.string(),
8
+ message: z.string(),
9
+ details: z.record(z.string(), z.unknown()).optional()
10
+ }),
11
+ message: ({ operation, message }) => `Testing operation '${operation}' failed: ${message}`,
12
+ retryable: false,
13
+ internal: false,
14
+ httpStatusCode: 500
15
+ });
16
+ const TestingUnsupportedPlatformError = defineError({
17
+ code: "TESTING_UNSUPPORTED_PLATFORM",
18
+ context: z.object({
19
+ platform: z.string(),
20
+ operation: z.string()
21
+ }),
22
+ message: ({ platform, operation }) => `Unsupported platform '${platform}' for testing operation '${operation}'`,
23
+ retryable: false,
24
+ internal: false,
25
+ httpStatusCode: 400
26
+ });
27
+ async function withTestingContext(error, operation, message, details) {
28
+ return (await AlienError.from(error)).withContext(TestingOperationFailedError.create({
29
+ operation,
30
+ message,
31
+ details
32
+ }));
33
+ }
34
+ //#endregion
35
+ export { TestingUnsupportedPlatformError as n, withTestingContext as r, TestingOperationFailedError as t };
36
+
37
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","names":[],"sources":["../src/errors.ts"],"sourcesContent":["import { AlienError, defineError } from \"@alienplatform/core\"\nimport * as z from \"zod/v4\"\n\nexport const TestingOperationFailedError = defineError({\n code: \"TESTING_OPERATION_FAILED\",\n context: z.object({\n operation: z.string(),\n message: z.string(),\n details: z.record(z.string(), z.unknown()).optional(),\n }),\n message: ({ operation, message }) => `Testing operation '${operation}' failed: ${message}`,\n retryable: false,\n internal: false,\n httpStatusCode: 500,\n})\n\nexport const TestingUnsupportedPlatformError = defineError({\n code: \"TESTING_UNSUPPORTED_PLATFORM\",\n context: z.object({\n platform: z.string(),\n operation: z.string(),\n }),\n message: ({ platform, operation }) =>\n `Unsupported platform '${platform}' for testing operation '${operation}'`,\n retryable: false,\n internal: false,\n httpStatusCode: 400,\n})\n\nexport async function withTestingContext(\n error: unknown,\n operation: string,\n message: string,\n details?: Record<string, unknown>,\n): Promise<AlienError<any>> {\n return (await AlienError.from(error)).withContext(\n TestingOperationFailedError.create({\n operation,\n message,\n details,\n }),\n )\n}\n"],"mappings":";;;AAGA,MAAa,8BAA8B,YAAY;CACrD,MAAM;CACN,SAAS,EAAE,OAAO;EAChB,WAAW,EAAE,QAAQ;EACrB,SAAS,EAAE,QAAQ;EACnB,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,SAAS,CAAC,CAAC,UAAU;EACtD,CAAC;CACF,UAAU,EAAE,WAAW,cAAc,sBAAsB,UAAU,YAAY;CACjF,WAAW;CACX,UAAU;CACV,gBAAgB;CACjB,CAAC;AAEF,MAAa,kCAAkC,YAAY;CACzD,MAAM;CACN,SAAS,EAAE,OAAO;EAChB,UAAU,EAAE,QAAQ;EACpB,WAAW,EAAE,QAAQ;EACtB,CAAC;CACF,UAAU,EAAE,UAAU,gBACpB,yBAAyB,SAAS,2BAA2B,UAAU;CACzE,WAAW;CACX,UAAU;CACV,gBAAgB;CACjB,CAAC;AAEF,eAAsB,mBACpB,OACA,WACA,SACA,SAC0B;AAC1B,SAAQ,MAAM,WAAW,KAAK,MAAM,EAAE,YACpC,4BAA4B,OAAO;EACjC;EACA;EACA;EACD,CAAC,CACH"}
@@ -0,0 +1,140 @@
1
+ import { n as TestingUnsupportedPlatformError, r as withTestingContext, t as TestingOperationFailedError } from "./errors.js";
2
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { AlienError } from "@alienplatform/core";
5
+ import { PutParameterCommand, SSMClient } from "@aws-sdk/client-ssm";
6
+ import { ClientSecretCredential } from "@azure/identity";
7
+ import { SecretClient } from "@azure/keyvault-secrets";
8
+ import { SecretManagerServiceClient } from "@google-cloud/secret-manager";
9
+ //#region src/external-secrets.ts
10
+ /**
11
+ * External secrets - platform-native secret management
12
+ */
13
+ /**
14
+ * Set an external secret using platform-native tools
15
+ *
16
+ * Falls back to environment variables for cloud provider credentials.
17
+ */
18
+ async function setExternalSecret(platform, resourcePrefix, vaultName, secretKey, secretValue, _namespace, stateDir, deploymentId) {
19
+ try {
20
+ switch (platform) {
21
+ case "aws":
22
+ await setAWSSecret(resourcePrefix, vaultName, secretKey, secretValue);
23
+ break;
24
+ case "gcp":
25
+ await setGCPSecret(resourcePrefix, vaultName, secretKey, secretValue);
26
+ break;
27
+ case "azure":
28
+ await setAzureSecret(resourcePrefix, vaultName, secretKey, secretValue);
29
+ break;
30
+ case "local":
31
+ await setLocalSecret(vaultName, secretKey, secretValue, stateDir, deploymentId);
32
+ break;
33
+ default: {
34
+ const exhaustive = platform;
35
+ throw new AlienError(TestingUnsupportedPlatformError.create({
36
+ platform: String(exhaustive),
37
+ operation: "setExternalSecret"
38
+ }));
39
+ }
40
+ }
41
+ } catch (error) {
42
+ throw await withTestingContext(error, "setExternalSecret", "Failed to set external secret", {
43
+ platform,
44
+ resourcePrefix,
45
+ vaultName,
46
+ secretKey
47
+ });
48
+ }
49
+ }
50
+ /**
51
+ * Set AWS SSM Parameter Store secret
52
+ *
53
+ * Uses environment variables for credentials (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION).
54
+ */
55
+ async function setAWSSecret(resourcePrefix, vaultName, secretKey, secretValue) {
56
+ const client = new SSMClient({ region: process.env.AWS_REGION });
57
+ const parameterName = `/${resourcePrefix}-${vaultName}-${secretKey}`;
58
+ await client.send(new PutParameterCommand({
59
+ Name: parameterName,
60
+ Value: secretValue,
61
+ Type: "SecureString",
62
+ Overwrite: true
63
+ }));
64
+ }
65
+ /**
66
+ * Set GCP Secret Manager secret
67
+ *
68
+ * Uses environment variables for credentials (GOOGLE_APPLICATION_CREDENTIALS, GCP_PROJECT_ID).
69
+ */
70
+ async function setGCPSecret(resourcePrefix, vaultName, secretKey, secretValue) {
71
+ const client = new SecretManagerServiceClient();
72
+ const projectId = process.env.GCP_PROJECT_ID || process.env.GOOGLE_CLOUD_PROJECT;
73
+ if (!projectId) throw new AlienError(TestingOperationFailedError.create({
74
+ operation: "setGCPSecret",
75
+ message: "GCP project ID is required (set GCP_PROJECT_ID or GOOGLE_CLOUD_PROJECT env var)"
76
+ }));
77
+ const secretName = `${resourcePrefix}-${vaultName}-${secretKey}`;
78
+ const parent = `projects/${projectId}`;
79
+ const secretPath = `${parent}/secrets/${secretName}`;
80
+ try {
81
+ await client.createSecret({
82
+ parent,
83
+ secretId: secretName,
84
+ secret: { replication: { automatic: {} } }
85
+ });
86
+ } catch (error) {
87
+ if (!error.message?.includes("ALREADY_EXISTS")) throw await withTestingContext(error, "setGCPSecret", "Failed to create GCP secret");
88
+ }
89
+ await client.addSecretVersion({
90
+ parent: secretPath,
91
+ payload: { data: Buffer.from(secretValue, "utf8") }
92
+ });
93
+ }
94
+ /**
95
+ * Set Azure Key Vault secret
96
+ *
97
+ * Uses environment variables for credentials (AZURE_TENANT_ID, AZURE_CLIENT_ID, AZURE_CLIENT_SECRET).
98
+ */
99
+ async function setAzureSecret(resourcePrefix, vaultName, secretKey, secretValue) {
100
+ const vaultUrl = `https://${`${resourcePrefix}-${vaultName}`}.vault.azure.net`;
101
+ const tenantId = process.env.AZURE_TENANT_ID;
102
+ const clientId = process.env.AZURE_CLIENT_ID;
103
+ const clientSecret = process.env.AZURE_CLIENT_SECRET;
104
+ if (!tenantId || !clientId || !clientSecret) throw new AlienError(TestingOperationFailedError.create({
105
+ operation: "setAzureSecret",
106
+ message: "Azure credentials are required (set AZURE_TENANT_ID, AZURE_CLIENT_ID, and AZURE_CLIENT_SECRET env vars)"
107
+ }));
108
+ const client = new SecretClient(vaultUrl, new ClientSecretCredential(tenantId, clientId, clientSecret));
109
+ const azureSecretKey = secretKey.replace(/_/g, "-");
110
+ await client.setSecret(azureSecretKey, secretValue);
111
+ }
112
+ /**
113
+ * Set local dev secret by writing directly to the vault's secrets.json file.
114
+ *
115
+ * The local vault binding (LocalVault) reads from:
116
+ * {stateDir}/{deploymentId}/vault/{vaultName}/secrets.json
117
+ *
118
+ * We write to the same path so the running function can read it immediately.
119
+ */
120
+ async function setLocalSecret(vaultName, secretKey, secretValue, stateDir, deploymentId) {
121
+ if (!stateDir) throw new AlienError(TestingOperationFailedError.create({
122
+ operation: "setLocalSecret",
123
+ message: "stateDir is required for local vault set"
124
+ }));
125
+ if (!deploymentId) throw new AlienError(TestingOperationFailedError.create({
126
+ operation: "setLocalSecret",
127
+ message: "deploymentId is required for local vault set"
128
+ }));
129
+ const vaultDir = join(stateDir, deploymentId, "vault", vaultName);
130
+ const secretsFile = join(vaultDir, "secrets.json");
131
+ let secrets = {};
132
+ if (existsSync(secretsFile)) secrets = JSON.parse(readFileSync(secretsFile, "utf-8"));
133
+ secrets[secretKey] = secretValue;
134
+ mkdirSync(vaultDir, { recursive: true });
135
+ writeFileSync(secretsFile, JSON.stringify(secrets, null, 2));
136
+ }
137
+ //#endregion
138
+ export { setExternalSecret };
139
+
140
+ //# sourceMappingURL=external-secrets.js.map