Skip to content
Open
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Car, motorcycle and truck make and model dropdown

**The make/model picker you stopped hand-maintaining.** `make-model-dropdown` turns two `<select>` elements into a dependent make → model dropdown, populated live from the [VehiclesDB API](https://vehiclesdb.com/api): **14,000+ models across 850+ makes**, six vehicle kinds — cars, motorcycles (motorbikes), mopeds, vans, trucks and buses — reconciled from the official vehicle registers of 14 countries and refreshed monthly. One `kind` option turns the same widget into a car picker, a motorcycle picker or a truck picker.
**The make/model picker you stopped hand-maintaining.** `make-model-dropdown` turns two `<select>` elements into a dependent make → model dropdown, populated live from the [VehiclesDB API](https://vehiclesdb.com/api): **14,000+ models across 850+ makes**, six vehicle kinds — cars, motorcycles (motorbikes), mopeds, vans, trucks and buses — reconciled from the official vehicle registers of 15 countries and refreshed monthly. One `kind` option turns the same widget into a car picker, a motorcycle picker or a truck picker.

Zero dependencies. No build step. Works with plain HTML forms, React, Vue, Rails, anything that renders a `<select>`.

Expand Down
2 changes: 1 addition & 1 deletion make-model-dropdown.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*
* A dependent make → model picker for any web page, powered by the VehiclesDB
* API: 14,000+ models across 850+ makes (cars, motorcycles, mopeds, vans,
* trucks, buses), reconciled from official registers of 14 countries.
* trucks, buses), reconciled from official registers of 15 countries.
*
* Zero dependencies. No build step. MIT licensed.
* Data: CC-BY 4.0 (identity layer) — the widget renders attribution for you.
Expand Down
8 changes: 5 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "make-model-dropdown",
"version": "0.1.4",
"description": "Car make and model dropdown for any web page. A dependent make/model picker with live vehicle data from the VehiclesDB API: 14,000+ models, 850+ makes, cars and motorcycles to trucks and buses, from official registers of 14 countries. Zero dependencies.",
"version": "0.1.5",
"description": "Car make and model dropdown for any web page. A dependent make/model picker with live vehicle data from the VehiclesDB API: 14,000+ models, 850+ makes, cars and motorcycles to trucks and buses, from official registers of 15 countries. Zero dependencies.",
"keywords": [
"car make model dropdown",
"motorcycle make model dropdown",
Expand All @@ -25,7 +25,9 @@
"bugs": "https://github.com/vehiclesdb/make-model-dropdown/issues",
"license": "MIT",
"scripts": {
"test": "node --test"
"test": "node --test",
"sync:countries": "node scripts/sync-countries.mjs",
"prepublishOnly": "node scripts/sync-countries.mjs --check"
},
"main": "make-model-dropdown.js",
"exports": {
Expand Down
66 changes: 66 additions & 0 deletions scripts/sync-countries.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#!/usr/bin/env node
// sync-countries.mjs — keep "official registers of N countries" true.
//
// The country count in package.json's npm description, the library header and
// the README is a FACT about the VehiclesDB dataset, so it is read from the
// dataset's own manifest.json (`countries`, the register countries of the
// release) instead of being typed by hand. It said "14" for two releases after
// the dataset reached 15 (found 2026-10-10).
//
// node scripts/sync-countries.mjs # rewrite from the published manifest
// node scripts/sync-countries.mjs --manifest path # or a local/other manifest (path or URL)
// node scripts/sync-countries.mjs --check # exit 1 if any file is stale (prepublishOnly)
//
// Zero dependencies (Node >= 18 global fetch).
import { readFileSync, writeFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
import path from "node:path";

export const DEFAULT_MANIFEST = "https://raw.githubusercontent.com/vehiclesdb/vehiclesdb/main/manifest.json";
export const FILES = ["package.json", "make-model-dropdown.js", "README.md"];
const PHRASE = /(registers of )(\d+)( countries)/g;

export function countryCount(manifest) {
const list = manifest && manifest.countries;
if (!Array.isArray(list) || list.length === 0) throw new Error("manifest has no `countries` array");
return new Set(list.map((c) => String(c).toLowerCase())).size;
}

export function rewrite(text, n) {
return text.replace(PHRASE, (_m, a, _old, b) => `${a}${n}${b}`);
}

async function loadManifest(src) {
if (/^https?:\/\//.test(src)) {
const res = await fetch(src);
if (!res.ok) throw new Error(`manifest fetch ${res.status}: ${src}`);
return res.json();
}
return JSON.parse(readFileSync(src, "utf8"));
}

async function main(argv) {
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const i = argv.indexOf("--manifest");
const src = i >= 0 ? argv[i + 1] : DEFAULT_MANIFEST;
const check = argv.includes("--check");
const n = countryCount(await loadManifest(src));
const stale = [];
for (const f of FILES) {
const p = path.join(root, f);
const before = readFileSync(p, "utf8");
const after = rewrite(before, n);
if (after === before) continue;
stale.push(f);
if (!check) writeFileSync(p, after);
}
if (check && stale.length) {
console.error(`stale country count (manifest says ${n}): ${stale.join(", ")} — run: node scripts/sync-countries.mjs`);
process.exit(1);
}
console.log(`${check ? "checked" : "synced"}: ${n} countries (${src})${stale.length && !check ? `; updated ${stale.join(", ")}` : ""}`);
}

if (process.argv[1] && fileURLToPath(import.meta.url) === path.resolve(process.argv[1])) {
main(process.argv.slice(2)).catch((e) => { console.error(e.message); process.exit(1); });
}
14 changes: 14 additions & 0 deletions test/sync-countries.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { test } from "node:test";
import assert from "node:assert/strict";
import { countryCount, rewrite } from "../scripts/sync-countries.mjs";

test("counts distinct register countries from the manifest", () => {
assert.equal(countryCount({ countries: ["ar", "ca", "de", "GB", "gb"] }), 4);
assert.throws(() => countryCount({}), /no `countries`/);
});

test("rewrites every 'registers of N countries' phrase and nothing else", () => {
const src = "from official registers of 14 countries. 14,000+ models; registers of 14 countries";
assert.equal(rewrite(src, 15), "from official registers of 15 countries. 14,000+ models; registers of 15 countries");
assert.equal(rewrite("no phrase here", 15), "no phrase here");
});
Loading