testFilesWithoutAnExtension

tcmts.cli.test.e2e.files.testFilesWithoutAnExtension · #rd4vd7zo · term

A file without an extension (Dockerfile, Makefile) or a hidden one (.gitignore) is kept as written, not read as code: added into a namespace, written out by tcm edit under its own name (a hidden one's dot and all), checked and saved back with --into, patched, and exported as it's named.

import { okIn, tempDir } from "#codebase/tcmts/cli/test/e2e/harness";
import assert from "node:assert/strict";
import fs from "node:fs";
import path from "node:path";

export const testFilesWithoutAnExtension = () => {
  const dir = tempDir("extensionless");
  const ok = (...args: string[]) => okIn(dir, ...args);
  const file = (name: string) => path.join(dir, name);
  ok("init");
  fs.writeFileSync(file("Dockerfile"), 'FROM node:22\nCMD ["node", "server.js"]\n');
  fs.writeFileSync(file("Makefile"), "all:\n\techo hi\n");
  fs.writeFileSync(file(".gitignore"), "node_modules/\n");
  assert.match(
    ok("check", "Dockerfile", "--into", "deploy"),
    /new +deploy\.Dockerfile : text[\s\S]*tcm add Dockerfile --into deploy/,
  );
  assert.match(ok("add", "Dockerfile", "--into", "deploy"), /new +deploy\.Dockerfile : text/);
  assert.match(ok("add", "Makefile", "--into", "deploy"), /new +deploy\.Makefile : text/);
  assert.match(ok("add", ".gitignore", "--into", "app"), /new +app\.gitignore : text/);
  for (const f of ["Dockerfile", "Makefile", ".gitignore"]) fs.rmSync(file(f));
  // nothing named for what's in them
  assert.doesNotMatch(ok("ls"), /deploy\.(FROM|CMD|all)/);
  // edited and saved back under the same names
  assert.match(
    ok("edit", "deploy.Dockerfile"),
    /wrote deploy\.Dockerfile to Dockerfile \(saved with `tcm update Dockerfile --into deploy`\)/,
  );
  assert.match(
    ok("edit", "app.gitignore"),
    /wrote app\.gitignore to \.gitignore \(saved with `tcm update \.gitignore --into app`\)/,
  );
  assert.match(ok("edit", "deploy.Makefile"), /wrote deploy\.Makefile to Makefile/);
  assert.equal(fs.readFileSync(file(".gitignore"), "utf8"), "node_modules/\n");
  fs.writeFileSync(file("Dockerfile"), 'FROM node:24\nCMD ["node", "server.js"]\n');
  fs.writeFileSync(file(".gitignore"), "node_modules/\ndist/\n");
  fs.writeFileSync(file("Makefile"), "all:\n\techo bye\n");
  assert.match(
    ok("check", ".gitignore", "--into", "app"),
    /update +app\.gitignore[\s\S]*tcm update \.gitignore --into app/,
  );
  assert.match(ok("update", "Dockerfile", "--into", "deploy"), /update +deploy\.Dockerfile/);
  assert.match(ok("update", ".gitignore", "--into", "app"), /update +app\.gitignore/);
  assert.match(ok("update", "Makefile", "--into", "deploy"), /update +deploy\.Makefile/);
  assert.match(ok("view", "deploy.Dockerfile"), /FROM node:24/);
  assert.match(ok("view", "app.gitignore"), /dist\//);
  // patched as text, saved back as the file it is
  assert.match(
    ok("patch", "deploy.Dockerfile", "--old", "server.js", "--new", "main.js"),
    /update +deploy\.Dockerfile : text/,
  );
  assert.match(
    ok("patch", "app.gitignore", "--old", "dist/", "--new", "build/"),
    /update +app\.gitignore : text/,
  );
  // exported as they're named: a hidden one with its dot
  const out = path.join(dir, "out");
  ok("export", "deploy", path.join(out, "deploy"));
  ok("export", "app", path.join(out, "app"));
  assert.equal(
    fs.readFileSync(path.join(out, "deploy", "Dockerfile"), "utf8"),
    'FROM node:24\nCMD ["node", "main.js"]\n',
  );
  assert.equal(fs.readFileSync(path.join(out, "deploy", "Makefile"), "utf8"), "all:\n\techo bye\n");
  assert.equal(
    fs.readFileSync(path.join(out, "app", ".gitignore"), "utf8"),
    "node_modules/\nbuild/\n",
  );
  assert.deepEqual(fs.readdirSync(path.join(out, "app")), [".gitignore"]);
  // and an exported one saves back as it is
  assert.match(
    ok("check", path.join(out, "app", ".gitignore"), "--into", "app"),
    /identical +app\.gitignore/,
  );
}

uses