我可以在没有提示的情况下 运行 `stencil push` 命令吗?

Can I run `stencil push` command without prompt?

我正在尝试使用 bigcommerce 模板配置 bitbucket 管道。

问题是 stencil push 命令问了一些问题。我想自动回复这些问题。

这可能吗?

提示的问题如下: * 您想将您的主题应用到您位于 http://xxxxxxx/ 的商店吗? (y/N) * 您想应用哪种变体? - 光 - 大胆的 - 温暖

您需要对现有的 stencil-cli 进行更改才能使其正常工作。

Stencil-cli 使用 Commander 包。我的解决方案是创建一个附加标志,如果您提供变体名称,它会跳过所有提示。这是从 stencil-cli 版本 1.13.1 创建的,因此您可能需要修改示例。

里面 /bin/stencil-push:

#!/usr/bin/env node

require('colors');
const apiHost = 'https://api.bigcommerce.com';
const dotStencilFilePath = './.stencil';
const options = { dotStencilFilePath };
const pkg = require('../package.json');
const Program = require('commander');
const stencilPush = require('../lib/stencil-push');
const versionCheck = require('../lib/version-check');

Program
    .version(pkg.version)
    .option('--host [hostname]', 'specify the api host', apiHost)
    .option('-f, --file [filename]', 'specify the filename of the bundle to upload')
    .option('-a, --activate [variationname]', 'specify the variation of the theme to activate')    
    .parse(process.argv);

if (!versionCheck()) {
    return;
}

stencilPush(Object.assign({}, options, {
    apiHost: Program.host || apiHost,
    bundleZipPath: Program.file,
    activate: Program.activate,
}), (err, result) => {
    if (err) {
        console.log('not ok'.red + ` -- ${err}`);
        console.log('Please try again. If this error persists, please visit https://github.com/bigcommerce/stencil-cli/issues and submit an issue.');
    } else {
        console.log('ok'.green + ` -- ${result}`);
    }
});

里面 /lib/stencil-push.js:

'use strict';
const _ = require('lodash');
const async = require('async');
const Bundle = require('./stencil-bundle');
const fs = require('fs');
const Inquirer = require('inquirer');
const os = require('os');
const ProgressBar = require('progress');
const themeApiClient = require('./theme-api-client');
const themePath = process.cwd();
const themeConfig = require('./theme-config').getInstance(themePath);
const uuid = require('uuid4');
const utils = {};
const Wreck = require('wreck');

const bar = new ProgressBar('Processing [:bar] :percent; ETA: :etas', {
    complete: '=',
    incomplete: ' ',
    total: 100,
});

module.exports = utils;

function validateOptions(options, fields) {
    options = options || {};
    fields = fields || [];

    fields.forEach(field => {
        if (!_.has(options, field)) {
            throw new Error(`${field} is required!`);
        }
    });

    return options;
}

utils.readStencilConfigFile = (options, callback) => {
    options = validateOptions(options, ['dotStencilFilePath']);

    fs.readFile(options.dotStencilFilePath, { encoding: 'utf8' }, (err, data) => {
        if (err) {
            err.name = 'StencilConfigReadError';
            return callback(err);
        }

        callback(null, Object.assign({}, options, {
            config: JSON.parse(data),
        }));
    });
};

utils.getStoreHash = (options, callback) => {
    options = validateOptions(options, ['config.normalStoreUrl']);

    Wreck.get(`${options.config.normalStoreUrl}/admin/oauth/info`, { json: true, rejectUnauthorized: false }, (error, response, payload) => {
        if (error) {
            error.name = 'StoreHashReadError';
            return callback(error);
        }

        if (response.statusCode !== 200 || !payload.store_hash) {
            const err = new Error('Failed to retrieve store hash');
            err.name = 'StoreHashReadError';
            return callback(err);
        }

        callback(null, Object.assign({}, options, { storeHash: payload.store_hash }));
    });
};

