<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Hamza Mansoor</title>
    <description>The latest articles on DEV Community by Hamza Mansoor (@hamza_mansoor).</description>
    <link>https://dev.to/hamza_mansoor</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4012206%2F4dbac907-e4d5-44ab-8f39-672f18f885ab.png</url>
      <title>DEV Community: Hamza Mansoor</title>
      <link>https://dev.to/hamza_mansoor</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/hamza_mansoor"/>
    <language>en</language>
    <item>
      <title>Seed a PostgreSQL database with realistic, connected fake data in one command</title>
      <dc:creator>Hamza Mansoor</dc:creator>
      <pubDate>Tue, 28 Jul 2026 09:41:56 +0000</pubDate>
      <link>https://dev.to/hamza_mansoor/seed-a-postgresql-database-with-realistic-connected-fake-data-in-one-command-4b92</link>
      <guid>https://dev.to/hamza_mansoor/seed-a-postgresql-database-with-realistic-connected-fake-data-in-one-command-4b92</guid>
      <description>&lt;p&gt;Every time I start or test a project, I hit the same wall: the app works, but the database is empty. No customers, no orders, nothing to click through. So I write a seed script. Then a slightly different one on the next project. And the one after that.&lt;/p&gt;

&lt;p&gt;Faker solves half of it — it gives you names and emails. The annoying half is keeping the data &lt;strong&gt;consistent&lt;/strong&gt;: an &lt;code&gt;order&lt;/code&gt; has to reference a &lt;code&gt;customer&lt;/code&gt; that actually exists, a unique column has to stay unique, an enum can only hold its allowed values. Get that wrong and your app breaks on its own fake data.&lt;/p&gt;

&lt;p&gt;So I built &lt;strong&gt;fauxdata&lt;/strong&gt; and open-sourced it. Point it at a PostgreSQL database and it fills every table with realistic data in one command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx fauxdata &lt;span class="nt"&gt;--dsn&lt;/span&gt; postgres://localhost/mydb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No config. It reads your existing schema and figures out the rest.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsuqbobakw125owsp9dpq.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsuqbobakw125owsp9dpq.gif" alt="fauxdata terminal demo" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it in two minutes
&lt;/h2&gt;

&lt;p&gt;Spin up a throwaway Postgres:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; shop &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;postgres &lt;span class="nt"&gt;-p&lt;/span&gt; 5432:5432 postgres:16
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Give it a schema with the tricky bits — a foreign key, a unique column, and an enum:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TYPE&lt;/span&gt; &lt;span class="n"&gt;order_status&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nb"&gt;ENUM&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'pending'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'paid'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'shipped'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'cancelled'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt;         &lt;span class="nb"&gt;serial&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;email&lt;/span&gt;      &lt;span class="nb"&gt;varchar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;UNIQUE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;full_name&lt;/span&gt;  &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt;           &lt;span class="nb"&gt;serial&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;customer_id&lt;/span&gt;  &lt;span class="nb"&gt;integer&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;status&lt;/span&gt;       &lt;span class="n"&gt;order_status&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;total&lt;/span&gt;        &lt;span class="nb"&gt;numeric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Seed it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx fauxdata &lt;span class="nt"&gt;--dsn&lt;/span&gt; postgres://postgres:postgres@localhost:5432/postgres &lt;span class="nt"&gt;--rows&lt;/span&gt; 20
&lt;span class="c"&gt;# fauxdata: inserted 40 rows across 2 tables.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the data actually joins:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;full_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;
&lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;
&lt;span class="k"&gt;join&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;
&lt;span class="k"&gt;limit&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   full_name    |  total  |  status
----------------+---------+----------
 Andrea Gerlach | 7891.93 | shipped
 Pauline Larkin | 5868.83 | paid
 Leonard Rempel | 2210.50 | pending
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every &lt;code&gt;customer_id&lt;/code&gt; points to a real customer. Every status is a valid enum. Every email is unique.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it handles
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Referential integrity&lt;/strong&gt; — it reads your foreign keys and inserts parents before children, so references always resolve.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The awkward cases&lt;/strong&gt; — self-referencing tables (an employee whose manager is another employee) and circular references between tables, handled with a two-pass insert.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Constraints&lt;/strong&gt; — &lt;code&gt;NOT NULL&lt;/code&gt;, unique columns, enums, and composite primary keys.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reproducibility&lt;/strong&gt; — pass &lt;code&gt;--seed 42&lt;/code&gt; and you get byte-identical data every run, which makes it reliable in CI and shareable across a team.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Insert or inspect&lt;/strong&gt; — write straight to the database, or emit a SQL file you can review first:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx fauxdata &lt;span class="nt"&gt;--dsn&lt;/span&gt; postgres://localhost/mydb &lt;span class="nt"&gt;--output&lt;/span&gt; seed.sql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;p&gt;Under the hood it's TypeScript on Node, and the pipeline is small on purpose:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Introspect&lt;/strong&gt; — read tables, columns, types, keys, uniques, foreign keys, and enums straight from &lt;code&gt;information_schema&lt;/code&gt; / &lt;code&gt;pg_catalog&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Order&lt;/strong&gt; — build a dependency graph from the foreign keys and topologically sort it, deferring the minimum set of edges needed to break cycles and self-references.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generate&lt;/strong&gt; — pick a value per column: a config override if you gave one, then a column-name heuristic (&lt;code&gt;email&lt;/code&gt;, &lt;code&gt;*_at&lt;/code&gt;, &lt;code&gt;price&lt;/code&gt;…), then a fallback by SQL type. Uniqueness and enums are enforced as it goes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resolve &amp;amp; write&lt;/strong&gt; — fill foreign keys from already-inserted parent rows, then insert in one transaction (or write the SQL file).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;code&gt;node-postgres&lt;/code&gt; does the talking; &lt;code&gt;Faker&lt;/code&gt; provides the values.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why PostgreSQL first
&lt;/h2&gt;

