@swell/cli 1.0.1-alpha → 2.0.1-alpha.0

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.
Files changed (107) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +1 -1
  3. package/bin/dev.cmd +3 -0
  4. package/bin/dev.js +7 -0
  5. package/bin/run.cmd +3 -0
  6. package/bin/run.js +7 -0
  7. package/dist/app-command.d.ts +22 -0
  8. package/dist/app-command.js +45 -0
  9. package/dist/commands/app/_pull.d.ts +8 -0
  10. package/dist/commands/app/_pull.js +45 -0
  11. package/dist/commands/app/_uninstall.d.ts +12 -0
  12. package/dist/commands/app/_uninstall.js +45 -0
  13. package/dist/commands/app/info.d.ts +15 -0
  14. package/dist/commands/app/info.js +92 -0
  15. package/dist/commands/app/install.d.ts +17 -0
  16. package/dist/commands/app/install.js +177 -0
  17. package/dist/commands/app/push.d.ts +17 -0
  18. package/dist/commands/app/push.js +158 -0
  19. package/dist/commands/app/version.d.ts +18 -0
  20. package/dist/commands/app/version.js +156 -0
  21. package/dist/commands/app/watch.d.ts +15 -0
  22. package/dist/commands/app/watch.js +110 -0
  23. package/dist/commands/create/app.d.ts +21 -0
  24. package/dist/commands/create/app.js +210 -0
  25. package/dist/commands/create/content.d.ts +19 -0
  26. package/dist/commands/create/content.js +87 -0
  27. package/dist/commands/create/function.d.ts +25 -0
  28. package/dist/commands/create/function.js +191 -0
  29. package/dist/commands/create/index.d.ts +6 -0
  30. package/dist/commands/create/index.js +34 -0
  31. package/dist/commands/create/model.d.ts +19 -0
  32. package/dist/commands/create/model.js +87 -0
  33. package/dist/commands/create/notification.d.ts +25 -0
  34. package/dist/commands/create/notification.js +170 -0
  35. package/dist/commands/login.d.ts +22 -0
  36. package/dist/commands/login.js +77 -0
  37. package/dist/commands/logout.d.ts +8 -0
  38. package/dist/commands/logout.js +45 -0
  39. package/dist/commands/logs.d.ts +23 -0
  40. package/dist/commands/logs.js +207 -0
  41. package/dist/commands/switch.d.ts +10 -0
  42. package/dist/commands/switch.js +41 -0
  43. package/dist/commands/whoami.d.ts +7 -0
  44. package/dist/commands/whoami.js +24 -0
  45. package/dist/create-collection-command.d.ts +26 -0
  46. package/dist/create-collection-command.js +97 -0
  47. package/dist/create-command.d.ts +16 -0
  48. package/dist/create-command.js +59 -0
  49. package/dist/index.d.ts +1 -0
  50. package/dist/index.js +1 -0
  51. package/dist/lib/api.d.ts +13 -0
  52. package/dist/lib/api.js +117 -0
  53. package/dist/lib/app-config.d.ts +29 -0
  54. package/dist/lib/app-config.js +127 -0
  55. package/dist/lib/apps/app-config.d.ts +42 -0
  56. package/dist/lib/apps/app-config.js +167 -0
  57. package/dist/lib/apps/index.d.ts +67 -0
  58. package/dist/lib/apps/index.js +167 -0
  59. package/dist/lib/apps/paths.d.ts +18 -0
  60. package/dist/lib/apps/paths.js +54 -0
  61. package/dist/lib/bundle.d.ts +1 -0
  62. package/dist/lib/bundle.js +40 -0
  63. package/dist/lib/config.d.ts +37 -0
  64. package/dist/lib/config.js +131 -0
  65. package/dist/lib/constants.d.ts +5 -0
  66. package/dist/lib/constants.js +17 -0
  67. package/dist/lib/create/content.d.ts +23 -0
  68. package/dist/lib/create/content.js +40 -0
  69. package/dist/lib/create/function.d.ts +17 -0
  70. package/dist/lib/create/function.js +36 -0
  71. package/dist/lib/create/index.d.ts +13 -0
  72. package/dist/lib/create/index.js +20 -0
  73. package/dist/lib/create/model.d.ts +22 -0
  74. package/dist/lib/create/model.js +30 -0
  75. package/dist/lib/create/notification.d.ts +25 -0
  76. package/dist/lib/create/notification.js +1 -0
  77. package/dist/lib/info.d.ts +38 -0
  78. package/dist/lib/info.js +91 -0
  79. package/dist/lib/logs/index.d.ts +23 -0
  80. package/dist/lib/logs/index.js +88 -0
  81. package/dist/lib/logs/line-output.d.ts +5 -0
  82. package/dist/lib/logs/line-output.js +13 -0
  83. package/dist/lib/logs/output.d.ts +7 -0
  84. package/dist/lib/logs/output.js +8 -0
  85. package/dist/lib/logs/table-output.d.ts +11 -0
  86. package/dist/lib/logs/table-output.js +128 -0
  87. package/dist/lib/sessions.d.ts +9 -0
  88. package/dist/lib/sessions.js +110 -0
  89. package/dist/lib/stores.d.ts +63 -0
  90. package/dist/lib/stores.js +173 -0
  91. package/dist/lib/style.d.ts +24 -0
  92. package/dist/lib/style.js +29 -0
  93. package/dist/lib/swell-function-wrapper.d.ts +88 -0
  94. package/dist/lib/swell-function-wrapper.js +302 -0
  95. package/dist/remote-app-command.d.ts +36 -0
  96. package/dist/remote-app-command.js +367 -0
  97. package/dist/swell-command.d.ts +21 -0
  98. package/dist/swell-command.js +43 -0
  99. package/oclif.manifest.json +1085 -0
  100. package/package.json +89 -42
  101. package/LICENSE +0 -674
  102. package/dist/bin/multipart-parser-97941dba.js +0 -456
  103. package/dist/bin/multipart-parser-fc2dc43e.js +0 -456
  104. package/dist/bin/swell-96dae89a.js +0 -30638
  105. package/dist/bin/swell-cfd63c19.js +0 -30637
  106. package/dist/bin/swell.js +0 -24
  107. package/dist/man/swell.1 +0 -85
