Initial commit
All checks were successful
NPM Audit Check / Check NPM audit (push) Successful in -2m13s

This commit is contained in:
Cory Sanin 2025-08-22 01:04:31 -05:00
commit 4a260f112f
12 changed files with 514 additions and 0 deletions

27
.github/workflows/npm-audit.yml vendored Normal file
View File

@ -0,0 +1,27 @@
name: NPM Audit Check
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
schedule:
- cron: '15 16 * * 5'
jobs:
npm_audit:
name: Check NPM audit
runs-on: ubuntu-latest
timeout-minutes: 20
strategy:
fail-fast: true
permissions:
contents: read
steps:
- name: Checkout repository
uses: https://github.com/actions/checkout@v4
- name: NPM Audit
run: npm audit

104
.gitignore vendored Normal file
View File

@ -0,0 +1,104 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
# next.js build output
.next
# nuxt.js build output
.nuxt
# gatsby files
.cache/
public
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# custom .gitignore
config/config.json5
distribution/
.env

12
README.md Normal file
View File

@ -0,0 +1,12 @@
# morning-report
randomly generate a radio show-style weather report according to a flexible config file.
## Usage
```
npm install
npm run build
npm start
```

118
config/config.example.json5 Normal file
View File

@ -0,0 +1,118 @@
{
"programs": [
[
"intro music",
"intro",
"hi's and low's",
"notable weather",
"signoff"
]
],
"segments": {
"intro": [
"intro 1",
"intro 2"
],
"hi's and low's": [
"hilo 1",
"hilo 2"
],
"notable weather": [
"rain 1",
"rain 2",
"storm 1",
"storm 2",
"snow 1",
"hail 1"
],
"signoff": [
"signoff 1",
"signoff 2"
]
},
"sequences": {
"intro music": {
"tracks": [
"audio/intro_music.flac"
]
},
"intro 1": {
"tracks": [
"audio/intro_01.flac"
]
},
"intro 2": {
"tracks": [
"audio/intro_02.flac"
]
},
"hilo 1": {
"tracks": [
"audio/hi_01.flac",
"%cory hi",
"audio/lo_01.flac",
"%cory lo"
]
},
"hilo 2": {
"tracks": [
"audio/hi_02.flac",
"%cory hi",
"audio/lo_02.flac",
"%cory lo"
]
},
"rain 1": {
"condition": "weather == rain",
"tracks": [
"audio/rain1.flac"
]
},
"rain 2": {
"condition": "weather == rain",
"tracks": [
"audio/rain2.flac"
]
},
"storm 1": {
"condition": "weather == storm",
"tracks": [
"audio/storm1.flac"
]
},
"storm 2": {
"condition": "weather == storm",
"tracks": [
"audio/storm2.flac"
]
},
"snow 1": {
"condition": "weather == snow",
"tracks": [
"audio/snow1.flac"
]
},
"hail 1": {
"condition": "weather == hail",
"tracks": [
"audio/hail1.flac"
]
},
"signoff 1": {
"tracks": [
"audio/signoff_01.flac"
]
},
"signoff 2": {
"tracks": [
"audio/signoff_02.flac"
]
}
},
"voices": {
"cory": {
"directory": "audio/voice/cory/",
"extension": "flac"
}
}
}

115
package-lock.json generated Normal file
View File

@ -0,0 +1,115 @@
{
"name": "morning-report",
"version": "0.0.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "morning-report",
"version": "0.0.1",
"license": "MIT",
"dependencies": {
"json5": "2.2.3",
"openweathermap-ts": "1.2.10"
},
"devDependencies": {
"@types/node": "24.3.0",
"typescript": "5.9.2"
}
},
"node_modules/@types/node": {
"version": "24.3.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.3.0.tgz",
"integrity": "sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~7.10.0"
}
},
"node_modules/json5": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
"license": "MIT",
"bin": {
"json5": "lib/cli.js"
},
"engines": {
"node": ">=6"
}
},
"node_modules/node-fetch": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
"license": "MIT",
"dependencies": {
"whatwg-url": "^5.0.0"
},
"engines": {
"node": "4.x || >=6.0.0"
},
"peerDependencies": {
"encoding": "^0.1.0"
},
"peerDependenciesMeta": {
"encoding": {
"optional": true
}
}
},
"node_modules/openweathermap-ts": {
"version": "1.2.10",
"resolved": "https://registry.npmjs.org/openweathermap-ts/-/openweathermap-ts-1.2.10.tgz",
"integrity": "sha512-Zckv2aXN8ENSeAeroces2jJciLWb6aLNXEmvG6pmF+BcIMw2kwRo6++/AKUNoU5suOp47UWA6lllDV0TNm//OA==",
"license": "MIT",
"dependencies": {
"node-fetch": "^2.6.0"
}
},
"node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
"license": "MIT"
},
"node_modules/typescript": {
"version": "5.9.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz",
"integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
},
"node_modules/undici-types": {
"version": "7.10.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz",
"integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==",
"dev": true,
"license": "MIT"
},
"node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
"license": "BSD-2-Clause"
},
"node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"license": "MIT",
"dependencies": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
}
}
}
}

