dagger-env 0.6.8 → 1.1.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 1Password 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
- - 🔐 **1Password Integration**: Built-in command runner with `op run` support
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 1Password CLI (`op`) to be installed.
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 (1Password Integration)
76
+ ## Command Runner (Infisical or 1Password Integration)
77
77
 
78
- For projects using 1Password for secret management, `dagger-env` provides a convenient command runner that integrates with `op run`:
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,16 +100,11 @@ 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
- opVault: 'your-vault-id',
106
- opItem: 'your-item-id',
107
- opSections: [
108
- {
109
- id: 'your-section-id',
110
- label: 'Shared'
111
- }
112
- ],
105
+ projectId: 'your-project-id',
106
+ env: 'prod',
107
+ path: '/ci/my-repo',
113
108
  dockerCommands: ['build', 'deploy', 'test'],
114
109
  daggerEnv: myDaggerEnv
115
110
  })
@@ -121,18 +116,34 @@ await runDaggerCommand('test', {
121
116
  })
122
117
  ```
123
118
 
124
- ### Advanced Configuration
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:
125
122
 
126
123
  ```typescript
127
- // Advanced configuration with multiple sections and pre-command setup
128
124
  const runDaggerCommand = createDaggerCommandRunner({
129
125
  opVault: 'your-vault-id',
130
126
  opItem: 'your-item-id',
131
127
  opSections: [
132
- { id: 'shared-section-id', label: 'Shared' },
133
- { id: 'project-section-id', label: 'Project Specific' }
128
+ {
129
+ id: 'your-section-id',
130
+ label: 'Shared'
131
+ }
134
132
  ],
135
133
  dockerCommands: ['build', 'deploy', 'test'],
134
+ daggerEnv: myDaggerEnv
135
+ })
136
+ ```
137
+
138
+ ### Advanced Configuration
139
+
140
+ ```typescript
141
+ // Advanced configuration with pre-command setup
142
+ const runDaggerCommand = createDaggerCommandRunner({
143
+ projectId: 'your-project-id',
144
+ env: 'prod',
145
+ path: '/ci/my-repo',
146
+ dockerCommands: ['build', 'deploy', 'test'],
136
147
  beforeCommand: async () => {
137
148
  // Setup vendor files, modules, etc.
138
149
  console.log('Setting up environment...')
@@ -208,13 +219,22 @@ Returns array of secret names for a specific preset.
208
219
 
209
220
  #### `createDaggerCommandRunner(config)`
210
221
 
211
- Creates a function to run Dagger commands with 1Password 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.
212
223
 
213
- **Parameters:**
224
+ **Infisical parameters:**
225
+
226
+ - `config.projectId`: Infisical project ID
227
+ - `config.env`: Infisical environment slug (e.g. `prod`)
228
+ - `config.path`: Infisical folder path to fetch secrets from (e.g. `/ci/my-repo`)
229
+
230
+ **1Password parameters:**
214
231
 
215
232
  - `config.opVault`: 1Password vault ID
216
233
  - `config.opItem`: 1Password item ID
217
234
  - `config.opSections`: Array of 1Password sections to include for secrets
235
+
236
+ **Shared parameters:**
237
+
218
238
  - `config.dockerCommands`: Optional array of command names that should include Docker socket
219
239
  - `config.beforeCommand`: Optional async function to run before executing the command
220
240
  - `config.daggerEnv`: DaggerEnv instance for schema validation and type safety
@@ -1,8 +1,28 @@
1
+ import { z } from 'zod/v4';
1
2
  import type { DaggerEnv, DaggerEnvConfig } from './dagger-env.js';
2
3
  /**
3
- * Configuration for running Dagger commands with 1Password integration
4
+ * A single secret returned by `infisical export --format=json`
4
5
  */
5
- export interface RunDaggerCommandConfig<T extends DaggerEnvConfig = DaggerEnvConfig> {
6
+ export type InfisicalSecret = z.infer<typeof InfisicalSecret>;
7
+ export declare const InfisicalSecret: z.ZodObject<{
8
+ key: z.ZodString;
9
+ value: z.ZodString;
10
+ }, z.core.$strip>;
11
+ /**
12
+ * Infisical secrets provider configuration
13
+ */
14
+ export interface InfisicalProviderConfig {
15
+ /** Infisical project ID */
16
+ projectId: string;
17
+ /** Infisical environment slug (e.g. `prod`) */
18
+ env: string;
19
+ /** Infisical folder path to fetch secrets from (e.g. `/ci/my-repo`) */
20
+ path: string;
21
+ }
22
+ /**
23
+ * 1Password secrets provider configuration
24
+ */
25
+ export interface OnePasswordProviderConfig {
6
26
  /** 1Password vault ID */
7
27
  opVault: string;
8
28
  /** 1Password item ID */
@@ -12,6 +32,11 @@ export interface RunDaggerCommandConfig<T extends DaggerEnvConfig = DaggerEnvCon
12
32
  label: string;
13
33
  id: string;
14
34
  }>;
35
+ }
36
+ /**
37
+ * Configuration shared by all secrets providers
38
+ */
39
+ export interface RunDaggerCommandBaseConfig<T extends DaggerEnvConfig = DaggerEnvConfig> {
15
40
  /** Commands that should include Docker socket if available */
16
41
  dockerCommands?: string[];
17
42
  /** Hook to run before executing the command (e.g., vendor file setup) */
@@ -19,6 +44,10 @@ export interface RunDaggerCommandConfig<T extends DaggerEnvConfig = DaggerEnvCon
19
44
  /** DaggerEnv instance for schema validation and type safety */
20
45
  daggerEnv: DaggerEnv<T>;
21
46
  }
47
+ /**
48
+ * Configuration for running Dagger commands with Infisical or 1Password integration
49
+ */
50
+ export type RunDaggerCommandConfig<T extends DaggerEnvConfig = DaggerEnvConfig> = RunDaggerCommandBaseConfig<T> & (InfisicalProviderConfig | OnePasswordProviderConfig);
22
51
  /**
23
52
  * Options for individual command execution
24
53
  */
@@ -31,9 +60,10 @@ export interface RunDaggerCommandOptions {
31
60
  extraArgs?: string[];
32
61
  }
33
62
  /**
34
- * Creates a function to run Dagger commands with 1Password integration
63
+ * Creates a function to run Dagger commands with Infisical or 1Password integration
35
64
  * @param config Configuration for the command runner
36
65
  * @returns Function to execute Dagger commands
37
66
  */
38
- export declare function createDaggerCommandRunner<T extends DaggerEnvConfig>(config: RunDaggerCommandConfig<T>): (commandName: string, options?: RunDaggerCommandOptions) => Promise<void>;
67
+ export declare function createDaggerCommandRunner<T extends DaggerEnvConfig>(config: RunDaggerCommandBaseConfig<T> & InfisicalProviderConfig): (commandName: string, options?: RunDaggerCommandOptions) => Promise<void>;
68
+ export declare function createDaggerCommandRunner<T extends DaggerEnvConfig>(config: RunDaggerCommandBaseConfig<T> & OnePasswordProviderConfig): (commandName: string, options?: RunDaggerCommandOptions) => Promise<void>;
39
69
  //# 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":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAEjE;;GAEG;AACH,MAAM,WAAW,sBAAsB,CAAC,CAAC,SAAS,eAAe,GAAG,eAAe;IAClF,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;IAChD,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,IAKhC,aAAa,MAAM,EACnB,UAAU,uBAAuB,KAC/B,OAAO,CAAC,IAAI,CAAC,CAuFhB"}
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;CACvB;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,24 +1,42 @@
1
+ import { z } from 'zod/v4';
1
2
  import { $, fs } from 'zx';
2
3
  import { OPItem, OPSection } from './op.js';
4
+ export const InfisicalSecret = z.object({
5
+ key: z.string(),
6
+ value: z.string(),
7
+ });
3
8
  /**
4
- * Creates a function to run Dagger commands with 1Password integration
5
- * @param config Configuration for the command runner
6
- * @returns Function to execute Dagger commands
9
+ * Fetches secrets from Infisical via `infisical export`
7
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
+ }
8
31
  export function createDaggerCommandRunner(config) {
9
- const opItemUri = `op://${config.opVault}/${config.opItem}`;
10
32
  return async function runDaggerCommand(commandName, options) {
11
33
  const { args = {}, env = {}, extraArgs = [] } = options ?? {};
12
34
  // Run any pre-command setup
13
35
  if (config.beforeCommand) {
14
36
  await config.beforeCommand();
15
37
  }
16
- // Environment variables to pass to the `op run` command
38
+ // Environment variables to pass to the spawned command
17
39
  const envVars = {};
18
- // Pass dagger cloud token in CI because we don't have user auth
19
- if (process.env.CI !== undefined) {
20
- envVars.DAGGER_CLOUD_TOKEN = `${opItemUri}/DAGGER_CLOUD_TOKEN`;
21
- }
22
40
  const commandArgs = [...extraArgs];
23
41
  // Add Docker socket for specific commands if available
24
42
  if (config.dockerCommands?.includes(commandName)) {
@@ -31,17 +49,23 @@ export function createDaggerCommandRunner(config) {
31
49
  // Ignore if fs is not available or docker socket doesn't exist
32
50
  }
33
51
  }
34
- // Fetch 1Password item
35
- const opItem = OPItem.parse(await $ `op item get ${config.opItem} --vault ${config.opVault} --format json`.json());
36
- // Parse sections to include
37
- const sectionsToInclude = OPSection.array().parse(config.opSections);
38
- // Extract secrets from specified sections
39
- const secrets = opItem.fields
40
- .filter((f) => sectionsToInclude.some((s) => s.id === f.section?.id))
41
- .reduce((acc, f) => {
42
- acc[f.label] = f.value;
43
- return acc;
44
- }, {});
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
+ }
45
69
  // Build environment variables for Dagger
46
70
  const daggerEnv = { ...env };
47
71
  if (process.env.CI !== undefined) {
@@ -57,12 +81,10 @@ export function createDaggerCommandRunner(config) {
57
81
  secrets,
58
82
  });
59
83
  envVars.DAGGER_OPTIONS = JSON.stringify(daggerOptions);
60
- // Construct the command
84
+ // Construct the command. 1Password wraps `dagger call` in `op run`
85
+ // so that op:// references in the environment are resolved.
61
86
  const cmd = [
62
- 'op',
63
- 'run',
64
- '--no-masking',
65
- '--',
87
+ ...('opVault' in config ? ['op', 'run', '--no-masking', '--'] : []),
66
88
  'dagger',
67
89
  'call',
68
90
  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 { $, fs } from 'zx'\n\nimport { OPItem, OPSection } from './op.js'\n\nimport type { DaggerEnv, DaggerEnvConfig } from './dagger-env.js'\n\n/**\n * Configuration for running Dagger commands with 1Password integration\n */\nexport interface RunDaggerCommandConfig<T extends DaggerEnvConfig = DaggerEnvConfig> {\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\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 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: RunDaggerCommandConfig<T>\n) {\n\tconst opItemUri = `op://${config.opVault}/${config.opItem}`\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 `op run` command\n\t\tconst envVars: Record<string, string> = {}\n\n\t\t// Pass dagger cloud token in CI because we don't have user auth\n\t\tif (process.env.CI !== undefined) {\n\t\t\tenvVars.DAGGER_CLOUD_TOKEN = `${opItemUri}/DAGGER_CLOUD_TOKEN`\n\t\t}\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 1Password item\n\t\tconst opItem = OPItem.parse(\n\t\t\tawait $`op item get ${config.opItem} --vault ${config.opVault} --format json`.json()\n\t\t)\n\n\t\t// Parse sections to include\n\t\tconst sectionsToInclude = OPSection.array().parse(config.opSections)\n\n\t\t// Extract secrets from specified sections\n\t\tconst secrets = opItem.fields\n\t\t\t.filter((f) => sectionsToInclude.some((s) => s.id === f.section?.id))\n\t\t\t.reduce(\n\t\t\t\t(acc, f) => {\n\t\t\t\t\tacc[f.label] = f.value\n\t\t\t\t\treturn acc\n\t\t\t\t},\n\t\t\t\t{} as Record<string, string>\n\t\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'op',\n\t\t\t'run',\n\t\t\t'--no-masking',\n\t\t\t'--',\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}\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\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": "0.6.8",
3
+ "version": "1.1.0",
4
4
  "private": false,
5
5
  "description": "A type-safe, reusable environment configuration abstraction for Dagger modules.",
6
6
  "keywords": [
@@ -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 1Password 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'
@@ -56,8 +56,27 @@ const daggerEnv = createDaggerEnv({
56
56
  derivedEnvVars: {} as const,
57
57
  })
58
58
 
59
- // Create the command runner with 1Password configuration
59
+ // Create the command runner with Infisical configuration
60
60
  export const runDaggerCommand = createDaggerCommandRunner({
61
+ // Infisical configuration
62
+ projectId: 'your-project-id', // Replace with your actual Infisical project ID
63
+ env: 'prod', // Infisical environment slug
64
+ path: '/ci/my-repo', // Infisical folder path to fetch secrets from
65
+ // Commands that need Docker socket access
66
+ dockerCommands: ['build', 'test', 'deploy', 'migrate', 'seed'],
67
+ // Optional: Run setup before executing commands
68
+ beforeCommand: async () => {
69
+ // Example: Set up vendor files, download dependencies, etc.
70
+ console.log('Preparing Dagger environment...')
71
+ // const modules = await getDaggerModules()
72
+ // await setupDaggerVendorFiles(modules)
73
+ },
74
+ // Pass the DaggerEnv instance directly
75
+ daggerEnv,
76
+ })
77
+
78
+ // Alternatively, create a command runner with 1Password configuration
79
+ export const runDaggerCommandWithOnePassword = createDaggerCommandRunner({
61
80
  // 1Password configuration
62
81
  opVault: 'your-vault-id', // Replace with your actual vault ID
63
82
  opItem: 'your-item-id', // Replace with your actual item ID
@@ -73,13 +92,6 @@ export const runDaggerCommand = createDaggerCommandRunner({
73
92
  ],
74
93
  // Commands that need Docker socket access
75
94
  dockerCommands: ['build', 'test', 'deploy', 'migrate', 'seed'],
76
- // Optional: Run setup before executing commands
77
- beforeCommand: async () => {
78
- // Example: Set up vendor files, download dependencies, etc.
79
- console.log('Preparing Dagger environment...')
80
- // const modules = await getDaggerModules()
81
- // await setupDaggerVendorFiles(modules)
82
- },
83
95
  // Pass the DaggerEnv instance directly
84
96
  daggerEnv,
85
97
  })
@@ -0,0 +1,314 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
2
+ import { z } from 'zod/v4'
3
+
4
+ import { createDaggerEnv } from './dagger-env'
5
+ import { createDaggerCommandRunner } from './run-dagger-cmd'
6
+
7
+ import type { OPItem } from './op'
8
+ import type { RunDaggerCommandConfig } from './run-dagger-cmd'
9
+
10
+ const mocks = vi.hoisted(() => ({
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(),
15
+ /** Records the final command execution: (options, argv) */
16
+ spawn: vi.fn(
17
+ async (_opts: { env: Record<string, string | undefined> }, _argv: string[]) => undefined
18
+ ),
19
+ /** Mock for fs.exists() (docker socket probe) */
20
+ exists: vi.fn(async () => false),
21
+ }))
22
+
23
+ vi.mock('zx', () => {
24
+ const $ = (firstArg: unknown, ...vals: unknown[]) => {
25
+ if (Array.isArray(firstArg)) {
26
+ // Direct template call: $`infisical export ...` / $`op item get ...`
27
+ const pieces = firstArg as readonly string[]
28
+ const cmd = pieces.reduce(
29
+ (acc, piece, i) => acc + piece + (i < vals.length ? String(vals[i]) : ''),
30
+ ''
31
+ )
32
+ mocks.fetchCmd(cmd)
33
+ return { json: mocks.fetchJson }
34
+ }
35
+ // Options call: $({ env, stdio }) returns a template executor
36
+ return (_pieces: TemplateStringsArray, ...templateVals: unknown[]) =>
37
+ mocks.spawn(
38
+ firstArg as { env: Record<string, string | undefined> },
39
+ templateVals[0] as string[]
40
+ )
41
+ }
42
+ return { $, fs: { exists: mocks.exists } }
43
+ })
44
+
45
+ /**
46
+ * Environment variables passed to the spawned process
47
+ */
48
+ type SpawnEnv = Record<string, string | undefined>
49
+
50
+ function getSpawnCall(): { env: SpawnEnv; argv: string[] } {
51
+ expect(mocks.spawn).toHaveBeenCalledTimes(1)
52
+ const call = mocks.spawn.mock.calls.at(0)
53
+ if (!call) {
54
+ throw new Error('spawn was not called')
55
+ }
56
+ const [opts, argv] = call
57
+ return { env: opts.env, argv }
58
+ }
59
+
60
+ function createTestDaggerEnv() {
61
+ return createDaggerEnv({
62
+ args: z.object({
63
+ push: z.string().optional(),
64
+ }),
65
+ env: z.object({
66
+ CI: z.string().optional(),
67
+ GITHUB_ACTIONS: z.string().optional(),
68
+ NODE_ENV: z.string().optional(),
69
+ }),
70
+ secrets: z.object({
71
+ API_TOKEN: z.string(),
72
+ }),
73
+ secretPresets: {
74
+ api: ['API_TOKEN'],
75
+ },
76
+ derivedEnvVars: {},
77
+ })
78
+ }
79
+
80
+ function createRunner(overrides?: Partial<Pick<RunDaggerCommandConfig, 'dockerCommands'>>) {
81
+ return createDaggerCommandRunner({
82
+ projectId: 'test-project-id',
83
+ env: 'prod',
84
+ path: '/ci/test',
85
+ daggerEnv: createTestDaggerEnv(),
86
+ ...overrides,
87
+ })
88
+ }
89
+
90
+ function createOnePasswordRunner(
91
+ overrides?: Partial<Pick<RunDaggerCommandConfig, 'dockerCommands'>>
92
+ ) {
93
+ return createDaggerCommandRunner({
94
+ opVault: 'test-vault-id',
95
+ opItem: 'test-item-id',
96
+ opSections: [{ id: 'shared-section-id', label: 'Shared' }],
97
+ daggerEnv: createTestDaggerEnv(),
98
+ ...overrides,
99
+ })
100
+ }
101
+
102
+ /**
103
+ * A realistic `op item get --format json` payload with one field in the
104
+ * configured section and one field in an excluded section
105
+ */
106
+ const testOPItem: OPItem = {
107
+ id: 'test-item-id',
108
+ title: 'Test Item',
109
+ favorite: false,
110
+ version: 1,
111
+ vault: { id: 'test-vault-id', name: 'Test Vault' },
112
+ category: 'SECURE_NOTE',
113
+ last_edited_by: 'test-user',
114
+ created_at: '2024-01-01T00:00:00Z',
115
+ updated_at: '2024-01-01T00:00:00Z',
116
+ additional_information: '',
117
+ sections: [
118
+ { id: 'shared-section-id', label: 'Shared' },
119
+ { id: 'other-section-id', label: 'Other' },
120
+ ],
121
+ fields: [
122
+ {
123
+ id: 'field-1',
124
+ section: { id: 'shared-section-id', label: 'Shared' },
125
+ type: 'CONCEALED',
126
+ label: 'API_TOKEN',
127
+ value: 'test-api-token',
128
+ reference: 'op://test-vault-id/test-item-id/API_TOKEN',
129
+ },
130
+ {
131
+ id: 'field-2',
132
+ section: { id: 'other-section-id', label: 'Other' },
133
+ type: 'CONCEALED',
134
+ label: 'EXCLUDED_SECRET',
135
+ value: 'excluded-value',
136
+ reference: 'op://test-vault-id/test-item-id/EXCLUDED_SECRET',
137
+ },
138
+ ],
139
+ }
140
+
141
+ describe('createDaggerCommandRunner()', () => {
142
+ beforeEach(() => {
143
+ vi.clearAllMocks()
144
+ vi.stubEnv('CI', undefined)
145
+ vi.stubEnv('GITHUB_ACTIONS', undefined)
146
+ vi.stubEnv('DAGGER_CLOUD_TOKEN', undefined)
147
+ })
148
+
149
+ afterEach(() => {
150
+ vi.unstubAllEnvs()
151
+ })
152
+
153
+ describe('infisical', () => {
154
+ beforeEach(() => {
155
+ mocks.fetchJson.mockResolvedValue([
156
+ { key: 'API_TOKEN', value: 'test-api-token', comment: '' },
157
+ { key: 'DAGGER_CLOUD_TOKEN', value: 'test-dagger-cloud-token', comment: '' },
158
+ ])
159
+ })
160
+
161
+ it('builds the expected DAGGER_OPTIONS payload from the infisical export', async () => {
162
+ const runDaggerCommand = createRunner()
163
+ await runDaggerCommand('test-cmd', {
164
+ args: { push: 'true' },
165
+ env: { NODE_ENV: 'production' },
166
+ })
167
+
168
+ expect(mocks.fetchCmd).toHaveBeenCalledWith(
169
+ 'infisical export --silent --format=json --projectId test-project-id --env prod --path /ci/test'
170
+ )
171
+
172
+ const { env } = getSpawnCall()
173
+ expect(JSON.parse(env.DAGGER_OPTIONS as string)).toStrictEqual({
174
+ args: { push: 'true' },
175
+ env: { NODE_ENV: 'production' },
176
+ secrets: { API_TOKEN: 'test-api-token' },
177
+ })
178
+ expect(env.DAGGER_CLOUD_TOKEN).toBeUndefined()
179
+ })
180
+
181
+ it('passes DAGGER_CLOUD_TOKEN to the runner env (but not DAGGER_OPTIONS) in CI', async () => {
182
+ vi.stubEnv('CI', 'true')
183
+ vi.stubEnv('GITHUB_ACTIONS', 'true')
184
+
185
+ const runDaggerCommand = createRunner()
186
+ await runDaggerCommand('test-cmd')
187
+
188
+ const { env } = getSpawnCall()
189
+ const daggerOptions = JSON.parse(env.DAGGER_OPTIONS as string)
190
+ expect(daggerOptions.secrets).not.toHaveProperty('DAGGER_CLOUD_TOKEN')
191
+ expect(daggerOptions.env).toStrictEqual({ CI: 'true', GITHUB_ACTIONS: 'true' })
192
+ expect(env.DAGGER_CLOUD_TOKEN).toBe('test-dagger-cloud-token')
193
+ })
194
+
195
+ it('spawns dagger directly without an op wrapper', async () => {
196
+ const runDaggerCommand = createRunner()
197
+ await runDaggerCommand('test-cmd', { extraArgs: ['--verbose'] })
198
+
199
+ const { argv } = getSpawnCall()
200
+ expect(argv[0]).toBe('dagger')
201
+ expect(argv).not.toContain('op')
202
+ expect(argv).toStrictEqual([
203
+ 'dagger',
204
+ 'call',
205
+ 'test-cmd',
206
+ '--verbose',
207
+ '--options=env://DAGGER_OPTIONS',
208
+ ])
209
+ })
210
+ })
211
+
212
+ describe('1password', () => {
213
+ beforeEach(() => {
214
+ mocks.fetchJson.mockResolvedValue(testOPItem)
215
+ })
216
+
217
+ it('builds the expected DAGGER_OPTIONS payload from the op item sections', async () => {
218
+ const runDaggerCommand = createOnePasswordRunner()
219
+ await runDaggerCommand('test-cmd', {
220
+ args: { push: 'true' },
221
+ env: { NODE_ENV: 'production' },
222
+ })
223
+
224
+ expect(mocks.fetchCmd).toHaveBeenCalledWith(
225
+ 'op item get test-item-id --vault test-vault-id --format json'
226
+ )
227
+
228
+ const { env } = getSpawnCall()
229
+ expect(JSON.parse(env.DAGGER_OPTIONS as string)).toStrictEqual({
230
+ args: { push: 'true' },
231
+ env: { NODE_ENV: 'production' },
232
+ secrets: { API_TOKEN: 'test-api-token' },
233
+ })
234
+ expect(env.DAGGER_CLOUD_TOKEN).toBeUndefined()
235
+ })
236
+
237
+ it('wraps dagger call in op run --no-masking', async () => {
238
+ const runDaggerCommand = createOnePasswordRunner()
239
+ await runDaggerCommand('test-cmd', { extraArgs: ['--verbose'] })
240
+
241
+ const { argv } = getSpawnCall()
242
+ expect(argv).toStrictEqual([
243
+ 'op',
244
+ 'run',
245
+ '--no-masking',
246
+ '--',
247
+ 'dagger',
248
+ 'call',
249
+ 'test-cmd',
250
+ '--verbose',
251
+ '--options=env://DAGGER_OPTIONS',
252
+ ])
253
+ })
254
+
255
+ it('passes DAGGER_CLOUD_TOKEN as an op:// reference in CI', async () => {
256
+ vi.stubEnv('CI', 'true')
257
+ vi.stubEnv('GITHUB_ACTIONS', 'true')
258
+
259
+ const runDaggerCommand = createOnePasswordRunner()
260
+ await runDaggerCommand('test-cmd')
261
+
262
+ const { env } = getSpawnCall()
263
+ const daggerOptions = JSON.parse(env.DAGGER_OPTIONS as string)
264
+ expect(daggerOptions.secrets).not.toHaveProperty('DAGGER_CLOUD_TOKEN')
265
+ expect(daggerOptions.env).toStrictEqual({ CI: 'true', GITHUB_ACTIONS: 'true' })
266
+ expect(env.DAGGER_CLOUD_TOKEN).toBe('op://test-vault-id/test-item-id/DAGGER_CLOUD_TOKEN')
267
+ })
268
+ })
269
+
270
+ describe('docker socket', () => {
271
+ beforeEach(() => {
272
+ mocks.fetchJson.mockResolvedValue([
273
+ { key: 'API_TOKEN', value: 'test-api-token', comment: '' },
274
+ ])
275
+ })
276
+
277
+ it('appends --docker-socket for dockerCommands when the socket exists', async () => {
278
+ mocks.exists.mockResolvedValue(true)
279
+
280
+ const runDaggerCommand = createRunner({ dockerCommands: ['build'] })
281
+ await runDaggerCommand('build')
282
+
283
+ const { argv } = getSpawnCall()
284
+ expect(argv).toStrictEqual([
285
+ 'dagger',
286
+ 'call',
287
+ 'build',
288
+ '--docker-socket=/var/run/docker.sock',
289
+ '--options=env://DAGGER_OPTIONS',
290
+ ])
291
+ })
292
+
293
+ it('does not append --docker-socket for non-docker commands', async () => {
294
+ mocks.exists.mockResolvedValue(true)
295
+
296
+ const runDaggerCommand = createRunner({ dockerCommands: ['build'] })
297
+ await runDaggerCommand('test-cmd')
298
+
299
+ const { argv } = getSpawnCall()
300
+ expect(argv).not.toContain('--docker-socket=/var/run/docker.sock')
301
+ expect(mocks.exists).not.toHaveBeenCalled()
302
+ })
303
+
304
+ it('does not append --docker-socket when the socket does not exist', async () => {
305
+ mocks.exists.mockResolvedValue(false)
306
+
307
+ const runDaggerCommand = createRunner({ dockerCommands: ['build'] })
308
+ await runDaggerCommand('build')
309
+
310
+ const { argv } = getSpawnCall()
311
+ expect(argv).not.toContain('--docker-socket=/var/run/docker.sock')
312
+ })
313
+ })
314
+ })
@@ -1,3 +1,4 @@
1
+ import { z } from 'zod/v4'
1
2
  import { $, fs } from 'zx'
2
3
 
3
4
  import { OPItem, OPSection } from './op.js'
@@ -5,15 +6,42 @@ import { OPItem, OPSection } from './op.js'
5
6
  import type { DaggerEnv, DaggerEnvConfig } from './dagger-env.js'
6
7
 
7
8
  /**
8
- * Configuration for running Dagger commands with 1Password integration
9
+ * A single secret returned by `infisical export --format=json`
9
10
  */
10
- export interface RunDaggerCommandConfig<T extends DaggerEnvConfig = DaggerEnvConfig> {
11
+ export type InfisicalSecret = z.infer<typeof InfisicalSecret>
12
+ export const InfisicalSecret = z.object({
13
+ key: z.string(),
14
+ value: z.string(),
15
+ })
16
+
17
+ /**
18
+ * Infisical secrets provider configuration
19
+ */
20
+ export interface InfisicalProviderConfig {
21
+ /** Infisical project ID */
22
+ projectId: string
23
+ /** Infisical environment slug (e.g. `prod`) */
24
+ env: string
25
+ /** Infisical folder path to fetch secrets from (e.g. `/ci/my-repo`) */
26
+ path: string
27
+ }
28
+
29
+ /**
30
+ * 1Password secrets provider configuration
31
+ */
32
+ export interface OnePasswordProviderConfig {
11
33
  /** 1Password vault ID */
12
34
  opVault: string
13
35
  /** 1Password item ID */
14
36
  opItem: string
15
37
  /** 1Password sections to include for secrets */
16
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> {
17
45
  /** Commands that should include Docker socket if available */
18
46
  dockerCommands?: string[]
19
47
  /** Hook to run before executing the command (e.g., vendor file setup) */
@@ -22,6 +50,12 @@ export interface RunDaggerCommandConfig<T extends DaggerEnvConfig = DaggerEnvCon
22
50
  daggerEnv: DaggerEnv<T>
23
51
  }
24
52
 
53
+ /**
54
+ * Configuration for running Dagger commands with Infisical or 1Password integration
55
+ */
56
+ export type RunDaggerCommandConfig<T extends DaggerEnvConfig = DaggerEnvConfig> =
57
+ RunDaggerCommandBaseConfig<T> & (InfisicalProviderConfig | OnePasswordProviderConfig)
58
+
25
59
  /**
26
60
  * Options for individual command execution
27
61
  */
@@ -35,15 +69,61 @@ export interface RunDaggerCommandOptions {
35
69
  }
36
70
 
37
71
  /**
38
- * Creates a function to run Dagger commands with 1Password integration
72
+ * Fetches secrets from Infisical via `infisical export`
73
+ */
74
+ async function fetchInfisicalSecrets(
75
+ config: InfisicalProviderConfig
76
+ ): Promise<Record<string, string>> {
77
+ const exportedSecrets = InfisicalSecret.array().parse(
78
+ await $`infisical export --silent --format=json --projectId ${config.projectId} --env ${config.env} --path ${config.path}`.json()
79
+ )
80
+
81
+ return exportedSecrets.reduce(
82
+ (acc, s) => {
83
+ acc[s.key] = s.value
84
+ return acc
85
+ },
86
+ {} as Record<string, string>
87
+ )
88
+ }
89
+
90
+ /**
91
+ * Fetches secrets from a 1Password item, filtered to the configured sections
92
+ */
93
+ async function fetchOnePasswordSecrets(
94
+ config: OnePasswordProviderConfig
95
+ ): Promise<Record<string, string>> {
96
+ const opItem = OPItem.parse(
97
+ await $`op item get ${config.opItem} --vault ${config.opVault} --format json`.json()
98
+ )
99
+
100
+ const sectionsToInclude = OPSection.array().parse(config.opSections)
101
+
102
+ return opItem.fields
103
+ .filter((f) => sectionsToInclude.some((s) => s.id === f.section?.id))
104
+ .reduce(
105
+ (acc, f) => {
106
+ acc[f.label] = f.value
107
+ return acc
108
+ },
109
+ {} as Record<string, string>
110
+ )
111
+ }
112
+
113
+ /**
114
+ * Creates a function to run Dagger commands with Infisical or 1Password integration
39
115
  * @param config Configuration for the command runner
40
116
  * @returns Function to execute Dagger commands
41
117
  */
118
+ export function createDaggerCommandRunner<T extends DaggerEnvConfig>(
119
+ config: RunDaggerCommandBaseConfig<T> & InfisicalProviderConfig
120
+ ): (commandName: string, options?: RunDaggerCommandOptions) => Promise<void>
121
+ export function createDaggerCommandRunner<T extends DaggerEnvConfig>(
122
+ config: RunDaggerCommandBaseConfig<T> & OnePasswordProviderConfig
123
+ ): (commandName: string, options?: RunDaggerCommandOptions) => Promise<void>
42
124
  export function createDaggerCommandRunner<T extends DaggerEnvConfig>(
43
125
  config: RunDaggerCommandConfig<T>
44
126
  ) {
45
- const opItemUri = `op://${config.opVault}/${config.opItem}`
46
-
47
127
  return async function runDaggerCommand(
48
128
  commandName: string,
49
129
  options?: RunDaggerCommandOptions
@@ -55,14 +135,9 @@ export function createDaggerCommandRunner<T extends DaggerEnvConfig>(
55
135
  await config.beforeCommand()
56
136
  }
57
137
 
58
- // Environment variables to pass to the `op run` command
138
+ // Environment variables to pass to the spawned command
59
139
  const envVars: Record<string, string> = {}
60
140
 
61
- // Pass dagger cloud token in CI because we don't have user auth
62
- if (process.env.CI !== undefined) {
63
- envVars.DAGGER_CLOUD_TOKEN = `${opItemUri}/DAGGER_CLOUD_TOKEN`
64
- }
65
-
66
141
  const commandArgs: string[] = [...extraArgs]
67
142
 
68
143
  // Add Docker socket for specific commands if available
@@ -76,24 +151,24 @@ export function createDaggerCommandRunner<T extends DaggerEnvConfig>(
76
151
  }
77
152
  }
78
153
 
79
- // Fetch 1Password item
80
- const opItem = OPItem.parse(
81
- await $`op item get ${config.opItem} --vault ${config.opVault} --format json`.json()
82
- )
154
+ // Fetch secrets from the configured provider
155
+ let secrets: Record<string, string>
156
+ if ('opVault' in config) {
157
+ secrets = await fetchOnePasswordSecrets(config)
83
158
 
84
- // Parse sections to include
85
- const sectionsToInclude = OPSection.array().parse(config.opSections)
159
+ // Pass dagger cloud token in CI because we don't have user auth.
160
+ // The op:// reference is resolved by the `op run` wrapper below.
161
+ if (process.env.CI !== undefined) {
162
+ envVars.DAGGER_CLOUD_TOKEN = `op://${config.opVault}/${config.opItem}/DAGGER_CLOUD_TOKEN`
163
+ }
164
+ } else {
165
+ secrets = await fetchInfisicalSecrets(config)
86
166
 
87
- // Extract secrets from specified sections
88
- const secrets = opItem.fields
89
- .filter((f) => sectionsToInclude.some((s) => s.id === f.section?.id))
90
- .reduce(
91
- (acc, f) => {
92
- acc[f.label] = f.value
93
- return acc
94
- },
95
- {} as Record<string, string>
96
- )
167
+ // Pass dagger cloud token in CI because we don't have user auth
168
+ if (process.env.CI !== undefined && secrets.DAGGER_CLOUD_TOKEN !== undefined) {
169
+ envVars.DAGGER_CLOUD_TOKEN = secrets.DAGGER_CLOUD_TOKEN
170
+ }
171
+ }
97
172
 
98
173
  // Build environment variables for Dagger
99
174
  const daggerEnv: Record<string, string> = { ...env }
@@ -112,12 +187,10 @@ export function createDaggerCommandRunner<T extends DaggerEnvConfig>(
112
187
  })
113
188
  envVars.DAGGER_OPTIONS = JSON.stringify(daggerOptions)
114
189
 
115
- // Construct the command
190
+ // Construct the command. 1Password wraps `dagger call` in `op run`
191
+ // so that op:// references in the environment are resolved.
116
192
  const cmd: string[] = [
117
- 'op',
118
- 'run',
119
- '--no-masking',
120
- '--',
193
+ ...('opVault' in config ? ['op', 'run', '--no-masking', '--'] : []),
121
194
  'dagger',
122
195
  'call',
123
196
  commandName,