From 338e45f0d579e737629299c5df2d3e3ab4b3ad1d Mon Sep 17 00:00:00 2001 From: Dick Wolff Date: Mon, 6 Oct 2025 21:07:31 +0200 Subject: [PATCH 01/15] First set --- .gitignore | 5 +- Scripts/package-lock.json | 314 ++++++++++++++++++++++++++++++++++++++ Scripts/package.json | 18 +++ Scripts/tsconfig.json | 15 ++ Scripts/updateMintage.ts | 275 +++++++++++++++++++++++++++++++++ 5 files changed, 626 insertions(+), 1 deletion(-) create mode 100644 Scripts/package-lock.json create mode 100644 Scripts/package.json create mode 100644 Scripts/tsconfig.json create mode 100644 Scripts/updateMintage.ts diff --git a/.gitignore b/.gitignore index 1772843c..548217cf 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,7 @@ ################################################################################ /.vs -.DS_Store \ No newline at end of file +.DS_Store + +/Scripts/dist +/Scripts/node_modules \ No newline at end of file diff --git a/Scripts/package-lock.json b/Scripts/package-lock.json new file mode 100644 index 00000000..fe81856c --- /dev/null +++ b/Scripts/package-lock.json @@ -0,0 +1,314 @@ +{ + "name": "coin-mintage-updater", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "coin-mintage-updater", + "version": "1.0.0", + "dependencies": { + "fs-extra": "^11.1.1", + "path": "^0.12.7" + }, + "devDependencies": { + "@types/node": "^20.0.0", + "ts-node": "^10.9.0", + "typescript": "^5.0.0" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.19.19", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.19.tgz", + "integrity": "sha512-pb1Uqj5WJP7wrcbLU7Ru4QtA0+3kAXrkutGiD26wUKzSMgNNaPARTUDQmElUXp64kh3cWdou3Q0C7qwwxqSFmg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "license": "MIT" + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/fs-extra": { + "version": "11.3.2", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.2.tgz", + "integrity": "sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "license": "ISC" + }, + "node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "license": "ISC" + }, + "node_modules/path": { + "version": "0.12.7", + "resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz", + "integrity": "sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q==", + "license": "MIT", + "dependencies": { + "process": "^0.11.1", + "util": "^0.10.3" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/util": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", + "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", + "license": "MIT", + "dependencies": { + "inherits": "2.0.3" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, + "license": "MIT" + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + } + } +} diff --git a/Scripts/package.json b/Scripts/package.json new file mode 100644 index 00000000..99cd619a --- /dev/null +++ b/Scripts/package.json @@ -0,0 +1,18 @@ +{ + "name": "coin-mintage-updater", + "version": "1.0.0", + "description": "Script to automatically update mintage data for ongoing coin series", + "main": "updateMintage.js", + "scripts": { + "update": "ts-node updateMintage.ts" + }, + "devDependencies": { + "@types/node": "^20.0.0", + "typescript": "^5.0.0", + "ts-node": "^10.9.0" + }, + "dependencies": { + "fs-extra": "^11.1.1", + "path": "^0.12.7" + } +} \ No newline at end of file diff --git a/Scripts/tsconfig.json b/Scripts/tsconfig.json new file mode 100644 index 00000000..cb92404c --- /dev/null +++ b/Scripts/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "outDir": "./dist", + "rootDir": "./", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true + }, + "include": ["*.ts"], + "exclude": ["node_modules", "dist"] +} \ No newline at end of file diff --git a/Scripts/updateMintage.ts b/Scripts/updateMintage.ts new file mode 100644 index 00000000..bbdeb842 --- /dev/null +++ b/Scripts/updateMintage.ts @@ -0,0 +1,275 @@ +import { join, dirname, basename, resolve } from "path"; +import { readFileSync, writeFileSync, existsSync, readdirSync, Dirent } from "fs"; + +// Constants +const NEXT_YEAR = new Date().getFullYear() + 1; +const COUNTRIES_DIR = "Countries"; +const DENOMINATIONS = ["001", "002", "005", "010", "020", "050", "100", "200"]; + +// Interfaces +interface SerieMetadata { + id: string; + countryId: string; + startDate: string; + endDate?: string; + path: string; +} + +interface CoinFile { + path: string; + id: string; + countryId: string; + serieId: string; + denomination: string; +} + +class MintageUpdater { + private rootDir: string; + private activeSeries: SerieMetadata[] = []; + private coinFiles: CoinFile[] = []; + + constructor() { + this.rootDir = resolve(__dirname, ".."); + } + + /** + * Main function to update mintage data for ongoing coin series + */ + async main(): Promise { + try { + console.log("šŸš€ Starting mintage update process..."); + console.log(`šŸ“… Target year: ${NEXT_YEAR}`); + + await this.discoverActiveSeries(); + console.log(`šŸ” Found ${this.activeSeries.length} active series`); + + await this.findCoinFiles(); + console.log(`šŸ” Found ${this.coinFiles.length} coin files in active series`); + + const coinsNeedingUpdates = await this.findCoinsNeedingUpdates(); + + await this.addMintageRows(coinsNeedingUpdates); + + console.log("\nāœ… Mintage update process completed successfully!"); + + } catch (error) { + console.error("āŒ Error during mintage update process:", error); + throw error; + } + } + + /** + * Discover all active coin series (those without end dates) + */ + private async discoverActiveSeries(): Promise { + const countriesPath = join(this.rootDir, COUNTRIES_DIR); + + if (!existsSync(countriesPath)) { + console.warn(`Countries directory not found at: ${countriesPath}`); + return; + } + + const countries = readdirSync(countriesPath, { withFileTypes: true }) + .filter((dirent: Dirent) => dirent.isDirectory()) + .map((dirent: Dirent) => dirent.name); + + for (const country of countries) { + const countryPath = join(countriesPath, country); + const years = readdirSync(countryPath, { withFileTypes: true }) + .filter((dirent: Dirent) => dirent.isDirectory() && /^\d{4}$/.test(dirent.name)) + .map((dirent: Dirent) => dirent.name); + + for (const year of years) { + const serieIndexPath = join(countryPath, year, "index.md"); + if (existsSync(serieIndexPath)) { + const serieData = await this.parseSerieFile(serieIndexPath); + if (serieData && !serieData.endDate) { + this.activeSeries.push({ + ...serieData, + path: serieIndexPath + }); + } + } + } + } + } + + /** + * Parse serie index.md file to extract metadata + */ + private async parseSerieFile(filePath: string): Promise { + try { + const content = readFileSync(filePath, "utf-8"); + const frontMatterMatch = content.match(/^---\n([\s\S]*?)\n---/); + + if (!frontMatterMatch) return null; + + const frontMatter = frontMatterMatch[1]; + const id = this.extractFrontMatterValue(frontMatter, "id"); + const countryId = this.extractFrontMatterValue(frontMatter, "countryId"); + + // Extract start and end dates from content + const startDateMatch = content.match(/\*\*Startdate:\*\*\s*([^\\\n]+)/); + const endDateMatch = content.match(/\*\*Enddate:\*\*\s*([^\\\n]*)/); + + const startDate = startDateMatch ? startDateMatch[1].trim() : ""; + let endDate: string | undefined = undefined; + + if (endDateMatch) { + const endDateValue = endDateMatch[1].trim(); + // Only set endDate if it"s not empty and doesn"t start with ## (which would be the next header) + if (endDateValue && !endDateValue.startsWith("##")) { + endDate = endDateValue; + } + } + + if (!id || !countryId) return null; + + return { + id, + countryId, + startDate, + endDate, + path: filePath + }; + } catch (error) { + console.warn(`Warning: Could not parse serie file ${filePath}:`, error); + return null; + } + } + + /** + * Extract value from front matter + */ + private extractFrontMatterValue(frontMatter: string, key: string): string | null { + const match = frontMatter.match(new RegExp(`^${key}:\\s*(.+)$`, "m")); + return match ? match[1].trim() : null; + } + + /** + * Find all coin files in active series + */ + private async findCoinFiles(): Promise { + for (const serie of this.activeSeries) { + const serieDir = dirname(serie.path); + + for (const denomination of DENOMINATIONS) { + const coinFilePath = join(serieDir, `${denomination}.md`); + if (existsSync(coinFilePath)) { + this.coinFiles.push({ + path: coinFilePath, + id: `${serie.countryId}-${basename(serieDir)}-${denomination}`, + countryId: serie.countryId, + serieId: serie.id, + denomination + }); + } + } + } + } + + /** + * Check if a coin file has a specific year in its mintage table + */ + private async coinHasYear(filePath: string, year: number): Promise { + try { + const content = readFileSync(filePath, "utf-8"); + const yearPattern = new RegExp(`^\\|\\s*${year}\\s*\\|`, "m"); + return yearPattern.test(content); + } catch (error) { + console.warn(`Warning: Could not check year in ${filePath}:`, error); + return false; + } + } + + /** + * Find coins that need 2026 mintage updates + */ + private async findCoinsNeedingUpdates(): Promise { + const coinsNeedingUpdates: CoinFile[] = []; + + for (const coinFile of this.coinFiles) { + const has2026 = await this.coinHasYear(coinFile.path, NEXT_YEAR); + if (!has2026) { + coinsNeedingUpdates.push(coinFile); + } + } + + console.log(`Found ${coinsNeedingUpdates.length} coins needing ${NEXT_YEAR} updates`); + return coinsNeedingUpdates; + } + + /** + * Add mintage rows to coins that need them (simple approach - just append a new row) + */ + private async addMintageRows(coins: CoinFile[]): Promise { + let updatedCount = 0; + + for (const coin of coins) { + try { + await this.addMintageRowToCoin(coin); + updatedCount++; + if (updatedCount <= 5) { // Show first 5 updates + console.log(`āœ“ Added ${NEXT_YEAR} row to ${coin.countryId} ${coin.denomination}`); + } + } catch (error) { + console.error(`āœ— Failed to update ${coin.path}:`, error); + } + } + + console.log(`\nšŸ“ Updated ${updatedCount} coin files with ${NEXT_YEAR} mintage data`); + } + + /** + * Add a single mintage row to a coin file (simple approach) + */ + private async addMintageRowToCoin(coin: CoinFile): Promise { + const content = readFileSync(coin.path, "utf-8"); + + // Find the end of the mintage table (look for the last table row) + const tableRowPattern = /^(\|[^|]+\|[^|]+\|[^|]+\|[^|]+\|[^|]+\|)$/gm; + let lastMatch; + let match; + + while ((match = tableRowPattern.exec(content)) !== null) { + lastMatch = match; + } + + if (!lastMatch) { + throw new Error(`Could not find mintage table in ${coin.path}`); + } + + // Create the new 2026 row + const newRow = `| ${NEXT_YEAR} | | 0 | 0 | 0 |`; + + // Insert the new row right after the last table row with proper newline handling + const lastRowEnd = lastMatch.index + lastMatch[0].length; + const afterLastRow = content.slice(lastRowEnd); + + // Add newline + new row, making sure we don"t create extra blank lines + const newContent = content.slice(0, lastRowEnd) + "\n" + newRow + afterLastRow; + + writeFileSync(coin.path, newContent, "utf-8"); + } +} + +/** + * Main function - entry point of the script + */ +async function main(): Promise { + const updater = new MintageUpdater(); + await updater.main(); +} + +// Run the script if this file is executed directly +if (require.main === module) { + main().catch(error => { + console.error("Fatal error:", error); + process.exit(1); + }); +} + +export { + main, + MintageUpdater +}; From a47fbd8ad7631672842884ace70db183c190f14f Mon Sep 17 00:00:00 2001 From: Dick Wolff Date: Mon, 6 Oct 2025 21:11:31 +0200 Subject: [PATCH 02/15] Fix for german coins --- Scripts/updateMintage.ts | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/Scripts/updateMintage.ts b/Scripts/updateMintage.ts index bbdeb842..965b2ea8 100644 --- a/Scripts/updateMintage.ts +++ b/Scripts/updateMintage.ts @@ -221,7 +221,7 @@ class MintageUpdater { } /** - * Add a single mintage row to a coin file (simple approach) + * Add mintage row(s) to a coin file, German coins need 5 rows (A, D, F, G, J) */ private async addMintageRowToCoin(coin: CoinFile): Promise { const content = readFileSync(coin.path, "utf-8"); @@ -239,15 +239,27 @@ class MintageUpdater { throw new Error(`Could not find mintage table in ${coin.path}`); } - // Create the new 2026 row - const newRow = `| ${NEXT_YEAR} | | 0 | 0 | 0 |`; + // Create new rows - German coins need mint marks A, D, F, G, J + let newRows: string; + if (coin.countryId === "DE") { + // German coins: add 5 rows with different mint marks + const germanMintMarks = ["A", "D", "F", "G", "J"]; + const rows = germanMintMarks.map(mintMark => + `| ${NEXT_YEAR} | ${mintMark} | 0 | 0 | 0 |` + ); + newRows = rows.join("\n"); + } + else { + // Other countries: single row with empty mint mark + newRows = `| ${NEXT_YEAR} | | 0 | 0 | 0 |`; + } - // Insert the new row right after the last table row with proper newline handling + // Insert the new row(s) right after the last table row with proper newline handling const lastRowEnd = lastMatch.index + lastMatch[0].length; const afterLastRow = content.slice(lastRowEnd); - // Add newline + new row, making sure we don"t create extra blank lines - const newContent = content.slice(0, lastRowEnd) + "\n" + newRow + afterLastRow; + // Add newline + new row(s), making sure we don"t create extra blank lines + const newContent = content.slice(0, lastRowEnd) + "\n" + newRows + afterLastRow; writeFileSync(coin.path, newContent, "utf-8"); } From e05c8e05eed32a8f9c6491312299983f239c0eed Mon Sep 17 00:00:00 2001 From: Dick Wolff Date: Mon, 6 Oct 2025 21:16:47 +0200 Subject: [PATCH 03/15] Create pipeline --- .github/workflows/autocreate-new-mintages.yml | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 .github/workflows/autocreate-new-mintages.yml diff --git a/.github/workflows/autocreate-new-mintages.yml b/.github/workflows/autocreate-new-mintages.yml new file mode 100644 index 00000000..aac764bd --- /dev/null +++ b/.github/workflows/autocreate-new-mintages.yml @@ -0,0 +1,50 @@ + +name: Automatically create new mintages + +on: + pull_request: + branches: ["main"] + workflow_dispatch: + +jobs: + update-seed: + runs-on: ubuntu-latest + steps: + - name: Checkout Open Coin Data + uses: actions/checkout@v4 + with: + repository: coinection/open-coin-data + path: ocd + + - name: Set dynamic variables + run: | + echo "NEXT_YEAR=$(($(date +%Y) + 1))" >> $GITHUB_ENV + + - name: Create or update branch on Coinection + run: | + cd ./ocd + git checkout -b "update-mintages-$NEXT_YEAR" + + - name: Run Mintage Update Script + run: | + cd ./ocd/Scripts + npm install + npm run update + + - name: Commit and push changes to Open Coin Data + run: | + cd ./ocd + + if git diff --quiet; then + echo "No changes detected in Open Coin Data. Exiting gracefully..." + exit 0 + else + echo "Changes detected. Proceeding with commit and push..." + fi + + git config --global user.email "bot@coinection.eu" + git config --global user.name "Coinection" + + git add . + git commit -m "Update mintage data for $NEXT_YEAR" + git push origin "update-mintages-$NEXT_YEAR" \ No newline at end of file From 2dd7e22facade84f8382c306328a2abdd2b38a70 Mon Sep 17 00:00:00 2001 From: Dick Wolff Date: Mon, 6 Oct 2025 21:22:47 +0200 Subject: [PATCH 04/15] Update workflow --- .github/workflows/autocreate-new-mintages.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/autocreate-new-mintages.yml b/.github/workflows/autocreate-new-mintages.yml index aac764bd..fd662d6d 100644 --- a/.github/workflows/autocreate-new-mintages.yml +++ b/.github/workflows/autocreate-new-mintages.yml @@ -2,8 +2,8 @@ name: Automatically create new mintages on: - pull_request: - branches: ["main"] + schedule: + - cron: '0 0 1 12 *' # At 00:00 on December 1st every year workflow_dispatch: jobs: @@ -16,7 +16,7 @@ jobs: repository: coinection/open-coin-data path: ocd - - name: Set dynamic variables + - name: Set next year variable run: | echo "NEXT_YEAR=$(($(date +%Y) + 1))" >> $GITHUB_ENV @@ -33,7 +33,7 @@ jobs: - name: Commit and push changes to Open Coin Data run: | - cd ./ocd + cd ./ocd/Scripts if git diff --quiet; then echo "No changes detected in Open Coin Data. Exiting gracefully..." From d288726bea003fce6c673f3f0528f004a6fec03a Mon Sep 17 00:00:00 2001 From: Dick Wolff Date: Mon, 6 Oct 2025 21:27:07 +0200 Subject: [PATCH 05/15] Small fixes --- .github/workflows/autocreate-new-mintages.yml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/.github/workflows/autocreate-new-mintages.yml b/.github/workflows/autocreate-new-mintages.yml index fd662d6d..030118d9 100644 --- a/.github/workflows/autocreate-new-mintages.yml +++ b/.github/workflows/autocreate-new-mintages.yml @@ -1,5 +1,5 @@ -name: Automatically create new mintages +name: Automatically add new mintages on: schedule: @@ -7,7 +7,8 @@ on: workflow_dispatch: jobs: - update-seed: + automatically-add-mintages: + name: Automatically add new mintages runs-on: ubuntu-latest steps: - name: Checkout Open Coin Data @@ -20,10 +21,10 @@ jobs: run: | echo "NEXT_YEAR=$(($(date +%Y) + 1))" >> $GITHUB_ENV - - name: Create or update branch on Coinection + - name: Create or update branch run: | cd ./ocd - git checkout -b "update-mintages-$NEXT_YEAR" + git checkout -b "add-mintages-$NEXT_YEAR" - name: Run Mintage Update Script run: | @@ -33,7 +34,7 @@ jobs: - name: Commit and push changes to Open Coin Data run: | - cd ./ocd/Scripts + cd ./ocd if git diff --quiet; then echo "No changes detected in Open Coin Data. Exiting gracefully..." @@ -46,5 +47,5 @@ jobs: git config --global user.name "Coinection" git add . - git commit -m "Update mintage data for $NEXT_YEAR" - git push origin "update-mintages-$NEXT_YEAR" \ No newline at end of file + git commit -m "Add mintage data for $NEXT_YEAR" + git push origin "add-mintages-$NEXT_YEAR" \ No newline at end of file From 2fc150290e6a9154e12c0ad3af10666ee4d4f715 Mon Sep 17 00:00:00 2001 From: Dick Wolff Date: Mon, 6 Oct 2025 21:29:27 +0200 Subject: [PATCH 06/15] Fix some commments --- Scripts/updateMintage.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/Scripts/updateMintage.ts b/Scripts/updateMintage.ts index 965b2ea8..feb00e95 100644 --- a/Scripts/updateMintage.ts +++ b/Scripts/updateMintage.ts @@ -41,11 +41,8 @@ class MintageUpdater { console.log(`šŸ“… Target year: ${NEXT_YEAR}`); await this.discoverActiveSeries(); - console.log(`šŸ” Found ${this.activeSeries.length} active series`); - await this.findCoinFiles(); - console.log(`šŸ” Found ${this.coinFiles.length} coin files in active series`); - + const coinsNeedingUpdates = await this.findCoinsNeedingUpdates(); await this.addMintageRows(coinsNeedingUpdates); @@ -195,7 +192,7 @@ class MintageUpdater { } } - console.log(`Found ${coinsNeedingUpdates.length} coins needing ${NEXT_YEAR} updates`); + console.log(`šŸ•µļø Found ${coinsNeedingUpdates.length} coins needing ${NEXT_YEAR} updates`); return coinsNeedingUpdates; } @@ -210,7 +207,6 @@ class MintageUpdater { await this.addMintageRowToCoin(coin); updatedCount++; if (updatedCount <= 5) { // Show first 5 updates - console.log(`āœ“ Added ${NEXT_YEAR} row to ${coin.countryId} ${coin.denomination}`); } } catch (error) { console.error(`āœ— Failed to update ${coin.path}:`, error); From 4758d066b779bb5fba76608992ce55a477b6fee6 Mon Sep 17 00:00:00 2001 From: Dick Wolff Date: Mon, 6 Oct 2025 21:32:08 +0200 Subject: [PATCH 07/15] Fixes --- .github/workflows/autocreate-new-mintages.yml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/workflows/autocreate-new-mintages.yml b/.github/workflows/autocreate-new-mintages.yml index 237ffbc7..a655f731 100644 --- a/.github/workflows/autocreate-new-mintages.yml +++ b/.github/workflows/autocreate-new-mintages.yml @@ -24,7 +24,16 @@ jobs: - name: Create or update branch run: | cd ./ocd - git checkout -b "add-mintages-$NEXT_YEAR" + BRANCH_NAME="add-mintages-$NEXT_YEAR" + git fetch --all + + if git rev-parse --verify origin/$BRANCH_NAME; then + echo "Branch $BRANCH_NAME already exists in Coinection, checking out..." + git checkout $BRANCH_NAME + else + echo "Branch $BRANCH_NAME does not exist in Coinection, creating new branch..." + git checkout -b $BRANCH_NAME + fi - name: Run Mintage Update Script run: | From 414133255cb3ea7a3a29f1a25b0ee25d6cd186cf Mon Sep 17 00:00:00 2001 From: Dick Wolff Date: Mon, 6 Oct 2025 21:32:53 +0200 Subject: [PATCH 08/15] Use branch name variable instead --- .github/workflows/autocreate-new-mintages.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/autocreate-new-mintages.yml b/.github/workflows/autocreate-new-mintages.yml index a655f731..f44e50a7 100644 --- a/.github/workflows/autocreate-new-mintages.yml +++ b/.github/workflows/autocreate-new-mintages.yml @@ -20,11 +20,11 @@ jobs: - name: Set next year variable run: | echo "NEXT_YEAR=$(($(date +%Y) + 1))" >> $GITHUB_ENV + echo "BRANCH_NAME=add-mintages-$NEXT_YEAR" >> $GITHUB_ENV - name: Create or update branch run: | cd ./ocd - BRANCH_NAME="add-mintages-$NEXT_YEAR" git fetch --all if git rev-parse --verify origin/$BRANCH_NAME; then @@ -57,4 +57,4 @@ jobs: git add . git commit -m "Add mintage data for $NEXT_YEAR" - git push origin "add-mintages-$NEXT_YEAR" + git push origin "$BRANCH_NAME" From 208b8a550e15ba8cc7373b5479a3f7fcd56951f6 Mon Sep 17 00:00:00 2001 From: Dick Wolff Date: Mon, 6 Oct 2025 21:33:34 +0200 Subject: [PATCH 09/15] Add emoji --- Scripts/updateMintage.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Scripts/updateMintage.ts b/Scripts/updateMintage.ts index feb00e95..b0284cbb 100644 --- a/Scripts/updateMintage.ts +++ b/Scripts/updateMintage.ts @@ -62,7 +62,7 @@ class MintageUpdater { const countriesPath = join(this.rootDir, COUNTRIES_DIR); if (!existsSync(countriesPath)) { - console.warn(`Countries directory not found at: ${countriesPath}`); + console.warn(`šŸ•µļø Countries directory not found at: ${countriesPath}`); return; } From 0fb48e1f0ab857c2e0f303ce2e00fc0fe5d8e2c7 Mon Sep 17 00:00:00 2001 From: Dick Wolff Date: Mon, 6 Oct 2025 21:34:04 +0200 Subject: [PATCH 10/15] Fix log --- Scripts/updateMintage.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Scripts/updateMintage.ts b/Scripts/updateMintage.ts index b0284cbb..42ce14a9 100644 --- a/Scripts/updateMintage.ts +++ b/Scripts/updateMintage.ts @@ -38,7 +38,7 @@ class MintageUpdater { async main(): Promise { try { console.log("šŸš€ Starting mintage update process..."); - console.log(`šŸ“… Target year: ${NEXT_YEAR}`); + console.log(`šŸ“… Target year: ${NEXT_YEAR}\n`); await this.discoverActiveSeries(); await this.findCoinFiles(); From 804c30ff32f760f73878f956fb3118642d15b054 Mon Sep 17 00:00:00 2001 From: Dick Wolff Date: Mon, 6 Oct 2025 21:35:59 +0200 Subject: [PATCH 11/15] a --- .github/workflows/update-seed.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/update-seed.yml b/.github/workflows/update-seed.yml index 9a99c642..6cc29826 100644 --- a/.github/workflows/update-seed.yml +++ b/.github/workflows/update-seed.yml @@ -1,5 +1,5 @@ -name: Update Coinection Seed +name: Update Coinection seed on: pull_request: @@ -8,6 +8,7 @@ on: jobs: update-seed: + name: Update Coinection seed runs-on: ubuntu-latest steps: - name: Checkout Open Coin Data From bcb525b1582187a93a58ca3305a2bb6f5b7b876f Mon Sep 17 00:00:00 2001 From: Dick Wolff Date: Mon, 6 Oct 2025 21:36:54 +0200 Subject: [PATCH 12/15] Fix? --- .github/workflows/autocreate-new-mintages.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/autocreate-new-mintages.yml b/.github/workflows/autocreate-new-mintages.yml index f44e50a7..216262bf 100644 --- a/.github/workflows/autocreate-new-mintages.yml +++ b/.github/workflows/autocreate-new-mintages.yml @@ -19,7 +19,8 @@ jobs: - name: Set next year variable run: | - echo "NEXT_YEAR=$(($(date +%Y) + 1))" >> $GITHUB_ENV + $NEXT_YEAR=$(($(date +%Y) + 1)) + echo "NEXT_YEAR=$NEXT_YEAR" >> $GITHUB_ENV echo "BRANCH_NAME=add-mintages-$NEXT_YEAR" >> $GITHUB_ENV - name: Create or update branch From b5e2e87eed9ad31b26f374b3b9045edeaeaa0180 Mon Sep 17 00:00:00 2001 From: Dick Wolff Date: Mon, 6 Oct 2025 21:38:25 +0200 Subject: [PATCH 13/15] Fix --- .github/workflows/autocreate-new-mintages.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/autocreate-new-mintages.yml b/.github/workflows/autocreate-new-mintages.yml index 216262bf..00152228 100644 --- a/.github/workflows/autocreate-new-mintages.yml +++ b/.github/workflows/autocreate-new-mintages.yml @@ -19,7 +19,7 @@ jobs: - name: Set next year variable run: | - $NEXT_YEAR=$(($(date +%Y) + 1)) + NEXT_YEAR=$(($(date +%Y) + 1)) echo "NEXT_YEAR=$NEXT_YEAR" >> $GITHUB_ENV echo "BRANCH_NAME=add-mintages-$NEXT_YEAR" >> $GITHUB_ENV From 60dccdd44d3a19ae42931cc845e3a0a559edecd2 Mon Sep 17 00:00:00 2001 From: Dick Wolff Date: Mon, 6 Oct 2025 21:40:20 +0200 Subject: [PATCH 14/15] Rename file --- Scripts/{updateMintage.ts => add-mintages.ts} | 0 Scripts/package.json | 7 +++---- 2 files changed, 3 insertions(+), 4 deletions(-) rename Scripts/{updateMintage.ts => add-mintages.ts} (100%) diff --git a/Scripts/updateMintage.ts b/Scripts/add-mintages.ts similarity index 100% rename from Scripts/updateMintage.ts rename to Scripts/add-mintages.ts diff --git a/Scripts/package.json b/Scripts/package.json index 99cd619a..82a69a9e 100644 --- a/Scripts/package.json +++ b/Scripts/package.json @@ -1,10 +1,9 @@ { - "name": "coin-mintage-updater", + "name": "open-coin-data-mintage-updater", "version": "1.0.0", - "description": "Script to automatically update mintage data for ongoing coin series", - "main": "updateMintage.js", + "description": "Script to automatically add mintage data for next year for ongoing coin series", "scripts": { - "update": "ts-node updateMintage.ts" + "update": "ts-node add-mintages.ts" }, "devDependencies": { "@types/node": "^20.0.0", From 6f9bfbcecd167be1544552fc116a183481bb0e28 Mon Sep 17 00:00:00 2001 From: Dick Wolff Date: Mon, 6 Oct 2025 21:42:45 +0200 Subject: [PATCH 15/15] Aa --- Scripts/add-mintages.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Scripts/add-mintages.ts b/Scripts/add-mintages.ts index 42ce14a9..d9fb0f9e 100644 --- a/Scripts/add-mintages.ts +++ b/Scripts/add-mintages.ts @@ -62,7 +62,7 @@ class MintageUpdater { const countriesPath = join(this.rootDir, COUNTRIES_DIR); if (!existsSync(countriesPath)) { - console.warn(`šŸ•µļø Countries directory not found at: ${countriesPath}`); + console.warn(`āŒ Countries directory not found at: ${countriesPath}`); return; } @@ -130,7 +130,7 @@ class MintageUpdater { path: filePath }; } catch (error) { - console.warn(`Warning: Could not parse serie file ${filePath}:`, error); + console.warn(`āŒ Warning: Could not parse serie file ${filePath}:`, error); return null; } } @@ -174,7 +174,7 @@ class MintageUpdater { const yearPattern = new RegExp(`^\\|\\s*${year}\\s*\\|`, "m"); return yearPattern.test(content); } catch (error) { - console.warn(`Warning: Could not check year in ${filePath}:`, error); + console.warn(`āŒ Warning: Could not check year in ${filePath}:`, error); return false; } } @@ -209,7 +209,7 @@ class MintageUpdater { if (updatedCount <= 5) { // Show first 5 updates } } catch (error) { - console.error(`āœ— Failed to update ${coin.path}:`, error); + console.error(`āŒ Failed to update ${coin.path}:`, error); } } @@ -272,7 +272,7 @@ async function main(): Promise { // Run the script if this file is executed directly if (require.main === module) { main().catch(error => { - console.error("Fatal error:", error); + console.error("āŒ Fatal error:", error); process.exit(1); }); }