dagger-env 1.0.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # dagger-env
2
2
 
3
- A type-safe, reusable environment configuration abstraction for Dagger modules with full Zod v4 validation and Infisical integration.
3
+ A type-safe, reusable environment configuration abstraction for Dagger modules with full Zod v4 validation and Infisical or 1Password integration.
4
4
 
5
5
  ## Features
6
6
 
@@ -9,7 +9,7 @@ A type-safe, reusable environment configuration abstraction for Dagger modules w
9
9
  - 🎯 **Consistent**: Standardized API across all Dagger modules
10
10
  - 🛡️ **Validated**: Runtime validation of arguments, environment variables, and secrets
11
11
  - 📦 **Modular**: Secret presets and derived environment variables
12
- - 🔐 **Infisical Integration**: Built-in command runner backed by `infisical export`
12
+ - 🔐 **Infisical & 1Password Integration**: Built-in command runner backed by `infisical export` or `op run`
13
13
  - 🚀 **Easy to use**: Simple configuration-based setup
14
14
 
15
15
  ## Installation
@@ -18,7 +18,7 @@ A type-safe, reusable environment configuration abstraction for Dagger modules w
18
18
  npm install dagger-env zod
19
19
  ```
20
20
 
21
- **Note:** The command runner functionality (`dagger-env/run`) requires the Infisical CLI (`infisical`) to be installed and authenticated.
21
+ **Note:** The command runner functionality (`dagger-env/run`) requires either the Infisical CLI (`infisical`) or the 1Password CLI (`op`) to be installed and authenticated, depending on which provider you configure.
22
22
 
23
23
  ## Quick Start
24
24
 
@@ -73,9 +73,9 @@ export class MyModule {
73
73
  }
74
74
  ```
75
75
 
76
- ## Command Runner (Infisical Integration)
76
+ ## Command Runner (Infisical or 1Password Integration)
77
77
 
78
- For projects using Infisical for secret management, `dagger-env` provides a convenient command runner that fetches secrets with `infisical export` and passes them to `dagger call` via the `DAGGER_OPTIONS` environment variable:
78
+ `dagger-env` provides a convenient command runner that fetches secrets from Infisical (via `infisical export`) or 1Password (via `op item get`) and passes them to `dagger call` via the `DAGGER_OPTIONS` environment variable. The provider is selected by the shape of the config you pass to `createDaggerCommandRunner()`:
79
79
 
80
80
  ```typescript
81
81
  import { createDaggerEnv } from 'dagger-env'
@@ -100,7 +100,7 @@ const myDaggerEnv = createDaggerEnv({
100
100
  derivedEnvVars: {}
101
101
  })
102
102
 
103
- // Create a command runner - simply pass your DaggerEnv instance
103
+ // Create a command runner with Infisical - simply pass your DaggerEnv instance
104
104
  const runDaggerCommand = createDaggerCommandRunner({
105
105
  projectId: 'your-project-id',
106
106
  env: 'prod',
@@ -116,6 +116,25 @@ await runDaggerCommand('test', {
116
116
  })
117
117
  ```
118
118
 
119
+ ### 1Password Integration
120
+
121
+ To use 1Password instead of Infisical, pass `opVault`/`opItem`/`opSections` instead of `projectId`/`env`/`path`. Secrets are read from the specified sections of a 1Password item, and `dagger call` is wrapped in `op run --no-masking` so that `op://` references in the environment are resolved:
122
+
123
+ ```typescript
124
+ const runDaggerCommand = createDaggerCommandRunner({
125
+ opVault: 'your-vault-id',
126
+ opItem: 'your-item-id',
127
+ opSections: [
128
+ {
129
+ id: 'your-section-id',
130
+ label: 'Shared'
131
+ }
132
+ ],
133
+ dockerCommands: ['build', 'deploy', 'test'],
134
+ daggerEnv: myDaggerEnv
135
+ })
136
+ ```
137
+
119
138
  ### Advanced Configuration
120
139
 
