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