From 36fb89f9e50be16c4fce3c429c44eb63526249e1 Mon Sep 17 00:00:00 2001 From: Michael McRae Date: Sat, 20 Jun 2026 12:00:55 +1000 Subject: [PATCH 1/6] tempo.config.ts pattern --- packages/tempo/CHANGELOG.md | 10 +++ packages/tempo/bin/tempo.js | 62 +++++++++++++ packages/tempo/doc/tempo.config.md | 46 +++++++--- packages/tempo/importmap.json | 15 ---- packages/tempo/package.json | 7 +- packages/tempo/src/config/defineConfig.ts | 8 ++ packages/tempo/src/config/resolveConfig.ts | 86 +++++++++++++++++++ packages/tempo/src/tempo.class.ts | 12 +++ packages/tempo/src/tempo.index.ts | 1 + packages/tempo/src/tempo.version.ts | 2 +- packages/tempo/template/index.sample.html | 63 ++++++++++++++ .../tempo/template/tempo.config.sample.ts | 39 +++++++++ .../core/__fixtures__/config/tempo.config.js | 8 ++ packages/tempo/test/core/bootstrap.test.ts | 34 ++++++++ 14 files changed, 363 insertions(+), 30 deletions(-) create mode 100755 packages/tempo/bin/tempo.js delete mode 100644 packages/tempo/importmap.json create mode 100644 packages/tempo/src/config/defineConfig.ts create mode 100644 packages/tempo/src/config/resolveConfig.ts create mode 100644 packages/tempo/template/index.sample.html create mode 100644 packages/tempo/template/tempo.config.sample.ts create mode 100644 packages/tempo/test/core/__fixtures__/config/tempo.config.js create mode 100644 packages/tempo/test/core/bootstrap.test.ts diff --git a/packages/tempo/CHANGELOG.md b/packages/tempo/CHANGELOG.md index 51177aad..dc82905f 100644 --- a/packages/tempo/CHANGELOG.md +++ b/packages/tempo/CHANGELOG.md @@ -6,6 +6,16 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [3.2.3] - 2026-06-20 + +### Added +- **Centralized Configuration**: Introduced `tempo.config.ts` and `tempo.config.js` discovery. +- **Async Bootstrap**: Added `Tempo.bootstrap()` for safe, asynchronous ES Module configuration resolution without breaking the synchronous `Tempo.init()` API. +- **CLI Scaffolding**: Added an `npx @magmacomputing/tempo scaffold:all` CLI tool to easily bootstrap `tempo.config.ts` and HTML sandboxes into projects. + +### Changed +- **Zero-Dependency Resolution**: Implemented custom filesystem traversal to ensure robust config discovery without adding external dependencies (e.g., `cosmiconfig`). + ## [3.2.2] - 2026-06-18 ### Added diff --git a/packages/tempo/bin/tempo.js b/packages/tempo/bin/tempo.js new file mode 100755 index 00000000..7e911f5d --- /dev/null +++ b/packages/tempo/bin/tempo.js @@ -0,0 +1,62 @@ +#!/usr/bin/env node + +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +// CLI Arguments +const args = process.argv.slice(2); +const command = args[0]; + +// The location of the templates relative to this bin script (bin/tempo.js -> template/) +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const templateDir = path.join(__dirname, '..', 'template'); +const cwd = process.cwd(); + +// Helper to copy a file safely +function safelyCopy(sourceFile, targetFile, successMessage) { + const sourcePath = path.join(templateDir, sourceFile); + const targetPath = path.join(cwd, targetFile); + + if (fs.existsSync(targetPath)) { + console.error(`\x1b[31m[Tempo Scaffold] Aborted.\x1b[0m File already exists at: ${targetPath}`); + console.error(`We refused to overwrite your existing file. If you want to scaffold a new one, please delete the existing file first.`); + process.exit(1); + } + + if (!fs.existsSync(sourcePath)) { + console.error(`\x1b[31m[Tempo Scaffold] Error.\x1b[0m Could not find template file at: ${sourcePath}`); + process.exit(1); + } + + try { + fs.copyFileSync(sourcePath, targetPath); + console.log(`\x1b[32m[Tempo Scaffold] Success!\x1b[0m ${successMessage}`); + } catch (err) { + console.error(`\x1b[31m[Tempo Scaffold] Failed to copy file:\x1b[0m ${err.message}`); + process.exit(1); + } +} + +// Route commands +if (command === 'scaffold:config') { + safelyCopy('tempo.config.sample.ts', 'tempo.config.ts', 'Created tempo.config.ts'); +} else if (command === 'scaffold:html') { + safelyCopy('index.sample.html', 'index.html', 'Created index.html boilerplate'); +} else if (command === 'scaffold:all') { + safelyCopy('tempo.config.sample.ts', 'tempo.config.ts', 'Created tempo.config.ts'); + safelyCopy('index.sample.html', 'index.html', 'Created index.html boilerplate'); +} else { + console.log(` +\x1b[36m@magmacomputing/tempo\x1b[0m CLI + +Available Commands: + scaffold:config Copies a sample tempo.config.ts into your current directory. + scaffold:html Copies a working HTML sandbox (index.html) into your current directory. + scaffold:all Copies both files. + +Usage: + npx @magmacomputing/tempo +`); +} diff --git a/packages/tempo/doc/tempo.config.md b/packages/tempo/doc/tempo.config.md index 0b2b9a05..d199b880 100644 --- a/packages/tempo/doc/tempo.config.md +++ b/packages/tempo/doc/tempo.config.md @@ -20,13 +20,17 @@ Rather than scattering `Tempo.init()` or `Tempo.extend()` calls throughout your This mirrors modern ecosystem standards (like `vite.config.ts` or `tailwind.config.js`) and ensures that plugins, timezones, and custom aliases are consistently applied before any domain logic executes. +::: info +**Target Environment**: This automatic configuration discovery pattern relies on Node.js file system capabilities and is designed for Server, Fullstack, or Bundled environments (like Vite or Webpack). If you are using Tempo via a ` + + + +

