@brutalsystems/dray 0.1.3 → 0.1.4

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
@@ -57,7 +57,9 @@ Global flags: `--dry-run` (print the plan, run nothing), `--allow-dirty`
57
57
  "dockerfile": "Dockerfile", "context": ".",
58
58
  // optional: rebuild a cached deps layer when lockfiles change
59
59
  "depsImage": { "dockerfile": "Dockerfile.deps", "tag": "app:deps", "rebuildOn": ["uv.lock"] },
60
- // optional: inject --build-arg from an env file (e.g. VITE_* from .env.production)
60
+ // optional: inject --build-arg from an env file (e.g. VITE_* from .env.production),
61
+ // or from a sops-encrypted file decrypted in memory at build time (no plaintext on disk):
62
+ // "buildArgs": { "sopsEnvFile": "secrets.env", "prefix": "VITE_" }
61
63
  "buildArgs": { "envFile": ".env.production", "prefix": "VITE_" } },
62
64
  // image from another repo (cloned to a tmp dir, built, pushed):
63
65
  { "name": "svc", "ecr": "svc", "source": { "git": "https://github.com/org/svc", "ref": "main" } }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brutalsystems/dray",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "Convention-driven multi-repo deploy orchestrator (forge for shipping)",
5
5
  "bin": {
6
6
  "dray": "bin/dray.js"
@@ -1,4 +1,5 @@
1
1
  const fs = require('node:fs'); const path = require('node:path');
2
+ let _run = require('../primitives/exec').run; function _withRun(fn) { _run = fn; }
2
3
 
3
4
  function stripQuotes(v) {
4
5
  const t = v.trim();
@@ -8,27 +9,38 @@ function stripQuotes(v) {
8
9
  return t;
9
10
  }
10
11
 
12
+ // Parse dotenv-style text into ["KEY=VALUE", ...], filtered by `prefix`.
13
+ function parseEnvLines(text, prefix) {
14
+ const out = [];
15
+ for (const line of text.split('\n')) {
16
+ const m = line.match(/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/);
17
+ if (!m) continue;
18
+ const [, k, v] = m;
19
+ if (prefix && !k.startsWith(prefix)) continue;
20
+ out.push(`${k}=${stripQuotes(v)}`);
21
+ }
22
+ return out;
23
+ }
24
+
11
25
  // Returns ["KEY=VALUE", ...] for `docker build --build-arg`. An image may set
12
- // `buildArgs: { envFile, prefix }` to inject vars from an env file (e.g. VITE_*
13
- // from .env.production), and/or `buildArgs.values: { K: V }` for explicit pairs.
14
- function resolveBuildArgs(image, cwd) {
26
+ // `buildArgs: { envFile, prefix }` to inject vars from a plaintext env file
27
+ // (e.g. VITE_* from .env.production), `buildArgs: { sopsEnvFile, prefix }` to
28
+ // inject them from a sops-encrypted file decrypted in memory at build time (no
29
+ // plaintext on disk), and/or `buildArgs.values: { K: V }` for explicit pairs.
30
+ async function resolveBuildArgs(image, cwd, { dryRun = false } = {}) {
15
31
  const cfg = image && image.buildArgs;
16
32
  if (!cfg) return [];
17
33
  const out = [];
18
34
  if (cfg.envFile) {
19
35
  const p = path.join(cwd, cfg.envFile);
20
- if (fs.existsSync(p)) {
21
- for (const line of fs.readFileSync(p, 'utf8').split('\n')) {
22
- const m = line.match(/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/);
23
- if (!m) continue;
24
- const [, k, v] = m;
25
- if (cfg.prefix && !k.startsWith(cfg.prefix)) continue;
26
- out.push(`${k}=${stripQuotes(v)}`);
27
- }
28
- }
36
+ if (fs.existsSync(p)) out.push(...parseEnvLines(fs.readFileSync(p, 'utf8'), cfg.prefix));
37
+ }
38
+ if (cfg.sopsEnvFile) {
39
+ const { stdout } = await _run('sops', ['-d', '--output-type', 'dotenv', cfg.sopsEnvFile], { cwd, capture: true, dryRun });
40
+ out.push(...parseEnvLines(stdout || '', cfg.prefix));
29
41
  }
30
42
  for (const [k, v] of Object.entries(cfg.values || {})) out.push(`${k}=${v}`);
31
43
  return out;
32
44
  }
33
45
 
34
- module.exports = { resolveBuildArgs };
46
+ module.exports = { resolveBuildArgs, _withRun };
@@ -39,7 +39,7 @@ async function execute(steps, { dryRun = false, allowDirty = false, deps } = {})
39
39
  if (u._clone && !clones.includes(u._clone)) clones.push(u._clone);
40
40
  const cwd = u._clone ? u._clone.dir : u.repoPath;
41
41
  if (step.kind === 'deps') { if (d.depsCache.needsDepsRebuild(u.image, cwd, u.repo)) await d.docker.buildDeps(u.image, cwd, dryRun); }
42
- else if (step.kind === 'build') await d.docker.buildImage({ ecrUri: u.repoUri, sha, dockerfile: u.image.dockerfile, context: u.image.context || '.', platform: u.defaults.platform, cwd, dryRun, buildArgs: d.buildArgs.resolveBuildArgs(u.image, cwd) });
42
+ else if (step.kind === 'build') await d.docker.buildImage({ ecrUri: u.repoUri, sha, dockerfile: u.image.dockerfile, context: u.image.context || '.', platform: u.defaults.platform, cwd, dryRun, buildArgs: await d.buildArgs.resolveBuildArgs(u.image, cwd, { dryRun }) });
43
43
  else if (step.kind === 'push') {
44
44
  if (!loggedIn) { await d.docker.ecrLogin({ account: u.defaults.account, region: u.defaults.region, profile: u.defaults.profile, dryRun }); await d.docker.ensureRepo({ ecr: u.image.ecr, account: u.defaults.account, region: u.defaults.region, profile: u.defaults.profile, dryRun }); loggedIn = true; }
45
45
  await d.docker.pushImage({ ecrUri: u.repoUri, sha, dryRun });