All files / src freeze-spec-data.js

0% Statements 0/52
0% Branches 0/18
0% Functions 0/7
0% Lines 0/51

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134                                                                                                                                                                                                                                                                           
/**
 * @file freeze-spec-data.js
 * @description Reads the output path from specs.json, finds the highest versioned directory
 * in the destination path, and copies index.html to a new directory with an incremented version.
 * The user is prompted for a human-readable label for the snapshot; the label is persisted in
 * versions/labels.json so that create-versions-index.js can use it as link text.
 */
 
const fs = require('fs-extra');
const path = require('node:path');
const readline = require('node:readline');
const Logger = require('./utils/logger');
const { versions } = require('./utils/regex-patterns');
 
const config = fs.readJsonSync('specs.json');
const outputPath = config.specs[0].output_path;
 
const sourceFile = path.join(outputPath, 'index.html');
 
// snapshots/ lives at the repo root and is committed to the main branch.
// This is the source of truth — it survives gh-pages resets and fresh CI checkouts.
// docs/versions/ is the derived, deployed copy and is always rebuilt from here.
const destDir = 'snapshots';
 
// A snapshot can only be created when the specification has been built at least once.
// If index.html is missing, the user must run `npm run menu 1` (or `npm run menu 4`) first.
if (!fs.existsSync(sourceFile)) {
    Logger.error(
        `Cannot create a snapshot: "${sourceFile}" does not exist.\n` +
        `Please build the specification first (e.g. npm run menu 1 or npm run menu 9) and try again.`
    );
    process.exit(0);
}
 
if (!fs.existsSync(destDir)) {
    fs.mkdirSync(destDir, { recursive: true });
}
 
const dirs = fs.readdirSync(destDir).filter(file => fs.statSync(path.join(destDir, file)).isDirectory());
let highestVersion = 0;
const versionPattern = versions.pattern;
 
dirs.forEach(dir => {
    const match = dir.match(versionPattern);
    if (match) {
        const version = parseInt(match[1], 10);
        if (version > highestVersion) {
            highestVersion = version;
        }
    }
});
 
const newVersion = highestVersion + 1;
const newVersionDir = path.join(destDir, `v${newVersion}`);
const defaultLabel = `v${newVersion}`;
 
/**
 * Prompts the user for a single line of input, showing a default value.
 * Pressing Enter without typing accepts the default.
 * @param {string} question - The question text shown to the user.
 * @param {string} defaultValue - The value used when the user presses Enter without typing.
 * @returns {Promise<string>}
 */
function prompt(question, defaultValue) {
    const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
    return new Promise(resolve => {
        rl.question(`${question} [${defaultValue}]: `, answer => {
            rl.close();
            resolve(answer.trim() || defaultValue);
        });
    });
}
 
/**
 * Persists the label for the given directory name in versions/labels.json.
 * Existing entries are preserved; only the new key is added or updated.
 * @param {string} labelsFile - Absolute path to labels.json.
 * @param {string} dirName - The version directory name (e.g. "v3").
 * @param {string} label - The human-readable label to store.
 */
function saveLabel(labelsFile, dirName, label) {
    const existing = fs.existsSync(labelsFile) ? fs.readJsonSync(labelsFile) : {};
    existing[dirName] = label;
    fs.writeJsonSync(labelsFile, existing, { spaces: 2 });
}
 
/**
 * Main flow: resolve the label, create the version directory, copy the snapshot,
 * save the label, and regenerate the versions index.
 *
 * Label resolution order:
 *  1. FREEZE_LABEL environment variable — set by the menu.yml GitHub Actions workflow
 *     so that GitHubUi (or any non-interactive caller) can supply a custom label.
 *  2. Interactive readline prompt — only used when stdin is a real TTY (local CLI).
 *  3. Default label (e.g. "v3") — fallback for non-interactive environments without
 *     an explicit FREEZE_LABEL (e.g. running freeze directly inside a CI script).
 */
async function run() {
    let label;
 
    if (process.env.FREEZE_LABEL) {
        // Non-interactive path: label supplied by caller via environment variable
        label = process.env.FREEZE_LABEL;
    } else if (process.stdin.isTTY) {
        // Interactive path: prompt the user, defaulting to the auto-generated name
        label = await prompt('Enter a label for this snapshot', defaultLabel);
    } else {
        // Non-interactive path without an explicit label: use the auto-generated default
        label = defaultLabel;
    }
 
    if (!fs.existsSync(newVersionDir)) {
        fs.mkdirSync(newVersionDir, { recursive: true });
    }
 
    const destFile = path.join(newVersionDir, 'index.html');
    fs.copyFileSync(sourceFile, destFile);
 
    const labelsFile = path.join(destDir, 'labels.json');
    saveLabel(labelsFile, `v${newVersion}`, label);
 
    Logger.success(`Created a freezed specification version in ${destFile}`);
 
    // Sync snapshots/ → docs/versions/ so the local preview reflects the
    // new snapshot immediately, then regenerate the versions index page.
    const syncSnapshots = require('./pipeline/configuration/sync-snapshots.js');
    syncSnapshots(outputPath);
 
    const createVersionsIndex = require('./pipeline/configuration/create-versions-index.js');
    createVersionsIndex(outputPath);
}
 
run();