Ctrl K
ComunidadGuías…

scripts/production-compose.test.mjs

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
import { test } from "node:test";
import assert from "node:assert/strict";
import {
  readFile,
  writeFile,
  mkdtemp,
  mkdir,
  rm,
  readdir,
  copyFile,
} from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { parseEnv } from "node:util";
import { spawnSync } from "node:child_process";
import { createRequire } from "node:module";
const require = createRequire(new URL("../app/package.json", import.meta.url));
const { parse } = require("yaml");
const root = new URL("../", import.meta.url);

const compose = parse(
  await readFile(new URL("deploy/compose.production.yml", root), "utf8"),
);
const production = parseEnv(
  await readFile(new URL("deploy/.env.production.example", root), "utf8"),
);
const local = parseEnv(
  await readFile(new URL("app/.env.example", root), "utf8"),
);

test("production Compose forwards documented app configuration, with explicit image/build exceptions", () => {
  const imageOrBuild = new Set([
    "DEVPUSH_AGENT_BINARY_DIR",
    "VITE_DEVPUSH_BACKUPS_ENABLED",
  ]);
  const composeOnly = new Set([
    "DEVPUSH_HTTP_PORT",
    "DEVPUSH_PRODUCTION_IMAGE",
    "DEVPUSH_DB_BACKUP_RETENTION_DAYS",
  ]);
  for (const key of new Set([
    ...Object.keys(local),
    ...Object.keys(production),
  ])) {
    if (imageOrBuild.has(key) || composeOnly.has(key)) continue;
    assert.ok(
      key in production,
      `${key} missing from production environment example`,
    );
    assert.ok(
      compose.services.app.environment[key]?.startsWith("${" + key + ":"),
      `${key} not forwarded to app`,
    );
  }
  assert.equal(
    compose.services.app.build.args.VITE_DEVPUSH_BACKUPS_ENABLED,
    "${DEVPUSH_BACKUPS_ENABLED:-false}",
  );
  for (const service of Object.values(compose.services)) {
    assert.equal(
      service.image,
      "${DEVPUSH_PRODUCTION_IMAGE:-devpush-app:local}",
    );
  }
  for (const key of Object.keys(compose.services.app.environment).filter(
    (key) => /DATABASE|STORAGE/.test(key),
  )) {
    assert.equal(
      compose.services.migrate.environment[key],
      compose.services.app.environment[key],
      `${key} differs between app and migrations`,
    );
  }
});

test(
  "Docker build context excludes local secrets and state but preserves source and examples",
  {
    skip: process.env.DEVPUSH_RUN_DOCKER_TESTS !== "1",
  },
  async () => {
    const dir = await mkdtemp(join(tmpdir(), "devpush-build-context-"));
    try {
      const context = join(dir, "context");
      const output = join(dir, "output");
      await mkdir(context);
      await copyFile(
        new URL(".dockerignore", root),
        join(context, ".dockerignore"),
      );
      const excluded = [
        ".env",
        "app/.env",
        "deploy/.env.production",
        "app/.env.local",
        "app/.cloudflare-secrets.json",
        "app/.cloudflare-database.env",
        "app/.data/devpush.sqlite",
        "app/.wrangler/state/database.sqlite",
        "app/wrangler.jsonc",
        "app/wrangler.pending.jsonc",
        "app/public/uploads/avatar.png",
        ".data/logs.sqlite",
        "data/devpush.sqlite",
        ".tmp/private-note",
        "app/node_modules/dependency.js",
        ".pnpm-store/package/index.js",
      ];
      const included = [
        "app/src/index.ts",
        "app/.env.example",
        "deploy/.env.production.example",
        "app/wrangler.example.jsonc",
        "app/src/server/backups/index.ts",
      ];
      for (const path of [...excluded, ...included]) {
        const target = join(context, path);
        await mkdir(join(target, ".."), { recursive: true });
        await writeFile(target, "fixture");
      }
      await writeFile(join(context, "Dockerfile"), "FROM scratch\nCOPY . /\n");
      const result = spawnSync(
        "docker",
        ["build", "--output", `type=local,dest=${output}`, context],
        { encoding: "utf8", timeout: 60000 },
      );
      assert.equal(result.status, 0, result.stderr);
      const files = await readdir(output, { recursive: true });
      for (const path of excluded)
        assert.ok(!files.includes(path), `${path} leaked into build context`);
      for (const path of included)
        assert.ok(files.includes(path), `${path} excluded from build context`);
    } finally {
      await rm(dir, { recursive: true, force: true });
    }
  },
);