34
package.json Normal file
View File

@ -0,0 +1,34 @@
{
"name": "morning-report",
"version": "0.0.1",
"description": "Procedurally generates a radio weather report",
"keywords": [
"weather",
"radio",
"audio"
],
"repository": {
"type": "git",
"url": "ssh://git@git.sanin.dev:3689/corysanin/morning-report.git"
},
"license": "MIT",
"author": {
"name": "Cory Sanin",
"email": "corysanin@outlook.com",
"url": "https://sanin.dev/"
},
"type": "module",
"main": "distribution/index.js",
"scripts": {
"build": "npx tsc",
"start": "node distribution/index.js"
},
"dependencies": {
"json5": "2.2.3",
"openweathermap-ts": "1.2.10"
},
"devDependencies": {
"typescript": "5.9.2",
"@types/node": "24.3.0"
}
}

23
src/index.ts Normal file
View File

@ -0,0 +1,23 @@
import path from 'path';
import fsp from 'fs/promises';
import json5 from 'json5';
import Sequencer from './sequencer.js';
import type {Programs, Segments, Sequences} from './sequencer.js';
import type { Voices } from './voice.js';
interface Config {
programs: Programs,
segments: Segments,
sequences: Sequences,
voices: Voices
}
console.log('morning-report\nCory Sanin 2025\n');
const config: Config = json5.parse(await fsp.readFile(process.env['CONFIG'] || path.join('config', 'config.json5'), { encoding: 'utf-8' }));
const sequence = Sequencer(config);
console.log(sequence.join('\n'));
export type { Config };

54
src/sequencer.ts Normal file
View File

@ -0,0 +1,54 @@
import type { Config } from './index.js';
type SegmentName = string;
type SequenceName = string;
type Programs = SegmentName[][];
type Segments = { [segment: SegmentName]: SequenceName[] };
type Sequence = {
condition?: string;
tracks: string[];
}
type Sequences = { [sequence: SequenceName]: Sequence };
let config: Config = null;
function selectOne<T>(arr: T[]): T {
return arr[Math.floor(Math.random() * arr.length)];
}
function conditionIsMet(condition: string | undefined = undefined): boolean {
if (typeof condition !== 'string') {
return true;
}
// TODO: parse condition, return bool
return false;
}
function processSequence(sequence: Sequence): string[] {
const tracks = sequence.tracks;
// TODO: process voice macros
return tracks;
}
function processSegment(segment: SegmentName): string[] {
if (!(segment in config.segments)) {
return processSequence(config.sequences[segment]);
}
const potentialSequences: SequenceName[] = config.segments[segment].filter(s => conditionIsMet(config.sequences[s].condition));
if (potentialSequences.length === 0) {
return [];
}
return processSequence(config.sequences[selectOne(potentialSequences)]);
}
function Sequencer(conf: Config): string[] {
config = conf;
const sequence: string[] = [];
const program: SegmentName[] = selectOne(conf.programs);
program.forEach(segment => sequence.push(...processSegment(segment)));
return sequence;
}
export default Sequencer;
export { Sequencer };
export type { SegmentName, SequenceName, Programs, Segments, Sequence, Sequences };

0
src/stitcher.ts Normal file
View File

10
src/voice.ts Normal file
View File

@ -0,0 +1,10 @@
interface Voice {
directory: string;
extension: string;
}
type Voices = { [name: string]: Voice };
export type { Voice, Voices };

0
src/weather.ts Normal file
View File

17
tsconfig.json Normal file
View File

@ -0,0 +1,17 @@
{
"compilerOptions": {
"lib": ["ESNext"],
"module": "NodeNext",
"target": "ESNext",
"esModuleInterop": true,
"skipLibCheck": true,
"moduleResolution": "node16",
"sourceMap": true,
"inlineSources": true,
"rootDir": "./src",
"outDir": "./distribution"
},
"include": ["src/**/*"],
"exclude": ["**/*.spec.ts"]
}