&lt;p&gt;Because I'd rather do one database really well than five halfway. Postgres is where most of us start, so it's where fauxdata starts. MySQL and SQLite are the next targets — and since the generator works on an abstract schema model, adding them is mostly a new reader, not a rewrite.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it / break it
&lt;/h2&gt;

&lt;p&gt;It's MIT-licensed, needs no account, and installs with &lt;code&gt;npx&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;npm: &lt;a href="https://www.npmjs.com/package/fauxdata" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/fauxdata&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/hamzamansoorch/fauxdata" rel="noopener noreferrer"&gt;https://github.com/hamzamansoorch/fauxdata&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you run it against a real-world schema, I'd like to know what breaks — odd types, exotic constraints, anything. Issues and PRs welcome.&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>node</category>
      <category>opensource</category>
      <category>testing</category>
    </item>
    <item>
      <title>I built an offline, no-AI tool that turns your terminal history into runbooks</title>
      <dc:creator>Hamza Mansoor</dc:creator>
      <pubDate>Sat, 11 Jul 2026 11:09:07 +0000</pubDate>
      <link>https://dev.to/hamza_mansoor/i-built-an-offline-no-ai-tool-that-turns-your-terminal-history-into-runbooks-3ln1</link>
      <guid>https://dev.to/hamza_mansoor/i-built-an-offline-no-ai-tool-that-turns-your-terminal-history-into-runbooks-3ln1</guid>
      <description>&lt;p&gt;Every team has that one deploy only a single person really knows how to run. The steps live in their head, and the doc that's supposed to explain it is three commands out of date.&lt;/p&gt;

&lt;p&gt;I got tired of that, so I built &lt;strong&gt;runscribe&lt;/strong&gt;: you record a terminal session the way you'd normally work, and it turns what you did into a clean, re-runnable runbook. No screen recording, no wiki page that rots, and — deliberately — no AI.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🔒 &lt;strong&gt;100% local.&lt;/strong&gt; No account, no network, no telemetry. Nothing is uploaded, ever.&lt;/li&gt;
&lt;li&gt;🤖 &lt;strong&gt;No AI.&lt;/strong&gt; The runbook is produced deterministically from what actually ran. Same session in, same runbook out.&lt;/li&gt;
&lt;li&gt;📝 &lt;strong&gt;Plain Markdown or HTML out.&lt;/strong&gt; Hand-editable, diff-able, readable without runscribe installed.&lt;/li&gt;
&lt;li&gt;▶️ &lt;strong&gt;It re-runs.&lt;/strong&gt; The runbook isn't just text — hand it back and step through it later.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The loop
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Record&lt;/strong&gt; — run your commands as normal. &lt;code&gt;#&lt;/code&gt; adds a note, &lt;code&gt;##&lt;/code&gt; starts a section.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;runscribe record
&lt;span class="k"&gt;*&lt;/span&gt; recording - commands run &lt;span class="k"&gt;for &lt;/span&gt;real&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;# note, ## section, exit to finish&lt;/span&gt;
runscribe:api&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="c"&gt;## Deploy the API&lt;/span&gt;
runscribe:api&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="c"&gt;# bump the release tag first&lt;/span&gt;
runscribe:api&lt;span class="nv"&gt;$ &lt;/span&gt;git tag v2.3.0
runscribe:api&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;DEPLOY_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ghp_R2d9nQx7b4Kp...A1
runscribe:api&lt;span class="nv"&gt;$ &lt;/span&gt;./deploy.sh production
runscribe:api&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;exit
&lt;/span&gt;recorded 3 commands -&amp;gt; .runscribe/sessions/20260710.jsonl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Build&lt;/strong&gt; — turn the session into a runbook, with secrets scrubbed automatically.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;runscribe build &lt;span class="nt"&gt;--last&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; deploy.md
&lt;span class="k"&gt;done &lt;/span&gt;3 commands &lt;span class="o"&gt;(&lt;/span&gt;1 redacted&lt;span class="o"&gt;)&lt;/span&gt; -&amp;gt; deploy.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You get clean Markdown: sections become headings, notes become prose, and each command is a fenced block tagged with a marker so it can be replayed later. That &lt;code&gt;DEPLOY_TOKEN&lt;/code&gt; shows up as &lt;code&gt;&amp;lt;REDACTED&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Run&lt;/strong&gt; — replay the runbook step by step, later or on another machine.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;runscribe run deploy.md
running 2 step&lt;span class="o"&gt;(&lt;/span&gt;s&lt;span class="o"&gt;)&lt;/span&gt; from deploy.md
value &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;{{&lt;/span&gt;ENV&lt;span class="o"&gt;}}&lt;/span&gt;: production
&lt;span class="c"&gt;## Deploy the API&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;git tag v2.3.0
  run this step? &lt;span class="o"&gt;[&lt;/span&gt;Y]es / &lt;span class="o"&gt;[&lt;/span&gt;s]kip / &lt;span class="o"&gt;[&lt;/span&gt;q]uit: y
