@swell/cli 2.0.1-alpha.13 → 2.0.1-alpha.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app-command.d.ts +1 -1
- package/dist/app-command.js +8 -8
- package/dist/commands/app/dev.d.ts +18 -0
- package/dist/commands/app/dev.js +31 -0
- package/dist/commands/app/frontend/deploy.d.ts +17 -0
- package/dist/commands/app/frontend/deploy.js +44 -0
- package/dist/commands/app/frontend/dev.d.ts +21 -0
- package/dist/commands/app/frontend/dev.js +111 -0
- package/dist/commands/app/info.js +15 -8
- package/dist/commands/app/init.d.ts +12 -0
- package/dist/commands/app/init.js +34 -0
- package/dist/commands/app/install.d.ts +4 -4
- package/dist/commands/app/install.js +37 -42
- package/dist/commands/app/pull.d.ts +15 -0
- package/dist/commands/app/pull.js +50 -0
- package/dist/commands/app/push.d.ts +11 -14
- package/dist/commands/app/push.js +71 -143
- package/dist/commands/app/release.d.ts +13 -0
- package/dist/commands/app/release.js +150 -0
- package/dist/commands/app/version.d.ts +7 -6
- package/dist/commands/app/version.js +64 -43
- package/dist/commands/app/watch.d.ts +2 -4
- package/dist/commands/app/watch.js +3 -83
- package/dist/commands/create/app.d.ts +35 -11
- package/dist/commands/create/app.js +150 -144
- package/dist/commands/create/content.d.ts +2 -2
- package/dist/commands/create/content.js +10 -10
- package/dist/commands/create/frontend.d.ts +12 -0
- package/dist/commands/create/frontend.js +41 -0
- package/dist/commands/create/function.d.ts +3 -3
- package/dist/commands/create/function.js +22 -22
- package/dist/commands/create/model.d.ts +2 -2
- package/dist/commands/create/model.js +11 -11
- package/dist/commands/create/notification.d.ts +4 -4
- package/dist/commands/create/notification.js +38 -40
- package/dist/commands/login.d.ts +1 -1
- package/dist/commands/login.js +12 -9
- package/dist/commands/logs.js +2 -1
- package/dist/commands/theme/dev.d.ts +23 -0
- package/dist/commands/theme/dev.js +167 -0
- package/dist/commands/theme/init.d.ts +9 -0
- package/dist/commands/theme/init.js +15 -0
- package/dist/commands/theme/pull.d.ts +20 -0
- package/dist/commands/theme/pull.js +65 -0
- package/dist/commands/theme/push.d.ts +19 -0
- package/dist/commands/theme/push.js +92 -0
- package/dist/create-app-command.d.ts +20 -0
- package/dist/create-app-command.js +254 -0
- package/dist/create-collection-command.d.ts +9 -9
- package/dist/create-collection-command.js +23 -23
- package/dist/{create-command.d.ts → create-config-command.d.ts} +1 -1
- package/dist/{create-command.js → create-config-command.js} +9 -7
- package/dist/lib/api.d.ts +6 -4
- package/dist/lib/api.js +58 -29
- package/dist/lib/app-config.d.ts +13 -5
- package/dist/lib/app-config.js +60 -23
- package/dist/lib/apps/app-config.d.ts +22 -8
- package/dist/lib/apps/app-config.js +202 -68
- package/dist/lib/apps/index.d.ts +93 -13
- package/dist/lib/apps/index.js +150 -50
- package/dist/lib/apps/paths.d.ts +14 -7
- package/dist/lib/apps/paths.js +73 -28
- package/dist/lib/bundle.js +4 -5
- package/dist/lib/config.d.ts +5 -1
- package/dist/lib/config.js +14 -0
- package/dist/lib/constants.d.ts +5 -0
- package/dist/lib/constants.js +22 -3
- package/dist/lib/create/content.d.ts +2 -2
- package/dist/lib/create/content.js +1 -1
- package/dist/lib/create/function.d.ts +2 -2
- package/dist/lib/create/function.js +2 -2
- package/dist/lib/create/index.d.ts +1 -1
- package/dist/lib/create/index.js +11 -7
- package/dist/lib/create/model.d.ts +2 -2
- package/dist/lib/create/notification.d.ts +2 -2
- package/dist/lib/info.js +2 -2
- package/dist/lib/sessions.js +2 -2
- package/dist/lib/stores.d.ts +5 -2
- package/dist/lib/stores.js +9 -6
- package/dist/lib/style.d.ts +2 -0
- package/dist/lib/style.js +2 -0
- package/dist/lib/swell-function-wrapper.d.ts +2 -2
- package/dist/lib/swell-function-wrapper.js +2 -2
- package/dist/push-app-command.d.ts +73 -0
- package/dist/push-app-command.js +765 -0
- package/dist/remote-app-command.d.ts +35 -9
- package/dist/remote-app-command.js +436 -164
- package/oclif.manifest.json +841 -125
- package/package.json +13 -1
- package/dist/commands/app/_pull.d.ts +0 -8
- package/dist/commands/app/_pull.js +0 -45
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import BrowserSync from 'browser-sync';
|
|
4
|
+
import { Flags } from '@oclif/core';
|
|
5
|
+
import getPort, { portNumbers } from 'get-port';
|
|
6
|
+
import { spawn, execSync } from 'child_process';
|
|
7
|
+
import { findUp } from 'find-up';
|
|
8
|
+
import { default as localConfig } from '../../lib/config.js';
|
|
9
|
+
import { PushAppCommand } from '../../push-app-command.js';
|
|
10
|
+
import style from '../../lib/style.js';
|
|
11
|
+
const FALLBACK_PORT = 3000;
|
|
12
|
+
export default class AppThemeDev extends PushAppCommand {
|
|
13
|
+
appType = 'theme';
|
|
14
|
+
static summary = `Run a theme in dev mode from your local machine.`;
|
|
15
|
+
static examples = [
|
|
16
|
+
'swell theme dev',
|
|
17
|
+
'swell theme dev --local',
|
|
18
|
+
'swell theme dev --bundle',
|
|
19
|
+
'swell theme dev --storefront-select',
|
|
20
|
+
'swell theme dev --storefront-id <id>',
|
|
21
|
+
];
|
|
22
|
+
static flags = {
|
|
23
|
+
'storefront-id': Flags.string({
|
|
24
|
+
description: 'identify a storefront to preview and push theme files to',
|
|
25
|
+
}),
|
|
26
|
+
'storefront-select': Flags.boolean({
|
|
27
|
+
description: 'prompt to select a storefront to preview and push theme files to',
|
|
28
|
+
default: false,
|
|
29
|
+
}),
|
|
30
|
+
local: Flags.boolean({
|
|
31
|
+
description: 'connect to a running local storefront app dev server',
|
|
32
|
+
}),
|
|
33
|
+
bundle: Flags.boolean({
|
|
34
|
+
description: 'bundle assets',
|
|
35
|
+
}),
|
|
36
|
+
'no-push': Flags.boolean({
|
|
37
|
+
description: 'skip pushing theme files initially',
|
|
38
|
+
}),
|
|
39
|
+
};
|
|
40
|
+
static orientation = {
|
|
41
|
+
env: 'test',
|
|
42
|
+
};
|
|
43
|
+
constructor(argv, config) {
|
|
44
|
+
super(argv, config);
|
|
45
|
+
}
|
|
46
|
+
async run() {
|
|
47
|
+
const { flags } = await this.parse(AppThemeDev);
|
|
48
|
+
const { local } = flags;
|
|
49
|
+
const noPush = flags['no-push'];
|
|
50
|
+
if (!(await this.ensureAppExists(undefined, false))) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (!noPush) {
|
|
54
|
+
await this.pushAppConfigs();
|
|
55
|
+
}
|
|
56
|
+
await this.getStorefrontToPush(flags);
|
|
57
|
+
this.logStorefrontConnected();
|
|
58
|
+
this.saveCurrentStorefront();
|
|
59
|
+
await this.startThemeDevServer(local);
|
|
60
|
+
}
|
|
61
|
+
async findAndInstallDependencies() {
|
|
62
|
+
const spinner = ora('Locating package.json...').start();
|
|
63
|
+
try {
|
|
64
|
+
const packageJsonLockPath = await findUp('package-lock.json', {
|
|
65
|
+
type: 'file',
|
|
66
|
+
});
|
|
67
|
+
if (packageJsonLockPath) {
|
|
68
|
+
spinner.succeed('package-lock.json found. Skipping npm install.');
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
const packageJsonPath = await findUp('package.json', { type: 'file' });
|
|
72
|
+
if (!packageJsonPath) {
|
|
73
|
+
spinner.info('package.json not found. Skipping bundle process.');
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
spinner.succeed(`Found package.json at ${packageJsonPath}`);
|
|
77
|
+
spinner.start('Installing dependencies...');
|
|
78
|
+
execSync('npm install', { stdio: 'inherit' });
|
|
79
|
+
spinner.succeed('Dependencies installed');
|
|
80
|
+
return packageJsonPath;
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
spinner.fail('Error locating package.json. Skipping bundle process.');
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
async findAndTryScriptCommands() {
|
|
88
|
+
try {
|
|
89
|
+
const data = await fs.promises.readFile('package.json', 'utf8');
|
|
90
|
+
const packageJson = JSON.parse(data);
|
|
91
|
+
if (!packageJson.scripts) {
|
|
92
|
+
console.error('No scripts found in package.json. Try adding scripts to your package file.');
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const preferredScripts = ['dev', 'watch'];
|
|
96
|
+
const scriptName = preferredScripts.find((script) => packageJson.scripts[script]);
|
|
97
|
+
if (!scriptName) {
|
|
98
|
+
console.warn('Neither "dev" nor "watch" scripts found in package.json');
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const childProcess = spawn('npm', ['run', scriptName], {
|
|
102
|
+
stdio: 'pipe',
|
|
103
|
+
shell: true,
|
|
104
|
+
env: { ...process.env, BROWSERSLIST_IGNORE_OLD_DATA: 'true' },
|
|
105
|
+
});
|
|
106
|
+
childProcess.stdout.on('data', (data) => {
|
|
107
|
+
const lines = data.toString().split(/\r?\n/);
|
|
108
|
+
lines.forEach((line) => {
|
|
109
|
+
const trimmedLine = line.trim();
|
|
110
|
+
if (trimmedLine) {
|
|
111
|
+
console.log(`${style.success('📦')} ${trimmedLine}`);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
childProcess.on('exit', (code) => {
|
|
116
|
+
if (code === 0) {
|
|
117
|
+
console.log(`${scriptName} completed successfully.`);
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
console.error(`${scriptName} exited with code ${code}.`);
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
catch (err) {
|
|
125
|
+
console.error('Error processing package.json:', err);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
async startThemeDevServer(local) {
|
|
129
|
+
const currentStore = localConfig.getDefaultStore();
|
|
130
|
+
const sessionId = localConfig.getSessionId(currentStore);
|
|
131
|
+
const spinner = ora();
|
|
132
|
+
const bundle = process.argv.includes('--bundle');
|
|
133
|
+
if (bundle) {
|
|
134
|
+
await this.findAndInstallDependencies();
|
|
135
|
+
await this.findAndTryScriptCommands();
|
|
136
|
+
}
|
|
137
|
+
// Find an open port starting at 3000
|
|
138
|
+
const freePort = (await getPort({ port: portNumbers(3000, 3100) })) || FALLBACK_PORT;
|
|
139
|
+
spinner.start(`Starting theme dev server on port ${freePort}...`);
|
|
140
|
+
const storefrontUrl = local
|
|
141
|
+
? this.localFrontendUrl(currentStore, sessionId)
|
|
142
|
+
: this.storefrontFrontendUrl(currentStore, this.storefront, undefined, true);
|
|
143
|
+
const bs = BrowserSync.create();
|
|
144
|
+
bs.init({
|
|
145
|
+
ui: false,
|
|
146
|
+
notify: false,
|
|
147
|
+
proxy: {
|
|
148
|
+
target: storefrontUrl,
|
|
149
|
+
proxyReq: [
|
|
150
|
+
(proxyReq) => {
|
|
151
|
+
proxyReq.setHeader('swell-request-id', `${currentStore}_${Date.now()}`);
|
|
152
|
+
},
|
|
153
|
+
],
|
|
154
|
+
},
|
|
155
|
+
logLevel: 'silent',
|
|
156
|
+
open: false,
|
|
157
|
+
});
|
|
158
|
+
await this.watchForChanges({
|
|
159
|
+
onChange: () => bs.reload(),
|
|
160
|
+
});
|
|
161
|
+
spinner.stop();
|
|
162
|
+
this.log(`Watching for changes. View your theme at ${style.link(`http://localhost:${freePort}`)}\n`);
|
|
163
|
+
}
|
|
164
|
+
logAppLocalUrl(storeId, sessionId) {
|
|
165
|
+
this.log(`View it at ${style.link(this.localFrontendUrl(storeId, sessionId))}\n`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import InitApp from '../app/init.js';
|
|
2
|
+
export default class InitTheme extends InitApp {
|
|
3
|
+
appType = 'theme';
|
|
4
|
+
static description = 'Initialize theme swell.json in the current directory.';
|
|
5
|
+
static examples = [
|
|
6
|
+
{
|
|
7
|
+
command: 'swell theme init',
|
|
8
|
+
description: 'Initialize theme following command prompts.',
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
command: 'swell theme init -y',
|
|
12
|
+
description: 'Initialize theme and accept all default values.',
|
|
13
|
+
},
|
|
14
|
+
];
|
|
15
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import AppPull from '../app/pull.js';
|
|
2
|
+
export default class AppThemePull extends AppPull {
|
|
3
|
+
appType: string;
|
|
4
|
+
static args: {
|
|
5
|
+
appId: import("@oclif/core/lib/interfaces/parser.js").Arg<string, Record<string, unknown>>;
|
|
6
|
+
targetPath: import("@oclif/core/lib/interfaces/parser.js").Arg<string, Record<string, unknown>>;
|
|
7
|
+
};
|
|
8
|
+
static flags: {
|
|
9
|
+
force: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
10
|
+
'sync-app': import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
11
|
+
'storefront-select': import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
12
|
+
'storefront-id': import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
13
|
+
};
|
|
14
|
+
static summary: string;
|
|
15
|
+
static description: string;
|
|
16
|
+
static examples: string[];
|
|
17
|
+
static orientation: {};
|
|
18
|
+
static delayOrientation: boolean;
|
|
19
|
+
run(): Promise<void>;
|
|
20
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { Args, Flags } from '@oclif/core';
|
|
2
|
+
import { ThemeConfigPaths, } from '../../lib/apps/index.js';
|
|
3
|
+
import style from '../../lib/style.js';
|
|
4
|
+
import AppPull from '../app/pull.js';
|
|
5
|
+
export default class AppThemePull extends AppPull {
|
|
6
|
+
appType = 'theme';
|
|
7
|
+
static args = {
|
|
8
|
+
appId: Args.string({
|
|
9
|
+
default: '',
|
|
10
|
+
description: 'theme app id to pull',
|
|
11
|
+
}),
|
|
12
|
+
targetPath: Args.string({
|
|
13
|
+
default: '',
|
|
14
|
+
description: 'path to download theme files into',
|
|
15
|
+
}),
|
|
16
|
+
};
|
|
17
|
+
static flags = {
|
|
18
|
+
force: Flags.boolean({
|
|
19
|
+
description: 'force pull all theme files',
|
|
20
|
+
default: false,
|
|
21
|
+
}),
|
|
22
|
+
'sync-app': Flags.boolean({
|
|
23
|
+
description: 'pull app files in addition to theme files to',
|
|
24
|
+
default: false,
|
|
25
|
+
}),
|
|
26
|
+
'storefront-select': Flags.boolean({
|
|
27
|
+
description: 'prompt to select a storefront to pull theme files to',
|
|
28
|
+
default: false,
|
|
29
|
+
}),
|
|
30
|
+
'storefront-id': Flags.string({
|
|
31
|
+
description: 'identify a storefront to pull theme files from',
|
|
32
|
+
}),
|
|
33
|
+
};
|
|
34
|
+
static summary = 'Pull theme files from Swell to your local machine.';
|
|
35
|
+
static description = `Pull all theme files, a specific file, or a specific configuration
|
|
36
|
+
type from a theme in your store's test environment to your local machine.
|
|
37
|
+
|
|
38
|
+
If APPID is not specified, you will be prompted with a list of themes to choose from.
|
|
39
|
+
|
|
40
|
+
Theme file directories:
|
|
41
|
+
${Object.values(ThemeConfigPaths)
|
|
42
|
+
.map((dir) => style.path(`${dir}/`))
|
|
43
|
+
.join('\n')}`;
|
|
44
|
+
static examples = [
|
|
45
|
+
'swell theme pull',
|
|
46
|
+
'swell theme pull mytheme',
|
|
47
|
+
'swell theme pull mytheme mytheme-folder',
|
|
48
|
+
'swell theme pull --force',
|
|
49
|
+
'swell theme pull --storefront-select',
|
|
50
|
+
'swell theme pull --storefront-id <id>',
|
|
51
|
+
];
|
|
52
|
+
static orientation = {};
|
|
53
|
+
static delayOrientation = true;
|
|
54
|
+
async run() {
|
|
55
|
+
const { args, flags } = await this.parse(AppThemePull);
|
|
56
|
+
const { force } = flags;
|
|
57
|
+
this.themeSyncApp = flags['sync-app'];
|
|
58
|
+
await this.ensureLoggedIn();
|
|
59
|
+
await this.getStorefrontToPull(args, flags);
|
|
60
|
+
await this.logOrientation();
|
|
61
|
+
const { pulled, removed } = await this.pullAppConfigs(force);
|
|
62
|
+
this.logPulledChanges(pulled, removed);
|
|
63
|
+
this.saveCurrentStorefront();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import AppPush from '../app/push.js';
|
|
2
|
+
export default class AppThemePush extends AppPush {
|
|
3
|
+
appType: string;
|
|
4
|
+
static args: {
|
|
5
|
+
file: import("@oclif/core/lib/interfaces/parser.js").Arg<string | undefined, Record<string, unknown>>;
|
|
6
|
+
};
|
|
7
|
+
static flags: {
|
|
8
|
+
force: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
9
|
+
'sync-app': import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
10
|
+
'storefront-select': import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
11
|
+
'storefront-id': import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
12
|
+
};
|
|
13
|
+
static summary: string;
|
|
14
|
+
static description: string;
|
|
15
|
+
static examples: string[];
|
|
16
|
+
static orientation: {};
|
|
17
|
+
static delayOrientation: boolean;
|
|
18
|
+
run(): Promise<void>;
|
|
19
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { Args, Flags } from '@oclif/core';
|
|
2
|
+
import { ThemeConfigPaths, isPathDirectory, filePathExists, } from '../../lib/apps/index.js';
|
|
3
|
+
import style from '../../lib/style.js';
|
|
4
|
+
import AppPush from '../app/push.js';
|
|
5
|
+
export default class AppThemePush extends AppPush {
|
|
6
|
+
appType = 'theme';
|
|
7
|
+
static args = {
|
|
8
|
+
file: Args.string({
|
|
9
|
+
char: 'f',
|
|
10
|
+
description: 'relative path to a configuration file or directory to push',
|
|
11
|
+
}),
|
|
12
|
+
};
|
|
13
|
+
static flags = {
|
|
14
|
+
force: Flags.boolean({
|
|
15
|
+
description: 'force push all files',
|
|
16
|
+
default: false,
|
|
17
|
+
}),
|
|
18
|
+
'sync-app': Flags.boolean({
|
|
19
|
+
description: 'push app files in addition to theme files',
|
|
20
|
+
default: false,
|
|
21
|
+
}),
|
|
22
|
+
'storefront-select': Flags.boolean({
|
|
23
|
+
description: 'prompt to select a storefront to push theme files to',
|
|
24
|
+
default: false,
|
|
25
|
+
}),
|
|
26
|
+
'storefront-id': Flags.string({
|
|
27
|
+
description: 'identify a storefront to push theme files to',
|
|
28
|
+
}),
|
|
29
|
+
};
|
|
30
|
+
static summary = 'Push local files to your Swell theme.';
|
|
31
|
+
static description = `Push all theme files, a specific file, or a specific configuration
|
|
32
|
+
type to an theme in your store's test environment.
|
|
33
|
+
|
|
34
|
+
If the theme does not exist, it will be created and its global ID saved to a .swellrc file.
|
|
35
|
+
|
|
36
|
+
- If no file is specified, all configuration files will be pushed to the store.
|
|
37
|
+
This includes the theme icon (assets/icon.png) and swell.json.
|
|
38
|
+
- If a file is specified, only that file will be pushed to the store.
|
|
39
|
+
- If a directory is specified, only files in that directory will be pushed.
|
|
40
|
+
|
|
41
|
+
Theme file directories:
|
|
42
|
+
${Object.values(ThemeConfigPaths)
|
|
43
|
+
.map((dir) => style.path(`${dir}/`))
|
|
44
|
+
.join('\n')}`;
|
|
45
|
+
static examples = [
|
|
46
|
+
'swell theme push',
|
|
47
|
+
'swell theme push assets',
|
|
48
|
+
'swell theme push theme/sections',
|
|
49
|
+
'swell theme push --sync-app',
|
|
50
|
+
'swell theme push --force',
|
|
51
|
+
'swell theme push --storefront-select',
|
|
52
|
+
'swell theme push --storefront-id <id>',
|
|
53
|
+
];
|
|
54
|
+
static orientation = {};
|
|
55
|
+
static delayOrientation = true;
|
|
56
|
+
async run() {
|
|
57
|
+
const { args, flags } = await this.parse(AppThemePush);
|
|
58
|
+
const { file } = args;
|
|
59
|
+
const { force } = flags;
|
|
60
|
+
this.themeSyncApp = flags['sync-app'];
|
|
61
|
+
if (!(await this.ensureAppExists(file, this.themeSyncApp))) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
if (!this.themeSyncApp && this.appCreated) {
|
|
65
|
+
this.themeSyncApp = true;
|
|
66
|
+
}
|
|
67
|
+
await this.getStorefrontToPush(flags);
|
|
68
|
+
await this.logOrientation();
|
|
69
|
+
if (file) {
|
|
70
|
+
const { filePath, relativePath } = this.resolvePushPaths(file);
|
|
71
|
+
// Push individual files if not referring to the whole app path
|
|
72
|
+
if (filePath !== this.appPath) {
|
|
73
|
+
if (!filePathExists(filePath)) {
|
|
74
|
+
this.error(`Path ${style.path(file)} does not exist.`);
|
|
75
|
+
}
|
|
76
|
+
// Targeting a directory
|
|
77
|
+
if (isPathDirectory(filePath)) {
|
|
78
|
+
await this.pushFilePath(relativePath, force);
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
await this.pushFile(relativePath);
|
|
82
|
+
}
|
|
83
|
+
this.log();
|
|
84
|
+
this.logStorefrontFrontendUrl(this.storefront);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
await this.pushAppConfigs(force);
|
|
89
|
+
this.logStorefrontFrontendUrl(this.storefront);
|
|
90
|
+
this.saveCurrentStorefront();
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { SwellCommand } from './swell-command.js';
|
|
2
|
+
export declare abstract class CreateAppCommand extends SwellCommand {
|
|
3
|
+
private devApi;
|
|
4
|
+
static baseFlags: {
|
|
5
|
+
yes: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
6
|
+
frontend: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
7
|
+
'storefront-app': import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
8
|
+
};
|
|
9
|
+
constructor(argv: string[], config: any);
|
|
10
|
+
execWithStdio(cwd: string, command: string, onOutput?: (string: string) => false | any): Promise<void>;
|
|
11
|
+
createAppConfigFolders(swellConfig: any): Promise<void>;
|
|
12
|
+
doesPackageManagerExist(packageManager: string): Promise<boolean>;
|
|
13
|
+
findPackageManager(pkg?: string): Promise<string | undefined>;
|
|
14
|
+
setupPackage(name: string, config: any, pkg: string): Promise<void>;
|
|
15
|
+
tryPackageSetup(name: string, config: any, pkg: string): Promise<void>;
|
|
16
|
+
getInstalledStorefrontApps(): Promise<boolean | any[]>;
|
|
17
|
+
createThemeApp(config: any, flags: any, installedStorefrontApp: any): Promise<boolean>;
|
|
18
|
+
createStorefrontApp(swellConfig: any, flags: any, directCreate?: boolean): Promise<boolean>;
|
|
19
|
+
createFrontendApp(swellConfig: any, flags: any, directCreate?: boolean, kind?: string): Promise<boolean>;
|
|
20
|
+
}
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import { confirm, select } from '@inquirer/prompts';
|
|
2
|
+
import { Flags } from '@oclif/core';
|
|
3
|
+
import { exec } from 'node:child_process';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { promisify } from 'node:util';
|
|
6
|
+
import { $ } from 'execa';
|
|
7
|
+
import Stream from 'stream';
|
|
8
|
+
import ora from 'ora';
|
|
9
|
+
import Api from './lib/api.js';
|
|
10
|
+
import { FrontendProjectTypes, getAllConfigPaths, writeFile, writeJsonFile, } from './lib/apps/index.js';
|
|
11
|
+
import style from './lib/style.js';
|
|
12
|
+
import { SwellCommand } from './swell-command.js';
|
|
13
|
+
const execAsync = promisify(exec);
|
|
14
|
+
export class CreateAppCommand extends SwellCommand {
|
|
15
|
+
devApi;
|
|
16
|
+
static baseFlags = {
|
|
17
|
+
yes: Flags.boolean({
|
|
18
|
+
char: 'y',
|
|
19
|
+
description: `accept all default values`,
|
|
20
|
+
}),
|
|
21
|
+
frontend: Flags.string({
|
|
22
|
+
description: `create a starter framework for a hosted frontend`,
|
|
23
|
+
options: FrontendProjectTypes.map(({ slug }) => slug),
|
|
24
|
+
}),
|
|
25
|
+
'storefront-app': Flags.string({
|
|
26
|
+
description: `id of an installed storefront app to create a theme for, if applicable`,
|
|
27
|
+
}),
|
|
28
|
+
};
|
|
29
|
+
constructor(argv, config) {
|
|
30
|
+
super(argv, config);
|
|
31
|
+
this.devApi = new Api(undefined, 'test');
|
|
32
|
+
}
|
|
33
|
+
async execWithStdio(cwd, command, onOutput) {
|
|
34
|
+
const $$ = $({
|
|
35
|
+
cwd,
|
|
36
|
+
shell: true,
|
|
37
|
+
stdin: 'inherit',
|
|
38
|
+
stderr: 'inherit',
|
|
39
|
+
stdout: 'pipe',
|
|
40
|
+
});
|
|
41
|
+
const outStream = new Stream.Writable();
|
|
42
|
+
outStream._write = (chunk, _encoding, next) => {
|
|
43
|
+
const string = chunk.toString();
|
|
44
|
+
const out = onOutput?.(string);
|
|
45
|
+
if (out !== false) {
|
|
46
|
+
console.log(string);
|
|
47
|
+
}
|
|
48
|
+
next();
|
|
49
|
+
};
|
|
50
|
+
try {
|
|
51
|
+
await $$ `${command}`.pipeStdout?.(outStream);
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
console.log(err);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
async createAppConfigFolders(swellConfig) {
|
|
58
|
+
for (const type of getAllConfigPaths(swellConfig.get('type'))) {
|
|
59
|
+
// Create a config folder
|
|
60
|
+
// eslint-disable-next-line no-await-in-loop
|
|
61
|
+
await execAsync(`mkdir -p ${type}`, {
|
|
62
|
+
cwd: path.dirname(swellConfig.path),
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
async doesPackageManagerExist(packageManager) {
|
|
67
|
+
try {
|
|
68
|
+
await execAsync(`${packageManager} --version`);
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
async findPackageManager(pkg = '') {
|
|
76
|
+
const packageManager = pkg;
|
|
77
|
+
// Determine if system has yarn or npm install
|
|
78
|
+
if (packageManager) {
|
|
79
|
+
if (await this.doesPackageManagerExist(packageManager)) {
|
|
80
|
+
return packageManager;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
if (await this.doesPackageManagerExist('npm')) {
|
|
85
|
+
return 'npm';
|
|
86
|
+
}
|
|
87
|
+
if (await this.doesPackageManagerExist('yarn')) {
|
|
88
|
+
return 'yarn';
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
async setupPackage(name, config, pkg) {
|
|
93
|
+
const execAsync = promisify(exec);
|
|
94
|
+
const configPath = path.dirname(config.path);
|
|
95
|
+
const packageJson = {
|
|
96
|
+
description: config.get('description'),
|
|
97
|
+
devDependencies: {
|
|
98
|
+
'@swell/app-types': '^1.0.5',
|
|
99
|
+
},
|
|
100
|
+
name,
|
|
101
|
+
scripts: {},
|
|
102
|
+
version: config.get('version'),
|
|
103
|
+
};
|
|
104
|
+
const tsConfig = {
|
|
105
|
+
compilerOptions: {
|
|
106
|
+
lib: ['esnext', 'webworker'],
|
|
107
|
+
module: 'esnext',
|
|
108
|
+
target: 'esnext',
|
|
109
|
+
types: ['@swell/app-types'],
|
|
110
|
+
},
|
|
111
|
+
exclude: ['node_modules'],
|
|
112
|
+
include: ['**/*.ts'],
|
|
113
|
+
};
|
|
114
|
+
await writeJsonFile(path.join(configPath, 'package.json'), packageJson);
|
|
115
|
+
await writeJsonFile(path.join(configPath, 'tsconfig.json'), tsConfig);
|
|
116
|
+
await writeFile(path.join(configPath, '.gitignore'), `node_modules`);
|
|
117
|
+
try {
|
|
118
|
+
const packageManager = await this.findPackageManager(pkg);
|
|
119
|
+
await execAsync(`${packageManager} install`, {
|
|
120
|
+
cwd: configPath,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
this.error(error.message);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
async tryPackageSetup(name, config, pkg) {
|
|
128
|
+
try {
|
|
129
|
+
await this.setupPackage(name, config, pkg);
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
this.log(`There was an error setting up the package manager: ${error.message}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
async getInstalledStorefrontApps() {
|
|
136
|
+
const spinner = ora();
|
|
137
|
+
spinner.start('Retrieving installed storefront apps...');
|
|
138
|
+
const installedApps = await this.devApi.get({
|
|
139
|
+
adminPath: `/client/apps`,
|
|
140
|
+
});
|
|
141
|
+
const storefrontInstalledApps = installedApps.results.filter((installedApp) => installedApp.app?.type === 'storefront');
|
|
142
|
+
if (storefrontInstalledApps.length === 0) {
|
|
143
|
+
spinner.fail(`You must have at least one storefront app installed in your ${style.appEnv('test')} environment before creating a theme.`);
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
spinner.stop();
|
|
147
|
+
return storefrontInstalledApps;
|
|
148
|
+
}
|
|
149
|
+
async createThemeApp(config, flags, installedStorefrontApp) {
|
|
150
|
+
const spinner = ora();
|
|
151
|
+
const configPath = path.dirname(config.path);
|
|
152
|
+
const defaultThemeConfigs = await this.devApi.get({
|
|
153
|
+
adminPath: `/client/apps/${installedStorefrontApp.app_id}/theme-template-configs`,
|
|
154
|
+
});
|
|
155
|
+
if (defaultThemeConfigs.results?.length > 0) {
|
|
156
|
+
const { name: appName, version: appVersion } = installedStorefrontApp.app;
|
|
157
|
+
const shouldCreate = flags.yes ||
|
|
158
|
+
(await confirm({
|
|
159
|
+
message: `Create a theme template for ${style.appConfigValue(appName)} ${appVersion ? `v${appVersion}` : ''}?`,
|
|
160
|
+
}));
|
|
161
|
+
if (shouldCreate) {
|
|
162
|
+
this.log();
|
|
163
|
+
spinner.start(`Creating theme template...`);
|
|
164
|
+
try {
|
|
165
|
+
await execAsync(`mkdir -p theme`, {
|
|
166
|
+
cwd: configPath,
|
|
167
|
+
});
|
|
168
|
+
for (const themeConfig of defaultThemeConfigs.results) {
|
|
169
|
+
const filePath = themeConfig.file_path.replace(/^frontend\/theme-template\//, '');
|
|
170
|
+
const fileContent = themeConfig.file_data;
|
|
171
|
+
const isJson = filePath.endsWith('.json');
|
|
172
|
+
await execAsync(`mkdir -p ${path.dirname(filePath)}`, {
|
|
173
|
+
cwd: configPath,
|
|
174
|
+
});
|
|
175
|
+
if (isJson) {
|
|
176
|
+
await writeJsonFile(path.join(configPath, filePath), fileContent);
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
await writeFile(path.join(configPath, filePath), fileContent);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
catch (err) {
|
|
184
|
+
spinner.fail(`Error creating theme template:\n`);
|
|
185
|
+
this.log(err.stdout);
|
|
186
|
+
this.log();
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
spinner.succeed(`Theme template initialized in ${configPath}/theme/`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return true;
|
|
193
|
+
}
|
|
194
|
+
async createStorefrontApp(swellConfig, flags, directCreate) {
|
|
195
|
+
return await this.createFrontendApp(swellConfig, flags, directCreate, 'storefront');
|
|
196
|
+
}
|
|
197
|
+
async createFrontendApp(swellConfig, flags, directCreate, kind) {
|
|
198
|
+
const spinner = ora();
|
|
199
|
+
const configPath = path.dirname(swellConfig.path);
|
|
200
|
+
const frameworkType = flags.frontend ||
|
|
201
|
+
(await select({
|
|
202
|
+
choices: [
|
|
203
|
+
...FrontendProjectTypes.map(({ name, slug }) => ({
|
|
204
|
+
name,
|
|
205
|
+
value: slug,
|
|
206
|
+
})),
|
|
207
|
+
...(directCreate
|
|
208
|
+
? []
|
|
209
|
+
: [
|
|
210
|
+
{
|
|
211
|
+
name: 'Do not install a starter framework',
|
|
212
|
+
value: null,
|
|
213
|
+
},
|
|
214
|
+
]),
|
|
215
|
+
],
|
|
216
|
+
message: directCreate
|
|
217
|
+
? `Choose a framework for your hosted${kind ? ` ${kind} ` : ' '}app frontend`
|
|
218
|
+
: `Install a framework for your hosted${kind ? ` ${kind} ` : ' '}app frontend?`,
|
|
219
|
+
}));
|
|
220
|
+
if (frameworkType) {
|
|
221
|
+
const projectType = FrontendProjectTypes.find((type) => type.slug === frameworkType);
|
|
222
|
+
if (!projectType) {
|
|
223
|
+
this.error(`Could not find project type: ${frameworkType}`);
|
|
224
|
+
}
|
|
225
|
+
this.log();
|
|
226
|
+
spinner.start(`Creating ${projectType?.name} frontend app (this may take a while)...`);
|
|
227
|
+
try {
|
|
228
|
+
await execAsync(`mkdir -p frontend`, {
|
|
229
|
+
cwd: configPath,
|
|
230
|
+
});
|
|
231
|
+
await execAsync(projectType.installCommand, {
|
|
232
|
+
cwd: configPath,
|
|
233
|
+
});
|
|
234
|
+
// Use this command to debug output, i.e.e when command becomes non-responsive
|
|
235
|
+
/* await this.execWithStdio(
|
|
236
|
+
configPath,
|
|
237
|
+
projectType.installCommand,
|
|
238
|
+
); */
|
|
239
|
+
}
|
|
240
|
+
catch (err) {
|
|
241
|
+
spinner.fail(`Error creating ${projectType?.name} app:\n`);
|
|
242
|
+
this.log(err.stdout);
|
|
243
|
+
this.log();
|
|
244
|
+
return false;
|
|
245
|
+
}
|
|
246
|
+
spinner.succeed(`${projectType?.name} initialized app in ${configPath}/frontend/`);
|
|
247
|
+
if (directCreate) {
|
|
248
|
+
this.log();
|
|
249
|
+
}
|
|
250
|
+
return true;
|
|
251
|
+
}
|
|
252
|
+
return false;
|
|
253
|
+
}
|
|
254
|
+
}
|