@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.
Files changed (91) hide show
  1. package/dist/app-command.d.ts +1 -1
  2. package/dist/app-command.js +8 -8
  3. package/dist/commands/app/dev.d.ts +18 -0
  4. package/dist/commands/app/dev.js +31 -0
  5. package/dist/commands/app/frontend/deploy.d.ts +17 -0
  6. package/dist/commands/app/frontend/deploy.js +44 -0
  7. package/dist/commands/app/frontend/dev.d.ts +21 -0
  8. package/dist/commands/app/frontend/dev.js +111 -0
  9. package/dist/commands/app/info.js +15 -8
  10. package/dist/commands/app/init.d.ts +12 -0
  11. package/dist/commands/app/init.js +34 -0
  12. package/dist/commands/app/install.d.ts +4 -4
  13. package/dist/commands/app/install.js +37 -42
  14. package/dist/commands/app/pull.d.ts +15 -0
  15. package/dist/commands/app/pull.js +50 -0
  16. package/dist/commands/app/push.d.ts +11 -14
  17. package/dist/commands/app/push.js +71 -143
  18. package/dist/commands/app/release.d.ts +13 -0
  19. package/dist/commands/app/release.js +150 -0
  20. package/dist/commands/app/version.d.ts +7 -6
  21. package/dist/commands/app/version.js +64 -43
  22. package/dist/commands/app/watch.d.ts +2 -4
  23. package/dist/commands/app/watch.js +3 -83
  24. package/dist/commands/create/app.d.ts +35 -11
  25. package/dist/commands/create/app.js +150 -144
  26. package/dist/commands/create/content.d.ts +2 -2
  27. package/dist/commands/create/content.js +10 -10
  28. package/dist/commands/create/frontend.d.ts +12 -0
  29. package/dist/commands/create/frontend.js +41 -0
  30. package/dist/commands/create/function.d.ts +3 -3
  31. package/dist/commands/create/function.js +22 -22
  32. package/dist/commands/create/model.d.ts +2 -2
  33. package/dist/commands/create/model.js +11 -11
  34. package/dist/commands/create/notification.d.ts +4 -4
  35. package/dist/commands/create/notification.js +38 -40
  36. package/dist/commands/login.d.ts +1 -1
  37. package/dist/commands/login.js +12 -9
  38. package/dist/commands/logs.js +2 -1
  39. package/dist/commands/theme/dev.d.ts +23 -0
  40. package/dist/commands/theme/dev.js +167 -0
  41. package/dist/commands/theme/init.d.ts +9 -0
  42. package/dist/commands/theme/init.js +15 -0
  43. package/dist/commands/theme/pull.d.ts +20 -0
  44. package/dist/commands/theme/pull.js +65 -0
  45. package/dist/commands/theme/push.d.ts +19 -0
  46. package/dist/commands/theme/push.js +92 -0
  47. package/dist/create-app-command.d.ts +20 -0
  48. package/dist/create-app-command.js +254 -0
  49. package/dist/create-collection-command.d.ts +9 -9
  50. package/dist/create-collection-command.js +23 -23
  51. package/dist/{create-command.d.ts → create-config-command.d.ts} +1 -1
  52. package/dist/{create-command.js → create-config-command.js} +9 -7
  53. package/dist/lib/api.d.ts +6 -4
  54. package/dist/lib/api.js +58 -29
  55. package/dist/lib/app-config.d.ts +13 -5
  56. package/dist/lib/app-config.js +60 -23
  57. package/dist/lib/apps/app-config.d.ts +22 -8
  58. package/dist/lib/apps/app-config.js +202 -68
  59. package/dist/lib/apps/index.d.ts +93 -13
  60. package/dist/lib/apps/index.js +150 -50
  61. package/dist/lib/apps/paths.d.ts +14 -7
  62. package/dist/lib/apps/paths.js +73 -28
  63. package/dist/lib/bundle.js +4 -5
  64. package/dist/lib/config.d.ts +5 -1
  65. package/dist/lib/config.js +14 -0
  66. package/dist/lib/constants.d.ts +5 -0
  67. package/dist/lib/constants.js +22 -3
  68. package/dist/lib/create/content.d.ts +2 -2
  69. package/dist/lib/create/content.js +1 -1
  70. package/dist/lib/create/function.d.ts +2 -2
  71. package/dist/lib/create/function.js +2 -2
  72. package/dist/lib/create/index.d.ts +1 -1
  73. package/dist/lib/create/index.js +11 -7
  74. package/dist/lib/create/model.d.ts +2 -2
  75. package/dist/lib/create/notification.d.ts +2 -2
  76. package/dist/lib/info.js +2 -2
  77. package/dist/lib/sessions.js +2 -2
  78. package/dist/lib/stores.d.ts +5 -2
  79. package/dist/lib/stores.js +9 -6
  80. package/dist/lib/style.d.ts +2 -0
  81. package/dist/lib/style.js +2 -0
  82. package/dist/lib/swell-function-wrapper.d.ts +2 -2
  83. package/dist/lib/swell-function-wrapper.js +2 -2
  84. package/dist/push-app-command.d.ts +73 -0
  85. package/dist/push-app-command.js +765 -0
  86. package/dist/remote-app-command.d.ts +35 -9
  87. package/dist/remote-app-command.js +436 -164
  88. package/oclif.manifest.json +841 -125
  89. package/package.json +13 -1
  90. package/dist/commands/app/_pull.d.ts +0 -8
  91. package/dist/commands/app/_pull.js +0 -45
