Skip to content

Commit 91d3045

Browse files
committed
linting-improvement
1 parent 3881901 commit 91d3045

11 files changed

Lines changed: 62 additions & 29 deletions

File tree

bin/cli.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ program
141141
.option('-f, --force', 'Regenerate all narration (ignore cache)')
142142
.option('-s, --slide <id>', 'Generate narration for a specific slide only')
143143
.option('--dry-run', 'Preview what would be generated')
144+
.option('--rebuild-cache', 'Rebuild .narration-cache.json from existing audio (no TTS calls)')
144145
.action(async (options) => {
145146
const { narration } = await import('../lib/narration.js');
146147
await narration(options);

framework/scripts/generate-narration.js

Lines changed: 45 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ import {
4040
loadNarrationCache,
4141
narrationCacheKey,
4242
VOICE_SETTING_KEYS as _SHARED_VOICE_KEYS
43-
} from '../../lib/narration-parser.js';
43+
} from './narration-parser.js';
4444

4545
const __filename = fileURLToPath(import.meta.url);
4646
const __dirname = path.dirname(__filename);
@@ -65,6 +65,10 @@ const VERBOSE = args.includes('--verbose') || args.includes('-v');
6565
const SLIDE_FILTER = args.includes('--slide') ? args[args.indexOf('--slide') + 1] : null;
6666
const SHOW_PROVIDERS = args.includes('--providers') || args.includes('--provider');
6767
const SHOW_HELP = args.includes('--help') || args.includes('-h');
68+
// Rebuild .narration-cache.json from current slide text without calling any
69+
// TTS provider. Useful when audio is committed but the cache is missing
70+
// (e.g. after a fresh clone or migrating a project).
71+
const REBUILD_CACHE = args.includes('--rebuild-cache');
6872

6973
/**
7074
* Load environment variables from .env file
@@ -224,7 +228,7 @@ function parseSlideNarration(filePath, baseName) {
224228
}
225229

226230
// Removed: original inline parseSlideNarration / parseNarrationObject implementations
227-
// now live in lib/narration-parser.js so the build linter can reuse them.
231+
// now live in framework/scripts/narration-parser.js so the build linter can reuse them.
228232

229233
/**
230234
* Main execution
@@ -244,11 +248,12 @@ async function main() {
244248
245249
Usage:
246250
npm run narration Generate all changed narration
247-
npm run narration -- --force Regenerate all (ignore cache)
248-
npm run narration -- --dry-run Preview without generating
249-
npm run narration -- --slide <id> Generate specific slide only
250-
npm run narration -- --providers List available TTS providers
251-
npm run narration -- --verbose Show detailed output
251+
npm run narration -- --force Regenerate all (ignore cache)
252+
npm run narration -- --dry-run Preview without generating
253+
npm run narration -- --slide <id> Generate specific slide only
254+
npm run narration -- --rebuild-cache Rebuild cache from existing audio (no TTS calls)
255+
npm run narration -- --providers List available TTS providers
256+
npm run narration -- --verbose Show detailed output
252257
253258
Provider Selection:
254259
Set TTS_PROVIDER env var to: elevenlabs, openai, or azure
@@ -266,17 +271,22 @@ Examples:
266271
// Load environment variables
267272
loadEnv();
268273

269-
// Initialize TTS provider
274+
// Initialize TTS provider — skipped in --rebuild-cache mode since we
275+
// never call the provider when only refreshing the cache file.
270276
let provider;
271-
try {
272-
provider = getActiveProvider();
273-
provider.validateConfig();
274-
const defaultVoice = provider.getDefaultVoiceId();
275-
console.log(`🔊 Using TTS provider: ${provider.getName()} (voice: ${defaultVoice})\n`);
276-
} catch (error) {
277-
console.error(`❌ Provider error: ${error.message}\n`);
278-
printProviderHelp();
279-
process.exit(1);
277+
if (REBUILD_CACHE) {
278+
console.log('🔁 Rebuild-cache mode: hashing existing audio, no TTS calls.\n');
279+
} else {
280+
try {
281+
provider = getActiveProvider();
282+
provider.validateConfig();
283+
const defaultVoice = provider.getDefaultVoiceId();
284+
console.log(`🔊 Using TTS provider: ${provider.getName()} (voice: ${defaultVoice})\n`);
285+
} catch (error) {
286+
console.error(`❌ Provider error: ${error.message}\n`);
287+
printProviderHelp();
288+
process.exit(1);
289+
}
280290
}
281291

282292
// Load course config
@@ -375,7 +385,24 @@ Examples:
375385
const cacheKey = key === 'slide' ? source.src : `${source.src}#${key}`;
376386
const cachedHash = cache[cacheKey];
377387
const outputExists = fs.existsSync(outputPath);
378-
388+
389+
// --rebuild-cache: hash text+settings for existing audio and move on.
390+
// Never call TTS, never write or delete audio files.
391+
if (REBUILD_CACHE) {
392+
if (outputExists) {
393+
newCache[cacheKey] = contentHash;
394+
skipped++;
395+
if (VERBOSE) {
396+
const label = key === 'slide' ? source.slideId : `${source.slideId}#${key}`;
397+
console.log(` ✅ ${label}: cached (${path.relative(ROOT_DIR, outputPath)})`);
398+
}
399+
} else if (VERBOSE) {
400+
const label = key === 'slide' ? source.slideId : `${source.slideId}#${key}`;
401+
console.log(` ⏭️ ${label}: no audio yet, skipping`);
402+
}
403+
continue;
404+
}
405+
379406
if (cachedHash === contentHash && outputExists && !FORCE_REGENERATE) {
380407
if (VERBOSE) {
381408
const label = key === 'slide' ? source.slideId : `${source.slideId}#${key}`;

lib/authoring-api.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1117,13 +1117,14 @@ export async function buildCourse(options = {}) {
11171117
* picks up the same .env, course-config, and slide files a manual run would.
11181118
*
11191119
* @param {object} options
1120-
* @param {boolean} [options.dryRun=false] - Show what would be generated without calling TTS
1121-
* @param {boolean} [options.force=false] - Regenerate everything, ignoring the cache
1122-
* @param {string} [options.slide] - Limit to a single slide ID
1120+
* @param {boolean} [options.dryRun=false] - Show what would be generated without calling TTS
1121+
* @param {boolean} [options.force=false] - Regenerate everything, ignoring the cache
1122+
* @param {string} [options.slide] - Limit to a single slide ID
1123+
* @param {boolean} [options.rebuildCache=false] - Rebuild .narration-cache.json from existing audio without calling TTS
11231124
* @returns {Promise<{success, dryRun, summary, generated, skipped, errors, warnings, output, duration}>}
11241125
*/
11251126
export async function generateNarration(options = {}) {
1126-
const { dryRun = false, force = false, slide } = options;
1127+
const { dryRun = false, force = false, slide, rebuildCache = false } = options;
11271128
const startTime = Date.now();
11281129

11291130
let courseRoot;
@@ -1157,6 +1158,7 @@ export async function generateNarration(options = {}) {
11571158
if (force) args.push('--force');
11581159
if (dryRun) args.push('--dry-run');
11591160
if (slide) args.push('--slide', slide);
1161+
if (rebuildCache) args.push('--rebuild-cache');
11601162

11611163
return new Promise((resolve) => {
11621164
const child = spawn('node', args, {

lib/build-linter.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import {
1919
loadNarrationCache,
2020
narrationCacheKey,
2121
classifyNarrationFreshness
22-
} from './narration-parser.js';
22+
} from '../framework/scripts/narration-parser.js';
2323
import { fileURLToPath, pathToFileURL } from 'url';
2424
import {
2525
flattenStructure,

lib/mcp-prompts.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,10 @@ NOT idempotent: real runs call paid TTS APIs and write files. Prefer dryRun firs
448448
slide: {
449449
type: 'string',
450450
description: 'Optional slide ID filter (e.g. "intro"). Only narration for this slide is processed.'
451+
},
452+
rebuildCache: {
453+
type: 'boolean',
454+
description: 'Rebuild .narration-cache.json from existing audio files without calling TTS (default: false). Use after a fresh clone or when the cache is missing but audio is committed. Free; no API calls.'
451455
}
452456
},
453457
required: []

lib/mcp-server.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,8 @@ export async function startMcpServer(options = {}) {
287287
result = await generateNarration({
288288
dryRun: args?.dryRun === true,
289289
force: args?.force === true,
290-
slide: args?.slide
290+
slide: args?.slide,
291+
rebuildCache: args?.rebuildCache === true
291292
});
292293
break;
293294

lib/narration.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export async function narration(options = {}) {
3232
if (options.force) args.push('--force');
3333
if (options.slide) args.push('--slide', options.slide);
3434
if (options.dryRun) args.push('--dry-run');
35+
if (options.rebuildCache) args.push('--rebuild-cache');
3536

3637
return new Promise((resolve, reject) => {
3738
const child = spawn('node', args, {

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "coursecode",
3-
"version": "0.1.38",
3+
"version": "0.1.39",
44
"description": "Multi-format course authoring framework with CLI tools (SCORM 2004, SCORM 1.2, cmi5, LTI 1.3)",
55
"type": "module",
66
"bin": {

template/.gitignore

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,6 @@ Thumbs.db
2727
*.swp
2828
*.swo
2929

30-
# Narration cache
31-
.narration-cache.json
32-
3330
# CourseCode Cloud (project binding)
3431
.coursecode/
3532

0 commit comments

Comments
 (0)