Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ on:
branches: [main]
pull_request:

permissions:
contents: read

jobs:
test:
runs-on: ubuntu-latest
Expand All @@ -20,3 +23,10 @@ jobs:
- run: npm run typecheck
- run: npm test
- run: npm run build
- name: Check the published files
run: npm pack --dry-run
- name: Smoke test the ESM and CommonJS builds
run: |
node --input-type=module -e "import { validateCnp } from './dist/index.js'; if (!validateCnp('3000101221147').valid) process.exit(1); console.log('ESM ok')"
node -e "const { validateCnp } = require('./dist/index.cjs'); if (!validateCnp('3000101221147').valid) process.exit(1); console.log('CJS ok')"
test -f dist/index.d.ts && test -f dist/index.d.cts && echo "type declarations ok"
13 changes: 10 additions & 3 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,20 @@ permissions:
jobs:
publish:
runs-on: ubuntu-latest
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: 22
registry-url: https://registry.npmjs.org
- run: npm install
- run: npm publish --provenance --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- run: npm run typecheck && npm test
# Until an NPM_TOKEN secret is added, tags only create a GitHub release.
- name: Skip npm publish (no NPM_TOKEN secret yet)
if: env.NODE_AUTH_TOKEN == ''
run: echo "::notice::NPM_TOKEN is not set, so this release was not published to npm."
- name: Publish
if: env.NODE_AUTH_TOKEN != ''
run: npm publish --provenance --access public
10 changes: 5 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,6 @@ All notable changes to this project are documented here. The format follows [Kee

## [Unreleased]

### Added

- `validatePostalCode` / `isValidPostalCode` for six-digit Romanian postal code format validation

## [0.1.0] - 2026-10-27

### Added
Expand All @@ -16,4 +12,8 @@ All notable changes to this project are documented here. The format follows [Kee
- `validateCui` / `isValidCui`, with or without the `RO` prefix
- `validateIbanRo` / `isValidIbanRo`, with formatting and bank code
- Control-digit helpers: `cnpControlDigit`, `cuiControlDigit`, `ibanChecksumOk`
- Demo page that splits a code into its parts and explains each one
- `validatePostalCode` / `isValidPostalCode` for six-digit Romanian postal codes (thanks @jay23606, #12)
- Demo page that splits a code into its parts, explains each one and shows how the check digit is computed

[Unreleased]: https://github.com/Robert-GeorgeDev/ValidRO/compare/v0.1.0...HEAD
[0.1.0]: https://github.com/Robert-GeorgeDev/ValidRO/releases/tag/v0.1.0
7 changes: 3 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@

<p align="center">
<a href="https://github.com/Robert-GeorgeDev/ValidRO/actions/workflows/ci.yml"><img src="https://github.com/Robert-GeorgeDev/ValidRO/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
<a href="https://www.npmjs.com/package/validro"><img src="https://img.shields.io/npm/v/validro" alt="npm version"></a>
<a href="https://bundlephobia.com/package/validro"><img src="https://img.shields.io/bundlephobia/minzip/validro" alt="bundle size"></a>
<img src="https://img.shields.io/badge/dependencies-0-1D6B45" alt="zero dependencies">
<a href="LICENSE"><img src="https://img.shields.io/npm/l/validro" alt="MIT license"></a>
<a href="https://robert-georgedev.github.io/ValidRO/"><img src="https://img.shields.io/badge/demo-online-2747C4" alt="Demo online"></a>
<img src="https://img.shields.io/badge/dependencies-0-1D6B45" alt="Zero dependencies">
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-4A5873" alt="MIT license"></a>
</p>

<p align="center">
Expand Down
29 changes: 26 additions & 3 deletions docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
<meta property="og:title" content="ValidRO · verifică CNP, CUI și IBAN">
<meta property="og:description" content="Vezi ce înseamnă fiecare cifră și cum se calculează cifra de control. Local, fără server.">
<meta property="og:url" content="https://robert-georgedev.github.io/ValidRO/">
<link rel="canonical" href="https://robert-georgedev.github.io/ValidRO/">
<meta property="og:image" content="https://robert-georgedev.github.io/ValidRO/social-preview.png">
<meta name="twitter:card" content="summary_large_image">
<meta name="theme-color" content="#EDF1F6" media="(prefers-color-scheme: light)">
Expand Down Expand Up @@ -77,6 +78,7 @@
.kinds button:hover { color: var(--ink); }
.kinds button[aria-selected="true"] { background: var(--stamp); color: var(--sheet); }
.kinds button:focus-visible { outline-offset: -3px; }
.narrow-only { display: none; }

.field-head { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; margin-bottom: 6px; }
.field-head label { font-size: 0.95rem; color: var(--ink-2); }
Expand Down Expand Up @@ -185,6 +187,9 @@
.tool { padding: 18px 14px; }
.kinds { display: flex; }
.kinds button { flex: 1; padding: 10px 0; }
.kinds button { white-space: nowrap; }
.wide-only { display: none; }
.narrow-only { display: inline; }
input#code { font-size: 1.25rem; padding-right: 48px; }
.result { grid-template-columns: 1fr; }
.stamp { width: 72px; height: 72px; justify-self: end; margin-top: -8px; }
Expand Down Expand Up @@ -219,13 +224,14 @@ <h1>ValidRO</h1>
</a>
</header>

<p class="lede">Verifică un CNP, un CUI sau un IBAN, vezi ce înseamnă fiecare cifră și cum se calculează cifra de control.</p>
<p class="lede">Verifică un CNP, un CUI, un IBAN sau un cod poștal, vezi ce înseamnă fiecare cifră și cum se calculează cifra de control.</p>

<section class="tool" aria-label="Verificare">
<div class="kinds" role="tablist" aria-label="Ce verifici">
<button role="tab" id="tab-cnp" aria-selected="true" aria-controls="panel" data-kind="cnp">CNP</button>
<button role="tab" id="tab-cui" aria-selected="false" aria-controls="panel" data-kind="cui" tabindex="-1">CUI</button>
<button role="tab" id="tab-iban" aria-selected="false" aria-controls="panel" data-kind="iban" tabindex="-1">IBAN</button>
<button role="tab" id="tab-postal" aria-selected="false" aria-controls="panel" data-kind="postal" tabindex="-1" aria-label="Cod poștal"><span class="wide-only">Cod poștal</span><span class="narrow-only" aria-hidden="true">Poștal</span></button>
</div>

<div id="panel" role="tabpanel" aria-labelledby="tab-cnp">
Expand Down Expand Up @@ -281,6 +287,7 @@ <h2>Pentru dezvoltatori</h2>
<li><strong>CNP:</strong> dată reală (inclusiv ani bisecți), cod de județ, inclusiv codul 70 folosit pentru CNP-urile noi, cifra de control.</li>
<li><strong>CUI / CIF:</strong> cu sau fără prefixul RO, cifra de control.</li>
<li><strong>IBAN:</strong> structura românească de 24 de caractere și verificarea mod 97.</li>
<li><strong>Cod poștal:</strong> exact 6 cifre.</li>
</ul>
<p>Un cod valid e corect ca formă; asta nu înseamnă că aparține cuiva. Exemplele de pe pagină sunt fictive.</p>
</section>
Expand All @@ -292,7 +299,7 @@ <h2>Pentru dezvoltatori</h2>
</main>

<script type="module">
import { validateCnp, validateCui, validateIbanRo } from "./lib/index.js";
import { validateCnp, validateCui, validateIbanRo, validatePostalCode } from "./lib/index.js";

const REPO = "Robert-GeorgeDev/ValidRO";
const MONTHS = ["ianuarie", "februarie", "martie", "aprilie", "mai", "iunie", "iulie",
Expand All @@ -301,7 +308,7 @@ <h2>Pentru dezvoltatori</h2>
1: "bărbat, 1900–1999", 2: "femeie, 1900–1999", 3: "bărbat, 1800–1899", 4: "femeie, 1800–1899",
5: "bărbat, 2000–2099", 6: "femeie, 2000–2099", 7: "rezident străin", 8: "rezidentă străină", 9: "cetățean străin",
};
const NAMES = { cnp: "CNP", cui: "CUI", iban: "IBAN" };
const NAMES = { cnp: "CNP", cui: "CUI", iban: "IBAN", postal: "Cod poștal" };

/* Weighted-sum explanation shared by CNP and CUI */
function weightedTable(digits, key, products) {
Expand Down Expand Up @@ -439,6 +446,21 @@ <h2>Pentru dezvoltatori</h2>
</ol>`;
},
},
postal: {
label: "Codul poștal",
placeholder: "6 cifre, de ex. 010101",
inputmode: "numeric",
examples: [["010101", "valid"], ["01010", "prea scurt"]],
clean: (v) => v.replace(/\D/g, "").slice(0, 6),
total: 6,
complete: (v) => v.length === 6,
validate: validatePostalCode,
segments(v, r) {
return [{ text: v, size: 6, name: "cod poștal", meaning: r?.valid ? "format corect" : undefined }];
},
badSegments: () => [0],
math: () => null,
},
};

const $ = (id) => document.getElementById(id);
Expand All @@ -452,6 +474,7 @@ <h2>Pentru dezvoltatori</h2>
const v = raw.toUpperCase().replace(/[\s.\-]/g, "");
if (/^RO\d{2}[A-Z]{4}/.test(v)) return "iban";
if (/^\d{13}$/.test(v)) return "cnp";
if (/^\d{6}$/.test(v) && kind === "postal") return "postal";
if (/^(RO)?\d{2,10}$/.test(v)) return "cui";
return null;
}
Expand Down
Loading