&lt;span class="nv"&gt;$ &lt;/span&gt;./deploy.sh production
  run this step? &lt;span class="o"&gt;[&lt;/span&gt;Y]es / &lt;span class="o"&gt;[&lt;/span&gt;s]kip / &lt;span class="o"&gt;[&lt;/span&gt;q]uit: y
&lt;span class="k"&gt;done  &lt;/span&gt;ran 2, skipped 0, failed 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every command is shown before it runs, &lt;code&gt;{{PLACEHOLDER}}&lt;/code&gt; tokens get filled in (prompted, or via &lt;code&gt;--set NAME=value&lt;/code&gt;), and a failing step halts the run unless you pass &lt;code&gt;--keep-going&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Export to HTML&lt;/strong&gt; if you'd rather share a page than a Markdown file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;runscribe build &lt;span class="nt"&gt;--last&lt;/span&gt; &lt;span class="nt"&gt;--format&lt;/span&gt; html &lt;span class="nt"&gt;-o&lt;/span&gt; deploy.html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A single self-contained file, styles inlined, everything HTML-escaped.&lt;/p&gt;

&lt;h2&gt;
  
  
  A few things under the hood
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Deterministic redaction.&lt;/strong&gt; A regex ruleset scrubs common tokens, keys, and credentials, and you can extend it with a &lt;code&gt;.runscribe/redact.toml&lt;/code&gt; (extra patterns and literal strings). No ML, no guessing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;State that persists.&lt;/strong&gt; On POSIX, recording drives a single long-lived shell via a sentinel technique, so &lt;code&gt;cd&lt;/code&gt;, &lt;code&gt;export&lt;/code&gt;, and shell variables carry across steps just like a real session. There's an opt-in &lt;code&gt;--pty&lt;/code&gt; mode for colored output and simple TUIs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No &lt;code&gt;INTERNET&lt;/code&gt; anywhere.&lt;/strong&gt; There's no networking code in the project. That's the point — your shell history never leaves the machine.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ships as a binary too.&lt;/strong&gt; PyInstaller builds a one-file executable for Linux, macOS, and Windows, so a teammate without Python can still use it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why no AI?
&lt;/h2&gt;

