testFilesWithoutAnExtension
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/, ); }