diff --git a/packages/tpl/.gitignore b/packages/tpl/.gitignore new file mode 100644 index 000000000..da0a0baf7 --- /dev/null +++ b/packages/tpl/.gitignore @@ -0,0 +1,6 @@ +index.js +index.d.ts +index.js.map +lib/*.js +lib/*.d.ts +lib/*.js.map diff --git a/packages/tpl/index.js b/packages/tpl/index.js deleted file mode 100644 index bcbd8ac9b..000000000 --- a/packages/tpl/index.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('./lib/tpl'); diff --git a/packages/tpl/index.ts b/packages/tpl/index.ts new file mode 100644 index 000000000..d20fd9efb --- /dev/null +++ b/packages/tpl/index.ts @@ -0,0 +1,3 @@ +import tpl = require('./lib/tpl'); + +export = tpl; diff --git a/packages/tpl/lib/tpl.js b/packages/tpl/lib/tpl.ts similarity index 81% rename from packages/tpl/lib/tpl.js rename to packages/tpl/lib/tpl.ts index 360812637..90059b9fe 100644 --- a/packages/tpl/lib/tpl.js +++ b/packages/tpl/lib/tpl.ts @@ -6,13 +6,8 @@ const interpolate = /(? { +function tpl(string: string, data?: object | null): string { if (!data) { return string; } @@ -25,8 +20,10 @@ module.exports = (string, data) => { if (!(trimmed in data)) { throw new ReferenceError(`${trimmed} is not defined`); } - return data[trimmed]; + return String((data as Record)[trimmed]); }); // Replace our swapped out left braces and any escaped right braces return processedString.replace(/\\U\+007B/g, '{').replace(/\\}/g, '}'); -}; +} + +export = tpl; diff --git a/packages/tpl/package.json b/packages/tpl/package.json index abadfafb1..f6cb13201 100644 --- a/packages/tpl/package.json +++ b/packages/tpl/package.json @@ -10,20 +10,28 @@ }, "files": [ "index.js", - "lib" + "index.d.ts", + "lib/*.js", + "lib/*.d.ts" ], "main": "index.js", + "types": "index.d.ts", "publishConfig": { "access": "public" }, "scripts": { - "dev": "echo \"Implement me!\"", - "test": "NODE_ENV=testing vitest run --coverage", + "dev": "tsc --watch --preserveWatchOutput --sourceMap", + "build": "tsc", + "prepare": "tsc", + "pretest": "pnpm run build", + "test": "pnpm run test:types && NODE_ENV=testing vitest run --coverage", + "test:types": "tsc -p tsconfig.test.json --noEmit", "lint": "oxlint -c ../../.oxlintrc.json .", "posttest": "pnpm run lint" }, "dependencies": {}, "devDependencies": { - "sinon": "catalog:" + "@types/node": "24.13.1", + "typescript": "catalog:" } } diff --git a/packages/tpl/test/tpl.test.js b/packages/tpl/test/tpl.test.ts similarity index 78% rename from packages/tpl/test/tpl.test.js rename to packages/tpl/test/tpl.test.ts index 5d0b281ef..b80909750 100644 --- a/packages/tpl/test/tpl.test.js +++ b/packages/tpl/test/tpl.test.ts @@ -1,5 +1,5 @@ -const assert = require('assert/strict'); -const tpl = require('../'); +import assert from 'assert/strict'; +import tpl from '../'; describe('tpl', function () { it('Can handle a plain string', function () { @@ -13,7 +13,20 @@ describe('tpl', function () { const string = 'Go visit {url}'; const data = { url: 'https://example.com' }; - let result = tpl(string, data); + const result = tpl(string, data); + + assert.equal(result, 'Go visit https://example.com'); + }); + + it('Can handle typed data objects', function () { + interface TemplateData { + url: string; + } + + const string = 'Go visit {url}'; + const data: TemplateData = { url: 'https://example.com' }; + + const result = tpl(string, data); assert.equal(result, 'Go visit https://example.com'); }); @@ -24,7 +37,7 @@ describe('tpl', function () { totalMs: '500', }; - let result = tpl(string, data); + const result = tpl(string, data); assert.equal(result, '{{#get}} helper took 500ms to complete'); }); @@ -36,7 +49,7 @@ describe('tpl', function () { totalMs: '500', }; - let result = tpl(string, data); + const result = tpl(string, data); assert.equal(result, '{{#get}} helper took 500ms to complete'); }); @@ -46,7 +59,7 @@ describe('tpl', function () { helperName: 'get', totalMs: '500', }; - let result = tpl(string, data); + const result = tpl(string, data); assert.equal(result, 'The {{{helperName}}} helper is not available.'); }); @@ -56,7 +69,7 @@ describe('tpl', function () { helperName: 'get', totalMs: '500', }; - let result = tpl(string, data); + const result = tpl(string, data); assert.equal(result, 'The {{get}} helper is not available.'); }); @@ -66,7 +79,7 @@ describe('tpl', function () { helperName: 'get', totalMs: '500', }; - let result = tpl(string, data); + const result = tpl(string, data); assert.equal(result, 'The {{get}} helper is not available.'); }); @@ -76,7 +89,7 @@ describe('tpl', function () { helperName: 'get', totalMs: '500', }; - let result = tpl(string, data); + const result = tpl(string, data); assert.equal(result, 'The {{get}} helper is not available.'); }); @@ -86,7 +99,7 @@ describe('tpl', function () { totalMs: '500', }; - let resultFn = () => { + const resultFn = () => { tpl(string, data); }; assert.throws(resultFn, /helperName is not defined/); diff --git a/packages/tpl/tsconfig.json b/packages/tpl/tsconfig.json new file mode 100644 index 000000000..c455e3036 --- /dev/null +++ b/packages/tpl/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "rootDir": ".", + "sourceMap": false, + "types": [] + }, + "include": ["index.ts", "lib/**/*.ts"] +} diff --git a/packages/tpl/tsconfig.test.json b/packages/tpl/tsconfig.test.json new file mode 100644 index 000000000..885e5092e --- /dev/null +++ b/packages/tpl/tsconfig.test.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "types": ["node", "vitest/globals"] + }, + "include": ["index.ts", "lib/**/*.ts", "test/**/*.ts"] +} diff --git a/packages/tpl/vitest.config.ts b/packages/tpl/vitest.config.ts new file mode 100644 index 000000000..10013bd29 --- /dev/null +++ b/packages/tpl/vitest.config.ts @@ -0,0 +1,14 @@ +import { defineConfig, mergeConfig } from 'vitest/config'; +import rootConfig from '../../vitest.config'; + +export default mergeConfig( + rootConfig, + defineConfig({ + test: { + coverage: { + include: ['lib/**/*.js'], + exclude: ['**/*.ts', '**/*.d.ts'], + }, + }, + }), +); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ad5230ca0..39ee84d98 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -120,7 +120,7 @@ importers: version: link:../errors '@tryghost/nql': specifier: 0.13.1 - version: 0.13.1 + version: 0.13.1(supports-color@7.2.0) '@tryghost/tpl': specifier: workspace:* version: link:../tpl @@ -273,7 +273,7 @@ importers: dependencies: '@elastic/elasticsearch': specifier: 8.19.1 - version: 8.19.1 + version: 8.19.1(supports-color@7.2.0) '@tryghost/debug': specifier: workspace:* version: link:../debug @@ -326,7 +326,7 @@ importers: devDependencies: express: specifier: 5.2.1 - version: 5.2.1 + version: 5.2.1(supports-color@7.2.0) express-session: specifier: 1.19.0 version: 1.19.0 @@ -354,7 +354,7 @@ importers: devDependencies: express: specifier: 5.2.1 - version: 5.2.1 + version: 5.2.1(supports-color@7.2.0) sinon: specifier: 'catalog:' version: 22.0.0 @@ -413,7 +413,7 @@ importers: version: 4.4.0 rewire: specifier: 9.0.1 - version: 9.0.1 + version: 9.0.1(supports-color@7.2.0) sinon: specifier: 'catalog:' version: 22.0.0 @@ -475,7 +475,7 @@ importers: devDependencies: rewire: specifier: 9.0.1 - version: 9.0.1 + version: 9.0.1(supports-color@7.2.0) sinon: specifier: 'catalog:' version: 22.0.0 @@ -509,7 +509,7 @@ importers: devDependencies: supertest: specifier: 7.2.2 - version: 7.2.2 + version: 7.2.2(supports-color@7.2.0) packages/nodemailer: dependencies: @@ -575,7 +575,7 @@ importers: version: link:../logging express: specifier: 5.2.1 - version: 5.2.1 + version: 5.2.1(supports-color@7.2.0) prom-client: specifier: 15.1.3 version: 15.1.3 @@ -609,7 +609,7 @@ importers: version: 22.0.0 supertest: specifier: 7.2.2 - version: 7.2.2 + version: 7.2.2(supports-color@7.2.0) typescript: specifier: 'catalog:' version: 6.0.3 @@ -646,7 +646,7 @@ importers: version: 14.0.15 rewire: specifier: 9.0.1 - version: 9.0.1 + version: 9.0.1(supports-color@7.2.0) sinon: specifier: 'catalog:' version: 22.0.0 @@ -692,9 +692,12 @@ importers: packages/tpl: devDependencies: - sinon: + '@types/node': + specifier: 24.13.1 + version: 24.13.1 + typescript: specifier: 'catalog:' - version: 22.0.0 + version: 6.0.3 packages/validator: dependencies: @@ -750,7 +753,7 @@ importers: version: 8.0.0 extract-zip: specifier: 2.0.1 - version: 2.0.1 + version: 2.0.1(supports-color@7.2.0) devDependencies: folder-hash: specifier: 4.1.3 @@ -2385,6 +2388,9 @@ packages: '@types/methods@1.1.4': resolution: {integrity: sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==} + '@types/node@24.13.1': + resolution: {integrity: sha512-RSpUJGmvsJ1ZeBehQZFhIdpsz+bIpES0nIQXko4Ybq+N+kX6XvOq3Jo+iJ82FWLdblFq85AsMikd3m35jgezYg==} + '@types/node@24.13.2': resolution: {integrity: sha512-fRa09kZTgu8o71KFcDjUFuc7F+dEbZYZmkI0mg5YBTRs0yMKjYHsq/c0urDKeDb+D5qVgXOdFcuu+DZPKOITwA==} @@ -6268,16 +6274,16 @@ snapshots: '@breejs/later@4.2.0': {} - '@elastic/elasticsearch@8.19.1': + '@elastic/elasticsearch@8.19.1(supports-color@7.2.0)': dependencies: - '@elastic/transport': 8.10.1 + '@elastic/transport': 8.10.1(supports-color@7.2.0) apache-arrow: 21.1.0 tslib: 2.8.1 transitivePeerDependencies: - '@75lb/nature' - supports-color - '@elastic/transport@8.10.1': + '@elastic/transport@8.10.1(supports-color@7.2.0)': dependencies: '@opentelemetry/api': 1.9.1 '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) @@ -6399,12 +6405,12 @@ snapshots: '@eslint-community/eslint-utils@4.9.1(eslint@9.39.4)': dependencies: - eslint: 9.39.4 + eslint: 9.39.4(supports-color@7.2.0) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.2': {} - '@eslint/config-array@0.21.2': + '@eslint/config-array@0.21.2(supports-color@7.2.0)': dependencies: '@eslint/object-schema': 2.1.7 debug: 4.4.3(supports-color@7.2.0) @@ -6420,7 +6426,7 @@ snapshots: dependencies: '@types/json-schema': 7.0.15 - '@eslint/eslintrc@3.3.5': + '@eslint/eslintrc@3.3.5(supports-color@7.2.0)': dependencies: ajv: 6.15.0 debug: 4.4.3(supports-color@7.2.0) @@ -6941,7 +6947,7 @@ snapshots: dependencies: long-timeout: 0.1.1 - '@tryghost/mongo-knex@0.10.1': + '@tryghost/mongo-knex@0.10.1(supports-color@7.2.0)': dependencies: debug: 4.4.3(supports-color@7.2.0) lodash: 4.18.1 @@ -6956,9 +6962,9 @@ snapshots: dependencies: date-fns: 2.30.0 - '@tryghost/nql@0.13.1': + '@tryghost/nql@0.13.1(supports-color@7.2.0)': dependencies: - '@tryghost/mongo-knex': 0.10.1 + '@tryghost/mongo-knex': 0.10.1(supports-color@7.2.0) '@tryghost/mongo-utils': 0.6.5 '@tryghost/nql-lang': 0.6.6 lodash: 4.18.1 @@ -7036,6 +7042,10 @@ snapshots: '@types/methods@1.1.4': {} + '@types/node@24.13.1': + dependencies: + undici-types: 7.18.2 + '@types/node@24.13.2': dependencies: undici-types: 7.18.2 @@ -7417,7 +7427,7 @@ snapshots: bluebird@3.7.2: {} - body-parser@2.3.0: + body-parser@2.3.0(supports-color@7.2.0): dependencies: bytes: 3.1.2 content-type: 2.0.0 @@ -7872,14 +7882,14 @@ snapshots: eslint-visitor-keys@4.2.1: {} - eslint@9.39.4: + eslint@9.39.4(supports-color@7.2.0): dependencies: '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4) '@eslint-community/regexpp': 4.12.2 - '@eslint/config-array': 0.21.2 + '@eslint/config-array': 0.21.2(supports-color@7.2.0) '@eslint/config-helpers': 0.4.2 '@eslint/core': 0.17.0 - '@eslint/eslintrc': 3.3.5 + '@eslint/eslintrc': 3.3.5(supports-color@7.2.0) '@eslint/js': 9.39.4 '@eslint/plugin-kit': 0.4.1 '@humanfs/node': 0.16.8 @@ -7980,10 +7990,10 @@ snapshots: transitivePeerDependencies: - supports-color - express@5.2.1: + express@5.2.1(supports-color@7.2.0): dependencies: accepts: 2.0.0 - body-parser: 2.3.0 + body-parser: 2.3.0(supports-color@7.2.0) content-disposition: 1.1.0 content-type: 1.0.5 cookie: 0.7.2 @@ -7993,7 +8003,7 @@ snapshots: encodeurl: 2.0.0 escape-html: 1.0.3 etag: 1.8.1 - finalhandler: 2.1.1 + finalhandler: 2.1.1(supports-color@7.2.0) fresh: 2.0.0 http-errors: 2.0.1 merge-descriptors: 2.0.0 @@ -8004,8 +8014,8 @@ snapshots: proxy-addr: 2.0.7 qs: 6.15.2 range-parser: 1.2.1 - router: 2.2.0 - send: 1.2.1 + router: 2.2.0(supports-color@7.2.0) + send: 1.2.1(supports-color@7.2.0) serve-static: 2.2.1 statuses: 2.0.2 type-is: 2.1.0 @@ -8013,7 +8023,7 @@ snapshots: transitivePeerDependencies: - supports-color - extract-zip@2.0.1: + extract-zip@2.0.1(supports-color@7.2.0): dependencies: debug: 4.4.3(supports-color@7.2.0) get-stream: 5.2.0 @@ -8055,7 +8065,7 @@ snapshots: file-uri-to-path@1.0.0: {} - finalhandler@2.1.1: + finalhandler@2.1.1(supports-color@7.2.0): dependencies: debug: 4.4.3(supports-color@7.2.0) encodeurl: 2.0.0 @@ -9408,9 +9418,9 @@ snapshots: reusify@1.1.0: {} - rewire@9.0.1: + rewire@9.0.1(supports-color@7.2.0): dependencies: - eslint: 9.39.4 + eslint: 9.39.4(supports-color@7.2.0) pirates: 4.0.7 transitivePeerDependencies: - jiti @@ -9444,7 +9454,7 @@ snapshots: '@rolldown/binding-win32-arm64-msvc': 1.0.3 '@rolldown/binding-win32-x64-msvc': 1.0.3 - router@2.2.0: + router@2.2.0(supports-color@7.2.0): dependencies: debug: 4.4.3(supports-color@7.2.0) depd: 2.0.0 @@ -9475,7 +9485,7 @@ snapshots: semver@7.8.5: {} - send@1.2.1: + send@1.2.1(supports-color@7.2.0): dependencies: debug: 4.4.3(supports-color@7.2.0) encodeurl: 2.0.0 @@ -9496,7 +9506,7 @@ snapshots: encodeurl: 2.0.0 escape-html: 1.0.3 parseurl: 1.3.3 - send: 1.2.1 + send: 1.2.1(supports-color@7.2.0) transitivePeerDependencies: - supports-color @@ -9659,7 +9669,7 @@ snapshots: strip-json-comments@3.1.1: {} - superagent@10.3.0: + superagent@10.3.0(supports-color@7.2.0): dependencies: component-emitter: 1.3.1 cookiejar: 2.1.4 @@ -9673,11 +9683,11 @@ snapshots: transitivePeerDependencies: - supports-color - supertest@7.2.2: + supertest@7.2.2(supports-color@7.2.0): dependencies: cookie-signature: 1.2.2 methods: 1.1.2 - superagent: 10.3.0 + superagent: 10.3.0(supports-color@7.2.0) transitivePeerDependencies: - supports-color