@swell/cli 2.9.14 → 2.9.15

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.
@@ -115,10 +115,17 @@ The app must be connected to a partner account with access to publish.`;
115
115
  const spinner = ora();
116
116
  this.log();
117
117
  spinner.start(`${amend ? 'Updating release' : 'Releasing'} version ${style.appConfigValue(appVersion.version)}`);
118
- await this.updateVersion(appVersion.version, {
119
- ...releaseData,
120
- released: true,
121
- }, spinner);
118
+ try {
119
+ await this.updateVersion(appVersion.version, {
120
+ ...releaseData,
121
+ released: true,
122
+ });
123
+ }
124
+ catch (error) {
125
+ spinner.fail(error.message);
126
+ this.logReleaseDetailsHint(error);
127
+ return this.exit(1);
128
+ }
122
129
  spinner.succeed(`${style.appConfigValue(this.app.name)} release ${appVersion.version} ${amend ? 'updated' : 'released'}.`);
123
130
  }
124
131
  async validateCreateVersion(inputVersion, flags) {
@@ -138,7 +145,8 @@ The app must be connected to a partner account with access to publish.`;
138
145
  else {
139
146
  await this.config.runCommand('app:version', [
140
147
  version,
141
- ...(flags.message ? [`-m ${JSON.stringify(flags.message)}`] : []),
148
+ '--validate-release',
149
+ ...(flags.message ? ['-m', flags.message] : []),
142
150
  ...(flags['no-git-tag'] ? ['--no-git-tag'] : []),
143
151
  ...(flags.yes ? ['-y'] : []),
144
152
  ]);
@@ -10,6 +10,7 @@ export default class AppVersion extends PushAppCommand {
10
10
  'force-create-files': import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
11
11
  message: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
12
12
  'no-git-tag': import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
13
+ 'validate-release': import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
13
14
  yes: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
14
15
  };
15
16
  static orientation: {
@@ -64,6 +64,10 @@ behavior by using the --no-git-tag option.`;
64
64
  'no-git-tag': Flags.boolean({
65
65
  description: 'do not create a git tag for this version',
66
66
  }),
67
+ 'validate-release': Flags.boolean({
68
+ description: 'validate the app has all details required to release before creating the version',
69
+ hidden: true,
70
+ }),
67
71
  yes: Flags.boolean({
68
72
  char: 'y',
69
73
  description: 'skip confirmation prompt',
@@ -75,7 +79,7 @@ behavior by using the --no-git-tag option.`;
75
79
  static summary = 'Increment the version of your Swell app.';
76
80
  async run() {
77
81
  const { args, flags } = await this.parse(AppVersion);
78
- const { amend, 'no-git-tag': noGitTag, yes, 'force-create-files': forceCreateFiles = false, } = flags;
82
+ const { amend, 'no-git-tag': noGitTag, yes, 'force-create-files': forceCreateFiles = false, 'validate-release': validateRelease = false, } = flags;
79
83
  let { message } = flags;
80
84
  let { nextVersion } = args;
81
85
  if (!(await this.ensureAppExists(undefined, false))) {
@@ -116,7 +120,7 @@ behavior by using the --no-git-tag option.`;
116
120
  }
117
121
  return;
118
122
  }
119
- nextVersion = this.validateAndComputeVersion(nextVersion, localVersion);
123
+ nextVersion = await this.validateAndComputeVersion(nextVersion, localVersion);
120
124
  await this.pushAppConfigs();
121
125
  await this.deployAppFrontend(false, false);
122
126
  if (!message) {
@@ -139,11 +143,15 @@ behavior by using the --no-git-tag option.`;
139
143
  const spinner = ora();
140
144
  spinner.start('Creating version');
141
145
  try {
142
- await this.createRemoteVersion(nextVersion, message, spinner, forceCreateFiles);
146
+ await this.createRemoteVersion(nextVersion, message, spinner, {
147
+ forceCreateFiles,
148
+ validateRelease,
149
+ });
143
150
  }
144
151
  catch (error) {
145
152
  spinner.fail(error.stderr || error.message);
146
- return;
153
+ this.logReleaseDetailsHint(error);
154
+ return this.exit(1);
147
155
  }
148
156
  spinner.succeed(`${style.appConfigValue(this.app.name)} version ${nextVersion} created.`);
149
157
  // git is optional: commit and tag only when the app is inside a repository
@@ -161,7 +169,7 @@ behavior by using the --no-git-tag option.`;
161
169
  this.log(style.dim('Not a git repository, skipping git commit and tag.'));
162
170
  }
163
171
  }
164
- async createRemoteVersion(nextVersion, message, spinner, forceCreateFiles = false) {
172
+ async createRemoteVersion(nextVersion, message, spinner, { forceCreateFiles = false, validateRelease = false } = {}) {
165
173
  const body = {
166
174
  description: message,
167
175
  version: nextVersion,
@@ -169,6 +177,9 @@ behavior by using the --no-git-tag option.`;
169
177
  if (forceCreateFiles) {
170
178
  body.$force_create_files = true;
171
179
  }
180
+ if (validateRelease) {
181
+ body.$validate_release = true;
182
+ }
172
183
  // update the version on the remote
173
184
  const versionResponse = await this.api.post({ adminPath: `/apps/${this.app.id}/versions` }, {
174
185
  body,
@@ -203,16 +214,21 @@ behavior by using the --no-git-tag option.`;
203
214
  // tag the version in git
204
215
  await git.spawn(['tag', tag], { cwd: this.appPath });
205
216
  }
206
- validateAndComputeVersion(nextVersion, localVersion) {
217
+ async validateAndComputeVersion(nextVersion, localVersion) {
207
218
  let version = nextVersion;
208
219
  // compute the value of the next version and check if it's valid
209
220
  if (RELEASE_TYPES.includes(nextVersion)) {
210
221
  // increment the version
211
222
  version = semver.inc(localVersion, version);
212
223
  }
213
- if (semver.lte(version, localVersion)) {
224
+ if (semver.lt(version, localVersion)) {
214
225
  this.error(`Version ${version} must be greater than the current version ${localVersion}.`);
215
226
  }
227
+ // swell.json may declare a version that has not been created yet,
228
+ // e.g. the first version of a new app. Only reject it if it already exists.
229
+ if (semver.eq(version, localVersion) && (await this.getVersion(version))) {
230
+ this.error(`Version ${version} already exists.`);
231
+ }
216
232
  return version;
217
233
  }
218
234
  }
package/dist/lib/api.d.ts CHANGED
@@ -21,7 +21,7 @@ export default class Api {
21
21
  put(pathOpts: Api.Paths, options?: Api.RequestOptions): Promise<any>;
22
22
  delete(pathOpts: Api.Paths, options?: Api.RequestOptions): Promise<any>;
23
23
  isTestEnvEnabled(): Promise<boolean>;
24
- setEnv(envId: string): Promise<void>;
24
+ setEnv(envId: string, storeId?: string): Promise<void>;
25
25
  setStoreEnv(storeId: string, envId?: string): Promise<void>;
26
26
  setPublicKey(publicKey?: string): Promise<void>;
27
27
  private isFrontend;
package/dist/lib/api.js CHANGED
@@ -2,7 +2,7 @@ import fetch from 'node-fetch';
2
2
  import { stringify } from 'qs';
3
3
  import { getCurrentAppSlugId, hasAppContext } from './apps/index.js';
4
4
  import config from './config.js';
5
- import { API_BASE_URL, getAdminApiBaseUrl, getFrontendApiBaseUrl, } from './constants.js';
5
+ import { API_BASE_URL, getAdminApiBaseUrl, getFrontendApiBaseUrl, getLoginHost, } from './constants.js';
6
6
  import style from './style.js';
7
7
  export var HttpMethod;
8
8
  (function (HttpMethod) {
@@ -190,16 +190,21 @@ export default class Api {
190
190
  });
191
191
  return clientRecord.test_enabled !== false;
192
192
  }
193
- async setEnv(envId) {
193
+ async setEnv(envId, storeId) {
194
194
  const hasTestEnv = await this.isTestEnvEnabled();
195
195
  if (!hasTestEnv) {
196
- throw new Error('Test environment is not enabled for this store.');
196
+ const targetStoreId = storeId || this.storeId || config.getDefaultStore();
197
+ const settingsUrl = `${getLoginHost(targetStoreId)}/admin/settings/general`;
198
+ throw new Error(`Test environment is not enabled for this store.
199
+
200
+ Enable it in the dashboard under Settings > General > Test environment:
201
+ ${settingsUrl}`);
197
202
  }
198
203
  this.envId = envId;
199
204
  }
200
205
  async setStoreEnv(storeId, envId) {
201
206
  if (envId) {
202
- await this.setEnv(envId);
207
+ await this.setEnv(envId, storeId);
203
208
  }
204
209
  this.storeId = storeId;
205
210
  this.envId = envId;
@@ -76,6 +76,7 @@ export declare abstract class PushAppCommand extends RemoteAppCommand {
76
76
  }): void;
77
77
  logStorefrontConnected(): void;
78
78
  logStorefrontFrontendUrl(storefront?: any, branchId?: string): void;
79
+ logReleaseDetailsHint(error: any): void;
79
80
  pushFile(relativePath: string): Promise<void>;
80
81
  pushFilePath(relativePath: string, force?: boolean): Promise<void>;
81
82
  resolveAppPath(appId?: string, targetPath?: string): Promise<string>;
@@ -672,6 +672,14 @@ export class PushAppCommand extends RemoteAppCommand {
672
672
  const currentStore = localConfig.getDefaultStore();
673
673
  this.log(`View your storefront at ${style.link(this.storefrontFrontendUrl(currentStore, storefront, branchId))}.`);
674
674
  }
675
+ // Tell the user how to recover when the server rejects a release
676
+ // because app details are missing
677
+ logReleaseDetailsHint(error) {
678
+ if (!String(error?.message).includes('to release')) {
679
+ return;
680
+ }
681
+ this.log(style.dim(`Update swell.json and assets as needed, run ${style.command('swell app push')}, then run ${style.command('swell app release')} again.`));
682
+ }
675
683
  async pushFile(relativePath) {
676
684
  const relativePathPosix = toPosixPath(relativePath);
677
685
  const basePath = relativePathPosix.split('/')[0];
@@ -1359,6 +1359,13 @@
1359
1359
  "allowNo": false,
1360
1360
  "type": "boolean"
1361
1361
  },
1362
+ "validate-release": {
1363
+ "description": "validate the app has all details required to release before creating the version",
1364
+ "hidden": true,
1365
+ "name": "validate-release",
1366
+ "allowNo": false,
1367
+ "type": "boolean"
1368
+ },
1362
1369
  "yes": {
1363
1370
  "char": "y",
1364
1371
  "description": "skip confirmation prompt",
@@ -3594,5 +3601,5 @@
3594
3601
  ]
3595
3602
  }
3596
3603
  },
3597
- "version": "2.9.14"
3604
+ "version": "2.9.15"
3598
3605
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swell/cli",
3
- "version": "2.9.14",
3
+ "version": "2.9.15",
4
4
  "type": "module",
5
5
  "description": "Swell's command line interface/utility",
6
6
  "keywords": [