@@ -1,20 +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[];
1
+ import { PushAppCommand } from '../../push-app-command.js';
2
+ export default class AppPush extends PushAppCommand {
5
3
  static args: {
6
4
  file: import("@oclif/core/lib/interfaces/parser.js").Arg<string | undefined, Record<string, unknown>>;
7
5
  };
8
- static flags: {
9
- file: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
10
- };
11
- static orientation: {
12
- env: string;
13
- };
6
+ static flags: any;
7
+ static orientation: any;
14
8
  static summary: string;
9
+ static description: string;
10
+ static examples: string[];
11
+ static delayOrientation: boolean;
15
12
  run(): Promise<void>;
16
- private logDashboardUrl;
17
- private pushConfigPath;
18
- private pushFile;
19
- private confirmRemoveInstalledApp;
13
+ resolvePushPaths(file: string): {
14
+ filePath: string;
15
+ relativePath: string;
16
+ };
20
17
  }
@@ -1,171 +1,99 @@
1
- import { confirm } from '@inquirer/prompts';
2
- import { Flags, Args } from '@oclif/core';
1
+ import { Args, Flags } from '@oclif/core';
3
2
  import * as path from 'node:path';
4
- import ora from 'ora';
5
- import { appConfigFileExists } from '../../lib/app-config.js';
6
- import { ConfigPaths, allConfigFilesPathsByType, appConfigFromFile, getConfigTypeFromPath, getConfigTypeKeyFromValue, } from '../../lib/apps/index.js';
3
+ import { ConfigPaths, isPathDirectory, filePathExists, } from '../../lib/apps/index.js';
7
4
  import style from '../../lib/style.js';
