UNPKG

cxa-track

Version:

Convenient CLI to quickly update CxA tracked links

248 lines (214 loc) 6.56 kB
import process from 'process'; import path from 'path'; import { promises as fs } from 'fs'; import { fileURLToPath } from 'url'; import minimist from 'minimist'; import chalk from 'chalk'; import Conf from 'conf'; import clipboardy from 'clipboardy'; import updateNotifier from 'update-notifier'; import { isUrl, isLocale, parseQueryParams } from './util.js'; import { safeRun } from './run.js'; import { checkTrackedDomain, mergeTrackingCode, updateTrackingCodeInText } from './tracking.js'; import { updateTrackedUrlAndCopy } from './clipboard.js'; import { mergeFrontMatterTrackingCode } from './frontmatter.js'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const clipboardRefreshIntervalMs = 300; const appName = path.basename(process.argv[1]); const help = `${chalk.bold('Usage:')} ${appName} [options] [<files|URL>] You can omit the URL argument if you copy one in the clipboard. ${chalk.bold('Options:')} -s, --set <tracking-code> Set default tracking code -t, --track <tracking-code> Use specified tracking code -w, --watch Watch clipboard for URLs -k, --keep-locale Keep locale code in URLs -l, --locale <locale> Force locale code in URLs -e, --extra Extra query params to update -h, --help Show this help Tracking code format (fallback to default for missing values): ID area-ID area-ID-alias Locale format: <language>-<country>, example: en-us Extra format: <param1>=<value1>&<param2>=<value2>, example: foo=bar&baz=qux `; export async function cli(args) { const pkg = JSON.parse( await fs.readFile(path.join(__dirname, '../package.json'), 'utf8') ); updateNotifier({ pkg }).notify(); const options = minimist(args, { boolean: ['watch', 'version', 'help', 'keep-locale'], string: ['set', 'track', 'locale', 'extra'], alias: { w: 'watch', s: 'set', t: 'track', k: 'keep-locale', l: 'locale', e: 'extra', v: 'version', h: 'help' } }); const config = new Conf({ projectName: pkg.name, defaults: { trackingCode: null } }); if (options.version) { return console.log(pkg.version); } if (options.help) { return console.log(help); } safeRun(() => { if (options.set !== undefined) { const trackingCode = setDefaultTrackingCode(config, options.set); return console.log( `Default tracking code set to ${chalk.green(trackingCode)}` ); } const locale = options.locale || options['keep-locale']; const updateOptions = { partialTrackingCode: options.track, extraParams: options.extra ? parseQueryParams(options.extra) : undefined, locale }; if (locale && typeof locale === 'string' && !isLocale(locale)) { throw new Error('Invalid locale format'); } if (options.watch) { return watchClipboard(config, updateOptions); } updateTrackingCode(config, options._, updateOptions); }); } export function setDefaultTrackingCode(config, partialTrackingCode) { const trackingCode = mergeTrackingCode( config.get('trackingCode'), partialTrackingCode ); config.set('trackingCode', trackingCode); return trackingCode; } export function updateUrlInline(url, trackingCode, locale, extraParams) { return safeRun(() => { checkTrackedDomain(url); const newUrl = updateTrackedUrlAndCopy( url, trackingCode, locale, extraParams ); console.log(`${chalk.green('URL copied to clipboard!')}\n${newUrl}`); return newUrl; }); } export async function updateTrackingCodeInFiles( files, trackingCode, locale, extraParams ) { let updatedCount = 0; await Promise.all( files.map(async (file) => { try { const text = await fs.readFile(file, 'utf8'); const textTrackingCode = mergeFrontMatterTrackingCode( trackingCode, text ); const newText = updateTrackingCodeInText( text, textTrackingCode, locale, extraParams ); if (newText !== text) { await fs.writeFile(file, newText); ++updatedCount; console.log( `Updated ${file} with tracking code ${chalk.green( textTrackingCode )}` ); } } catch (error) { console.error(chalk.yellow(`yellow Error: ${error.message}`)); process.exitCode = -1; } }) ); console.log(`${updatedCount} file(s) updated`); } export function updateTrackingCode(config, filesOrUrl, options) { const { partialTrackingCode, locale, extraParams } = options; const trackingCode = mergeTrackingCode( config.get('trackingCode'), partialTrackingCode ); // Fetch URL from clipboard if (filesOrUrl.length === 0) { const clipboardText = clipboardy.readSync(); if (!isUrl(clipboardText)) { console.log(`${chalk.yellow('No URL found in clipboard')}\n\n${help}`); } else if ( !updateUrlInline(clipboardText, trackingCode, locale, extraParams) ) { console.log(`\n${help}`); } return; } // Update single URL if (filesOrUrl.length === 1 && isUrl(filesOrUrl)) { return updateUrlInline(filesOrUrl[0], trackingCode, locale, extraParams); } // Update in files return updateTrackingCodeInFiles( filesOrUrl, trackingCode, locale, extraParams ); } export function watchClipboard(config, options) { const { partialTrackingCode, locale, extraParams } = options; const trackingCode = mergeTrackingCode( config.get('trackingCode'), partialTrackingCode ); let previous = ''; setInterval(() => { let clipboard = clipboardy.readSync(); if (clipboard !== previous) { safeRun(() => { const clipboardTrackingCode = mergeFrontMatterTrackingCode( trackingCode, clipboard ); const newClipboard = updateTrackingCodeInText( clipboard, clipboardTrackingCode, locale, extraParams ); if (newClipboard !== clipboard) { clipboard = newClipboard; clipboardy.writeSync(clipboard); console.log(`Updated with code ${clipboardTrackingCode}`); } previous = clipboard; }, false); } }, clipboardRefreshIntervalMs); console.log( `Using tracking code ${chalk.green(trackingCode)}\n` + `Watching clipboard for URLs to update...\n` + `Press CRTL+C to exit` ); }