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