package/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 Swell Commerce Corp.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -138,4 +138,4 @@ swell model pull -m theme.origin.page --content -f models/page.json --store my-t
138
138
 
139
139
  swell model push -f models/orders.json --store my-test-store --secret-key my-dev-secret-key
140
140
  swell model push -c -f models/page.json --store my-test-store --secret-key my-dev-secret-key
141
- ```
141
+ ```
package/bin/dev.cmd ADDED
@@ -0,0 +1,3 @@
1
+ @echo off
2
+
3
+ node "%~dp0\dev" %*
package/bin/dev.js ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ts-node
2
+
3
+ // eslint-disable-next-line unicorn/prefer-top-level-await, node/shebang
4
+ (async () => {
5
+ const oclif = await import('@oclif/core');
6
+ await oclif.execute({ type: 'esm', development: true, dir: import.meta.url });
7
+ })();
package/bin/run.cmd ADDED
@@ -0,0 +1,3 @@
1
+ @echo off
2
+
3
+ node "%~dp0\run" %*
package/bin/run.js ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ts-node
2
+
3
+ // eslint-disable-next-line unicorn/prefer-top-level-await, node/shebang
4
+ (async () => {
5
+ const oclif = await import('@oclif/core');
6
+ await oclif.execute({ type: 'esm', dir: import.meta.url });
7
+ })();
@@ -0,0 +1,22 @@
1
+ import { Config } from '@oclif/core';
2
+ import { SwellCommand } from './swell-command.js';
3
+ /**
4
+ * A base class for Swell CLI commands that do not require an app to be saved
5
+ * to the API. If your command requires an app to be saved to the API, extend
6
+ * the `RemoteAppCommand` class instead.
7
+ *
8
+ * This class extends the `SwellCommand` class and adds:
9
+ *
10
+ * - a config instance
11
+ * - a shorthand attribute for the app directory
12
+ */
13
+ export declare abstract class AppCommand extends SwellCommand {
14
+ static baseFlags: {
15
+ 'app-path': import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
16
+ };
17
+ appConfig: any;
18
+ appPath: string;
19
+ constructor(argv: string[], config: Config);
20
+ init(): Promise<void>;
21
+ protected initBaseFlags(): Promise<void>;
22
+ }
@@ -0,0 +1,45 @@
1
+ import { Flags } from '@oclif/core';
2
+ import { default as appConfig } from './lib/app-config.js';
3
+ import { SwellCommand } from './swell-command.js';
4
+ /**
5
+ * A base class for Swell CLI commands that do not require an app to be saved
6
+ * to the API. If your command requires an app to be saved to the API, extend
7
+ * the `RemoteAppCommand` class instead.
8
+ *
9
+ * This class extends the `SwellCommand` class and adds:
10
+ *
11
+ * - a config instance
12
+ * - a shorthand attribute for the app directory
13
+ */
14
+ export class AppCommand extends SwellCommand {
15
+ // base flags for all AppCommands
16
+ // needs to be in a ES2021 format: https://github.com/oclif/oclif/issues/1100
17
+ static baseFlags = {
18
+ 'app-path': Flags.string({
19
+ description: 'path to the app directory',
20
+ }),
21
+ };
22
+ // local app configuration, what developers define
23
+ appConfig;
24
+ // the local path to the app directory
25
+ appPath = '';
26
+ constructor(argv, config) {
27
+ super(argv, config);
28
+ this.appConfig = appConfig();
29
+ this.appPath = this.appConfig.swDirPath;
30
+ }
31
+ async init() {
32
+ await super.init();
33
+ await this.initBaseFlags();
34
+ }
35
+ async initBaseFlags() {
36
+ const klass = super.ctor;
37
+ // ensure the base flags are available
38
+ klass.flags = { ...klass.flags, ...AppCommand.baseFlags };
39
+ const { flags } = await this.parse(klass);
40
+ // if the user passed an app path, use it: reload the app config
41
+ this.appConfig = appConfig(flags['app-path']);
42
+ this.appPath = this.appConfig.swDirPath;
43
+ this.debug(`config path loaded: ${this.appConfig.path}`);
44
+ }
45
+ }
@@ -0,0 +1,8 @@
1
+ import { RemoteAppCommand } from '../../remote-app-command.js';
2
+ export default class AppPull extends RemoteAppCommand {
3
+ static args: {
4
+ appId: import("@oclif/core/lib/interfaces/parser.js").Arg<string | undefined, Record<string, unknown>>;
5
+ };
6
+ static summary: string;
7
+ run(): Promise<void>;
8
+ }
@@ -0,0 +1,45 @@
1
+ import { Args } from '@oclif/core';
2
+ import { dasherize, pluralize, underscore } from 'inflection';
3
+ import snakeCase from 'lodash/snakeCase.js';
4
+ import * as path from 'node:path';
5
+ import { newConfig } from '../../lib/app-config.js';
6
+ import { ConfigPaths, writeJsonFile, } from '../../lib/apps/index.js';
7
+ import style from '../../lib/style.js';
8
+ import { RemoteAppCommand } from '../../remote-app-command.js';
9
+ export default class AppPull extends RemoteAppCommand {
10
+ static args = {
11
+ appId: Args.string({ description: 'app id to pull' }),
12
+ };
13
+ static summary = 'Pull app configuration from remote.';
14
+ async run() {
15
+ const { args } = await this.parse(AppPull);
16
+ const { appId } = args;
17
+ this.appPath = process.cwd();
18
+ if (!appId) {
19
+ this.error('You must provide an app id to pull');
20
+ }
21
+ this.log(`Will pull app configuration for ${style.appConfigValue(appId)} in ${this.appPath}`);
22
+ this.app = await this.getApp(appId);
23
+ this.debug('run: app', this.app);
24
+ // save app definition
25
+ const config = newConfig();
26
+ const appConfigJson = {
27
+ description: this.app.description || '',
28
+ id: snakeCase(this.app.name),
29
+ name: this.app.name,
30
+ version: this.app.version,
31
+ };
32
+ config.clear();
33
+ config.set(appConfigJson);
34
+ // save app configs
35
+ for (const config of this.app?.configs || []) {
36
+ if (!config)
37
+ continue;
38
+ const configType = config.type;
39
+ const typeDirName = ConfigPaths[configType];
40
+ const fileName = pluralize(dasherize(underscore(config.name)).toLowerCase());
41
+ const filePath = path.join(this.appPath, typeDirName, `${fileName}.json`);
42
+ writeJsonFile(filePath, config.values);
43
+ }
44
+ }
45
+ }
@@ -0,0 +1,12 @@
1
+ import { RemoteAppCommand } from '../../remote-app-command.js';
2
+ export default class AppUninstall extends RemoteAppCommand {
3
+ static description: string;
4
+ static flags: {
5
+ env: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
6
+ store: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
7
+ };
8
+ static orientation: {
9
+ env: string;
10
+ };
11
+ run(): Promise<void>;
12
+ }
@@ -0,0 +1,45 @@
1
+ import { confirm } from '@inquirer/prompts';
2
+ import { Flags } from '@oclif/core';
3
+ import config from '../../lib/config.js';
4
+ import style from '../../lib/style.js';
5
+ import { RemoteAppCommand } from '../../remote-app-command.js';
6
+ export default class AppUninstall extends RemoteAppCommand {
7
+ static description = 'Uninstalls an existing App';
8
+ static flags = {
9
+ env: Flags.string({
10
+ default: 'test',
11
+ description: 'environment to uninstall the app from',
12
+ }),
13
+ store: Flags.string({
14
+ char: 's',
15
+ description: 'store id to uninstall the app from',
16
+ }),
17
+ };
18
+ static orientation = {
19
+ env: 'test',
20
+ };
21
+ async run() {
22
+ const { flags } = await this.parse(AppUninstall);
23
+ const { env, store } = flags;
24
+ this.app = await this.getAppWithConfig();
25
+ const storeToUninstall = store || config.getDefaultStore();
26
+ config.ensureLoggedIn(storeToUninstall);
27
+ const uninstallMsg = `${style.appConfigValue(this.app.name)} from store ${style.storeId(storeToUninstall)} with version ${style.appConfigValue(this.app.version)}`;
28
+ const continueUninstall = await confirm({
29
+ message: `Uninstalling ${uninstallMsg}. Ok to continue?`,
30
+ });
31
+ if (!continueUninstall) {
32
+ this.logError('Uninstall cancelled');
33
+ return;
34
+ }
35
+ const clientApp = await this.api.get({ adminPath: `/client/apps/${this.app.id}` }, { query: { env } });
36
+ if (!clientApp) {
37
+ this.error(`Error uninstalling ${uninstallMsg}: app not installed on store.`);
38
+ }
39
+ if (clientApp.uninstalled) {
40
+ this.error(`Error uninstalling ${uninstallMsg}: app already uninstalled on store.`);
41
+ }
42
+ await this.api.put({ adminPath: `/client/apps/${this.app.id}` }, { body: { env, uninstalled: true } });
43
+ this.log(`${style.appConfigValue(this.app.name)} app uninstalled from store ${style.storeId(storeToUninstall)}.`);
44
+ }
45
+ }
@@ -0,0 +1,15 @@
1
+ import { RemoteAppCommand } from '../../remote-app-command.js';
2
+ export default class AppInfo extends RemoteAppCommand {
3
+ static args: {
4
+ versions: import("@oclif/core/lib/interfaces/parser.js").Arg<string | undefined, Record<string, unknown>>;
5
+ };
6
+ static description: string;
7
+ static examples: {
8
+ command: string;
9
+ description: string;
10
+ }[];
11
+ static summary: string;
12
+ run(): Promise<void>;
13
+ private showAppInfo;
14
+ private showVersions;
15
+ }
@@ -0,0 +1,92 @@
1
+ import { Args } from '@oclif/core';
2
+ import ora from 'ora';
3
+ import { getBorderCharacters, table } from 'table';
4
+ import { FieldDefinitions } from '../../lib/info.js';
5
+ import style from '../../lib/style.js';
6
+ import { RemoteAppCommand } from '../../remote-app-command.js';
7
+ // The app fields will be displayed in the order they are defined here
8
+ const FIELDS_TO_DISPLAY = [
9
+ 'name',
10
+ 'description',
11
+ 'version',
12
+ 'public_id',
13
+ 'published',
14
+ 'date_created',
15
+ 'date_updated',
16
+ 'installed',
17
+ 'client',
18
+ ];
19
+ export default class AppInfo extends RemoteAppCommand {
20
+ static args = {
21
+ versions: Args.string({ description: 'list all app versions' }),
22
+ };
23
+ static description = `The command shows the latest information about your app including the name,
24
+ description, version, public ID, test store, and more. If the ${style.command('versions')} argument is passed,
25
+ it will output all versions of the app instead.`;
26
+ static examples = [
27
+ {
28
+ command: 'swell app info',
29
+ description: 'Show general information about the app.',
30
+ },
31
+ {
32
+ command: 'swell app info versions',
33
+ description: 'Show all established versions of the app.',
34
+ },
35
+ ];
36
+ static summary = 'Show information about your Swell app.';
37
+ async run() {
38
+ const { args } = await this.parse(AppInfo);
39
+ const { versions } = args;
40
+ if (versions) {
41
+ await this.showVersions();
42
+ return;
43
+ }
44
+ await this.showAppInfo();
45
+ }
46
+ async showAppInfo() {
47
+ const spinner = ora();
48
+ spinner.start('Retrieving app info...');
49
+ this.app = await this.getAppWithConfig().catch((error) => {
50
+ spinner.fail(`Error fetching app info: ${JSON.stringify(this.app)}`);
51
+ throw error;
52
+ });
53
+ if (spinner.isSpinning) {
54
+ spinner.stop();
55
+ }
56
+ // we pull versions to make sure that what is displayed is the latest
57
+ const versions = await this.getVersions();
58
+ // Output the app field information, e.g.:
59
+ // Name: My App
60
+ const fieldDefinitions = new FieldDefinitions(this.app, versions);
61
+ for (const field of FIELDS_TO_DISPLAY) {
62
+ const fieldDef = fieldDefinitions.getFieldDefinition(field);
63
+ this.log(style.appConfigName(`${fieldDef.label}:`), fieldDef.value);
64
+ }
65
+ // dashboard url is built, not part of the app object
66
+ // we display it at the end for better readability and simplicity
67
+ this.log(style.appConfigName('Dashboard:'), style.link(this.dashboardUrl({}, this.app.client_id)));
68
+ }
69
+ async showVersions() {
70
+ const spinner = ora();
71
+ spinner.start('Retrieving app versions...');
72
+ this.app = await this.getAppWithConfig().catch((error) => {
73
+ spinner.fail(`Error fetching app info: ${JSON.stringify(this.app)}`);
74
+ throw error;
75
+ });
76
+ const versions = await this.handleRequestErrors(async () => this.getVersions(), () => spinner.fail('Error fetching app versions'));
77
+ if (versions.length === 0) {
78
+ spinner.warn('No versions found');
79
+ return;
80
+ }
81
+ spinner.succeed(`${this.app.name} app versions:\n`);
82
+ const displayData = versions.map((appVersion) => [
83
+ style.appConfigValue(appVersion.version),
84
+ appVersion.notes,
85
+ new Date(appVersion.date_created).toUTCString(),
86
+ ]);
87
+ this.log(table(displayData, {
88
+ border: getBorderCharacters('void'),
89
+ drawHorizontalLine: () => false,
90
+ }));
91
+ }
92
+ }
@@ -0,0 +1,17 @@
1
+ import { RemoteAppCommand } from '../../remote-app-command.js';
2
+ export default class AppInstall extends RemoteAppCommand {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {
6
+ env: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
7
+ store: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
8
+ version: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
9
+ };
10
+ static summary: string;
11
+ run(): Promise<void>;
12
+ private installApp;
13
+ private installConfirmationMessage;
14
+ private updateApp;
15
+ private getInstalledApp;
16
+ private confirmRemoveDevelopmentApp;
17
+ }
@@ -0,0 +1,177 @@
1
+ import { confirm } from '@inquirer/prompts';
2
+ import { Flags } from '@oclif/core';
3
+ import * as semver from 'semver';
4
+ import ora from 'ora';
5
+ import config from '../../lib/config.js';
6
+ import style from '../../lib/style.js';
7
+ import { selectLoggedInStoreId, selectEnvironmentId, } from '../../lib/stores.js';
8
+ import { RemoteAppCommand } from '../../remote-app-command.js';
9
+ export default class AppInstall extends RemoteAppCommand {
10
+ static description = `Without a version specified, install the latest version of the app.
11
+
12
+ Without a store specified, install the app in the Live environment of the current store.`;
13
+ static examples = [
14
+ 'swell install',
15
+ 'swell install -v 3.2.7',
16
+ 'swell install -v 1.0.0 -s my-store-id',
17
+ ];
18
+ static flags = {
19
+ env: Flags.string({
20
+ char: 'e',
21
+ description: 'target environment to install the app, defaults to live',
22
+ }),
23
+ store: Flags.string({
24
+ char: 's',
25
+ description: 'target store id to install the app, defaults to the current store',
26
+ }),
27
+ version: Flags.string({
28
+ char: 'v',
29
+ description: 'version of the app to install, defaults to latest',
30
+ async parse(input) {
31
+ if (!semver.valid(input)) {
32
+ throw new Error(`Invalid version: ${input}`);
33
+ }
34
+ return input;
35
+ },
36
+ }),
37
+ };
38
+ static summary = 'Install an existing app in another store environment.';
39
+ async run() {
40
+ const { flags } = await this.parse(AppInstall);
41
+ const { env, store, version } = flags;
42
+ this.app = await this.getAppWithConfig();
43
+ const versions = await this.getVersions(version);
44
+ if (versions.length === 0) {
45
+ this.error(`${version ? `Version ${version} not found` : 'No versions found'} for ${style.appConfigValue(this.app.name)} app. ${version
46
+ ? ''
47
+ : `Create a version with the ${style.command('swell version')} command first.`}`);
48
+ }
49
+ const versionToInstall = version
50
+ ? versions.find((ver) => ver.version === version)?.version
51
+ : versions[0].version;
52
+ let storeToInstall = store;
53
+ if (!storeToInstall) {
54
+ const loggedInStores = config.get('stores') || [];
55
+ if (loggedInStores.length > 0) {
56
+ storeToInstall = await selectLoggedInStoreId('Which store do you want to install the app in?');
57
+ }
58
+ if (!storeToInstall) {
59
+ return;
60
+ }
61
+ }
62
+ let envToInstall = env;
63
+ if (!envToInstall) {
64
+ envToInstall = await selectEnvironmentId();
65
+ }
66
+ if (envToInstall === 'live') {
67
+ envToInstall = undefined;
68
+ }
69
+ if (envToInstall && envToInstall !== 'test') {
70
+ this.error(`${envToInstall} is not a valid environment.`);
71
+ }
72
+ if (envToInstall === 'test' && storeToInstall === this.app.client_id) {
73
+ this.error(`You cannot install an app in the test environment of the same store.`);
74
+ }
75
+ config.ensureLoggedIn(storeToInstall);
76
+ this.api.setStoreEnv(storeToInstall, envToInstall);
77
+ // check if app is already installed on store
78
+ const existingInstalledApp = await this.getInstalledApp();
79
+ const existingVersion = existingInstalledApp?.id
80
+ ? existingInstalledApp.version
81
+ : null;
82
+ // confirm install unless params were passed explicitly
83
+ let extraMessage = '';
84
+ if (!store || !env) {
85
+ const continueInstall = await confirm({
86
+ message: this.installConfirmationMessage(storeToInstall, envToInstall, versionToInstall, existingVersion),
87
+ });
88
+ if (!continueInstall) {
89
+ // we don't want to throw an error as it is a valid exit
90
+ return;
91
+ }
92
+ }
93
+ else {
94
+ extraMessage = ` ${style.appConfigValue(this.app.name)} v${versionToInstall} on ${style.storeId(storeToInstall)} [${envToInstall ? envToInstall : 'live'}]`;
95
+ }
96
+ const shouldContinue = await this.confirmRemoveDevelopmentApp(storeToInstall);
97
+ if (!shouldContinue) {
98
+ return;
99
+ }
100
+ const spinner = ora();
101
+ // install or update app depending on whether it's already installed
102
+ let request;
103
+ if (existingInstalledApp?.id) {
104
+ spinner.start(`Updating${extraMessage}...`);
105
+ request = async () => this.updateApp(versionToInstall, envToInstall);
106
+ }
107
+ else {
108
+ spinner.start(`Installing${extraMessage}...`);
109
+ request = async () => this.installApp(versionToInstall, envToInstall);
110
+ }
111
+ const response = await this.handleRequestErrors(request, () => spinner.fail('Something went wrong.'));
112
+ if (response?.errors) {
113
+ spinner.fail(`Error installing app: ${JSON.stringify(response.errors)}`);
114
+ }
115
+ spinner.succeed(`Install complete. Manage app settings in your dashboard at ${style.link(this.dashboardUrl({ flags: { env: envToInstall } }, storeToInstall))}.`);
116
+ }
117
+ async installApp(version, env) {
118
+ return this.api.post({ adminPath: `/client/apps` },
119
+ // eslint-disable-next-line camelcase
120
+ { body: { app_id: this.app.id, version } });
121
+ }
122
+ installConfirmationMessage(storeToInstall, envToInstall, versionToInstall, existingVersion) {
123
+ const message = [];
124
+ if (existingVersion && semver.gt(versionToInstall, existingVersion)) {
125
+ message.push('Updating');
126
+ }
127
+ else if (existingVersion &&
128
+ semver.lt(versionToInstall, existingVersion)) {
129
+ message.push('Rolling back');
130
+ }
131
+ else {
132
+ message.push('Install');
133
+ }
134
+ let versionToShow = versionToInstall;
135
+ versionToShow += versionToInstall === this.app.version ? ' (latest)' : '';
136
+ message.push(style.appConfigValue(this.app.name), `v${versionToShow}`, `on ${style.storeId(storeToInstall)} [${envToInstall ? envToInstall : 'live'}]?`);
137
+ return message.join(' ');
138
+ }
139
+ async updateApp(version, env) {
140
+ await this.api.put({ adminPath: `/client/apps/${this.app.id}` }, { body: { version } });
141
+ }
142
+ async getInstalledApp() {
143
+ // check if app is already installed on store
144
+ return await this.api.get({ adminPath: `/client/apps/${this.app.id}` }, {});
145
+ }
146
+ async confirmRemoveDevelopmentApp(storeToInstall) {
147
+ const existingSourcedApps = await this.api.get({ adminPath: `/client/apps` }, {
148
+ query: { source_id: this.app.id },
149
+ });
150
+ if (existingSourcedApps.results[0]?.id) {
151
+ const continueInstall = await confirm({
152
+ message: `${style.storeId(storeToInstall)} has a development version of ${style.appConfigValue(this.app.name)}. Do you want to replace it with an installed instance?`,
153
+ default: false,
154
+ });
155
+ if (!continueInstall) {
156
+ return false;
157
+ }
158
+ // uninstall the existing app
159
+ const spinner = ora();
160
+ spinner.start('Removing development app...');
161
+ try {
162
+ await this.api.put({
163
+ adminPath: `/client/apps/${existingSourcedApps.results[0].app_id}`,
164
+ }, {
165
+ body: { uninstalled: true },
166
+ });
167
+ spinner.succeed('Development app removed.');
168
+ }
169
+ catch (err) {
170
+ spinner.fail(`Error removing development app from ${style.storeId(storeToInstall)}.`);
171
+ this.logError(err.message);
172
+ return false;
173
+ }
174
+ }
175
+ return true;
176
+ }
177
+ }
@@ -0,0 +1,17 @@
1
+ import { RemoteAppCommand } from '../../remote-app-command.js';
2
+ export default class AppPush extends RemoteAppCommand {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {
6
+ file: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
7
+ };
8
+ static orientation: {
9
+ env: string;
10
+ };
11
+ static summary: string;
12
+ run(): Promise<void>;
13
+ private logDashboardUrl;
14
+ private pushConfigPath;
15
+ private pushFile;
16
+ private confirmRemoveInstalledApp;
17
+ }