thebespokepixel/term-ng

View on GitHub
termng.js

Summary

Maintainability
A
0 mins
Test Coverage
B
88%
#! /usr/bin/env node
import { dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import { simple } from 'trucolor';
import { truwrap } from 'truwrap';
import { TemplateTag, replaceSubstitutionTransformer, stripIndent } from 'common-tags';
import { box } from '@thebespokepixel/string';
import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';
import meta from '@thebespokepixel/meta';
import updateNotifier from 'update-notifier';

const name = "term-ng";
const version = "3.0.4";
const description = "Terminal/$TERM feature snooping and whitelisting";
const main = "index.js";
const types = "index.d.ts";
const type = "module";
const bin = {
    termng: "./termng.js"
};
const files = [
    "index.js",
    "index.d.ts"
];
const scripts = {
    build: "rollup -c && chmod 755 termng.js && npm run readme",
    test: "xo && c8 --reporter=text ava",
    "doc-serve": "documentation serve --watch --theme node_modules/documentation-theme-bespoke --github --config src/docs/documentation.yml --project-name $npm_package_name  --project-version $npm_package_version src/index.js",
    "doc-build": "documentation build --format html --output docs --theme node_modules/documentation-theme-bespoke --github --config src/docs/documentation.yml --project-name $npm_package_name  --project-version $npm_package_version src/index.js",
    readme: "compile-readme -u src/docs/example.md src/docs/readme.md > readme.md",
    coverage: "c8 --reporter=lcov ava; open coverage/lcov-report/index.html",
    prepublishOnly: "npx -p typescript tsc index.js --declaration --allowJs --emitDeclarationOnly",
    colors: "scripts/colortest.pl"
};
const repository = {
    type: "git",
    url: "https://github.com/thebespokepixel/term-ng.git"
};
const engines = {
    node: ">=14.0"
};
const keywords = [
    "24bit",
    "color",
    "ansi",
    "truecolor",
    "trucolor",
    "sgr",
    "cli",
    "tty",
    "iterm",
    "xterm"
];
const author = "Mark Griffiths <mark@thebespokepixel.com> (http://thebespokepixel.com/)";
const copyright = {
    year: "2021",
    owner: "The Bespoke Pixel"
};
const license = "MIT";
const bugs = {
    url: "https://github.com/thebespokepixel/term-ng/issues"
};
const homepage = "https://github.com/thebespokepixel/term-ng#readme";
const devDependencies = {
    "@rollup/plugin-commonjs": "^21.0.1",
    "@rollup/plugin-json": "^4.1.0",
    "@rollup/plugin-node-resolve": "^13.0.6",
    "@types/estree": "^0.0.50",
    ava: "^4.0.0-rc.1",
    c8: "^7.10.0",
    "documentation-theme-bespoke": "^2.0.14",
    "read-pkg": "^7.0.0",
    rollup: "^2.59.0",
    "rollup-plugin-cleanup": "^3.2.1",
    xo: "^0.46.4"
};
const dependencies = {
    "@thebespokepixel/meta": "^3.0.5",
    "@thebespokepixel/string": "^2.0.1",
    trucolor: "^4.0.4",
    truwrap: "^4.0.4",
    "update-notifier": "^5.1.0",
    yargs: "^17.2.1"
};
const xo = {
    semicolon: false,
    ignores: [
        "index.js",
        "termng.js",
        "index.d.ts",
        "docs/**",
        "coverage/**"
    ]
};
const badges = {
    github: "thebespokepixel",
    npm: "thebespokepixel",
    "libraries-io": "TheBespokePixel",
    codeclimate: "7ba2088efca500b3b4ff",
    name: "term-ng",
    providers: {
        aux1: {
            title: "github",
            text: "source",
            color: "4E73B6",
            link: "https://github.com/thebespokepixel/term-ng"
        }
    },
    readme: {
        "Publishing Status": [
            [
                "npm",
                "libraries-io-npm"
            ],
            [
                "travis-com",
                "rollup"
            ]
        ],
        "Development Status": [
            [
                "travis-com-dev",
                "libraries-io-github"
            ],
            [
                "snyk",
                "code-climate",
                "code-climate-coverage"
            ]
        ],
        "Documentation/Help": [
            "inch",
            "twitter"
        ]
    },
    docs: [
        [
            "aux1",
            "travis"
        ],
        [
            "code-climate",
            "code-climate-coverage"
        ],
        [
            "snyk",
            "libraries-io-npm"
        ]
    ]
};
var pkg = {
    name: name,
    version: version,
    description: description,
    main: main,
    types: types,
    type: type,
    bin: bin,
    files: files,
    scripts: scripts,
    repository: repository,
    engines: engines,
    keywords: keywords,
    author: author,
    copyright: copyright,
    license: license,
    bugs: bugs,
    homepage: homepage,
    devDependencies: devDependencies,
    dependencies: dependencies,
    xo: xo,
    badges: badges
};

const itermSession = process.env.ITERM_SESSION_ID && process.env.ITERM_SESSION_ID.indexOf(':') > 0;
const colorTermTruecolor = process.env.COLORTERM && process.env.COLORTERM.includes('truecolor');
const termColor16m = process.env.TERM_COLOR && process.env.TERM_COLOR.includes('16m');
const has16m = itermSession || colorTermTruecolor || termColor16m;
if (has16m && !(/-color/.test(process.argv.join('')))) {
    process.argv.splice(2, 0, '--color=16m');
}
const hasFlag = flag => {
    const terminatorPos = process.argv.indexOf('--');
    const prefix = /^-{1,2}/.test(flag) ? '' : '--';
    const pos = process.argv.indexOf(prefix + flag);
    return pos !== -1 && (terminatorPos === -1 ? true : pos < terminatorPos)
};
const support = level => {
    if (level === 0) {
        return false
    }
    return {
        level,
        hasBasic: true,
        has256: level >= 2,
        has16m: level >= 3
    }
};
let supportLevel = (() => {
    if (hasFlag('no-color') ||
        hasFlag('no-colors') ||
        hasFlag('color=false')) {
        return 0
    }
    if (hasFlag('color=16m') ||
        hasFlag('color=full') ||
        hasFlag('color=truecolor')) {
        return 3
    }
    if (hasFlag('color=256')) {
        return 2
    }
    if (hasFlag('color') ||
        hasFlag('colors') ||
        hasFlag('color=true') ||
        hasFlag('color=always')) {
        return 1
    }
    if (process.stdout && !process.stdout.isTTY) {
        return 0
    }
    if (process.platform === 'win32') {
        return 1
    }
    if ('CI' in process.env || 'TEAMCITY_VERSION' in process.env) {
        return 0
    }
    if ('COLORTERM' in process.env) {
        return 1
    }
    if (process.env.TERM === 'dumb') {
        return 0
    }
    if (/^xterm-256(?:color)?/.test(process.env.TERM)) {
        return 2
    }
    if (/^screen|^xterm|^vt100|color|ansi|cygwin|linux/i.test(process.env.TERM)) {
        return 1
    }
    return 0
})();
if (supportLevel === 0 && 'FORCE_COLOR' in process.env) {
    supportLevel = 1;
}
const supportsColor = process && support(supportLevel);
const termNG = {
    color: {
        level: supportsColor.level || 0,
        basic: supportsColor.hasBasic || false,
        hasBasic: supportsColor.hasBasic || false,
        has256: supportsColor.level >= 2,
        has16m: supportsColor.level >= 3
    },
    images: (process.env.TERM_IMAGES !== undefined) && supportsColor.level >= 2,
    audio: process.env.TERM_AUDIO !== undefined,
    font: {
        basic: process.env.TERM_FONT !== undefined,
        enhanced: process.env.TERM_FONT === 'full'
    },
    termcap: {
        basic: process.env.TERM_ENHANCED !== undefined && process.env.TERM_ENHANCED === 'disabled',
        enhanced: process.env.TERM_ENHANCED === 'enabled'
    },
    software: process.env.TERM_PROGRAM || process.env.TERMKIT_HOST_APP || process.env.TERM || process.env.GULP
};

const clr = simple({format: 'sgr'});
const metadata = meta(dirname(fileURLToPath(import.meta.url)));
const renderer = truwrap({
    right: 0,
    outStream: process.stderr,
});
const colorReplacer = new TemplateTag(
    replaceSubstitutionTransformer(
        /([a-zA-Z]+?)[:/|](.+)/,
        (match, colorName, content) => `${clr[colorName]}${content}${clr[colorName].out}`,
    ),
);
const title = box(colorReplacer`${'title|term-ng'}${`dim| │ ${metadata.version(3)}`}`, {
    borderColor: 'yellow',
    margin: {
        top: 1,
    },
    padding: {
        bottom: 0,
        top: 0,
        left: 2,
        right: 2,
    },
});
const yargsInstance = yargs(hideBin(process.argv))
    .strictOptions()
    .help(false)
    .version(false)
    .options({
        h: {
            alias: 'help',
            describe: 'Display this help.',
        },
        v: {
            alias: 'version',
            count: true,
            describe: 'Return the current version on stdout. -vv Return name & version.',
        },
        color: {
            describe: 'Force color depth --color=256|16m. Disable with --no-color',
        },
    })
    .command('has-color', 'Is basic color supported?')
    .command('has-256', 'Is 256 color supported?')
    .command('has-16m', 'Is 24 bit color supported?')
    .command('has-images', 'Are images supported? (set $TERM_IMAGES=enabled)')
    .command('has-audio', 'Is audio supported? (set $TERM_AUDIO=enabled)')
    .command('has-box-font', 'Is audio supported? (set $TERM_FONT=box)')
    .command('has-full-font', 'Is audio supported? (set $TERM_FONT=full)')
    .command('is-enhanced', 'Is the current terminal using an enhanced termcap? (set $TERM_ENHANCED=enabled)')
    .command('user-agent', 'Print the current terminal software');
const {argv} = yargsInstance;
const usage = stripIndent(colorReplacer)`
Allow user configured enhanced terminal capabilities to be queried.

The command will exit with status 0 if all the provided queries (except user-agent) are true, otherwise exits with status 1.

If user-agent is used, the command will return the string on stdout and exit status 0.

${clr.title}Usage:${clr.title.out}
${clr.command}termng ${clr.option}[command]${clr.option.out}`;
const epilogue = `${clr.title}${metadata.copyright}. ${clr.grey}Released under the MIT License.${clr.grey.out}`;
if (!(process.env.USER === 'root' && process.env.SUDO_USER !== process.env.USER)) {
    updateNotifier({
        pkg,
    }).notify();
}
if (argv.help) {
    (async () => {
        const usageContent = await yargsInstance.wrap(renderer.getWidth()).getHelp();
        renderer.write(title).break(2);
        renderer.write(usage);
        renderer.break(2);
        renderer.write(usageContent);
        renderer.break(2);
        renderer.write(epilogue);
        renderer.break(2);
        process.exit(0);
    })();
}
if (argv.version) {
    process.stdout.write(metadata.version(argv.version));
    process.exit(0);
}
if (argv._.includes('user-agent')) {
    process.stdout.write(termNG.software);
    process.exit(0);
}
const matrix = {
    'has-color': termNG.color.basic,
    'has-256': termNG.color.has256,
    'has-16m': termNG.color.has16m,
    'has-images': termNG.images,
    'has-audio': termNG.audio,
    'has-box-font': termNG.font.basic || termNG.font.enhanced,
    'has-full-font': termNG.font.enhanced,
    'is-enhanced': termNG.termcap.enhanced,
};
for (const query of argv._) {
    if (!matrix[query]) {
        process.exit(1);
    }
}