utils.getThemes = (options, callback) => {
    const config = options.config;

    themeApiClient.getThemes({
        accessToken: config.accessToken,
        apiHost: options.apiHost,
        clientId: config.clientId,
        storeHash: options.storeHash,
    }, (error, result) => {
        if (error) {
            return callback(error);
        }

        callback(null, Object.assign({}, options, {
            themes: result.themes,
        }));
    });
};

utils.generateBundle = (options, callback) => {
    let bundle;

    if (options.bundleZipPath) {
        return async.nextTick(callback.bind(null, null, options));
    }

    bundle = new Bundle(themePath, themeConfig, themeConfig.getRawConfig(), {
        dest: os.tmpdir(),
        name: uuid(),
    });

    bundle.initBundle((err, bundleZipPath) => {
        if (err) {
            err.name = 'BundleInitError';
            return callback(err);
        }

        callback(null, Object.assign(options, { bundleZipPath: options.bundleZipPath || bundleZipPath }));
    });
};

utils.uploadBundle = (options, callback) => {
    const config = options.config;

    themeApiClient.postTheme({
        accessToken: config.accessToken,
        apiHost: options.apiHost,
        bundleZipPath: options.bundleZipPath,
        clientId: config.clientId,
        storeHash: options.storeHash,
    }, (error, result) => {
        if (error) {
            error.name = 'ThemeUploadError';
            return callback(error);
        }

        callback(null, Object.assign({}, options, {
            jobId: result.jobId,
            themeLimitReached: !!result.themeLimitReached,
        }));
    });
};

utils.notifyUserOfThemeLimitReachedIfNecessary = (options, callback) => {
    if (options.themeLimitReached) {
        console.log('warning'.yellow + ` -- You have reached your upload limit.  In order to proceed, you'll need to delete at least one theme.`);
    }

    return async.nextTick(callback.bind(null, null, options));
};

utils.promptUserToDeleteThemesIfNecessary = (options, callback) => {
    if (!options.themeLimitReached) {
        return async.nextTick(callback.bind(null, null, options));
    }

    const questions = [{
        choices: options.themes.map(theme => ({
            disabled: theme.is_active || !theme.is_private,
            name: theme.name,
            value: theme.uuid,
        })),
        message: 'Which theme(s) would you like to delete?',
        name: 'themeIdsToDelete',
        type: 'checkbox',
        validate: (val) => {
            if (val.length > 0) {
                return true;
            } else {
                return 'You must delete at least one theme';
            }
        },
    }];

    Inquirer.prompt(questions, (answers) => {
        callback(null, Object.assign({}, options, answers));
    });
};

utils.deleteThemesIfNecessary = (options, callback) => {
    const config = options.config;

    if (!options.themeLimitReached) {
        return async.nextTick(callback.bind(null, null, options));
    }

    async.parallel(options.themeIdsToDelete.map(themeId => {
        return cb => {
            themeApiClient.deleteThemeById(Object.assign({
                accessToken: config.accessToken,
                apiHost: options.apiHost,
                clientId: config.clientId,
                storeHash: options.storeHash,
                themeId,
            }, options), cb);
        }
    }), err => {
        if (err) {
            err.name = 'ThemeDeletionError';
            return callback(err);
        }

        callback(null, options);
    })
};

utils.uploadBundleAgainIfNecessary = (options, callback) => {
    if (!options.themeLimitReached) {
        return async.nextTick(callback.bind(null, null, options));
    }

    utils.uploadBundle(options, callback);
};

utils.notifyUserOfThemeUploadCompletion = (options, callback) => {
    console.log('ok'.green + ' -- Theme Upload Finished');
    return async.nextTick(callback.bind(null, null, options));
};

utils.markJobProgressPercentage = percentComplete => {
    bar.update(percentComplete / 100);
};

utils.markJobComplete = () => {
    utils.markJobProgressPercentage(100);
    console.log('ok'.green + ' -- Theme Processing Finished');
};

utils.pollForJobCompletion = () => {
    return async.retryable({
        interval: 1000,
        errorFilter: err => {
            if (err.name === "JobCompletionStatusCheckPendingError") {
                utils.markJobProgressPercentage(err.message);
                return true;
            }

            return false;
        },
        times: Number.POSITIVE_INFINITY,
    }, utils.checkIfJobIsComplete);
};