&lt;p&gt;Plenty of tools will "write your docs" now, but they do it by shipping your shell activity off to a model. If you've ever hesitated before pasting internal commands into a chatbot, that hesitation is the whole reason runscribe exists. A runbook is deterministic by nature: it should come out the same every time, and it should never require trusting a third party with what you typed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;runscribe
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;PyPI: &lt;a href="https://pypi.org/project/runscribe/" rel="noopener noreferrer"&gt;https://pypi.org/project/runscribe/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Code + demo: &lt;a href="https://github.com/hamzamansoorch/runscribe" rel="noopener noreferrer"&gt;https://github.com/hamzamansoorch/runscribe&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;License: MIT&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's open source and still early. If you write runbooks (or avoid writing them), I'd genuinely love feedback — and if you try it and something feels rough, open an issue. That's the most useful thing right now.&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>opensource</category>
      <category>python</category>
      <category>devops</category>
    </item>
    <item>
      <title>I built envcontract: give your .env file a contract (and stop leaking secrets)</title>
      <dc:creator>Hamza Mansoor</dc:creator>
      <pubDate>Thu, 02 Jul 2026 11:32:45 +0000</pubDate>
      <link>https://dev.to/hamza_mansoor/i-built-envcontract-give-your-env-file-a-contract-and-stop-leaking-secrets-386</link>
      <guid>https://dev.to/hamza_mansoor/i-built-envcontract-give-your-env-file-a-contract-and-stop-leaking-secrets-386</guid>
      <description>&lt;p&gt;Every project I've worked on relies on a &lt;code&gt;.env&lt;/code&gt; file — database URLs, ports, API keys. And every team hits the same three problems with it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Config drift.&lt;/strong&gt; A teammate adds a variable and forgets to mention it. Everyone else's app breaks with a cryptic error. "Works on my machine."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Silent misconfiguration.&lt;/strong&gt; Someone writes &lt;code&gt;PORT=eighty&lt;/code&gt; instead of &lt;code&gt;8080&lt;/code&gt;, and it surfaces as a confusing crash hours later.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Leaked secrets.&lt;/strong&gt; Someone commits a real &lt;code&gt;.env&lt;/code&gt; and exposes live credentials on GitHub — one of the most common and costly security mistakes in software.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The &lt;code&gt;.env&lt;/code&gt; file runs our apps, but it has no validation, no documentation, and no safety net. So I built a small tool to give it one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The idea: a contract for your .env
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;envcontract&lt;/code&gt; introduces one committed file — &lt;code&gt;.env.schema&lt;/code&gt; — that describes what your configuration should look like: which variables are required, their types, and rules. Crucially, it &lt;strong&gt;never contains the secret values themselves&lt;/strong&gt;. That file is safe to commit and share with your whole team.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;url&lt;/span&gt;
    &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;secret&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;PORT&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;int&lt;/span&gt;
    &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3000"&lt;/span&gt;
    &lt;span class="na"&gt;min&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
    &lt;span class="na"&gt;max&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;65535&lt;/span&gt;
  &lt;span class="na"&gt;STRIPE_KEY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
    &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;secret&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;pattern&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;^sk_(test|live)_[A-Za-z0-9]+$"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Four commands
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;envcontract
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;envcontract init&lt;/code&gt;&lt;/strong&gt; — generates the schema from your existing &lt;code&gt;.env&lt;/code&gt;, with every value stripped out and likely secrets auto-flagged.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;envcontract check&lt;/code&gt;&lt;/strong&gt; — validates any &lt;code&gt;.env&lt;/code&gt; against the schema: missing keys, wrong types, failed patterns, out-of-range values. Exits non-zero, so it works in CI.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;envcontract diff&lt;/code&gt;&lt;/strong&gt; — shows when your local &lt;code&gt;.env&lt;/code&gt; has drifted from the team's contract.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;envcontract guard&lt;/code&gt;&lt;/strong&gt; — a pre-commit hook that blocks you from committing real secret values before they ever reach your repo.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The design choices I care about
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;100% local.&lt;/strong&gt; envcontract makes zero network calls and collects zero telemetry. There's an automated test that fails the build if any socket is ever opened. A tool that handles secrets should never send them anywhere — and this one literally can't.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Framework-agnostic.&lt;/strong&gt; It reads plain &lt;code&gt;.env&lt;/code&gt; files, so it works with Python, Node, Go, Ruby, PHP — anything.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Focused.&lt;/strong&gt; It is not a secrets manager (like Vault or Doppler) and not a generic secret scanner (like gitleaks). It's the small "contract + validation" layer between them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MIT licensed.&lt;/strong&gt; Genuinely free — personal or commercial. Fork it, ship it, build on it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;If &lt;code&gt;.env&lt;/code&gt; headaches sound familiar, give it a spin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;envcontract
&lt;span class="nb"&gt;cd &lt;/span&gt;your-project
envcontract init
envcontract check
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Code and docs: &lt;a href="https://github.com/hamzamansoorch/envcontract" rel="noopener noreferrer"&gt;https://github.com/hamzamansoorch/envcontract&lt;/a&gt;&lt;br&gt;
On PyPI: &lt;a href="https://pypi.org/project/envcontract/" rel="noopener noreferrer"&gt;https://pypi.org/project/envcontract/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'd love feedback — especially on the schema format and whether the guard heuristics match how your team handles secrets. And if it's useful, a star helps others find it.&lt;/p&gt;

</description>
      <category>python</category>
      <category>opensource</category>
      <category>devops</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
