From b11de7b9812baf5e7028d1a1f1532f2447dfc001 Mon Sep 17 00:00:00 2001 From: Kevin Ansfield Date: Mon, 16 Mar 2026 13:02:20 +0000 Subject: [PATCH 1/2] Rename files for kg-default-atoms TypeScript migration --- .../kg-default-atoms/{lib/atoms/index.js => src/atoms/index.ts} | 0 .../{lib/atoms/soft-return.js => src/atoms/soft-return.ts} | 0 .../test/atoms/{soft-return.test.js => soft-return.test.ts} | 0 .../kg-default-atoms/test/utils/{assertions.js => assertions.ts} | 0 packages/kg-default-atoms/test/utils/{index.js => index.ts} | 0 .../kg-default-atoms/test/utils/{overrides.js => overrides.ts} | 0 6 files changed, 0 insertions(+), 0 deletions(-) rename packages/kg-default-atoms/{lib/atoms/index.js => src/atoms/index.ts} (100%) rename packages/kg-default-atoms/{lib/atoms/soft-return.js => src/atoms/soft-return.ts} (100%) rename packages/kg-default-atoms/test/atoms/{soft-return.test.js => soft-return.test.ts} (100%) rename packages/kg-default-atoms/test/utils/{assertions.js => assertions.ts} (100%) rename packages/kg-default-atoms/test/utils/{index.js => index.ts} (100%) rename packages/kg-default-atoms/test/utils/{overrides.js => overrides.ts} (100%) diff --git a/packages/kg-default-atoms/lib/atoms/index.js b/packages/kg-default-atoms/src/atoms/index.ts similarity index 100% rename from packages/kg-default-atoms/lib/atoms/index.js rename to packages/kg-default-atoms/src/atoms/index.ts diff --git a/packages/kg-default-atoms/lib/atoms/soft-return.js b/packages/kg-default-atoms/src/atoms/soft-return.ts similarity index 100% rename from packages/kg-default-atoms/lib/atoms/soft-return.js rename to packages/kg-default-atoms/src/atoms/soft-return.ts diff --git a/packages/kg-default-atoms/test/atoms/soft-return.test.js b/packages/kg-default-atoms/test/atoms/soft-return.test.ts similarity index 100% rename from packages/kg-default-atoms/test/atoms/soft-return.test.js rename to packages/kg-default-atoms/test/atoms/soft-return.test.ts diff --git a/packages/kg-default-atoms/test/utils/assertions.js b/packages/kg-default-atoms/test/utils/assertions.ts similarity index 100% rename from packages/kg-default-atoms/test/utils/assertions.js rename to packages/kg-default-atoms/test/utils/assertions.ts diff --git a/packages/kg-default-atoms/test/utils/index.js b/packages/kg-default-atoms/test/utils/index.ts similarity index 100% rename from packages/kg-default-atoms/test/utils/index.js rename to packages/kg-default-atoms/test/utils/index.ts diff --git a/packages/kg-default-atoms/test/utils/overrides.js b/packages/kg-default-atoms/test/utils/overrides.ts similarity index 100% rename from packages/kg-default-atoms/test/utils/overrides.js rename to packages/kg-default-atoms/test/utils/overrides.ts From 192dac474f07c763adf60b31604a2b4a14614103 Mon Sep 17 00:00:00 2001 From: Kevin Ansfield Date: Wed, 18 Mar 2026 07:48:28 +0000 Subject: [PATCH 2/2] Migrate kg-default-atoms to TypeScript - Move lib/ to src/, rename .js to .ts - Add tsconfig.json (strict, NodeNext, ESM) - Add "type": "module" to package.json - Convert source and tests from CJS to ESM - Add type annotations throughout - Replace .eslintrc.js with eslint.config.js (flat config) - Output to build/ via tsc --- packages/kg-default-atoms/.gitignore | 2 + packages/kg-default-atoms/eslint.config.mjs | 47 ++++++++----------- packages/kg-default-atoms/index.js | 1 - packages/kg-default-atoms/package.json | 34 +++++++++++--- packages/kg-default-atoms/src/atoms/index.ts | 4 +- .../kg-default-atoms/src/atoms/soft-return.ts | 14 +++++- packages/kg-default-atoms/src/index.ts | 1 + .../test/atoms/soft-return.test.ts | 17 ++++--- packages/kg-default-atoms/test/utils/index.ts | 4 +- .../kg-default-atoms/test/utils/overrides.ts | 11 +++-- packages/kg-default-atoms/tsconfig.cjs.json | 13 +++++ packages/kg-default-atoms/tsconfig.json | 22 +++++++++ packages/kg-default-atoms/tsconfig.test.json | 16 +++++++ 13 files changed, 133 insertions(+), 53 deletions(-) create mode 100644 packages/kg-default-atoms/.gitignore delete mode 100644 packages/kg-default-atoms/index.js create mode 100644 packages/kg-default-atoms/src/index.ts create mode 100644 packages/kg-default-atoms/tsconfig.cjs.json create mode 100644 packages/kg-default-atoms/tsconfig.json create mode 100644 packages/kg-default-atoms/tsconfig.test.json diff --git a/packages/kg-default-atoms/.gitignore b/packages/kg-default-atoms/.gitignore new file mode 100644 index 0000000000..0264be4400 --- /dev/null +++ b/packages/kg-default-atoms/.gitignore @@ -0,0 +1,2 @@ +build/ +tsconfig.tsbuildinfo diff --git a/packages/kg-default-atoms/eslint.config.mjs b/packages/kg-default-atoms/eslint.config.mjs index d43dd6ed67..49b87a9339 100644 --- a/packages/kg-default-atoms/eslint.config.mjs +++ b/packages/kg-default-atoms/eslint.config.mjs @@ -1,42 +1,33 @@ -import {fixupPluginRules} from '@eslint/compat'; +import {defineConfig} from 'eslint/config'; import eslint from '@eslint/js'; import ghostPlugin from 'eslint-plugin-ghost'; -import globals from 'globals'; +import tseslint from 'typescript-eslint'; -const ghost = fixupPluginRules(ghostPlugin); - -export default [ +export default defineConfig([ {ignores: ['build/**']}, - eslint.configs.recommended, { - files: ['**/*.js'], - plugins: {ghost}, + files: ['**/*.ts'], + extends: [ + eslint.configs.recommended, + tseslint.configs.recommended + ], languageOptions: { - globals: globals.node + parserOptions: {ecmaVersion: 2022, sourceType: 'module'} }, + plugins: {ghost: ghostPlugin}, rules: { - ...ghostPlugin.configs.node.rules, - // match ESLint 8 behavior for catch clause variables - 'no-unused-vars': ['error', {caughtErrors: 'none'}], - // disable rules incompatible with ESLint 9 flat config - 'ghost/filenames/match-exported-class': 'off', - 'ghost/filenames/match-exported': 'off', - 'ghost/filenames/match-regex': 'off' + ...ghostPlugin.configs.ts.rules, + '@typescript-eslint/no-explicit-any': 'error' } }, { - files: ['test/**/*.js'], - plugins: {ghost}, - languageOptions: { - globals: { - ...globals.node, - ...globals.mocha, - should: true, - sinon: true - } - }, + files: ['test/**/*.ts'], rules: { - ...ghostPlugin.configs.test.rules + ...ghostPlugin.configs['ts-test'].rules, + 'ghost/mocha/no-global-tests': 'off', + 'ghost/mocha/handle-done-callback': 'off', + 'ghost/mocha/no-mocha-arrows': 'off', + 'ghost/mocha/max-top-level-suites': 'off' } } -]; +]); diff --git a/packages/kg-default-atoms/index.js b/packages/kg-default-atoms/index.js deleted file mode 100644 index d05c343b8d..0000000000 --- a/packages/kg-default-atoms/index.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('./lib/atoms'); diff --git a/packages/kg-default-atoms/package.json b/packages/kg-default-atoms/package.json index b4b6d5c8a6..9959f07b00 100644 --- a/packages/kg-default-atoms/package.json +++ b/packages/kg-default-atoms/package.json @@ -1,13 +1,25 @@ { "name": "@tryghost/kg-default-atoms", "version": "5.1.7", - "repository": "https://github.com/TryGhost/Koenig/tree/master/packages/kg-default-atoms", + "repository": "https://github.com/TryGhost/Koenig/tree/main/packages/kg-default-atoms", "author": "Ghost Foundation", "license": "MIT", - "main": "index.js", + "main": "build/cjs/index.js", + "module": "build/esm/index.js", + "types": "build/esm/index.d.ts", + "exports": { + ".": { + "types": "./build/esm/index.d.ts", + "import": "./build/esm/index.js", + "require": "./build/cjs/index.js" + } + }, "scripts": { "dev": "echo \"Implement me!\"", - "test": "NODE_ENV=testing c8 --all --reporter text --reporter cobertura mocha './test/**/*.test.js'", + "build": "tsc && tsc -p tsconfig.cjs.json && echo '{\"type\":\"module\"}' > build/esm/package.json", + "prepare": "tsc && tsc -p tsconfig.cjs.json && echo '{\"type\":\"module\"}' > build/esm/package.json", + "pretest": "tsc && tsc -p tsconfig.cjs.json && echo '{\"type\":\"module\"}' > build/esm/package.json && tsc -p tsconfig.test.json", + "test": "NODE_ENV=testing c8 --all --reporter text --reporter cobertura mocha --require tsx './test/**/*.test.ts'", "lint": "eslint . --cache", "posttest": "yarn lint" }, @@ -15,14 +27,24 @@ "node": "^22.13.1 || ^24.0.0" }, "files": [ - "index.js", - "lib" + "build" ], "publishConfig": { "access": "public" }, "devDependencies": { + "@eslint/js": "9.28.0", + "@types/mocha": "10.0.10", + "@types/node": "22.15.29", + "@types/should": "13.0.0", + "@types/sinon": "17.0.4", "c8": "11.0.0", - "simple-dom": "1.4.0" + "mocha": "11.7.5", + "should": "13.2.3", + "simple-dom": "1.4.0", + "sinon": "21.0.1", + "tsx": "4.19.4", + "typescript": "5.8.3", + "typescript-eslint": "8.33.1" } } diff --git a/packages/kg-default-atoms/src/atoms/index.ts b/packages/kg-default-atoms/src/atoms/index.ts index 380ec8c7c4..5f1b94f293 100644 --- a/packages/kg-default-atoms/src/atoms/index.ts +++ b/packages/kg-default-atoms/src/atoms/index.ts @@ -1,3 +1,3 @@ -const softReturn = require('./soft-return'); +import softReturn from './soft-return.js'; -module.exports = [softReturn]; +export const atoms = [softReturn]; diff --git a/packages/kg-default-atoms/src/atoms/soft-return.ts b/packages/kg-default-atoms/src/atoms/soft-return.ts index 3841940081..acd132a694 100644 --- a/packages/kg-default-atoms/src/atoms/soft-return.ts +++ b/packages/kg-default-atoms/src/atoms/soft-return.ts @@ -1,7 +1,17 @@ -module.exports = { +export interface AtomEnv { + dom: { + createElement(tag: string): unknown; + }; +} + +export interface AtomOpts { + env: AtomEnv; +} + +export default { name: 'soft-return', type: 'dom', - render(opts) { + render(opts: AtomOpts) { return opts.env.dom.createElement('br'); } }; diff --git a/packages/kg-default-atoms/src/index.ts b/packages/kg-default-atoms/src/index.ts new file mode 100644 index 0000000000..87a256d8d2 --- /dev/null +++ b/packages/kg-default-atoms/src/index.ts @@ -0,0 +1 @@ +export {atoms} from './atoms/index.js'; diff --git a/packages/kg-default-atoms/test/atoms/soft-return.test.ts b/packages/kg-default-atoms/test/atoms/soft-return.test.ts index afe94b9443..6c2d839718 100644 --- a/packages/kg-default-atoms/test/atoms/soft-return.test.ts +++ b/packages/kg-default-atoms/test/atoms/soft-return.test.ts @@ -1,19 +1,18 @@ -// Switch these lines once there are useful utils -// const testUtils = require('../utils'); -require('../utils'); +import '../utils/index.js'; +import atom from '../../src/atoms/soft-return.js'; +import {Document as SimpleDomDocument, HTMLSerializer, voidMap} from 'simple-dom'; +import type {SerializableNode} from '@simple-dom/interface'; -const atom = require('../../lib/atoms/soft-return'); -const SimpleDom = require('simple-dom'); -const serializer = new SimpleDom.HTMLSerializer(SimpleDom.voidMap); +const serializer = new HTMLSerializer(voidMap); describe('Soft return atom', function () { it('generates a `br` tag', function () { - let opts = { + const opts = { env: { - dom: new SimpleDom.Document() + dom: new SimpleDomDocument() } }; - serializer.serialize(atom.render(opts)).should.match('
'); + serializer.serialize(atom.render(opts) as SerializableNode).should.match('
'); }); }); diff --git a/packages/kg-default-atoms/test/utils/index.ts b/packages/kg-default-atoms/test/utils/index.ts index 0d67d86ff8..f385b7bf56 100644 --- a/packages/kg-default-atoms/test/utils/index.ts +++ b/packages/kg-default-atoms/test/utils/index.ts @@ -5,7 +5,7 @@ */ // Require overrides - these add globals for tests -require('./overrides'); +import './overrides.js'; // Require assertions - adds custom should assertions -require('./assertions'); +import './assertions.js'; diff --git a/packages/kg-default-atoms/test/utils/overrides.ts b/packages/kg-default-atoms/test/utils/overrides.ts index 90203424ee..6138e929ff 100644 --- a/packages/kg-default-atoms/test/utils/overrides.ts +++ b/packages/kg-default-atoms/test/utils/overrides.ts @@ -2,9 +2,14 @@ // Taken from the should wiki, this is how to make should global // Should is a global in our eslint test config -global.should = require('should').noConflict(); -should.extend(); +import should from 'should'; +import sinon from 'sinon'; + +// @types/should is incomplete — noConflict and extend exist at runtime +const shouldModule = should as unknown as {noConflict(): typeof should; extend(): void}; +Object.defineProperty(globalThis, 'should', {value: shouldModule.noConflict(), writable: true, configurable: true}); +shouldModule.extend(); // Sinon is a simple case // Sinon is a global in our eslint test config -global.sinon = require('sinon'); +Object.defineProperty(globalThis, 'sinon', {value: sinon, writable: true, configurable: true}); diff --git a/packages/kg-default-atoms/tsconfig.cjs.json b/packages/kg-default-atoms/tsconfig.cjs.json new file mode 100644 index 0000000000..bd981491bc --- /dev/null +++ b/packages/kg-default-atoms/tsconfig.cjs.json @@ -0,0 +1,13 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "NodeNext", + "moduleResolution": "NodeNext", + "outDir": "build/cjs", + "verbatimModuleSyntax": false, + "declaration": false, + "declarationMap": false, + "sourceMap": false, + "incremental": false + } +} diff --git a/packages/kg-default-atoms/tsconfig.json b/packages/kg-default-atoms/tsconfig.json new file mode 100644 index 0000000000..e2ebf36cde --- /dev/null +++ b/packages/kg-default-atoms/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "target": "es2022", + "module": "ES2022", + "moduleResolution": "bundler", + "rootDir": "src", + "outDir": "build/esm", + "strict": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "incremental": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "isolatedModules": true + }, + "include": [ + "src/**/*" + ] +} diff --git a/packages/kg-default-atoms/tsconfig.test.json b/packages/kg-default-atoms/tsconfig.test.json new file mode 100644 index 0000000000..b092804ad8 --- /dev/null +++ b/packages/kg-default-atoms/tsconfig.test.json @@ -0,0 +1,16 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "rootDir": ".", + "outDir": null, + "noEmit": true, + "declaration": false, + "declarationMap": false, + "sourceMap": false, + "incremental": false + }, + "include": [ + "src/**/*", + "test/**/*" + ] +}