@applitools/eyes-storybook
Version:
280 lines (247 loc) • 8.56 kB
JavaScript
#!/usr/bin/env node
const fs = require('fs').promises;
const {input, confirm} = require('@inquirer/prompts');
const yargs = require('yargs/yargs');
const chalk = require('chalk');
const {pathToFileURL} = require('url');
const utils = require('@applitools/utils');
const {hideBin} = require('yargs/helpers');
yargs(hideBin(process.argv))
.command({
command: '*',
builder: yargs => {
const args = yargs.options({
apiKey: {
describe:
"Your Applitools Team's API key (here's how to obtain it: https://applitools.com/docs/topics/overview/obtain-api-key.html)",
type: 'string',
},
serverUrl: {describe: 'Your Eyes dedicated cloud server URL', type: 'string'},
});
return args;
},
handler: handleError(init),
})
.help()
.parse();
function handleError(asyncFunc) {
return async (...args) =>
asyncFunc(...args).catch(err => {
if (err.name === 'ExitPromptError') {
log('See you next time!');
} else {
error(`Error while setting up Eyes: ${err.message}`);
}
process.exit(1);
});
}
async function init(args) {
log(chalk.yellow('Setting up Eyes-Storybook for your project:'));
let {config, fileName, isCorrupted, isESM} = await loadExistingConfig();
if (isCorrupted) {
log(chalk.yellow(`\nWarning: Your configuration file '${fileName}' appears to be corrupted.`));
const shouldOverwrite = await confirm({
message:
'Would you like to create a new one? This will replace the corrupted file (Default Yes):',
default: true,
});
if (!shouldOverwrite) {
log('Aborting setup. Please fix your configuration file manually or remove it.');
process.exit(0);
}
// By letting this block finish, the script proceeds as if no config was found
}
// If no config exists or is corrupted, run the interactive setup
let apiKey,
serverUrl,
warnForApiKey = false;
let shouldUpdate = !config;
if (shouldUpdate) {
let shouldUseEnvApiKey = false;
if (utils.general.getEnvValue('API_KEY')) {
shouldUseEnvApiKey = await confirm({
message:
'Detected APPLITOOLS_API_KEY environment variable. Would you like it to be used? (default Yes):',
default: true,
});
if (shouldUseEnvApiKey) {
apiKey = 'process.env.APPLITOOLS_API_KEY';
}
warnForApiKey = !shouldUseEnvApiKey; // Warn if the user has an env var but doesn't use it
}
let shouldUseEnvServerUrl = false;
for (const url of ['EYES_SERVER_URL', 'SERVER_URL']) {
if (utils.general.getEnvValue(url)) {
shouldUseEnvServerUrl = await confirm({
message: `Detected APPLITOOLS_${url} environment variable. Would you like it to be used? (default Yes):`,
default: true,
});
if (shouldUseEnvServerUrl) {
serverUrl = `process.env.APPLITOOLS_${url}`;
break; // Use the first one found
}
}
}
if (!shouldUseEnvApiKey) {
apiKey =
args.apiKey ??
(await input({
message:
'Enter your API key (https://applitools.com/docs/topics/overview/obtain-api-key.html):',
}));
warnForApiKey = apiKey !== ''; // Warn if the user provided any API key directly
}
if (!shouldUseEnvServerUrl) {
serverUrl =
args.serverUrl ??
(await input({
message: 'Enter your Eyes server URL (default is https://eyes.applitools.com):',
}));
}
config = {
...(config || {}),
appName: 'My Storybook App',
batchName: 'My Storybook',
showLogs: false,
};
const fileContent = generateConfigContent(isESM);
await fs.writeFile(fileName, fileContent, 'utf8');
}
printPostlude();
// --- Helper Functions ---
async function loadExistingConfig() {
const configFiles = [
'applitools.config.js',
'applitools.config.mjs',
'applitools.config.cjs',
'eyes.config.js',
];
for (const file of configFiles) {
try {
const fileUrl = pathToFileURL(file).href;
const module = await import(`${fileUrl}?t=${Date.now()}`); // Append timestamp to bypass cache
const isESM = file.endsWith('.mjs');
return {config: module.default || module, fileName: file, isCorrupted: false, isESM};
} catch (err) {
if (err.code === 'ERR_MODULE_NOT_FOUND') {
continue;
}
return {config: null, fileName: file, isCorrupted: true};
}
}
// No file was found in any of the possible locations.
// Check if type: module is set in package.json in cwd
let fileName = 'applitools.config.js';
let isESM = false;
try {
const pkgRaw = await fs.readFile('./package.json', 'utf8');
const pkg = JSON.parse(pkgRaw);
if (pkg.type === 'module') {
isESM = true;
fileName = 'applitools.config.mjs';
}
} catch (e) {
// ignore if package.json not found or invalid
}
return {config: null, fileName, isCorrupted: false, isESM};
}
function generateConfigContent(isESM) {
const envConfigMap = {
batchName: 'APPLITOOLS_BATCH_NAME',
batchId: 'APPLITOOLS_BATCH_ID',
showLogs: 'APPLITOOLS_SHOW_LOGS',
batchSequenceName: 'APPLITOOLS_BATCH_SEQUENCE_NAME',
proxy: 'APPLITOOLS_PROXY',
notifyOnCompletion: 'APPLITOOLS_NOTIFY_ON_COMPLETION',
concurrentTabs: 'APPLITOOLS_CONCURRENT_TABS',
appName: 'APPLITOOLS_APP_NAME',
};
const configLines = [];
// apiKey
if (apiKey === 'process.env.APPLITOOLS_API_KEY') {
configLines.push('apiKey: process.env.APPLITOOLS_API_KEY,');
} else if (apiKey) {
configLines.push(
`apiKey: '${apiKey}', // Warning: 'apiKey' is not obscured, consider setting it via environment variable APPLITOOLS_API_KEY`,
);
} else {
configLines.push("// apiKey: '',");
}
// serverUrl
if (serverUrl && serverUrl.startsWith('process.env.')) {
configLines.push(`serverUrl: ${serverUrl},`);
} else if (serverUrl) {
configLines.push(`serverUrl: '${serverUrl}',`);
}
// other config
const otherKeys = [
'appName',
'batchName',
'batchId',
'showLogs',
'batchSequenceName',
'proxy',
'notifyOnCompletion',
'concurrentTabs',
'navigationWaitUntil',
'waitBeforeCapture',
];
for (const key of otherKeys) {
const envVar = envConfigMap[key];
if (envVar && utils.general.getEnvValue(envVar.replace('APPLITOOLS_', ''))) {
configLines.push(`${key}: process.env.${envVar},`);
} else if (Object.prototype.hasOwnProperty.call(config, key)) {
const value = config[key];
if (value !== undefined) {
configLines.push(typeof value === 'string' ? `${key}: '${value}',` : `${key}: ${value},`);
}
}
}
configLines.push(`// browsersInfo: [
// {width: 1024, height: 768, name: 'firefox'},
// {width: 1024, height: 768, name: 'chrome'},
// {iosDeviceInfo: {deviceName: 'iPhone 16'}},
// {chromeEmulationInfo: {deviceName: 'Galaxy S20'}},
// ]`);
const configObject = `{
${configLines.join('\n ')}
}`;
if (isESM) {
return `/**\n * @type {import('@applitools/eyes-storybook').ApplitoolsConfig}\n */\nconst config = ${configObject};\nexport default config;\n`;
} else {
return `/**\n * @type {import('@applitools/eyes-storybook').ApplitoolsConfig}\n **/\nmodule.exports = ${configObject};\n`;
}
}
function printPostlude() {
log('');
if (shouldUpdate) {
log(
chalk.green('✔ Success!'),
chalk.bold('\n'),
chalk.bold('Eyes Storybook is now set up!'),
chalk.bold('Please visit our Storybook documentation to learn more.'),
chalk.bold('https://applitools.com/tutorials/sdks/storybook/quickstart'),
);
} else {
log(
chalk.green('✔ Good news! Eyes Storybook is already properly configured!'),
chalk.bold('\n'),
chalk.bold('Please visit our Storybook documentation to learn more.'),
chalk.bold('https://applitools.com/tutorials/sdks/storybook/quickstart'),
);
}
if (warnForApiKey) {
log(
chalk.yellow(
'\nWarning: Consider setting your API key via the APPLITOOLS_API_KEY environment variable.',
),
);
}
}
}
function log(...args) {
console.log(...args);
}
function error(...args) {
console.error(...args);
}