@swell/cli
Version:
Swell's command line interface/utility
164 lines (163 loc) • 6.52 kB
JavaScript
import { Flags } from '@oclif/core';
import { findUp } from 'find-up';
import { execSync, spawn } from 'node:child_process';
import fs from 'node:fs';
import ora from 'ora';
import { default as localConfig } from '../../lib/config.js';
import style from '../../lib/style.js';
import { PushAppCommand } from '../../push-app-command.js';
import { ThemeSync } from '../../lib/theme-sync.js';
export default class AppThemeDev extends PushAppCommand {
appType = 'theme';
static examples = [
'swell theme dev',
'swell theme dev --local',
'swell theme dev --bundle',
'swell theme dev --storefront-select',
'swell theme dev --storefront-id <id>',
];
static flags = {
bundle: Flags.boolean({
description: 'bundle assets',
}),
force: Flags.boolean({
default: false,
description: 'force push all configurations',
}),
local: Flags.boolean({
description: 'connect to a running local storefront app dev server',
}),
'no-push': Flags.boolean({
description: 'skip pushing theme files initially',
}),
'storefront-id': Flags.string({
description: 'identify a storefront to preview and push theme files to',
}),
'storefront-select': Flags.boolean({
default: false,
description: 'prompt to select a storefront to preview and push theme files to',
}),
env: Flags.string({
char: 'e',
description: 'target environment to push to, defaults to live',
}),
};
static summary = `Run a theme in dev mode from your local machine.`;
logAppLocalUrl(storeId, sessionId) {
this.log(`View it at ${style.link(this.localFrontendUrl(storeId, sessionId))}\n`);
}
async run() {
const { flags } = await this.parse(AppThemeDev);
const { force, local } = flags;
const noPush = flags['no-push'];
await this.ensureLoggedIn();
// Set env from default storefront first
const defaultStorefront = localConfig.getDefaultStorefront(this.appPath);
await this.setStorefrontEnv(flags, defaultStorefront);
if (!(await this.ensureAppExists(undefined, false))) {
return;
}
if (local) {
const currentStore = localConfig.getDefaultStore();
const sessionId = localConfig.getSessionId(currentStore);
this.storefrontLocalUrl = this.localFrontendUrl(currentStore, sessionId);
}
await this.getStorefrontToPush(flags);
await this.logOrientation({ env: this.api.envId || 'live' });
this.logStorefrontConnected();
this.saveCurrentStorefront();
if (!noPush) {
await this.pushAppConfigs(force);
}
await this.startThemeDevServer(local);
}
async findAndInstallDependencies() {
const spinner = ora('Locating package.json...').start();
try {
const packageJsonLockPath = await findUp('package-lock.json', {
type: 'file',
});
if (packageJsonLockPath) {
spinner.succeed('package-lock.json found. Skipping npm install.');
return null;
}
const packageJsonPath = await findUp('package.json', { type: 'file' });
if (!packageJsonPath) {
spinner.info('package.json not found. Skipping bundle process.');
return null;
}
spinner.succeed(`Found package.json at ${packageJsonPath}`);
spinner.start('Installing dependencies...');
execSync('npm install', { stdio: 'inherit' });
spinner.succeed('Dependencies installed');
return packageJsonPath;
}
catch {
spinner.fail('Error locating package.json. Skipping bundle process.');
return null;
}
}
async findAndTryScriptCommands() {
try {
const data = await fs.promises.readFile('package.json', 'utf8');
const packageJson = JSON.parse(data);
if (!packageJson.scripts) {
console.error('No scripts found in package.json. Try adding scripts to your package file.');
return;
}
if (!packageJson.scripts.bundle) {
console.warn('"bundle" script not found in package.json');
return;
}
const childProcess = spawn('npm', ['run', 'bundle'], {
env: { ...process.env, BROWSERSLIST_IGNORE_OLD_DATA: 'true' },
shell: true,
stdio: 'pipe',
});
childProcess.stdout.on('data', (data) => {
const lines = data.toString().split(/\r?\n/);
for (const line of lines) {
const trimmedLine = line.trim();
if (trimmedLine) {
console.log(trimmedLine);
}
}
});
childProcess.on('exit', (code) => {
if (code === 0) {
console.log(`bundle completed successfully.`);
}
else {
console.error(`bundle exited with code ${code}.`);
}
});
}
catch (error) {
console.error('Error processing package.json:', error);
}
}
async startThemeDevServer(local) {
const currentStore = localConfig.getDefaultStore();
const spinner = ora();
const bundle = process.argv.includes('--bundle');
if (bundle) {
await this.findAndInstallDependencies();
await this.findAndTryScriptCommands();
}
spinner.start(`Starting theme dev server...`);
const storefrontUrl = local
? this.storefrontLocalUrl
: this.storefrontFrontendUrl(currentStore, this.storefront, undefined, true);
// Create and start theme sync
const themeSync = new ThemeSync({
targetUrl: storefrontUrl,
});
const port = await themeSync.start();
spinner.succeed(`Theme sync server started on port ${port}\n`);
await this.watchForChanges({
onChange: () => themeSync.reload(),
});
spinner.stop();
this.log(`Watching for changes. View your theme at ${style.link(`http://localhost:${port}`)}\n`);
}
}