Tempo Hello World

+
+ Initializing Tempo... +
+ + + + + \ No newline at end of file diff --git a/packages/tempo/template/tempo.config.sample.ts b/packages/tempo/template/tempo.config.sample.ts new file mode 100644 index 00000000..f8d48c04 --- /dev/null +++ b/packages/tempo/template/tempo.config.sample.ts @@ -0,0 +1,39 @@ +import { defineConfig } from '@magmacomputing/tempo'; + +/** + * Tempo Configuration + * + * This file acts as a centralized configuration for your application. + * When `Tempo.init()` is called without arguments, it will automatically + * discover and apply the settings below. + * + * Note: These settings act as overrides to the robust defaults in `support.default.ts`. + */ +export default defineConfig({ + // ------------------------------------------------------------------------- + // ------------------------------------------------------------------------- + // Core Engine Settings + // ------------------------------------------------------------------------- + /** Defines the default time zone (e.g. 'America/New_York' or 'UTC') */ + // timeZone: 'UTC', + + /** Defines the default locale used when formatting / parsing */ + // locale: 'en-US', + + // ------------------------------------------------------------------------- + // Layouts (Format Presets) + // ------------------------------------------------------------------------- + layouts: { + /** Example of a custom layout accessible via now.format('iso_date') */ + // iso_date: '{yyyy}-{mm}-{dd}', + // time_short: '{hh}:{mi} {mer}' + }, + + // ------------------------------------------------------------------------- + // Plugins + // ------------------------------------------------------------------------- + plugins: [ + // Import and instantiate your plugins here + // TickerPlugin({ interval: 1000 }) + ] +}); diff --git a/packages/tempo/test/core/__fixtures__/config/tempo.config.js b/packages/tempo/test/core/__fixtures__/config/tempo.config.js new file mode 100644 index 00000000..2865613a --- /dev/null +++ b/packages/tempo/test/core/__fixtures__/config/tempo.config.js @@ -0,0 +1,8 @@ +export default { + timeZone: 'Europe/Paris', + registry: { + periods: { + 'custom-bootstrap-period': '13:00' + } + } +}; diff --git a/packages/tempo/test/core/bootstrap.test.ts b/packages/tempo/test/core/bootstrap.test.ts new file mode 100644 index 00000000..807615be --- /dev/null +++ b/packages/tempo/test/core/bootstrap.test.ts @@ -0,0 +1,34 @@ +import { Tempo } from '#tempo'; +import path from 'node:path'; + +describe('Tempo.bootstrap()', () => { + afterEach(() => { + Tempo.init(); + }); + + it('should dynamically discover and load a configuration file', async () => { + // Resolve the fixture directory from the workspace root + const fixtureDir = path.resolve('./test/core/__fixtures__/config'); + + await Tempo.bootstrap({ cwd: fixtureDir }); + + expect(Tempo.config.timeZone).toBe('Europe/Paris'); + + const t = new Tempo('custom-bootstrap-period'); + expect(t.isValid).toBe(true); + expect(t.format('{h24}:{mi}')).toBe('13:00'); + }); + + it('should allow explicitly passing a configFile', async () => { + const fixtureDir = path.resolve('./test/core/__fixtures__/config'); + + // Pass the explicit file path + await Tempo.bootstrap({ configFile: path.join(fixtureDir, 'tempo.config.js') }); + + expect(Tempo.config.timeZone).toBe('Europe/Paris'); + + const t = new Tempo('custom-bootstrap-period'); + expect(t.isValid).toBe(true); + expect(t.format('{h24}:{mi}')).toBe('13:00'); + }); +}); From bd3d880e34dd8e6b2b860cafb5d9c21ff88d8d8f Mon Sep 17 00:00:00 2001 From: Michael McRae Date: Sat, 20 Jun 2026 12:21:46 +1000 Subject: [PATCH 2/6] PR 1st review --- packages/tempo/bin/tempo.js | 8 +++++++ packages/tempo/doc/tempo.config.md | 5 ++-- packages/tempo/src/config/resolveConfig.ts | 8 +++++-- packages/tempo/template/index.sample.html | 27 ++++++++++++++++++---- 4 files changed, 39 insertions(+), 9 deletions(-) diff --git a/packages/tempo/bin/tempo.js b/packages/tempo/bin/tempo.js index 7e911f5d..0bb5f30b 100755 --- a/packages/tempo/bin/tempo.js +++ b/packages/tempo/bin/tempo.js @@ -45,6 +45,14 @@ if (command === 'scaffold:config') { } else if (command === 'scaffold:html') { safelyCopy('index.sample.html', 'index.html', 'Created index.html boilerplate'); } else if (command === 'scaffold:all') { + const configExists = fs.existsSync(path.join(cwd, 'tempo.config.ts')); + const htmlExists = fs.existsSync(path.join(cwd, 'index.html')); + if (configExists || htmlExists) { + const existingFiles = [configExists ? 'tempo.config.ts' : null, htmlExists ? 'index.html' : null].filter(Boolean).join(' and '); + console.error(`\x1b[31m[Tempo Scaffold] Aborted.\x1b[0m File(s) already exist: ${existingFiles}`); + console.error(`We refused to overwrite your existing file(s). If you want to scaffold new ones, please delete the existing file(s) first.`); + process.exit(1); + } safelyCopy('tempo.config.sample.ts', 'tempo.config.ts', 'Created tempo.config.ts'); safelyCopy('index.sample.html', 'index.html', 'Created index.html boilerplate'); } else { diff --git a/packages/tempo/doc/tempo.config.md b/packages/tempo/doc/tempo.config.md index d199b880..caf74a6f 100644 --- a/packages/tempo/doc/tempo.config.md +++ b/packages/tempo/doc/tempo.config.md @@ -21,7 +21,7 @@ Rather than scattering `Tempo.init()` or `Tempo.extend()` calls throughout your This mirrors modern ecosystem standards (like `vite.config.ts` or `tailwind.config.js`) and ensures that plugins, timezones, and custom aliases are consistently applied before any domain logic executes. ::: info -**Target Environment**: This automatic configuration discovery pattern relies on Node.js file system capabilities and is designed for Server, Fullstack, or Bundled environments (like Vite or Webpack). If you are using Tempo via a `