121
140
  ```typescript
@@ -200,13 +219,22 @@ Returns array of secret names for a specific preset.
200
219
 
201
220
  #### `createDaggerCommandRunner(config)`
202
221
 
203
- Creates a function to run Dagger commands with Infisical integration.
222
+ Creates a function to run Dagger commands with Infisical or 1Password integration. Pass either the Infisical or 1Password parameters to select the provider.
204
223
 
205
- **Parameters:**
224
+ **Infisical parameters:**
206
225
 
207
226
  - `config.projectId`: Infisical project ID
208
227
  - `config.env`: Infisical environment slug (e.g. `prod`)
209
228
  - `config.path`: Infisical folder path to fetch secrets from (e.g. `/ci/my-repo`)
229
+
230
+ **1Password parameters:**
231
+
232
+ - `config.opVault`: 1Password vault ID
233
+ - `config.opItem`: 1Password item ID
234
+ - `config.opSections`: Array of 1Password sections to include for secrets
235
+
236
+ **Shared parameters:**
237
+
210
238
  - `config.dockerCommands`: Optional array of command names that should include Docker socket
211
239
  - `config.beforeCommand`: Optional async function to run before executing the command
212
240
  - `config.daggerEnv`: DaggerEnv instance for schema validation and type safety
package/dist/op.d.ts ADDED
@@ -0,0 +1,90 @@
1
+ import { z } from 'zod/v4';
2
+ /**
3
+ * 1Password vault information
4
+ */
5
+ export type OPVault = z.infer<typeof OPVault>;
6
+ export declare const OPVault: z.ZodObject<{
7
+ id: z.ZodString;
8
+ name: z.ZodString;
9
+ }, z.core.$strip>;
10
+ /**
11
+ * 1Password item section information
12
+ */
13
+ export type OPSection = z.infer<typeof OPSection>;
14
+ export declare const OPSection: z.ZodObject<{
15
+ id: z.ZodString;
16
+ label: z.ZodOptional<z.ZodString>;
17
+ }, z.core.$strip>;
18
+ /**
19
+ * 1Password item field
20
+ */
21
+ export type OPField = z.infer<typeof OPField>;
22
+ export declare const OPField: z.ZodObject<{
23
+ id: z.ZodString;
24
+ section: z.ZodOptional<z.ZodObject<{
25
+ id: z.ZodString;
26
+ label: z.ZodOptional<z.ZodString>;
27
+ }, z.core.$strip>>;
28
+ type: z.ZodEnum<{
29
+ STRING: "STRING";
30
+ CONCEALED: "CONCEALED";
31
+ MONTH_YEAR: "MONTH_YEAR";
32
+ }>;
33
+ purpose: z.ZodOptional<z.ZodEnum<{
34
+ NOTES: "NOTES";
35
+ }>>;
36
+ label: z.ZodString;
37
+ value: z.ZodString;
38
+ reference: z.ZodString;
39
+ }, z.core.$strip>;
40
+ /**
41
+ * 1Password item category
42
+ */
43
+ export type OPCategory = z.infer<typeof OPCategory>;
44
+ export declare const OPCategory: z.ZodEnum<{
45
+ SECURE_NOTE: "SECURE_NOTE";
46
+ }>;
47
+ /**
48
+ * Complete 1Password item structure
49
+ */
50
+ export type OPItem = z.infer<typeof OPItem>;
51
+ export declare const OPItem: z.ZodObject<{
52
+ id: z.ZodString;
53
+ title: z.ZodString;
54
+ favorite: z.ZodBoolean;
55
+ version: z.ZodInt;
56
+ vault: z.ZodObject<{
57
+ id: z.ZodString;
58
+ name: z.ZodString;
59
+ }, z.core.$strip>;
60
+ category: z.ZodEnum<{
61
+ SECURE_NOTE: "SECURE_NOTE";
62
+ }>;
63
+ last_edited_by: z.ZodString;
64
+ created_at: z.ZodISODateTime;
65
+ updated_at: z.ZodISODateTime;
66
+ additional_information: z.ZodString;
67
+ sections: z.ZodArray<z.ZodObject<{
68
+ id: z.ZodString;
69
+ label: z.ZodOptional<z.ZodString>;
70
+ }, z.core.$strip>>;
71
+ fields: z.ZodArray<z.ZodObject<{
72
+ id: z.ZodString;
73
+ section: z.ZodOptional<z.ZodObject<{
74
+ id: z.ZodString;
75
+ label: z.ZodOptional<z.ZodString>;
76
+ }, z.core.$strip>>;
77
+ type: z.ZodEnum<{
78
+ STRING: "STRING";
79
+ CONCEALED: "CONCEALED";
80
+ MONTH_YEAR: "MONTH_YEAR";
81
+ }>;
82
+ purpose: z.ZodOptional<z.ZodEnum<{
83
+ NOTES: "NOTES";
84
+ }>>;
85
+ label: z.ZodString;
86
+ value: z.ZodString;
87
+ reference: z.ZodString;
88
+ }, z.core.$strip>>;
89
+ }, z.core.$strip>;
90
+ //# sourceMappingURL=op.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"op.d.ts","sourceRoot":"","sources":["../src/op.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,QAAQ,CAAA;AAE1B;;GAEG;AACH,MAAM,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,OAAO,CAAC,CAAA;AAC7C,eAAO,MAAM,OAAO;;;iBAGlB,CAAA;AAEF;;GAEG;AACH,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,SAAS,CAAC,CAAA;AACjD,eAAO,MAAM,SAAS;;;iBAGpB,CAAA;AAEF;;GAEG;AACH,MAAM,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,OAAO,CAAC,CAAA;AAC7C,eAAO,MAAM,OAAO;;;;;;;;;;;;;;;;;iBAQlB,CAAA;AAEF;;GAEG;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAA;AACnD,eAAO,MAAM,UAAU;;EAA0B,CAAA;AAEjD;;GAEG;AACH,MAAM,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,MAAM,CAAC,CAAA;AAC3C,eAAO,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAajB,CAAA"}
package/dist/op.js ADDED
@@ -0,0 +1,34 @@
1
+ import { z } from 'zod/v4';
2
+ export const OPVault = z.object({
3
+ id: z.string(),
4
+ name: z.string(),
5
+ });
6
+ export const OPSection = z.object({
7
+ id: z.string(),
8
+ label: z.string().optional(),
9
+ });
10
+ export const OPField = z.object({
11
+ id: z.string(),
12
+ section: OPSection.optional(),
13
+ type: z.enum(['STRING', 'CONCEALED', 'MONTH_YEAR']),
14
+ purpose: z.enum(['NOTES']).optional(),
15
+ label: z.string(),
16
+ value: z.string(),
17
+ reference: z.string(),
18
+ });
19
+ export const OPCategory = z.enum(['SECURE_NOTE']);
20
+ export const OPItem = z.object({
21
+ id: z.string(),
22
+ title: z.string(),
23
+ favorite: z.boolean(),
24
+ version: z.int(),
25
+ vault: OPVault,
26
+ category: OPCategory,
27
+ last_edited_by: z.string(),
28
+ created_at: z.iso.datetime(),
29
+ updated_at: z.iso.datetime(),
30
+ additional_information: z.string(),
31
+ sections: z.array(OPSection),
32
+ fields: z.array(OPField),
33
+ });
34
+ //# sourceMappingURL=op.js.map
package/dist/op.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"op.js","sourceRoot":"","sources":["../src/op.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,QAAQ,CAAA;AAM1B,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,CAAC,MAAM,CAAC;IAC/B,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE;IACd,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;CAChB,CAAC,CAAA;AAMF,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,CAAC,MAAM,CAAC;IACjC,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE;IACd,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAC5B,CAAC,CAAA;AAMF,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,CAAC,MAAM,CAAC;IAC/B,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE;IACd,OAAO,EAAE,SAAS,CAAC,QAAQ,EAAE;IAC7B,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,WAAW,EAAE,YAAY,CAAC,CAAC;IACnD,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,EAAE;IACrC,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;IACjB,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;IACjB,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE;CACrB,CAAC,CAAA;AAMF,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,aAAa,CAAC,CAAC,CAAA;AAMjD,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC;IAC9B,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE;IACd,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;IACjB,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE;IACrB,OAAO,EAAE,CAAC,CAAC,GAAG,EAAE;IAChB,KAAK,EAAE,OAAO;IACd,QAAQ,EAAE,UAAU;IACpB,cAAc,EAAE,CAAC,CAAC,MAAM,EAAE;IAC1B,UAAU,EAAE,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE;IAC5B,UAAU,EAAE,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE;IAC5B,sBAAsB,EAAE,CAAC,CAAC,MAAM,EAAE;IAClC,QAAQ,EAAE,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC;IAC5B,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC;CACxB,CAAC,CAAA","sourcesContent":["import { z } from 'zod/v4'\n\n/**\n * 1Password vault information\n */\nexport type OPVault = z.infer<typeof OPVault>\nexport const OPVault = z.object({\n\tid: z.string(),\n\tname: z.string(),\n})\n\n/**\n * 1Password item section information\n */\nexport type OPSection = z.infer<typeof OPSection>\nexport const OPSection = z.object({\n\tid: z.string(),\n\tlabel: z.string().optional(),\n})\n\n/**\n * 1Password item field\n */\nexport type OPField = z.infer<typeof OPField>\nexport const OPField = z.object({\n\tid: z.string(),\n\tsection: OPSection.optional(),\n\ttype: z.enum(['STRING', 'CONCEALED', 'MONTH_YEAR']),\n\tpurpose: z.enum(['NOTES']).optional(),\n\tlabel: z.string(),\n\tvalue: z.string(),\n\treference: z.string(),\n})\n\n/**\n * 1Password item category\n */\nexport type OPCategory = z.infer<typeof OPCategory>\nexport const OPCategory = z.enum(['SECURE_NOTE'])\n\n/**\n * Complete 1Password item structure\n */\nexport type OPItem = z.infer<typeof OPItem>\nexport const OPItem = z.object({\n\tid: z.string(),\n\ttitle: z.string(),\n\tfavorite: z.boolean(),\n\tversion: z.int(),\n\tvault: OPVault,\n\tcategory: OPCategory,\n\tlast_edited_by: z.string(),\n\tcreated_at: z.iso.datetime(),\n\tupdated_at: z.iso.datetime(),\n\tadditional_information: z.string(),\n\tsections: z.array(OPSection),\n\tfields: z.array(OPField),\n})\n"]}
@@ -9,22 +9,52 @@ export declare const InfisicalSecret: z.ZodObject<{
9
9
  value: z.ZodString;
10
10
  }, z.core.$strip>;
11
11
  /**
12
- * Configuration for running Dagger commands with Infisical integration
12
+ * Infisical secrets provider configuration
13
13
  */
14
- export interface RunDaggerCommandConfig<T extends DaggerEnvConfig = DaggerEnvConfig> {
14
+ export interface InfisicalProviderConfig {
15
15
  /** Infisical project ID */
16
16
  projectId: string;
17
17
  /** Infisical environment slug (e.g. `prod`) */
18
18
  env: string;
19
19
  /** Infisical folder path to fetch secrets from (e.g. `/ci/my-repo`) */
20
20
  path: string;
21
+ }
22
+ /**
23
+ * 1Password secrets provider configuration
24
+ */
25
+ export interface OnePasswordProviderConfig {
26
+ /** 1Password vault ID */
27
+ opVault: string;
28
+ /** 1Password item ID */
29
+ opItem: string;
30
+ /** 1Password sections to include for secrets */
31
+ opSections: Array<{
32
+ label: string;
33
+ id: string;
34
+ }>;
35
+ }
36
+ /**
37
+ * Configuration shared by all secrets providers
38
+ */
39
+ export interface RunDaggerCommandBaseConfig<T extends DaggerEnvConfig = DaggerEnvConfig> {
21
40
  /** Commands that should include Docker socket if available */
22
41
  dockerCommands?: string[];
23
42
  /** Hook to run before executing the command (e.g., vendor file setup) */
24
43
  beforeCommand?: () => Promise<void>;
25
44
  /** DaggerEnv instance for schema validation and type safety */
26
45
  daggerEnv: DaggerEnv<T>;
46
+ /**
47
+ * Secrets to pass to Dagger alongside the ones fetched from the provider,
48
+ * taking precedence on conflict. Entries with an undefined value are
49
+ * skipped, so short-lived credentials that only exist in some environments
50
+ * (e.g. a CI-issued token) can be forwarded without a separate config path.
51
+ */
52
+ additionalSecrets?: Record<string, string | undefined>;
27
53
  }
54
+ /**
55
+ * Configuration for running Dagger commands with Infisical or 1Password integration
56
+ */
57
+ export type RunDaggerCommandConfig<T extends DaggerEnvConfig = DaggerEnvConfig> = RunDaggerCommandBaseConfig<T> & (InfisicalProviderConfig | OnePasswordProviderConfig);
28
58
  /**
29
59
  * Options for individual command execution
30
60
  */
@@ -37,9 +67,10 @@ export interface RunDaggerCommandOptions {
37
67
  extraArgs?: string[];
38
68
  }
39
69
  /**
40
- * Creates a function to run Dagger commands with Infisical integration
70
+ * Creates a function to run Dagger commands with Infisical or 1Password integration
41
71
  * @param config Configuration for the command runner
42
72
  * @returns Function to execute Dagger commands
43
73
  */
44
- export declare function createDaggerCommandRunner<T extends DaggerEnvConfig>(config: RunDaggerCommandConfig<T>): (commandName: string, options?: RunDaggerCommandOptions) => Promise<void>;
74
+ export declare function createDaggerCommandRunner<T extends DaggerEnvConfig>(config: RunDaggerCommandBaseConfig<T> & InfisicalProviderConfig): (commandName: string, options?: RunDaggerCommandOptions) => Promise<void>;
75
+ export declare function createDaggerCommandRunner<T extends DaggerEnvConfig>(config: RunDaggerCommandBaseConfig<T> & OnePasswordProviderConfig): (commandName: string, options?: RunDaggerCommandOptions) => Promise<void>;
45
76
  //# sourceMappingURL=run-dagger-cmd.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"run-dagger-cmd.d.ts","sourceRoot":"","sources":["../src/run-dagger-cmd.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,QAAQ,CAAA;AAG1B,OAAO,KAAK,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAEjE;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAA;AAC7D,eAAO,MAAM,eAAe;;;iBAG1B,CAAA;AAEF;;GAEG;AACH,MAAM,WAAW,sBAAsB,CAAC,CAAC,SAAS,eAAe,GAAG,eAAe;IAClF,2BAA2B;IAC3B,SAAS,EAAE,MAAM,CAAA;IACjB,+CAA+C;IAC/C,GAAG,EAAE,MAAM,CAAA;IACX,uEAAuE;IACvE,IAAI,EAAE,MAAM,CAAA;IACZ,8DAA8D;IAC9D,cAAc,CAAC,EAAE,MAAM,EAAE,CAAA;IACzB,yEAAyE;IACzE,aAAa,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IACnC,+DAA+D;IAC/D,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC,CAAA;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACvC,8CAA8C;IAC9C,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAC1B,uCAAuC;IACvC,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC5B,wCAAwC;IACxC,SAAS,CAAC,EAAE,MAAM,EAAE,CAAA;CACpB;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,CAAC,SAAS,eAAe,EAClE,MAAM,EAAE,sBAAsB,CAAC,CAAC,CAAC,IAGhC,aAAa,MAAM,EACnB,UAAU,uBAAuB,KAC/B,OAAO,CAAC,IAAI,CAAC,CA8EhB"}
1
+ {"version":3,"file":"run-dagger-cmd.d.ts","sourceRoot":"","sources":["../src/run-dagger-cmd.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,QAAQ,CAAA;AAK1B,OAAO,KAAK,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAEjE;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAA;AAC7D,eAAO,MAAM,eAAe;;;iBAG1B,CAAA;AAEF;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACvC,2BAA2B;IAC3B,SAAS,EAAE,MAAM,CAAA;IACjB,+CAA+C;IAC/C,GAAG,EAAE,MAAM,CAAA;IACX,uEAAuE;IACvE,IAAI,EAAE,MAAM,CAAA;CACZ;AAED;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACzC,yBAAyB;IACzB,OAAO,EAAE,MAAM,CAAA;IACf,wBAAwB;IACxB,MAAM,EAAE,MAAM,CAAA;IACd,gDAAgD;IAChD,UAAU,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAChD;AAED;;GAEG;AACH,MAAM,WAAW,0BAA0B,CAAC,CAAC,SAAS,eAAe,GAAG,eAAe;IACtF,8DAA8D;IAC9D,cAAc,CAAC,EAAE,MAAM,EAAE,CAAA;IACzB,yEAAyE;IACzE,aAAa,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IACnC,+DAA+D;IAC/D,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC,CAAA;IACvB;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAA;CACtD;AAED;;GAEG;AACH,MAAM,MAAM,sBAAsB,CAAC,CAAC,SAAS,eAAe,GAAG,eAAe,IAC7E,0BAA0B,CAAC,CAAC,CAAC,GAAG,CAAC,uBAAuB,GAAG,yBAAyB,CAAC,CAAA;AAEtF;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACvC,8CAA8C;IAC9C,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAC1B,uCAAuC;IACvC,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC5B,wCAAwC;IACxC,SAAS,CAAC,EAAE,MAAM,EAAE,CAAA;CACpB;AA4CD;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,CAAC,SAAS,eAAe,EAClE,MAAM,EAAE,0BAA0B,CAAC,CAAC,CAAC,GAAG,uBAAuB,GAC7D,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,uBAAuB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;AAC5E,wBAAgB,yBAAyB,CAAC,CAAC,SAAS,eAAe,EAClE,MAAM,EAAE,0BAA0B,CAAC,CAAC,CAAC,GAAG,yBAAyB,GAC/D,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,uBAAuB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA"}
@@ -1,14 +1,33 @@
1
1
  import { z } from 'zod/v4';
2
2
  import { $, fs } from 'zx';
3
+ import { OPItem, OPSection } from './op.js';
3
4
  export const InfisicalSecret = z.object({
4
5
  key: z.string(),
5
6
  value: z.string(),
6
7
  });
7
8
  /**
8
- * Creates a function to run Dagger commands with Infisical integration
9
- * @param config Configuration for the command runner
10
- * @returns Function to execute Dagger commands
9
+ * Fetches secrets from Infisical via `infisical export`
11
10
  */
11
+ async function fetchInfisicalSecrets(config) {
12
+ const exportedSecrets = InfisicalSecret.array().parse(await $ `infisical export --silent --format=json --projectId ${config.projectId} --env ${config.env} --path ${config.path}`.json());
13
+ return exportedSecrets.reduce((acc, s) => {
14
+ acc[s.key] = s.value;
15
+ return acc;
16
+ }, {});
17
+ }
18
+ /**
19
+ * Fetches secrets from a 1Password item, filtered to the configured sections
20
+ */
21
+ async function fetchOnePasswordSecrets(config) {
22
+ const opItem = OPItem.parse(await $ `op item get ${config.opItem} --vault ${config.opVault} --format json`.json());
23
+ const sectionsToInclude = OPSection.array().parse(config.opSections);
24
+ return opItem.fields
25
+ .filter((f) => sectionsToInclude.some((s) => s.id === f.section?.id))
26
+ .reduce((acc, f) => {
27
+ acc[f.label] = f.value;
28
+ return acc;
29
+ }, {});
30
+ }
12
31
  export function createDaggerCommandRunner(config) {
13
32
  return async function runDaggerCommand(commandName, options) {
14
33
  const { args = {}, env = {}, extraArgs = [] } = options ?? {};
@@ -16,7 +35,7 @@ export function createDaggerCommandRunner(config) {
16
35
  if (config.beforeCommand) {
17
36
  await config.beforeCommand();
18
37
  }
19
- // Environment variables to pass to the `dagger` command
38
+ // Environment variables to pass to the spawned command
20
39
  const envVars = {};
21
40
  const commandArgs = [...extraArgs];
22
41
  // Add Docker socket for specific commands if available
@@ -30,16 +49,27 @@ export function createDaggerCommandRunner(config) {
30
49
  // Ignore if fs is not available or docker socket doesn't exist
31
50
  }
32
51
  }
33
- // Fetch secrets from Infisical
34
- const exportedSecrets = InfisicalSecret.array().parse(await $ `infisical export --silent --format=json --projectId ${config.projectId} --env ${config.env} --path ${config.path}`.json());
35
- // Extract secrets into a key/value map
36
- const secrets = exportedSecrets.reduce((acc, s) => {
37
- acc[s.key] = s.value;
38
- return acc;
39
- }, {});
40
- // Pass dagger cloud token in CI because we don't have user auth
41
- if (process.env.CI !== undefined && secrets.DAGGER_CLOUD_TOKEN !== undefined) {
42
- envVars.DAGGER_CLOUD_TOKEN = secrets.DAGGER_CLOUD_TOKEN;
52
+ // Fetch secrets from the configured provider
53
+ let secrets;
54
+ if ('opVault' in config) {
55
+ secrets = await fetchOnePasswordSecrets(config);
56
+ // Pass dagger cloud token in CI because we don't have user auth.
57
+ // The op:// reference is resolved by the `op run` wrapper below.
58
+ if (process.env.CI !== undefined) {
59
+ envVars.DAGGER_CLOUD_TOKEN = `op://${config.opVault}/${config.opItem}/DAGGER_CLOUD_TOKEN`;
60
+ }
61
+ }
62
+ else {
63
+ secrets = await fetchInfisicalSecrets(config);
64
+ // Pass dagger cloud token in CI because we don't have user auth
65
+ if (process.env.CI !== undefined && secrets.DAGGER_CLOUD_TOKEN !== undefined) {
66
+ envVars.DAGGER_CLOUD_TOKEN = secrets.DAGGER_CLOUD_TOKEN;
67
+ }
68
+ }
69
+ for (const [name, value] of Object.entries(config.additionalSecrets ?? {})) {
70
+ if (value !== undefined) {
71
+ secrets[name] = value;
72
+ }
43
73
  }