8
- import { RemoteAppCommand } from '../../remote-app-command.js';
9
- import { default as swellConfig } from '../../lib/config.js';
10
- export default class AppPush extends RemoteAppCommand {
11
- static description = `Push all app configurations, a specific file, or a specific configuration
12
- type to an app in your store's test environment. If the app does not exist yet,
13
- it will be created and its global ID saved to a .swellrc file.
14
-
15
- - If no file is specified, all configuration files will be pushed to the store.
16
- This includes the app icon (assets/icon.png) and swell.json.
17
- - If a file is specified, only that file will be pushed to the store.
18
- - If a directory is specified, only files in that directory will be pushed.
19
-
20
- App configuration directories:
21
- ${Object.values(ConfigPaths)
22
- .map((dir) => style.path(`${dir}/`))
23
- .join('\n')}`;
24
- static examples = [
25
- 'swell app push',
26
- 'swell app push -f content',
27
- 'swell app push -f models/products.json',
28
- ];
5
+ import { PushAppCommand } from '../../push-app-command.js';
6
+ export default class AppPush extends PushAppCommand {
29
7
  static args = {
30
8
  file: Args.string({
31
9
  char: 'f',
32
10
  description: 'relative path to a configuration file or directory to push',
33
11
  }),
34
12
  };
35
- // Deprecating the flags in favor of args
36
13
  static flags = {
37
- file: Flags.string({
38
- char: 'f',
39
- description: 'relative path to a configuration file or directory to push',
14
+ force: Flags.boolean({
15
+ description: 'force push all configurations',
16
+ default: false,
17
+ }),
18
+ 'no-deploy': Flags.boolean({
19
+ description: 'skip deploying app frontend',
40
20
  }),
41
21
  };
42
22
  static orientation = {
43
23
  env: 'test',
44
24
  };
45
- static summary = 'Push configuration files to your Swell app.';
25
+ static summary = 'Push local files to your Swell app.';
26
+ static description = `Push all app files, a specific file, or a specific configuration
27
+ type to an app in your store's test environment.
28
+
29
+ If the app does not exist, it will be created and its global ID saved to a .swellrc file.
30
+
31
+ - If no file is specified, all configuration files will be pushed to the store.
32
+ This includes the app icon (assets/icon.png) and swell.json.
33
+ - If a file is specified, only that file will be pushed to the store.
34
+ - If a directory is specified, only files in that directory will be pushed.
35
+
36
+ App file directories:
37
+ ${Object.values(ConfigPaths)
38
+ .map((dir) => style.path(`${dir}/`))
39
+ .join('\n')}`;
40
+ static examples = [
41
+ 'swell app push',
42
+ 'swell app push content',
43
+ 'swell app push models/products.json',
44
+ ];
45
+ static delayOrientation = true;
46
46
  async run() {
47
47
  const { args, flags } = await this.parse(AppPush);
48
- const { file: fileArg } = args;
49
- const { file: fileFlag } = flags;
50
- const file = fileArg || fileFlag;
51
- const currentStore = swellConfig.getDefaultStore();
52
- this.api.setStoreEnv(currentStore, 'test');
53
- this.app = await this.getAppWithConfig('').catch((_err) => {
54
- // Ignore so we can continue to create it
55
- return {};
56
- });
57
- const confirmExistingApp = await this.confirmRemoveInstalledApp(currentStore);
58
- if (confirmExistingApp === false) {
48
+ const { file } = args;
49
+ const { force } = flags;
50
+ const noDeploy = flags['no-deploy'];
51
+ if (this.swellConfig.store?.type === 'theme') {
52
+ await this.config.runCommand('theme:push', [
53
+ ...(file ? [file] : []),
54
+ ...(force ? ['--force'] : []),
55
+ '--sync-app',
56
+ ]);
59
57
  return;
60
58
  }
61
- // If app exists but owned by another client, offer to create a new instance
62
- if (this.app.client_id && this.app.client_id !== currentStore) {
63
- if (!confirmExistingApp?.id) {
64
- const otherAppName = this.app.name || this.appConfig.get('name');
65
- const confirmPushNew = await confirm({
66
- message: `${style.appConfigValue(otherAppName)} has a dev version in another environment. Do you want to create a new development instance in ${style.storeId(currentStore)}?`,
67
- default: false,
68
- });
69
- if (!confirmPushNew) {
70
- return;
71
- }
72
- }
73
- // create a new app
74
- this.app = await this.getCreateUpdateApp();
75
- if (!this.app) {
76
- return;
77
- }
78
- }
79
- else if (!file) {
80
- // update existing app if not targeting a file
81
- this.app = await this.getCreateUpdateApp(this.app);
59
+ await this.logOrientation();
60
+ if (!(await this.ensureAppExists(file))) {
61
+ return;
82
62
  }
83
63
  if (file) {
84
- const filePath = path.join(this.appPath, file);
85
- if (!appConfigFileExists(filePath)) {
86
- this.error(`Path ${style.path(file)} does not exist`);
87
- }
88
- const filePathParsed = path.parse(file);
89
- // if no directory is identified and no extension, it means the user
90
- // passed in a config directory
91
- if (!filePathParsed.dir && !filePathParsed.ext) {
92
- await this.pushConfigPath(filePathParsed.base);
93
- this.logDashboardUrl(flags, currentStore);
64
+ const { filePath, relativePath } = this.resolvePushPaths(file);
65
+ // Push individual files if not referring to the whole app path
66
+ if (filePath !== this.appPath) {
67
+ if (!filePathExists(filePath)) {
68
+ this.error(`Path ${style.path(file)} does not exist.`);
69
+ }
70
+ // Targeting a directory
71
+ if (isPathDirectory(filePath)) {
72
+ await this.pushFilePath(relativePath, force);
73
+ }
74
+ else {
75
+ await this.pushFile(relativePath);
76
+ }
77
+ this.log();
78
+ this.logDashboardUrl(flags);
94
79
  return;
95
80
  }
96
- await this.pushFile(filePath);
97
- this.logDashboardUrl(flags, currentStore);
98
- return;
99
- }
100
- const syncResult = await this.syncAppConfigs();
101
- const { installed, removed } = syncResult;
102
- this.log(`Pushed ${installed.length} ${installed.length === 1 ? 'configuration' : 'configurations'}${removed.length > 0 ? ` (removed ${removed.length}).` : '.'}`);
103
- this.logDashboardUrl(flags, currentStore);
104
- }
105
- logDashboardUrl(flags, storeId) {
106
- this.log(`View the ${style.appConfigValue(this.app.name)} app in your dashboard at ${style.link(this.dashboardUrl(flags, storeId))}.`);
107
- }
108
- async pushConfigPath(path) {
109
- const configType = getConfigTypeFromPath(path);
110
- const configTypeKey = getConfigTypeKeyFromValue(configType);
111
- if (!configTypeKey) {
112
- this.error(`Invalid config path: ${path}`);
113
81
  }
114
- let pushedFiles = 0;
115
- for (const { configFile } of allConfigFilesPathsByType(this.appPath, configTypeKey)) {
116
- const appConfig = appConfigFromFile(configFile, configType);
117
- // eslint-disable-next-line no-await-in-loop
118
- await this.pushConfigFile(appConfig);
119
- pushedFiles += 1;
120
- }
121
- if (pushedFiles === 0) {
122
- this.log(`No valid files to push found for ${style.path(path)}.`);
123
- }
124
- else {
125
- this.log(`Pushed ${style.path(path)} to ${style.appConfigValue(this.app.name)}.`);
82
+ await this.pushAppConfigs(force);
83
+ if (!noDeploy) {
84
+ await this.deployAppFrontend(force);
85
+ this.log();
126
86
  }
87
+ this.logDashboardUrl(flags);
127
88
  }
128
- async pushFile(file) {
129
- const configType = getConfigTypeFromPath(path.basename(path.dirname(file)));
130
- if (!configType) {
131
- this.error(`File ${file} is not a valid file to push.`);
132
- }
133
- const appConfig = appConfigFromFile(file, configType);
134
- await this.pushConfigFile(appConfig);
135
- }
136
- async confirmRemoveInstalledApp(currentStore) {
137
- if (!this.app.private_id) {
138
- return true;
139
- }
140
- const existingInstalledApp = await this.api.get({
141
- adminPath: `/client/apps/${this.app.private_id}`,
142
- }, { query: { versioned: true } });
143
- if (existingInstalledApp) {
144
- const continuePush = await confirm({
145
- message: `${style.storeId(currentStore)} has an installed version of ${style.appConfigValue(this.app.name)} v${existingInstalledApp.version}. Do you want to replace it with a development instance?`,
146
- default: false,
147
- });
148
- if (!continuePush) {
149
- return false;
150
- }
151
- // uninstall the existing app
152
- const spinner = ora();
153
- spinner.start('Removing installed app...');
154
- try {
155
- await this.api.put({
156
- adminPath: `/client/apps/${this.app.id}`,
157
- }, {
158
- body: { uninstalled: true },
159
- });
160
- spinner.succeed('Installed app removed.');
161
- this.log();
162
- }
163
- catch (err) {
164
- spinner.fail(`Error removing installed app from ${style.storeId(currentStore)}.`);
165
- this.logError(err.message);
166
- return false;
167
- }
89
+ resolvePushPaths(file) {
90
+ // Join the appPath and file to get the absolute filePath
91
+ const filePath = path.resolve(process.cwd(), file);
92
+ // Check if filePath is outside of appPath
93
+ const relativePath = path.relative(this.appPath, filePath);
94
+ if (relativePath.startsWith('..') || path.isAbsolute(relativePath)) {
95
+ this.error(`Path ${style.path(file)} is outside of the current app directory.`);
168
96
  }
169
- return existingInstalledApp;
97
+ return { filePath, relativePath };
170
98
  }
171
99
  }
@@ -0,0 +1,13 @@
1
+ import { PushAppCommand } from '../../push-app-command.js';
2
+ export default class AppRelease extends PushAppCommand {
3
+ static args: {
4
+ version: import("@oclif/core/lib/interfaces/parser.js").Arg<string | undefined, Record<string, unknown>>;
5
+ };
6
+ static flags: any;
7
+ static summary: string;
8
+ static description: string;
9
+ static examples: string[];
10
+ run(): Promise<void>;
11
+ private validateCreateVersion;
12
+ validateReleaseDetails(): void;
13
+ }
@@ -0,0 +1,150 @@
1
+ import { Args, Flags } from '@oclif/core';
2
+ import { CLIError } from '@oclif/errors';
3
+ import { confirm } from '@inquirer/prompts';
4
+ import ora from 'ora';
5
+ import * as fs from 'node:fs';
6
+ import * as semver from 'semver';
7
+ import style from '../../lib/style.js';
8
+ import { PushAppCommand } from '../../push-app-command.js';
9
+ export default class AppRelease extends PushAppCommand {
10
+ static args = {
11
+ version: Args.string({
12
+ description: 'semver version to release',
13
+ async parse(input) {
14
+ const parsedInput = semver.valid(input);
15
+ if (parsedInput) {
16
+ return parsedInput;
17
+ }
18
+ throw new CLIError(`Invalid version: ${input}`);
19
+ },
20
+ }),
21
+ };
22
+ static flags = {
23
+ 'release-notes': Flags.string({
24
+ description: 'relative path to a file containing release notes',
25
+ }),
26
+ message: Flags.string({
27
+ char: 'm',
28
+ description: 'description of the changes in this version for internal use',
29
+ }),
30
+ amend: Flags.boolean({
31
+ description: 'amend the description or release notes of an existing release',
32
+ }),
33
+ 'not-supported': Flags.boolean({
34
+ description: 'indicates the version is not officially supported by the developer',
35
+ }),
36
+ 'no-git-tag': Flags.boolean({
37
+ description: 'if the version is being created, do not create a git tag',
38
+ }),
39
+ };
40
+ static summary = 'Release a new version of your an app for publishing to the App Store.';
41
+ static description = `App released are reviewed by the Swell team for quality and functionality before publishing.
42
+
43
+ If the version does not exist, you will be prompted to create it.
44
+
45
+ Use the --amend flag to update the description or release notes of an existing release version.
46
+
47
+ Use the --supported=false flag to indicate the version is not officially supported by the developer.
48
+
49
+ The app must be connected to a partner account with access to publish.`;
50
+ static examples = [
51
+ 'swell app release',
52
+ 'swell app release 2.0.0',
53
+ 'swell app release 2.0.0 --amend --release-notes ./releases/v2.0.0.md',
54
+ ];
55
+ async run() {
56
+ const { args, flags } = await this.parse(AppRelease);
57
+ const { amend, 'release-notes': releaseNotesFile, 'not-supported': notSupported, } = flags;
58
+ if (!(await this.ensureAppExists(undefined, false))) {
59
+ return;
60
+ }
61
+ let releaseNotes;
62
+ if (releaseNotesFile) {
63
+ try {
64
+ releaseNotes = fs.readFileSync(releaseNotesFile, 'utf8');
65
+ }
66
+ catch {
67
+ this.error(`Release notes could not be loaded from \`${releaseNotesFile}\``);
68
+ }
69
+ }
70
+ const appVersion = await this.validateCreateVersion(args.version, flags);
71
+ if (!appVersion) {
72
+ return;
73
+ }
74
+ if (appVersion.released && !amend) {
75
+ throw new Error(`Version ${appVersion.version} has already been released. Use --amend to update version details.`);
76
+ }
77
+ const releaseData = {
78
+ description: flags.message ? flags.message : appVersion.description,
79
+ release_notes: releaseNotesFile ? releaseNotes : appVersion.release_notes,
80
+ supported: notSupported
81
+ ? false
82
+ : appVersion.supported === undefined
83
+ ? true
84
+ : appVersion.supported,
85
+ };
86
+ if (!amend) {
87
+ const continueVersion = await confirm({
88
+ message: `Release ${style.appConfigValue(this.app.name)} version ${appVersion.version}?`,
89
+ });
90
+ if (!continueVersion) {
91
+ return;
92
+ }
93
+ }
94
+ const spinner = ora();
95
+ this.log();
96
+ spinner.start(`${amend ? 'Updating release' : 'Releasing'} version ${style.appConfigValue(appVersion.version)}`);
97
+ await this.updateVersion(appVersion.version, {
98
+ ...releaseData,
99
+ released: true,
100
+ }, spinner);
101
+ spinner.succeed(`${style.appConfigValue(this.app.name)} release ${appVersion.version} ${amend ? 'updated' : 'released'}.`);
102
+ }
103
+ async validateCreateVersion(inputVersion, flags) {
104
+ let version = inputVersion;
105
+ if (!version) {
106
+ version = this.app.version;
107
+ if (!version) {
108
+ throw new Error('No version specified');
109
+ }
110
+ }
111
+ this.validateReleaseDetails();
112
+ let appVersion = await this.getVersion(version);
113
+ if (!appVersion) {
114
+ if (!flags.amend) {
115
+ await this.config.runCommand('app:version', [
116
+ version,
117
+ ...(flags.message ? [`-m ${JSON.stringify(flags.message)}`] : []),
118
+ ...(flags['no-git-tag'] ? ['--no-git-tag'] : []),
119
+ ]);
120
+ this.log();
121
+ appVersion = await this.getVersion(version);
122
+ if (!appVersion) {
123
+ throw new Error(`An error occurred while creating version ${version}.`);
124
+ }
125
+ }
126
+ else {
127
+ throw new Error(`Version ${version} does not exist.`);
128
+ }
129
+ }
130
+ return appVersion;
131
+ }
132
+ validateReleaseDetails() {
133
+ if (!this.app.description) {
134
+ throw new Error('App must have a description');
135
+ }
136
+ const requiresPreviewImages = this.app.type === 'storefront' &&
137
+ (!this.app.kind || this.app.kind === 'shop') &&
138
+ this.app.storefront?.theme.provider !== 'app';
139
+ if (requiresPreviewImages) {
140
+ if (!this.app.preview_image) {
141
+ throw new Error('Storefront app must have a preview image to release');
142
+ }
143
+ }
144
+ else {
145
+ if (this.app.type !== 'theme' && !this.app.logo_icon) {
146
+ throw new Error('App must have a logo icon to release');
147
+ }
148
+ }
149
+ }
150
+ }
@@ -1,18 +1,19 @@
1
- import { RemoteAppCommand } from '../../remote-app-command.js';
2
- export default class AppVersion extends RemoteAppCommand {
1
+ import { PushAppCommand } from '../../push-app-command.js';
2
+ export default class AppVersion extends PushAppCommand {
3
3
  static args: {
4
4
  nextVersion: import("@oclif/core/lib/interfaces/parser.js").Arg<string | undefined, Record<string, unknown>>;
5
5
  };
6
- static description: string;
7
- static examples: string[];
8
6
  static flags: {
9
- 'git-tag': import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
10
7
  message: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
8
+ amend: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
9
+ 'no-git-tag': import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
11
10
  };
12
11
  static summary: string;
12
+ static description: string;
13
+ static examples: string[];
13
14
  run(): Promise<void>;
14
15
  private gitCommit;
15
16
  private gitTag;
16
- private updateRemote;
17
+ private createRemoteVersion;
17
18
  private validateAndComputeVersion;
18
19
  }
@@ -2,10 +2,10 @@ import { confirm, input } from '@inquirer/prompts';
2
2
  import git from '@npmcli/git';
3
3
  import { Args, Flags } from '@oclif/core';
4
4
  import { CLIError } from '@oclif/errors';
5
- import * as semver from 'semver';
6
5
  import ora from 'ora';
6
+ import * as semver from 'semver';
7
7
  import style from '../../lib/style.js';
8
- import { RemoteAppCommand } from '../../remote-app-command.js';
8
+ import { PushAppCommand } from '../../push-app-command.js';
9
9
  const RELEASE_TYPES = [
10
10
  'major',
11
11
  'premajor',
@@ -15,8 +15,7 @@ const RELEASE_TYPES = [
15
15
  'prepatch',
16
16
  'prerelease',
17
17
  ];
18
- const TAG_VERSION_PREFIX = 'v';
19
- export default class AppVersion extends RemoteAppCommand {
18
+ export default class AppVersion extends PushAppCommand {
20
19
  static args = {
21
20
  nextVersion: Args.string({
22
21
  description: `semver version or release type: ${[
@@ -35,6 +34,19 @@ export default class AppVersion extends RemoteAppCommand {
35
34
  },
36
35
  }),
37
36
  };
37
+ static flags = {
38
+ message: Flags.string({
39
+ char: 'm',
40
+ description: 'description of the changes in this version for internal use',
41
+ }),
42
+ amend: Flags.boolean({
43
+ description: 'amend the description an existing version',
44
+ }),
45
+ 'no-git-tag': Flags.boolean({
46
+ description: 'do not create a git tag for this version',
47
+ }),
48
+ };
49
+ static summary = 'Increment the version of your Swell app.';
38
50
  static description = `When executed without specifying NEXTVERSION, the command will display the current
39
51
  version of your app.
40
52
 
@@ -45,47 +57,51 @@ The version change will be committed to the Git repository using the provided
45
57
  message or a default similar to the tag, e.g. "v1.0.1".
46
58
 
47
59
  By default, it creates a Git tag for the specified version. You can disable this
48
- behavior by using the --git-tag=false option.`;
60
+ behavior by using the --no-git-tag option.`;
49
61
  static examples = [
50
62
  'swell app version',
51
63
  'swell app version minor',
52
64
  'swell app version 1.0.0 -m "First release"',
53
65
  ];
54
- static flags = {
55
- 'git-tag': Flags.boolean({
56
- default: true,
57
- description: 'create a git tag for this version (default true)',
58
- }),
59
- message: Flags.string({
60
- char: 'm',
61
- description: 'description of the changes in this version',
62
- }),
63
- };
64
- static summary = 'Increment the version of your Swell app.';
65
66
  async run() {
66
67
  const { args, flags } = await this.parse(AppVersion);
67
- const { 'git-tag': gitTag } = flags;
68
+ const { amend, 'no-git-tag': noGitTag } = flags;
68
69
  let { message } = flags;
69
70
  let { nextVersion } = args;
70
- // load app from remote
71
- this.app = await this.getAppWithConfig();
71
+ if (!(await this.ensureAppExists(undefined, false))) {
72
+ return;
73
+ }
72
74
  const remoteVersion = this.app.version;
73
- const versions = await this.getVersions();
74
- // if the user didn't set a version, show the current one
75
+ if (amend) {
76
+ const amendVersion = nextVersion || remoteVersion;
77
+ const appVersion = await this.getVersion(amendVersion);
78
+ if (appVersion) {
79
+ await this.updateVersion(amendVersion, {
80
+ description: message,
81
+ });
82
+ }
83
+ else {
84
+ this.error(`Version ${amendVersion} does not exist yet.`);
85
+ }
86
+ return;
87
+ }
88
+ // If the user didn't specify a version, show the latest one
75
89
  if (!nextVersion) {
76
- if (versions.length > 0) {
77
- const lastVersion = versions[0].version;
78
- this.log(`${style.appConfigValue(this.app.name)} latest version is ${style.appConfigValue(lastVersion)}`);
90
+ const latestVersion = await this.getVersion();
91
+ if (latestVersion) {
92
+ this.log(`${style.appConfigValue(this.app.name)} latest remote version is ${style.appConfigValue(latestVersion.version)}.`);
79
93
  }
80
94
  else {
81
- this.log(`${style.appConfigValue(this.app.name)} has not been versioned yet`);
95
+ this.error(`${style.appConfigValue(this.app.name)} has not been versioned yet.`);
82
96
  }
83
97
  return;
84
98
  }
85
99
  nextVersion = this.validateAndComputeVersion(nextVersion, remoteVersion);
100
+ await this.pushAppConfigs();
101
+ await this.deployAppFrontend(false, false);
86
102
  if (!message) {
87
103
  message = await input({
88
- message: 'Describe the changes in this version',
104
+ message: `Describe the changes in version ${nextVersion}:`,
89
105
  });
90
106
  }
91
107
  const continueVersion = await confirm({
@@ -95,18 +111,19 @@ behavior by using the --git-tag=false option.`;
95
111
  return;
96
112
  }
97
113
  if (!message) {
98
- message = `${TAG_VERSION_PREFIX}${nextVersion}`;
114
+ message = nextVersion;
99
115
  }
116
+ this.log();
100
117
  const spinner = ora();
101
- spinner.start('Creating version...');
118
+ spinner.start('Creating version');
102
119
  try {
103
- await this.updateRemote(nextVersion, message, spinner);
120
+ await this.createRemoteVersion(nextVersion, message, spinner);
104
121
  await this.gitCommit(message);
105
- if (gitTag)
122
+ if (!noGitTag)
106
123
  await this.gitTag(nextVersion);
107
124
  }
108
- catch (err) {
109
- spinner.fail(err.message);
125
+ catch (error) {
126
+ spinner.fail(error.message);
110
127
  return;
111
128
  }
112
129
  spinner.succeed(`${style.appConfigValue(this.app.name)} version ${nextVersion} created.`);
@@ -114,30 +131,36 @@ behavior by using the --git-tag=false option.`;
114
131
  async gitCommit(message) {
115
132
  // check if there are changes in the app config file
116
133
  // if there are no changes, there is nothing to commit
117
- const configStatus = await git.spawn(['status', this.appConfig.path], {
118
- cwd: this.appConfig.cwd,
134
+ const configStatus = await git.spawn(['status', this.swellConfig.path], {
135
+ cwd: this.swellConfig.cwd,
119
136
  });
120
137
  if (configStatus.stdout.includes('nothing to commit')) {
121
138
  return;
122
139
  }
123
140
  // add the app config file to commit - this will contain the new version
124
- await git.spawn(['add', this.appConfig.path]);
141
+ await git.spawn(['add', this.swellConfig.path]);
125
142
  // commit the version change to git. It skips the git hooks with -n
126
143
  await git.spawn(['commit', '-n', '-m', message]);
127
144
  }
128
145
  async gitTag(nextVersion) {
129
- const tag = `${TAG_VERSION_PREFIX}${nextVersion}`;
146
+ const tag = nextVersion;
130
147
  // tag the version in git
131
148
  await git.spawn(['tag', tag], { cwd: process.cwd() });
132
149
  }
133
- async updateRemote(nextVersion, message, spinner) {
150
+ async createRemoteVersion(nextVersion, message, spinner) {
134
151
  // update the version on the remote
135
- const versionResponse = await this.api.post({ adminPath: `/apps/${this.app.id}/versions` }, { body: { description: message, version: nextVersion } });
152
+ const versionResponse = await this.api.post({ adminPath: `/apps/${this.app.id}/versions` }, {
153
+ body: { description: message, version: nextVersion },
154
+ spinner,
155
+ onAsyncGetPath: (response) => ({
156
+ adminPath: `/apps/${this.app.id}/versions/${response.id}`,
157
+ }),
158
+ });
136
159
  if (!versionResponse || !versionResponse.version) {
137
160
  spinner.fail('Something went wrong.');
138
161
  }
139
162
  // update the local app config file since remote was successful
140
- this.appConfig.set('version', nextVersion);
163
+ this.swellConfig.set('version', nextVersion);
141
164
  }
142
165
  validateAndComputeVersion(nextVersion, remoteVersion) {
143
166
  let version = nextVersion;
@@ -146,10 +169,8 @@ behavior by using the --git-tag=false option.`;
146
169
  // increment the version
147
170
  version = semver.inc(remoteVersion, version);
148
171
  }
149
- this.debug(`next version: ${version}`, `remote version: ${remoteVersion}`);
150
- if (semver.eq(version, remoteVersion) ||
151
- semver.lt(version, remoteVersion)) {
152
- this.error(`The version you are trying to create ${version} needs to be greater than the current version ${remoteVersion}`);
172
+ if (semver.lt(version, remoteVersion)) {
173
+ this.error(`Version ${version} must be greater than the current version ${remoteVersion}.`);
153
174
  }
154
175
  return version;
155
176
  }