@swell/cli 2.9.13 → 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.
- package/dist/commands/app/push.js +2 -2
- package/dist/commands/app/release.js +13 -5
- package/dist/commands/app/version.d.ts +1 -0
- package/dist/commands/app/version.js +42 -14
- package/dist/lib/api.d.ts +1 -1
- package/dist/lib/api.js +9 -4
- package/dist/lib/apps/index.d.ts +4 -1
- package/dist/push-app-command.d.ts +1 -0
- package/dist/push-app-command.js +8 -0
- package/oclif.manifest.json +9 -2
- package/package.json +1 -1
|
@@ -17,8 +17,8 @@ type to an app in your store's test environment.
|
|
|
17
17
|
If the app does not exist, it will be created and its global ID saved to a .swellrc file.
|
|
18
18
|
|
|
19
19
|
- If no file is specified, all configuration files will be pushed to the store.
|
|
20
|
-
This includes the app icon (assets/icon.png),
|
|
21
|
-
and swell.json.
|
|
20
|
+
This includes the app icon (assets/icon.png), screenshots (assets/screenshots/),
|
|
21
|
+
README.md, and swell.json.
|
|
22
22
|
- If a file is specified, only that file will be pushed to the store.
|
|
23
23
|
- If a directory is specified, only files in that directory will be pushed.
|
|
24
24
|
|
|
@@ -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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
-
|
|
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,18 +143,33 @@ 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,
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
+
await this.createRemoteVersion(nextVersion, message, spinner, {
|
|
147
|
+
forceCreateFiles,
|
|
148
|
+
validateRelease,
|
|
149
|
+
});
|
|
146
150
|
}
|
|
147
151
|
catch (error) {
|
|
148
152
|
spinner.fail(error.stderr || error.message);
|
|
149
|
-
|
|
153
|
+
this.logReleaseDetailsHint(error);
|
|
154
|
+
return this.exit(1);
|
|
150
155
|
}
|
|
151
156
|
spinner.succeed(`${style.appConfigValue(this.app.name)} version ${nextVersion} created.`);
|
|
157
|
+
// git is optional: commit and tag only when the app is inside a repository
|
|
158
|
+
if (await git.find({ cwd: this.appPath })) {
|
|
159
|
+
try {
|
|
160
|
+
await this.gitCommit(message);
|
|
161
|
+
if (!noGitTag)
|
|
162
|
+
await this.gitTag(nextVersion);
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
this.log(style.funcWarn(`Git commit/tag failed: ${(error.stderr || error.message).trim()}`));
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
this.log(style.dim('Not a git repository, skipping git commit and tag.'));
|
|
170
|
+
}
|
|
152
171
|
}
|
|
153
|
-
async createRemoteVersion(nextVersion, message, spinner, forceCreateFiles = false) {
|
|
172
|
+
async createRemoteVersion(nextVersion, message, spinner, { forceCreateFiles = false, validateRelease = false } = {}) {
|
|
154
173
|
const body = {
|
|
155
174
|
description: message,
|
|
156
175
|
version: nextVersion,
|
|
@@ -158,6 +177,9 @@ behavior by using the --no-git-tag option.`;
|
|
|
158
177
|
if (forceCreateFiles) {
|
|
159
178
|
body.$force_create_files = true;
|
|
160
179
|
}
|
|
180
|
+
if (validateRelease) {
|
|
181
|
+
body.$validate_release = true;
|
|
182
|
+
}
|
|
161
183
|
// update the version on the remote
|
|
162
184
|
const versionResponse = await this.api.post({ adminPath: `/apps/${this.app.id}/versions` }, {
|
|
163
185
|
body,
|
|
@@ -173,34 +195,40 @@ behavior by using the --no-git-tag option.`;
|
|
|
173
195
|
this.swellConfig.set('version', nextVersion);
|
|
174
196
|
}
|
|
175
197
|
async gitCommit(message) {
|
|
198
|
+
const cwd = this.appPath;
|
|
176
199
|
// check if there are changes in the app config file
|
|
177
200
|
// if there are no changes, there is nothing to commit
|
|
178
201
|
const configStatus = await git.spawn(['status', this.swellConfig.path], {
|
|
179
|
-
cwd
|
|
202
|
+
cwd,
|
|
180
203
|
});
|
|
181
204
|
if (configStatus.stdout.includes('nothing to commit')) {
|
|
182
205
|
return;
|
|
183
206
|
}
|
|
184
207
|
// add the app config file to commit - this will contain the new version
|
|
185
|
-
await git.spawn(['add', this.swellConfig.path]);
|
|
208
|
+
await git.spawn(['add', this.swellConfig.path], { cwd });
|
|
186
209
|
// commit the version change to git. It skips the git hooks with -n
|
|
187
|
-
await git.spawn(['commit', '-n', '-m', message]);
|
|
210
|
+
await git.spawn(['commit', '-n', '-m', message], { cwd });
|
|
188
211
|
}
|
|
189
212
|
async gitTag(nextVersion) {
|
|
190
213
|
const tag = nextVersion;
|
|
191
214
|
// tag the version in git
|
|
192
|
-
await git.spawn(['tag', tag], { cwd:
|
|
215
|
+
await git.spawn(['tag', tag], { cwd: this.appPath });
|
|
193
216
|
}
|
|
194
|
-
validateAndComputeVersion(nextVersion, localVersion) {
|
|
217
|
+
async validateAndComputeVersion(nextVersion, localVersion) {
|
|
195
218
|
let version = nextVersion;
|
|
196
219
|
// compute the value of the next version and check if it's valid
|
|
197
220
|
if (RELEASE_TYPES.includes(nextVersion)) {
|
|
198
221
|
// increment the version
|
|
199
222
|
version = semver.inc(localVersion, version);
|
|
200
223
|
}
|
|
201
|
-
if (semver.
|
|
224
|
+
if (semver.lt(version, localVersion)) {
|
|
202
225
|
this.error(`Version ${version} must be greater than the current version ${localVersion}.`);
|
|
203
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
|
+
}
|
|
204
232
|
return version;
|
|
205
233
|
}
|
|
206
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
|
-
|
|
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;
|
package/dist/lib/apps/index.d.ts
CHANGED
|
@@ -16,7 +16,6 @@ export interface App {
|
|
|
16
16
|
[key: string]: string;
|
|
17
17
|
};
|
|
18
18
|
configs?: AppConfig[];
|
|
19
|
-
cover_image?: unknown;
|
|
20
19
|
date_created?: string;
|
|
21
20
|
date_updated?: string;
|
|
22
21
|
demo_url?: string;
|
|
@@ -39,6 +38,10 @@ export interface App {
|
|
|
39
38
|
title: string;
|
|
40
39
|
}>;
|
|
41
40
|
id?: string;
|
|
41
|
+
images?: Array<{
|
|
42
|
+
file?: unknown;
|
|
43
|
+
id: string;
|
|
44
|
+
}>;
|
|
42
45
|
installed?: {
|
|
43
46
|
date_created: string;
|
|
44
47
|
id: string;
|
|
@@ -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>;
|
package/dist/push-app-command.js
CHANGED
|
@@ -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];
|
package/oclif.manifest.json
CHANGED
|
@@ -1180,7 +1180,7 @@
|
|
|
1180
1180
|
"name": "file"
|
|
1181
1181
|
}
|
|
1182
1182
|
},
|
|
1183
|
-
"description": "Push all app files, a specific file, or a specific configuration\ntype to an app in your store's test environment.\n\nIf the app does not exist, it will be created and its global ID saved to a .swellrc file.\n\n- If no file is specified, all configuration files will be pushed to the store.\n This includes the app icon (assets/icon.png),
|
|
1183
|
+
"description": "Push all app files, a specific file, or a specific configuration\ntype to an app in your store's test environment.\n\nIf the app does not exist, it will be created and its global ID saved to a .swellrc file.\n\n- If no file is specified, all configuration files will be pushed to the store.\n This includes the app icon (assets/icon.png), screenshots (assets/screenshots/),\n README.md, and swell.json.\n- If a file is specified, only that file will be pushed to the store.\n- If a directory is specified, only files in that directory will be pushed.\n\nApp file directories:\nassets/\ncontent/\nfrontend/\ncomponents/\nfunctions/\nmodels/\nnotifications/\nsettings/\ntheme/\nwebhooks/",
|
|
1184
1184
|
"examples": [
|
|
1185
1185
|
"swell app push",
|
|
1186
1186
|
"swell app push content",
|
|
@@ -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.
|
|
3604
|
+
"version": "2.9.15"
|
|
3598
3605
|
}
|