44
74
  // Build environment variables for Dagger
45
75
  const daggerEnv = { ...env };
@@ -56,8 +86,10 @@ export function createDaggerCommandRunner(config) {
56
86
  secrets,
57
87
  });
58
88
  envVars.DAGGER_OPTIONS = JSON.stringify(daggerOptions);
59
- // Construct the command
89
+ // Construct the command. 1Password wraps `dagger call` in `op run`
90
+ // so that op:// references in the environment are resolved.
60
91
  const cmd = [
92
+ ...('opVault' in config ? ['op', 'run', '--no-masking', '--'] : []),
61
93
  'dagger',
62
94
  'call',
63
95
  commandName,
@@ -1 +1 @@
1
- {"version":3,"file":"run-dagger-cmd.js","sourceRoot":"","sources":["../src/run-dagger-cmd.ts"],"names":[],"mappings":"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","sourcesContent":["import { z } from 'zod/v4'\nimport { $, fs } from 'zx'\n\nimport type { DaggerEnv, DaggerEnvConfig } from './dagger-env.js'\n\n/**\n * A single secret returned by `infisical export --format=json`\n */\nexport type InfisicalSecret = z.infer<typeof InfisicalSecret>\nexport const InfisicalSecret = z.object({\n\tkey: z.string(),\n\tvalue: z.string(),\n})\n\n/**\n * Configuration for running Dagger commands with Infisical integration\n */\nexport interface RunDaggerCommandConfig<T extends DaggerEnvConfig = DaggerEnvConfig> {\n\t/** Infisical project ID */\n\tprojectId: string\n\t/** Infisical environment slug (e.g. `prod`) */\n\tenv: string\n\t/** Infisical folder path to fetch secrets from (e.g. `/ci/my-repo`) */\n\tpath: string\n\t/** Commands that should include Docker socket if available */\n\tdockerCommands?: string[]\n\t/** Hook to run before executing the command (e.g., vendor file setup) */\n\tbeforeCommand?: () => Promise<void>\n\t/** DaggerEnv instance for schema validation and type safety */\n\tdaggerEnv: DaggerEnv<T>\n}\n\n/**\n * Options for individual command execution\n */\nexport interface RunDaggerCommandOptions {\n\t/** Arguments to pass to the Dagger command */\n\targs?: Record<string, any>\n\t/** Additional environment variables */\n\tenv?: Record<string, string>\n\t/** Additional command-line arguments */\n\textraArgs?: string[]\n}\n\n/**\n * Creates a function to run Dagger commands with Infisical integration\n * @param config Configuration for the command runner\n * @returns Function to execute Dagger commands\n */\nexport function createDaggerCommandRunner<T extends DaggerEnvConfig>(\n\tconfig: RunDaggerCommandConfig<T>\n) {\n\treturn async function runDaggerCommand(\n\t\tcommandName: string,\n\t\toptions?: RunDaggerCommandOptions\n\t): Promise<void> {\n\t\tconst { args = {}, env = {}, extraArgs = [] } = options ?? {}\n\n\t\t// Run any pre-command setup\n\t\tif (config.beforeCommand) {\n\t\t\tawait config.beforeCommand()\n\t\t}\n\n\t\t// Environment variables to pass to the `dagger` command\n\t\tconst envVars: Record<string, string> = {}\n\n\t\tconst commandArgs: string[] = [...extraArgs]\n\n\t\t// Add Docker socket for specific commands if available\n\t\tif (config.dockerCommands?.includes(commandName)) {\n\t\t\ttry {\n\t\t\t\tif (await fs.exists('/var/run/docker.sock')) {\n\t\t\t\t\tcommandArgs.push('--docker-socket=/var/run/docker.sock')\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Ignore if fs is not available or docker socket doesn't exist\n\t\t\t}\n\t\t}\n\n\t\t// Fetch secrets from Infisical\n\t\tconst exportedSecrets = InfisicalSecret.array().parse(\n\t\t\tawait $`infisical export --silent --format=json --projectId ${config.projectId} --env ${config.env} --path ${config.path}`.json()\n\t\t)\n\n\t\t// Extract secrets into a key/value map\n\t\tconst secrets = exportedSecrets.reduce(\n\t\t\t(acc, s) => {\n\t\t\t\tacc[s.key] = s.value\n\t\t\t\treturn acc\n\t\t\t},\n\t\t\t{} as Record<string, string>\n\t\t)\n\n\t\t// Pass dagger cloud token in CI because we don't have user auth\n\t\tif (process.env.CI !== undefined && secrets.DAGGER_CLOUD_TOKEN !== undefined) {\n\t\t\tenvVars.DAGGER_CLOUD_TOKEN = secrets.DAGGER_CLOUD_TOKEN\n\t\t}\n\n\t\t// Build environment variables for Dagger\n\t\tconst daggerEnv: Record<string, string> = { ...env }\n\t\tif (process.env.CI !== undefined) {\n\t\t\tdaggerEnv.CI = process.env.CI\n\t\t}\n\t\tif (process.env.GITHUB_ACTIONS !== undefined) {\n\t\t\tdaggerEnv.GITHUB_ACTIONS = process.env.GITHUB_ACTIONS\n\t\t}\n\n\t\t// Validate and serialize dagger options\n\t\tconst daggerOptions = config.daggerEnv.getOptionsSchema().parse({\n\t\t\targs,\n\t\t\tenv: daggerEnv,\n\t\t\tsecrets,\n\t\t})\n\t\tenvVars.DAGGER_OPTIONS = JSON.stringify(daggerOptions)\n\n\t\t// Construct the command\n\t\tconst cmd: string[] = [\n\t\t\t'dagger',\n\t\t\t'call',\n\t\t\tcommandName,\n\t\t\t...commandArgs,\n\t\t\t'--options=env://DAGGER_OPTIONS',\n\t\t]\n\n\t\t// Execute the command\n\t\tawait $({\n\t\t\tenv: {\n\t\t\t\t...process.env,\n\t\t\t\t...envVars,\n\t\t\t},\n\t\t\tstdio: 'inherit',\n\t\t})`${cmd}`\n\t}\n}\n"]}
1
+ {"version":3,"file":"run-dagger-cmd.js","sourceRoot":"","sources":["../src/run-dagger-cmd.ts"],"names":[],"mappings":"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","sourcesContent":["import { z } from 'zod/v4'\nimport { $, fs } from 'zx'\n\nimport { OPItem, OPSection } from './op.js'\n\nimport type { DaggerEnv, DaggerEnvConfig } from './dagger-env.js'\n\n/**\n * A single secret returned by `infisical export --format=json`\n */\nexport type InfisicalSecret = z.infer<typeof InfisicalSecret>\nexport const InfisicalSecret = z.object({\n\tkey: z.string(),\n\tvalue: z.string(),\n})\n\n/**\n * Infisical secrets provider configuration\n */\nexport interface InfisicalProviderConfig {\n\t/** Infisical project ID */\n\tprojectId: string\n\t/** Infisical environment slug (e.g. `prod`) */\n\tenv: string\n\t/** Infisical folder path to fetch secrets from (e.g. `/ci/my-repo`) */\n\tpath: string\n}\n\n/**\n * 1Password secrets provider configuration\n */\nexport interface OnePasswordProviderConfig {\n\t/** 1Password vault ID */\n\topVault: string\n\t/** 1Password item ID */\n\topItem: string\n\t/** 1Password sections to include for secrets */\n\topSections: Array<{ label: string; id: string }>\n}\n\n/**\n * Configuration shared by all secrets providers\n */\nexport interface RunDaggerCommandBaseConfig<T extends DaggerEnvConfig = DaggerEnvConfig> {\n\t/** Commands that should include Docker socket if available */\n\tdockerCommands?: string[]\n\t/** Hook to run before executing the command (e.g., vendor file setup) */\n\tbeforeCommand?: () => Promise<void>\n\t/** DaggerEnv instance for schema validation and type safety */\n\tdaggerEnv: DaggerEnv<T>\n\t/**\n\t * Secrets to pass to Dagger alongside the ones fetched from the provider,\n\t * taking precedence on conflict. Entries with an undefined value are\n\t * skipped, so short-lived credentials that only exist in some environments\n\t * (e.g. a CI-issued token) can be forwarded without a separate config path.\n\t */\n\tadditionalSecrets?: Record<string, string | undefined>\n}\n\n/**\n * Configuration for running Dagger commands with Infisical or 1Password integration\n */\nexport type RunDaggerCommandConfig<T extends DaggerEnvConfig = DaggerEnvConfig> =\n\tRunDaggerCommandBaseConfig<T> & (InfisicalProviderConfig | OnePasswordProviderConfig)\n\n/**\n * Options for individual command execution\n */\nexport interface RunDaggerCommandOptions {\n\t/** Arguments to pass to the Dagger command */\n\targs?: Record<string, any>\n\t/** Additional environment variables */\n\tenv?: Record<string, string>\n\t/** Additional command-line arguments */\n\textraArgs?: string[]\n}\n\n/**\n * Fetches secrets from Infisical via `infisical export`\n */\nasync function fetchInfisicalSecrets(\n\tconfig: InfisicalProviderConfig\n): Promise<Record<string, string>> {\n\tconst exportedSecrets = InfisicalSecret.array().parse(\n\t\tawait $`infisical export --silent --format=json --projectId ${config.projectId} --env ${config.env} --path ${config.path}`.json()\n\t)\n\n\treturn exportedSecrets.reduce(\n\t\t(acc, s) => {\n\t\t\tacc[s.key] = s.value\n\t\t\treturn acc\n\t\t},\n\t\t{} as Record<string, string>\n\t)\n}\n\n/**\n * Fetches secrets from a 1Password item, filtered to the configured sections\n */\nasync function fetchOnePasswordSecrets(\n\tconfig: OnePasswordProviderConfig\n): Promise<Record<string, string>> {\n\tconst opItem = OPItem.parse(\n\t\tawait $`op item get ${config.opItem} --vault ${config.opVault} --format json`.json()\n\t)\n\n\tconst sectionsToInclude = OPSection.array().parse(config.opSections)\n\n\treturn opItem.fields\n\t\t.filter((f) => sectionsToInclude.some((s) => s.id === f.section?.id))\n\t\t.reduce(\n\t\t\t(acc, f) => {\n\t\t\t\tacc[f.label] = f.value\n\t\t\t\treturn acc\n\t\t\t},\n\t\t\t{} as Record<string, string>\n\t\t)\n}\n\n/**\n * Creates a function to run Dagger commands with Infisical or 1Password integration\n * @param config Configuration for the command runner\n * @returns Function to execute Dagger commands\n */\nexport function createDaggerCommandRunner<T extends DaggerEnvConfig>(\n\tconfig: RunDaggerCommandBaseConfig<T> & InfisicalProviderConfig\n): (commandName: string, options?: RunDaggerCommandOptions) => Promise<void>\nexport function createDaggerCommandRunner<T extends DaggerEnvConfig>(\n\tconfig: RunDaggerCommandBaseConfig<T> & OnePasswordProviderConfig\n): (commandName: string, options?: RunDaggerCommandOptions) => Promise<void>\nexport function createDaggerCommandRunner<T extends DaggerEnvConfig>(\n\tconfig: RunDaggerCommandConfig<T>\n) {\n\treturn async function runDaggerCommand(\n\t\tcommandName: string,\n\t\toptions?: RunDaggerCommandOptions\n\t): Promise<void> {\n\t\tconst { args = {}, env = {}, extraArgs = [] } = options ?? {}\n\n\t\t// Run any pre-command setup\n\t\tif (config.beforeCommand) {\n\t\t\tawait config.beforeCommand()\n\t\t}\n\n\t\t// Environment variables to pass to the spawned command\n\t\tconst envVars: Record<string, string> = {}\n\n\t\tconst commandArgs: string[] = [...extraArgs]\n\n\t\t// Add Docker socket for specific commands if available\n\t\tif (config.dockerCommands?.includes(commandName)) {\n\t\t\ttry {\n\t\t\t\tif (await fs.exists('/var/run/docker.sock')) {\n\t\t\t\t\tcommandArgs.push('--docker-socket=/var/run/docker.sock')\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Ignore if fs is not available or docker socket doesn't exist\n\t\t\t}\n\t\t}\n\n\t\t// Fetch secrets from the configured provider\n\t\tlet secrets: Record<string, string>\n\t\tif ('opVault' in config) {\n\t\t\tsecrets = await fetchOnePasswordSecrets(config)\n\n\t\t\t// Pass dagger cloud token in CI because we don't have user auth.\n\t\t\t// The op:// reference is resolved by the `op run` wrapper below.\n\t\t\tif (process.env.CI !== undefined) {\n\t\t\t\tenvVars.DAGGER_CLOUD_TOKEN = `op://${config.opVault}/${config.opItem}/DAGGER_CLOUD_TOKEN`\n\t\t\t}\n\t\t} else {\n\t\t\tsecrets = await fetchInfisicalSecrets(config)\n\n\t\t\t// Pass dagger cloud token in CI because we don't have user auth\n\t\t\tif (process.env.CI !== undefined && secrets.DAGGER_CLOUD_TOKEN !== undefined) {\n\t\t\t\tenvVars.DAGGER_CLOUD_TOKEN = secrets.DAGGER_CLOUD_TOKEN\n\t\t\t}\n\t\t}\n\n\t\tfor (const [name, value] of Object.entries(config.additionalSecrets ?? {})) {\n\t\t\tif (value !== undefined) {\n\t\t\t\tsecrets[name] = value\n\t\t\t}\n\t\t}\n\n\t\t// Build environment variables for Dagger\n\t\tconst daggerEnv: Record<string, string> = { ...env }\n\t\tif (process.env.CI !== undefined) {\n\t\t\tdaggerEnv.CI = process.env.CI\n\t\t}\n\t\tif (process.env.GITHUB_ACTIONS !== undefined) {\n\t\t\tdaggerEnv.GITHUB_ACTIONS = process.env.GITHUB_ACTIONS\n\t\t}\n\n\t\t// Validate and serialize dagger options\n\t\tconst daggerOptions = config.daggerEnv.getOptionsSchema().parse({\n\t\t\targs,\n\t\t\tenv: daggerEnv,\n\t\t\tsecrets,\n\t\t})\n\t\tenvVars.DAGGER_OPTIONS = JSON.stringify(daggerOptions)\n\n\t\t// Construct the command. 1Password wraps `dagger call` in `op run`\n\t\t// so that op:// references in the environment are resolved.\n\t\tconst cmd: string[] = [\n\t\t\t...('opVault' in config ? ['op', 'run', '--no-masking', '--'] : []),\n\t\t\t'dagger',\n\t\t\t'call',\n\t\t\tcommandName,\n\t\t\t...commandArgs,\n\t\t\t'--options=env://DAGGER_OPTIONS',\n\t\t]\n\n\t\t// Execute the command\n\t\tawait $({\n\t\t\tenv: {\n\t\t\t\t...process.env,\n\t\t\t\t...envVars,\n\t\t\t},\n\t\t\tstdio: 'inherit',\n\t\t})`${cmd}`\n\t}\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dagger-env",
3
- "version": "1.0.0",
3
+ "version": "1.2.0",
4
4
  "private": false,
5
5
  "description": "A type-safe, reusable environment configuration abstraction for Dagger modules.",
6
6
  "keywords": [
@@ -28,6 +28,12 @@
28
28
  "default": "./dist/index.js"
29
29
  }
30
30
  },
31
+ "./op": {
32
+ "import": {
33
+ "types": "./dist/op.d.ts",
34
+ "default": "./dist/op.js"
35
+ }
36
+ },
31
37
  "./run": {
32
38
  "import": {
33
39
  "types": "./dist/run-dagger-cmd.d.ts",
@@ -59,7 +65,7 @@
59
65
  "access": "public"
60
66
  },
61
67
  "scripts": {
62
- "build": "runx build tsc ./src/index.ts ./src/run-dagger-cmd.ts",
68
+ "build": "runx build tsc ./src/index.ts ./src/op.ts ./src/run-dagger-cmd.ts",
63
69
  "check:exports": "runx check --exports",
64
70
  "check:lint": "run-eslint",
65
71
  "check:types": "run-tsc",
@@ -2,7 +2,7 @@
2
2
  * Example usage of the runDaggerCommand abstraction
3
3
  *
4
4
  * This example shows a realistic configuration for a project that uses Dagger
5
- * with Infisical integration for secret management.
5
+ * with Infisical or 1Password integration for secret management.
6
6
  *
7
7
  * Import pattern:
8
8
  * - Main DaggerEnv functionality: import { createDaggerEnv } from 'dagger-env'
@@ -74,3 +74,24 @@ export const runDaggerCommand = createDaggerCommandRunner({
74
74
  // Pass the DaggerEnv instance directly
75
75
  daggerEnv,
76
76
  })
77
+
78
+ // Alternatively, create a command runner with 1Password configuration
79
+ export const runDaggerCommandWithOnePassword = createDaggerCommandRunner({
80
+ // 1Password configuration
81
+ opVault: 'your-vault-id', // Replace with your actual vault ID
82
+ opItem: 'your-item-id', // Replace with your actual item ID
83
+ opSections: [
84
+ {
85
+ id: 'shared-section-id',
86
+ label: 'Shared',
87
+ },
88
+ {
89
+ id: 'production-section-id',
90
+ label: 'Production',
91
+ },
92
+ ],
93
+ // Commands that need Docker socket access
94
+ dockerCommands: ['build', 'test', 'deploy', 'migrate', 'seed'],
95
+ // Pass the DaggerEnv instance directly
96
+ daggerEnv,
97
+ })
package/src/op.ts ADDED
@@ -0,0 +1,58 @@
1
+ import { z } from 'zod/v4'
2
+
3
+ /**
4
+ * 1Password vault information
5
+ */
6
+ export type OPVault = z.infer<typeof OPVault>
7
+ export const OPVault = z.object({
8
+ id: z.string(),
9
+ name: z.string(),
10
+ })
11
+
12
+ /**
13
+ * 1Password item section information
14
+ */
15
+ export type OPSection = z.infer<typeof OPSection>
16
+ export const OPSection = z.object({
17
+ id: z.string(),
18
+ label: z.string().optional(),
19
+ })
20
+
21
+ /**
22
+ * 1Password item field
23
+ */
24
+ export type OPField = z.infer<typeof OPField>
25
+ export const OPField = z.object({
26
+ id: z.string(),
27
+ section: OPSection.optional(),
28
+ type: z.enum(['STRING', 'CONCEALED', 'MONTH_YEAR']),
29
+ purpose: z.enum(['NOTES']).optional(),
30
+ label: z.string(),
31
+ value: z.string(),
32
+ reference: z.string(),
33
+ })
34
+
35
+ /**
36
+ * 1Password item category
37
+ */
38
+ export type OPCategory = z.infer<typeof OPCategory>
39
+ export const OPCategory = z.enum(['SECURE_NOTE'])
40
+
41
+ /**
42
+ * Complete 1Password item structure
43
+ */
44
+ export type OPItem = z.infer<typeof OPItem>
45
+ export const OPItem = z.object({
46
+ id: z.string(),
47
+ title: z.string(),
48
+ favorite: z.boolean(),
49
+ version: z.int(),
50
+ vault: OPVault,
51
+ category: OPCategory,
52
+ last_edited_by: z.string(),
53
+ created_at: z.iso.datetime(),
54
+ updated_at: z.iso.datetime(),
55
+ additional_information: z.string(),
56
+ sections: z.array(OPSection),
57
+ fields: z.array(OPField),
58
+ })
@@ -4,13 +4,14 @@ import { z } from 'zod/v4'
4
4
  import { createDaggerEnv } from './dagger-env'
5
5
  import { createDaggerCommandRunner } from './run-dagger-cmd'
6
6
 
7
+ import type { OPItem } from './op'
7
8
  import type { RunDaggerCommandConfig } from './run-dagger-cmd'
8
9
 
9
10
  const mocks = vi.hoisted(() => ({
10
- /** Resolves the JSON payload returned by `infisical export` */
11
- exportJson: vi.fn(),
12
- /** Records the rendered `infisical export` command string */
13
- exportCmd: vi.fn(),
11
+ /** Resolves the JSON payload returned by the secrets CLI (`infisical export` / `op item get`) */
12
+ fetchJson: vi.fn(),
13
+ /** Records the rendered secrets fetch command string */
14
+ fetchCmd: vi.fn(),
14
15
  /** Records the final command execution: (options, argv) */
15
16
  spawn: vi.fn(
16
17
  async (_opts: { env: Record<string, string | undefined> }, _argv: string[]) => undefined
@@ -22,14 +23,14 @@ const mocks = vi.hoisted(() => ({
22
23
  vi.mock('zx', () => {
23
24
  const $ = (firstArg: unknown, ...vals: unknown[]) => {
24
25
  if (Array.isArray(firstArg)) {
25
- // Direct template call: $`infisical export ...`
26
+ // Direct template call: $`infisical export ...` / $`op item get ...`
26
27
  const pieces = firstArg as readonly string[]
27
28
  const cmd = pieces.reduce(
28
29
  (acc, piece, i) => acc + piece + (i < vals.length ? String(vals[i]) : ''),
29
30
  ''
30
31
  )
31
- mocks.exportCmd(cmd)
32
- return { json: mocks.exportJson }
32
+ mocks.fetchCmd(cmd)
33
+ return { json: mocks.fetchJson }
33
34
  }
34
35
  // Options call: $({ env, stdio }) returns a template executor
35
36
  return (_pieces: TemplateStringsArray, ...templateVals: unknown[]) =>
@@ -56,8 +57,8 @@ function getSpawnCall(): { env: SpawnEnv; argv: string[] } {
56
57
  return { env: opts.env, argv }
57
58
  }
58
59
 
59
- function createRunner(overrides?: Partial<Pick<RunDaggerCommandConfig, 'dockerCommands'>>) {
60
- const daggerEnv = createDaggerEnv({
60
+ function createTestDaggerEnv() {
61
+ return createDaggerEnv({
61
62
  args: z.object({
62
63
  push: z.string().optional(),
63
64
  }),
@@ -68,89 +69,274 @@ function createRunner(overrides?: Partial<Pick<RunDaggerCommandConfig, 'dockerCo
68
69
  }),
69
70
  secrets: z.object({
70
71
  API_TOKEN: z.string(),
72
+ FORWARDED_TOKEN: z.string().optional(),
71
73
  }),
72
74
  secretPresets: {
73
75
  api: ['API_TOKEN'],
74
76
  },
75
77
  derivedEnvVars: {},
76
78
  })
79
+ }
77
80
 
81
+ function createRunner(
82
+ overrides?: Partial<Pick<RunDaggerCommandConfig, 'dockerCommands' | 'additionalSecrets'>>
83
+ ) {
78
84
  return createDaggerCommandRunner({
79
85
  projectId: 'test-project-id',
80
86
  env: 'prod',
81
87
  path: '/ci/test',
82
- daggerEnv,
88
+ daggerEnv: createTestDaggerEnv(),
83
89
  ...overrides,
84
90
  })
85
91
  }
86
92
 
93
+ function createOnePasswordRunner(
94
+ overrides?: Partial<Pick<RunDaggerCommandConfig, 'dockerCommands' | 'additionalSecrets'>>
95
+ ) {
96
+ return createDaggerCommandRunner({
97
+ opVault: 'test-vault-id',
98
+ opItem: 'test-item-id',
99
+ opSections: [{ id: 'shared-section-id', label: 'Shared' }],
100
+ daggerEnv: createTestDaggerEnv(),
101
+ ...overrides,
102
+ })
103
+ }
104
+
105
+ /**
106
+ * A realistic `op item get --format json` payload with one field in the
107
+ * configured section and one field in an excluded section
108
+ */
109
+ const testOPItem: OPItem = {
110
+ id: 'test-item-id',
111
+ title: 'Test Item',
112
+ favorite: false,
113
+ version: 1,
114
+ vault: { id: 'test-vault-id', name: 'Test Vault' },
115
+ category: 'SECURE_NOTE',
116
+ last_edited_by: 'test-user',
117
+ created_at: '2024-01-01T00:00:00Z',
118
+ updated_at: '2024-01-01T00:00:00Z',
119
+ additional_information: '',
120
+ sections: [
121
+ { id: 'shared-section-id', label: 'Shared' },
122
+ { id: 'other-section-id', label: 'Other' },
123
+ ],
124
+ fields: [
125
+ {
126
+ id: 'field-1',
127
+ section: { id: 'shared-section-id', label: 'Shared' },
128
+ type: 'CONCEALED',
129
+ label: 'API_TOKEN',
130
+ value: 'test-api-token',
131
+ reference: 'op://test-vault-id/test-item-id/API_TOKEN',
132
+ },
133
+ {
134
+ id: 'field-2',
135
+ section: { id: 'other-section-id', label: 'Other' },
136
+ type: 'CONCEALED',
137
+ label: 'EXCLUDED_SECRET',
138
+ value: 'excluded-value',
139
+ reference: 'op://test-vault-id/test-item-id/EXCLUDED_SECRET',
140
+ },
141
+ ],
142
+ }
143
+
87
144
  describe('createDaggerCommandRunner()', () => {
88
145
  beforeEach(() => {
89
146
  vi.clearAllMocks()
90
147
  vi.stubEnv('CI', undefined)
91
148
  vi.stubEnv('GITHUB_ACTIONS', undefined)
92
149
  vi.stubEnv('DAGGER_CLOUD_TOKEN', undefined)
93
- mocks.exportJson.mockResolvedValue([
94
- { key: 'API_TOKEN', value: 'test-api-token', comment: '' },
95
- { key: 'DAGGER_CLOUD_TOKEN', value: 'test-dagger-cloud-token', comment: '' },
96
- ])
97
150
  })
98
151
 
99
152
  afterEach(() => {
100
153
  vi.unstubAllEnvs()
101
154
  })
102
155
 
103
- it('builds the expected DAGGER_OPTIONS payload from the infisical export', async () => {
104
- const runDaggerCommand = createRunner()
105
- await runDaggerCommand('test-cmd', {
106
- args: { push: 'true' },
107
- env: { NODE_ENV: 'production' },
156
+ describe('infisical', () => {
157
+ beforeEach(() => {
158
+ mocks.fetchJson.mockResolvedValue([
159
+ { key: 'API_TOKEN', value: 'test-api-token', comment: '' },
160
+ { key: 'DAGGER_CLOUD_TOKEN', value: 'test-dagger-cloud-token', comment: '' },
161
+ ])
108
162
  })
109
163
 
110
- expect(mocks.exportCmd).toHaveBeenCalledWith(
111
- 'infisical export --silent --format=json --projectId test-project-id --env prod --path /ci/test'
112
- )
164
+ it('builds the expected DAGGER_OPTIONS payload from the infisical export', async () => {
165
+ const runDaggerCommand = createRunner()
166
+ await runDaggerCommand('test-cmd', {
167
+ args: { push: 'true' },
168
+ env: { NODE_ENV: 'production' },
169
+ })
113
170
 
114
- const { env } = getSpawnCall()
115
- expect(JSON.parse(env.DAGGER_OPTIONS as string)).toStrictEqual({
116
- args: { push: 'true' },
117
- env: { NODE_ENV: 'production' },
118
- secrets: { API_TOKEN: 'test-api-token' },
171
+ expect(mocks.fetchCmd).toHaveBeenCalledWith(
172
+ 'infisical export --silent --format=json --projectId test-project-id --env prod --path /ci/test'
173
+ )
174
+
175
+ const { env } = getSpawnCall()
176
+ expect(JSON.parse(env.DAGGER_OPTIONS as string)).toStrictEqual({
177
+ args: { push: 'true' },
178
+ env: { NODE_ENV: 'production' },
179
+ secrets: { API_TOKEN: 'test-api-token' },
180
+ })
181
+ expect(env.DAGGER_CLOUD_TOKEN).toBeUndefined()
182
+ })
183
+
184
+ it('passes DAGGER_CLOUD_TOKEN to the runner env (but not DAGGER_OPTIONS) in CI', async () => {
185
+ vi.stubEnv('CI', 'true')
186
+ vi.stubEnv('GITHUB_ACTIONS', 'true')
187
+
188
+ const runDaggerCommand = createRunner()
189
+ await runDaggerCommand('test-cmd')
190
+
191
+ const { env } = getSpawnCall()
192
+ const daggerOptions = JSON.parse(env.DAGGER_OPTIONS as string)
193
+ expect(daggerOptions.secrets).not.toHaveProperty('DAGGER_CLOUD_TOKEN')
194
+ expect(daggerOptions.env).toStrictEqual({ CI: 'true', GITHUB_ACTIONS: 'true' })
195
+ expect(env.DAGGER_CLOUD_TOKEN).toBe('test-dagger-cloud-token')
196
+ })
197
+
198
+ it('spawns dagger directly without an op wrapper', async () => {
199
+ const runDaggerCommand = createRunner()
200
+ await runDaggerCommand('test-cmd', { extraArgs: ['--verbose'] })
201
+
202
+ const { argv } = getSpawnCall()
203
+ expect(argv[0]).toBe('dagger')
204
+ expect(argv).not.toContain('op')
205
+ expect(argv).toStrictEqual([
206
+ 'dagger',
207
+ 'call',
208
+ 'test-cmd',
209
+ '--verbose',
210
+ '--options=env://DAGGER_OPTIONS',
211
+ ])
119
212
  })
120
- expect(env.DAGGER_CLOUD_TOKEN).toBeUndefined()
121
213
  })
122
214
 
123
- it('passes DAGGER_CLOUD_TOKEN to the runner env (but not DAGGER_OPTIONS) in CI', async () => {
124
- vi.stubEnv('CI', 'true')
125
- vi.stubEnv('GITHUB_ACTIONS', 'true')
215
+ describe('1password', () => {
216
+ beforeEach(() => {
217
+ mocks.fetchJson.mockResolvedValue(testOPItem)
218
+ })
219
+
220
+ it('builds the expected DAGGER_OPTIONS payload from the op item sections', async () => {
221
+ const runDaggerCommand = createOnePasswordRunner()
222
+ await runDaggerCommand('test-cmd', {
223
+ args: { push: 'true' },
224
+ env: { NODE_ENV: 'production' },
225
+ })
226
+
227
+ expect(mocks.fetchCmd).toHaveBeenCalledWith(
228
+ 'op item get test-item-id --vault test-vault-id --format json'
229
+ )
230
+
231
+ const { env } = getSpawnCall()
232
+ expect(JSON.parse(env.DAGGER_OPTIONS as string)).toStrictEqual({
233
+ args: { push: 'true' },
234
+ env: { NODE_ENV: 'production' },
235
+ secrets: { API_TOKEN: 'test-api-token' },
236
+ })
237
+ expect(env.DAGGER_CLOUD_TOKEN).toBeUndefined()
238
+ })
126
239
 
127
- const runDaggerCommand = createRunner()
128
- await runDaggerCommand('test-cmd')
240
+ it('wraps dagger call in op run --no-masking', async () => {
241
+ const runDaggerCommand = createOnePasswordRunner()
242
+ await runDaggerCommand('test-cmd', { extraArgs: ['--verbose'] })
129
243
 
130
- const { env } = getSpawnCall()
131
- const daggerOptions = JSON.parse(env.DAGGER_OPTIONS as string)
132
- expect(daggerOptions.secrets).not.toHaveProperty('DAGGER_CLOUD_TOKEN')
133
- expect(daggerOptions.env).toStrictEqual({ CI: 'true', GITHUB_ACTIONS: 'true' })
134
- expect(env.DAGGER_CLOUD_TOKEN).toBe('test-dagger-cloud-token')
244
+ const { argv } = getSpawnCall()
245
+ expect(argv).toStrictEqual([
246
+ 'op',
247
+ 'run',
248
+ '--no-masking',
249
+ '--',
250
+ 'dagger',
251
+ 'call',
252
+ 'test-cmd',
253
+ '--verbose',
254
+ '--options=env://DAGGER_OPTIONS',
255
+ ])
256
+ })
257
+
258
+ it('passes DAGGER_CLOUD_TOKEN as an op:// reference in CI', async () => {
259
+ vi.stubEnv('CI', 'true')
260
+ vi.stubEnv('GITHUB_ACTIONS', 'true')
261
+
262
+ const runDaggerCommand = createOnePasswordRunner()
263
+ await runDaggerCommand('test-cmd')
264
+
265
+ const { env } = getSpawnCall()
266
+ const daggerOptions = JSON.parse(env.DAGGER_OPTIONS as string)
267
+ expect(daggerOptions.secrets).not.toHaveProperty('DAGGER_CLOUD_TOKEN')
268
+ expect(daggerOptions.env).toStrictEqual({ CI: 'true', GITHUB_ACTIONS: 'true' })
269
+ expect(env.DAGGER_CLOUD_TOKEN).toBe('op://test-vault-id/test-item-id/DAGGER_CLOUD_TOKEN')
270
+ })
135
271
  })
136
272
 
137
- it('spawns dagger directly without an op wrapper', async () => {
138
- const runDaggerCommand = createRunner()
139
- await runDaggerCommand('test-cmd', { extraArgs: ['--verbose'] })
140
-
141
- const { argv } = getSpawnCall()
142
- expect(argv[0]).toBe('dagger')
143
- expect(argv).not.toContain('op')
144
- expect(argv).toStrictEqual([
145
- 'dagger',
146
- 'call',
147
- 'test-cmd',
148
- '--verbose',
149
- '--options=env://DAGGER_OPTIONS',
150
- ])
273
+ describe('additionalSecrets', () => {
274
+ beforeEach(() => {
275
+ mocks.fetchJson.mockResolvedValue([
276
+ { key: 'API_TOKEN', value: 'test-api-token', comment: '' },
277
+ ])
278
+ })
279
+
280
+ it('merges additional secrets into the provider secrets', async () => {
281
+ const runDaggerCommand = createRunner({
282
+ additionalSecrets: { FORWARDED_TOKEN: 'test-forwarded-token' },
283
+ })
284
+ await runDaggerCommand('test-cmd')
285
+
286
+ const { env } = getSpawnCall()
287
+ expect(JSON.parse(env.DAGGER_OPTIONS as string).secrets).toStrictEqual({
288
+ API_TOKEN: 'test-api-token',
289
+ FORWARDED_TOKEN: 'test-forwarded-token',
290
+ })
291
+ })
292
+
293
+ it('skips additional secrets with an undefined value', async () => {
294
+ const runDaggerCommand = createRunner({
295
+ additionalSecrets: { FORWARDED_TOKEN: undefined },
296
+ })
297
+ await runDaggerCommand('test-cmd')
298
+
299
+ const { env } = getSpawnCall()
300
+ expect(JSON.parse(env.DAGGER_OPTIONS as string).secrets).toStrictEqual({
301
+ API_TOKEN: 'test-api-token',
302
+ })
303
+ })
304
+
305
+ it('takes precedence over a provider secret of the same name', async () => {
306
+ const runDaggerCommand = createRunner({
307
+ additionalSecrets: { API_TOKEN: 'test-override-token' },
308
+ })
309
+ await runDaggerCommand('test-cmd')
310
+
311
+ const { env } = getSpawnCall()
312
+ expect(JSON.parse(env.DAGGER_OPTIONS as string).secrets).toStrictEqual({
313
+ API_TOKEN: 'test-override-token',
314
+ })
315
+ })
316
+
317
+ it('merges additional secrets for the 1password provider', async () => {
318
+ mocks.fetchJson.mockResolvedValue(testOPItem)
319
+
320
+ const runDaggerCommand = createOnePasswordRunner({
321
+ additionalSecrets: { FORWARDED_TOKEN: 'test-forwarded-token' },
322
+ })
323
+ await runDaggerCommand('test-cmd')
324
+
325
+ const { env } = getSpawnCall()
326
+ expect(JSON.parse(env.DAGGER_OPTIONS as string).secrets).toStrictEqual({
327
+ API_TOKEN: 'test-api-token',
328
+ FORWARDED_TOKEN: 'test-forwarded-token',
329
+ })
330
+ })
151
331
  })
152
332
 
153
333
  describe('docker socket', () => {
334
+ beforeEach(() => {
335
+ mocks.fetchJson.mockResolvedValue([
336
+ { key: 'API_TOKEN', value: 'test-api-token', comment: '' },
337
+ ])
338
+ })
339
+
154
340
  it('appends --docker-socket for dockerCommands when the socket exists', async () => {
155
341
  mocks.exists.mockResolvedValue(true)
156
342
 
@@ -1,6 +1,8 @@
1
1
  import { z } from 'zod/v4'
2
2
  import { $, fs } from 'zx'
3
3
 
4
+ import { OPItem, OPSection } from './op.js'
5
+
4
6
  import type { DaggerEnv, DaggerEnvConfig } from './dagger-env.js'
5
7
 
6
8
  /**
@@ -13,23 +15,54 @@ export const InfisicalSecret = z.object({
13
15
  })
14
16
 
15
17
  /**
16
- * Configuration for running Dagger commands with Infisical integration
18
+ * Infisical secrets provider configuration
17
19
  */
18
- export interface RunDaggerCommandConfig<T extends DaggerEnvConfig = DaggerEnvConfig> {
20
+ export interface InfisicalProviderConfig {
19
21
  /** Infisical project ID */
20
22
  projectId: string
21
23
  /** Infisical environment slug (e.g. `prod`) */
22
24
  env: string
23
25
  /** Infisical folder path to fetch secrets from (e.g. `/ci/my-repo`) */
24
26
  path: string
27
+ }
28
+
29
+ /**
30
+ * 1Password secrets provider configuration
31
+ */
32
+ export interface OnePasswordProviderConfig {
33
+ /** 1Password vault ID */
34
+ opVault: string
35
+ /** 1Password item ID */
36
+ opItem: string
37
+ /** 1Password sections to include for secrets */
38
+ opSections: Array<{ label: string; id: string }>
39
+ }
40
+
41
+ /**
42
+ * Configuration shared by all secrets providers
43
+ */
44
+ export interface RunDaggerCommandBaseConfig<T extends DaggerEnvConfig = DaggerEnvConfig> {
25
45
  /** Commands that should include Docker socket if available */
26
46
  dockerCommands?: string[]
27
47
  /** Hook to run before executing the command (e.g., vendor file setup) */
28
48
  beforeCommand?: () => Promise<void>
29
49
  /** DaggerEnv instance for schema validation and type safety */
30
50
  daggerEnv: DaggerEnv<T>
51
+ /**
52
+ * Secrets to pass to Dagger alongside the ones fetched from the provider,
53
+ * taking precedence on conflict. Entries with an undefined value are
54
+ * skipped, so short-lived credentials that only exist in some environments
55
+ * (e.g. a CI-issued token) can be forwarded without a separate config path.
56
+ */
57
+ additionalSecrets?: Record<string, string | undefined>
31
58
  }
32
59
 
60
+ /**
61
+ * Configuration for running Dagger commands with Infisical or 1Password integration
62
+ */
63
+ export type RunDaggerCommandConfig<T extends DaggerEnvConfig = DaggerEnvConfig> =
64
+ RunDaggerCommandBaseConfig<T> & (InfisicalProviderConfig | OnePasswordProviderConfig)
65
+
33
66
  /**
34
67
  * Options for individual command execution
35
68
  */
@@ -43,10 +76,58 @@ export interface RunDaggerCommandOptions {
43
76
  }
44
77
 
45
78
  /**
46
- * Creates a function to run Dagger commands with Infisical integration
79
+ * Fetches secrets from Infisical via `infisical export`
80
+ */
81
+ async function fetchInfisicalSecrets(
82
+ config: InfisicalProviderConfig
83
+ ): Promise<Record<string, string>> {
84
+ const exportedSecrets = InfisicalSecret.array().parse(
85
+ await $`infisical export --silent --format=json --projectId ${config.projectId} --env ${config.env} --path ${config.path}`.json()
86
+ )
87
+
88
+ return exportedSecrets.reduce(
89
+ (acc, s) => {
90
+ acc[s.key] = s.value
91
+ return acc
92
+ },
93
+ {} as Record<string, string>
94
+ )
95
+ }
96
+
97
+ /**
98
+ * Fetches secrets from a 1Password item, filtered to the configured sections
99
+ */
100
+ async function fetchOnePasswordSecrets(
101
+ config: OnePasswordProviderConfig
102
+ ): Promise<Record<string, string>> {
103
+ const opItem = OPItem.parse(
104
+ await $`op item get ${config.opItem} --vault ${config.opVault} --format json`.json()
105
+ )
106
+
107
+ const sectionsToInclude = OPSection.array().parse(config.opSections)
108
+
109
+ return opItem.fields
110
+ .filter((f) => sectionsToInclude.some((s) => s.id === f.section?.id))
111
+ .reduce(
112
+ (acc, f) => {
113
+ acc[f.label] = f.value
114
+ return acc
115
+ },
116
+ {} as Record<string, string>
117
+ )
118
+ }
119
+
120
+ /**
121
+ * Creates a function to run Dagger commands with Infisical or 1Password integration
47
122
  * @param config Configuration for the command runner
48
123
  * @returns Function to execute Dagger commands
49
124
  */
125
+ export function createDaggerCommandRunner<T extends DaggerEnvConfig>(
126
+ config: RunDaggerCommandBaseConfig<T> & InfisicalProviderConfig
127
+ ): (commandName: string, options?: RunDaggerCommandOptions) => Promise<void>
128
+ export function createDaggerCommandRunner<T extends DaggerEnvConfig>(
129
+ config: RunDaggerCommandBaseConfig<T> & OnePasswordProviderConfig
130
+ ): (commandName: string, options?: RunDaggerCommandOptions) => Promise<void>
50
131
  export function createDaggerCommandRunner<T extends DaggerEnvConfig>(
51
132
  config: RunDaggerCommandConfig<T>
52
133
  ) {
@@ -61,7 +142,7 @@ export function createDaggerCommandRunner<T extends DaggerEnvConfig>(
61
142
  await config.beforeCommand()
62
143
  }
63
144
 
64
- // Environment variables to pass to the `dagger` command
145
+ // Environment variables to pass to the spawned command
65
146
  const envVars: Record<string, string> = {}
66
147
 
67
148
  const commandArgs: string[] = [...extraArgs]
@@ -77,23 +158,29 @@ export function createDaggerCommandRunner<T extends DaggerEnvConfig>(
77
158
  }
78
159
  }
79
160
 
80
- // Fetch secrets from Infisical
81
- const exportedSecrets = InfisicalSecret.array().parse(
82
- await $`infisical export --silent --format=json --projectId ${config.projectId} --env ${config.env} --path ${config.path}`.json()
83
- )
161
+ // Fetch secrets from the configured provider
162
+ let secrets: Record<string, string>
163
+ if ('opVault' in config) {
164
+ secrets = await fetchOnePasswordSecrets(config)
84
165
 
85
- // Extract secrets into a key/value map
86
- const secrets = exportedSecrets.reduce(
87
- (acc, s) => {
88
- acc[s.key] = s.value
89
- return acc
90
- },
91
- {} as Record<string, string>
92
- )
166
+ // Pass dagger cloud token in CI because we don't have user auth.
167
+ // The op:// reference is resolved by the `op run` wrapper below.
168
+ if (process.env.CI !== undefined) {
169
+ envVars.DAGGER_CLOUD_TOKEN = `op://${config.opVault}/${config.opItem}/DAGGER_CLOUD_TOKEN`
170
+ }
171
+ } else {
172
+ secrets = await fetchInfisicalSecrets(config)
93
173
 
94
- // Pass dagger cloud token in CI because we don't have user auth
95
- if (process.env.CI !== undefined && secrets.DAGGER_CLOUD_TOKEN !== undefined) {
96
- envVars.DAGGER_CLOUD_TOKEN = secrets.DAGGER_CLOUD_TOKEN
174
+ // Pass dagger cloud token in CI because we don't have user auth
175
+ if (process.env.CI !== undefined && secrets.DAGGER_CLOUD_TOKEN !== undefined) {
176
+ envVars.DAGGER_CLOUD_TOKEN = secrets.DAGGER_CLOUD_TOKEN
177
+ }
178
+ }
179
+
180
+ for (const [name, value] of Object.entries(config.additionalSecrets ?? {})) {
181
+ if (value !== undefined) {
182
+ secrets[name] = value
183
+ }
97
184
  }
98
185
 
99
186
  // Build environment variables for Dagger
@@ -113,8 +200,10 @@ export function createDaggerCommandRunner<T extends DaggerEnvConfig>(
113
200
  })
114
201
  envVars.DAGGER_OPTIONS = JSON.stringify(daggerOptions)
115
202
 
116
- // Construct the command
203
+ // Construct the command. 1Password wraps `dagger call` in `op run`
204
+ // so that op:// references in the environment are resolved.
117
205
  const cmd: string[] = [
206
+ ...('opVault' in config ? ['op', 'run', '--no-masking', '--'] : []),
118
207
  'dagger',
119
208
  'call',
120
209
  commandName,