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/lib/atoms/index.js b/packages/kg-default-atoms/lib/atoms/index.js
deleted file mode 100644
index 380ec8c7c4..0000000000
--- a/packages/kg-default-atoms/lib/atoms/index.js
+++ /dev/null
@@ -1,3 +0,0 @@
-const softReturn = require('./soft-return');
-
-module.exports = [softReturn];
diff --git a/packages/kg-default-atoms/lib/atoms/soft-return.js b/packages/kg-default-atoms/lib/atoms/soft-return.js
deleted file mode 100644
index 3841940081..0000000000
--- a/packages/kg-default-atoms/lib/atoms/soft-return.js
+++ /dev/null
@@ -1,7 +0,0 @@
-module.exports = {
- name: 'soft-return',
- type: 'dom',
- render(opts) {
- return opts.env.dom.createElement('br');
- }
-};
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
new file mode 100644
index 0000000000..5f1b94f293
--- /dev/null
+++ b/packages/kg-default-atoms/src/atoms/index.ts
@@ -0,0 +1,3 @@
+import softReturn from './soft-return.js';
+
+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
new file mode 100644
index 0000000000..acd132a694
--- /dev/null
+++ b/packages/kg-default-atoms/src/atoms/soft-return.ts
@@ -0,0 +1,17 @@
+export interface AtomEnv {
+ dom: {
+ createElement(tag: string): unknown;
+ };
+}
+
+export interface AtomOpts {
+ env: AtomEnv;
+}
+
+export default {
+ name: 'soft-return',
+ type: 'dom',
+ 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.js b/packages/kg-default-atoms/test/atoms/soft-return.test.js
deleted file mode 100644
index afe94b9443..0000000000
--- a/packages/kg-default-atoms/test/atoms/soft-return.test.js
+++ /dev/null
@@ -1,19 +0,0 @@
-// Switch these lines once there are useful utils
-// const testUtils = require('../utils');
-require('../utils');
-
-const atom = require('../../lib/atoms/soft-return');
-const SimpleDom = require('simple-dom');
-const serializer = new SimpleDom.HTMLSerializer(SimpleDom.voidMap);
-
-describe('Soft return atom', function () {
- it('generates a `br` tag', function () {
- let opts = {
- env: {
- dom: new SimpleDom.Document()
- }
- };
-
- serializer.serialize(atom.render(opts)).should.match('
');
- });
-});
diff --git a/packages/kg-default-atoms/test/atoms/soft-return.test.ts b/packages/kg-default-atoms/test/atoms/soft-return.test.ts
new file mode 100644
index 0000000000..6c2d839718
--- /dev/null
+++ b/packages/kg-default-atoms/test/atoms/soft-return.test.ts
@@ -0,0 +1,18 @@
+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 serializer = new HTMLSerializer(voidMap);
+
+describe('Soft return atom', function () {
+ it('generates a `br` tag', function () {
+ const opts = {
+ env: {
+ dom: new SimpleDomDocument()
+ }
+ };
+
+ serializer.serialize(atom.render(opts) as SerializableNode).should.match('
');
+ });
+});
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 76%
rename from packages/kg-default-atoms/test/utils/index.js
rename to packages/kg-default-atoms/test/utils/index.ts
index 0d67d86ff8..f385b7bf56 100644
--- a/packages/kg-default-atoms/test/utils/index.js
+++ 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.js b/packages/kg-default-atoms/test/utils/overrides.js
deleted file mode 100644
index 90203424ee..0000000000
--- a/packages/kg-default-atoms/test/utils/overrides.js
+++ /dev/null
@@ -1,10 +0,0 @@
-// This file is required before any test is run
-
-// 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();
-
-// Sinon is a simple case
-// Sinon is a global in our eslint test config
-global.sinon = require('sinon');
diff --git a/packages/kg-default-atoms/test/utils/overrides.ts b/packages/kg-default-atoms/test/utils/overrides.ts
new file mode 100644
index 0000000000..6138e929ff
--- /dev/null
+++ b/packages/kg-default-atoms/test/utils/overrides.ts
@@ -0,0 +1,15 @@
+// This file is required before any test is run
+
+// Taken from the should wiki, this is how to make should global
+// Should is a global in our eslint test config
+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
+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/**/*"
+ ]
+}