utils.checkIfJobIsComplete = (options, callback) => {
    const config = options.config;

    themeApiClient.getJob({
        accessToken: config.accessToken,
        apiHost: options.apiHost,
        clientId: config.clientId,
        storeHash: options.storeHash,
        bundleZipPath: options.bundleZipPath,
        jobId: options.jobId,
    }, (error, result) => {
        if (error) {
            return callback(error);
        }

        utils.markJobComplete();

        callback(null, Object.assign({}, options, result));
    });
};

utils.promptUserWhetherToApplyTheme = (options, callback) => {
    if (options.activate) {
        callback(null, Object.assign({}, options, { applyTheme: true }));
    } else {
        const questions = [{
            type: 'confirm',
            name: 'applyTheme',
            message: `Would you like to apply your theme to your store at ${options.config.normalStoreUrl}?`,
            default: false,
        }];

        Inquirer.prompt(questions, answers => {
            callback(null, Object.assign({}, options, { applyTheme: answers.applyTheme }));
        });
    };
};

utils.getVariations = (options, callback) => {
    if (!options.applyTheme) {
        return async.nextTick(callback.bind(null, null, options));
    }

    themeApiClient.getVariationsByThemeId({
        accessToken: options.accessToken,
        apiHost: options.apiHost,
        clientId: options.clientId,
        themeId: options.themeId,
        storeHash: options.storeHash,
    }, (error, result) => {
        if (error) {
            return callback(error);
        };
        if (options.activate !== true && options.activate !== undefined) {
            const findVariation = result.variations.find(item => item.name === options.activate);
            callback(null, Object.assign({}, options, { variationId: findVariation.uuid }));
        } else if (options.activate === true) {
            callback(null, Object.assign({}, options, { variationId: result.variations[0].uuid }));
        } else {
            callback(null, Object.assign({}, options, result));
        };
    });
};

utils.promptUserForVariation = (options, callback) => {
    if (!options.applyTheme) {
        return async.nextTick(callback.bind(null, null, options))
    }
    if (options.variationId) {
        callback(null, options);
    } else {
        const questions = [{
            type: 'list',
            name: 'variationId',
            message: 'Which variation would you like to apply?',
            choices: options.variations.map(variation => ({ name: variation.name, value: variation.uuid })),
        }];

        Inquirer.prompt(questions, answers => {
            console.log(answers);
            callback(null, Object.assign({}, options, answers));
        });
    };
};

utils.requestToApplyVariationWithRetrys = () => {
    return async.retryable({
        interval: 1000,
        errorFilter: err => {
            if (err.name === "VariationActivationTimeoutError") {
                console.log('warning'.yellow + ` -- Theme Activation Timed Out; Retrying...`);
                return true;
            }

            return false;
        },
        times: 3,
    }, utils.requestToApplyVariation);
};

utils.requestToApplyVariation = (options, callback) => {
    if (!options.applyTheme) {
        return async.nextTick(callback.bind(null, null, options));
    }

    themeApiClient.activateThemeByVariationId({
        accessToken: options.accessToken,
        apiHost: options.apiHost,
        clientId: options.clientId,
        storeHash: options.storeHash,
        variationId: options.variationId,
    }, (error, result) => {
        if (error) {
            return callback(error);
        }

        callback(null, Object.assign({}, options, result));
    });
};

utils.notifyUserOfCompletion = (options, callback) => {
    callback(null, 'Stencil Push Finished');
};

这让我可以使用 stencil push --activate bold 之类的东西来指定变体并跳过所有提示。

谢谢 Nikita Puza! 它就像一个魅力。我在 stencil 1.14.1 版本上应用了更改,源代码看起来完全一样。 唯一的区别是第二个文件被称为 stencil-push.utils.js 而不是 stencil-push.js

从版本 1.15.1 开始,这似乎可以通过 stencil push

-a, --activate [variationname] 开关使用

> stencil push -a "My Variant" 为我工作