@swell/cli 2.9.30 → 2.9.31
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/delete.d.ts +33 -0
- package/dist/commands/app/delete.js +179 -0
- package/oclif.manifest.json +54 -1
- package/package.json +1 -1
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { RemoteAppCommand } from '../../remote-app-command.js';
|
|
2
|
+
export default class AppDelete extends RemoteAppCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
confirm: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
7
|
+
yes: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
8
|
+
};
|
|
9
|
+
static orientation: {
|
|
10
|
+
env: string;
|
|
11
|
+
};
|
|
12
|
+
static summary: string;
|
|
13
|
+
run(): Promise<void>;
|
|
14
|
+
private deleteRequest;
|
|
15
|
+
private ensureDeleted;
|
|
16
|
+
private errorTheme;
|
|
17
|
+
/**
|
|
18
|
+
* Remove .swellrc when it holds the deleted app's id, so the next push
|
|
19
|
+
* creates a new app. Another id is left alone.
|
|
20
|
+
* @param {string} appId the deleted app
|
|
21
|
+
* @returns {Promise<boolean>} false when .swellrc names another app
|
|
22
|
+
*/
|
|
23
|
+
private removeSwellRc;
|
|
24
|
+
private errorBlocked;
|
|
25
|
+
/**
|
|
26
|
+
* The app named by swell.json, with the reasons it can't be deleted.
|
|
27
|
+
* Only the store that owns it may delete it.
|
|
28
|
+
* @param {string} privateId app id from swell.json
|
|
29
|
+
* @param {string} storeId the logged in store
|
|
30
|
+
* @returns {Promise<any>} the app
|
|
31
|
+
*/
|
|
32
|
+
private getOwnedApp;
|
|
33
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { confirm } from '@inquirer/prompts';
|
|
2
|
+
import { Flags } from '@oclif/core';
|
|
3
|
+
import * as fs from 'node:fs/promises';
|
|
4
|
+
import { readRcFile } from '../../lib/app-config.js';
|
|
5
|
+
import config from '../../lib/config.js';
|
|
6
|
+
import style from '../../lib/style.js';
|
|
7
|
+
import { RemoteAppCommand } from '../../remote-app-command.js';
|
|
8
|
+
export default class AppDelete extends RemoteAppCommand {
|
|
9
|
+
static description = `Deletes the app from the store that owns it, to free a slot under the store's app limit.
|
|
10
|
+
|
|
11
|
+
The app's versions, configs, functions and its development install in the test environment are removed. Records the app created in the store are not deleted.
|
|
12
|
+
|
|
13
|
+
An app can't be deleted while it is installed anywhere else, if it is linked to a partner, or if it was ever published. The command lists what blocks it.
|
|
14
|
+
|
|
15
|
+
After deleting, .swellrc is removed, so the next \`swell app push\` creates a new app.`;
|
|
16
|
+
static examples = [
|
|
17
|
+
'swell app delete',
|
|
18
|
+
'swell app delete --confirm',
|
|
19
|
+
'swell app delete --confirm -y',
|
|
20
|
+
];
|
|
21
|
+
static flags = {
|
|
22
|
+
confirm: Flags.boolean({
|
|
23
|
+
description: 'confirm deleting the app, instead of being asked',
|
|
24
|
+
}),
|
|
25
|
+
yes: Flags.boolean({
|
|
26
|
+
char: 'y',
|
|
27
|
+
description: 'skip all prompts; requires --confirm',
|
|
28
|
+
}),
|
|
29
|
+
};
|
|
30
|
+
static orientation = {
|
|
31
|
+
env: 'test',
|
|
32
|
+
};
|
|
33
|
+
static summary = 'Delete an app your store owns.';
|
|
34
|
+
async run() {
|
|
35
|
+
const { flags } = await this.parse(AppDelete);
|
|
36
|
+
const { confirm: confirmed, yes } = flags;
|
|
37
|
+
if (yes && !confirmed) {
|
|
38
|
+
this.error(`Missing --confirm for non-interactive mode.
|
|
39
|
+
|
|
40
|
+
Deleting an app can't be undone.
|
|
41
|
+
|
|
42
|
+
Example: ${style.command('swell app delete --confirm -y')}`, { exit: 1 });
|
|
43
|
+
}
|
|
44
|
+
if (!confirmed && !process.stdin.isTTY) {
|
|
45
|
+
this.error(`Missing --confirm: there is no terminal to ask for confirmation.
|
|
46
|
+
|
|
47
|
+
Example: ${style.command('swell app delete --confirm')}`, { exit: 1 });
|
|
48
|
+
}
|
|
49
|
+
if (this.swellConfig.get('type') === 'theme') {
|
|
50
|
+
this.errorTheme();
|
|
51
|
+
}
|
|
52
|
+
const privateId = this.app.private_id;
|
|
53
|
+
if (!privateId) {
|
|
54
|
+
this.error(`App ${style.appConfigName('id')} is not defined in swell.json.`, { exit: 1 });
|
|
55
|
+
}
|
|
56
|
+
const storeId = config.getDefaultStore();
|
|
57
|
+
config.ensureLoggedIn(storeId);
|
|
58
|
+
const app = await this.getOwnedApp(privateId, storeId);
|
|
59
|
+
const appLabel = `${style.appName(app.name)} (${app.id})`;
|
|
60
|
+
if (app.type === 'theme') {
|
|
61
|
+
this.errorTheme();
|
|
62
|
+
}
|
|
63
|
+
const blockers = app.delete_blockers || [];
|
|
64
|
+
if (blockers.length > 0) {
|
|
65
|
+
this.errorBlocked(appLabel, blockers);
|
|
66
|
+
}
|
|
67
|
+
if (!confirmed) {
|
|
68
|
+
const continueDelete = await confirm({
|
|
69
|
+
default: false,
|
|
70
|
+
message: `Delete ${app.name} from store ${storeId}? This can't be undone.`,
|
|
71
|
+
});
|
|
72
|
+
if (!continueDelete) {
|
|
73
|
+
this.log('Delete cancelled.');
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// The BFF hands out a key, and deletes only when it is sent back
|
|
78
|
+
const challenge = await this.deleteRequest(app.id);
|
|
79
|
+
this.ensureDeleted(appLabel, challenge);
|
|
80
|
+
if (!challenge?.confirm_delete_key) {
|
|
81
|
+
this.error(`Unable to delete app ${appLabel}: no confirmation key.`);
|
|
82
|
+
}
|
|
83
|
+
const result = await this.deleteRequest(app.id, challenge.confirm_delete_key);
|
|
84
|
+
this.ensureDeleted(appLabel, result);
|
|
85
|
+
const swellRcRemoved = await this.removeSwellRc(app.id);
|
|
86
|
+
this.log(`✓ Deleted app ${appLabel}`);
|
|
87
|
+
this.log(` Store: ${style.storeId(storeId)}`);
|
|
88
|
+
if (!swellRcRemoved) {
|
|
89
|
+
this.log(` .swellrc kept: it names another app`);
|
|
90
|
+
}
|
|
91
|
+
this.log();
|
|
92
|
+
this.log(`Run ${style.command('swell app push')} to create it again as a new app.`);
|
|
93
|
+
}
|
|
94
|
+
async deleteRequest(id, confirmDeleteKey) {
|
|
95
|
+
try {
|
|
96
|
+
return await this.api.delete({ adminPath: `/apps/${id}` }, confirmDeleteKey
|
|
97
|
+
? { body: { confirm_delete_key: confirmDeleteKey } }
|
|
98
|
+
: {});
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
return this.error(`Unable to delete app: ${errorMessage(error)}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
ensureDeleted(appLabel, response) {
|
|
105
|
+
const error = response?.errors?.app;
|
|
106
|
+
if (error?.blockers?.length > 0) {
|
|
107
|
+
this.errorBlocked(appLabel, error.blockers);
|
|
108
|
+
}
|
|
109
|
+
if (response?.errors) {
|
|
110
|
+
const messages = Object.values(response.errors).map((err) => err?.message || String(err));
|
|
111
|
+
this.error(`Unable to delete app ${appLabel}: ${messages.join(' ')}`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
errorTheme() {
|
|
115
|
+
this.error(`Themes can't be deleted with ${style.command('swell app delete')}. Delete an uploaded theme from the store's storefronts in the dashboard.`, { exit: 1 });
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Remove .swellrc when it holds the deleted app's id, so the next push
|
|
119
|
+
* creates a new app. Another id is left alone.
|
|
120
|
+
* @param {string} appId the deleted app
|
|
121
|
+
* @returns {Promise<boolean>} false when .swellrc names another app
|
|
122
|
+
*/
|
|
123
|
+
async removeSwellRc(appId) {
|
|
124
|
+
let rc;
|
|
125
|
+
try {
|
|
126
|
+
rc = await readRcFile(this.swellRcPath());
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
if (error.code === 'ENOENT') {
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
throw error;
|
|
133
|
+
}
|
|
134
|
+
if (rc.id && rc.id !== appId) {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
await fs.rm(this.swellRcPath(), { force: true });
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
errorBlocked(appLabel, blockers) {
|
|
141
|
+
this.error([
|
|
142
|
+
`App ${appLabel} can't be deleted:`,
|
|
143
|
+
...blockers.map((blocker) => ` - ${blocker.message}`),
|
|
144
|
+
].join('\n'), { exit: 1 });
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* The app named by swell.json, with the reasons it can't be deleted.
|
|
148
|
+
* Only the store that owns it may delete it.
|
|
149
|
+
* @param {string} privateId app id from swell.json
|
|
150
|
+
* @param {string} storeId the logged in store
|
|
151
|
+
* @returns {Promise<any>} the app
|
|
152
|
+
*/
|
|
153
|
+
async getOwnedApp(privateId, storeId) {
|
|
154
|
+
let app;
|
|
155
|
+
try {
|
|
156
|
+
app = await this.api.get({ adminPath: `/apps/${privateId}` }, { query: { delete_blockers: true } });
|
|
157
|
+
}
|
|
158
|
+
catch (error) {
|
|
159
|
+
const message = errorMessage(error);
|
|
160
|
+
if (message === 'App not found') {
|
|
161
|
+
this.error(`App ${style.appConfigValue(privateId)} was not found on store ${style.storeId(storeId)}. Nothing to delete.`, { exit: 1 });
|
|
162
|
+
}
|
|
163
|
+
this.error(`Unable to get app: ${message}`);
|
|
164
|
+
}
|
|
165
|
+
if (!app?.id || app.client_id !== storeId) {
|
|
166
|
+
this.error(`App ${style.appConfigValue(privateId)} is not owned by store ${style.storeId(storeId)}. Only the store that owns an app can delete it.`, { exit: 1 });
|
|
167
|
+
}
|
|
168
|
+
return app;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
function errorMessage(error) {
|
|
172
|
+
try {
|
|
173
|
+
const json = JSON.parse(error.message);
|
|
174
|
+
return typeof json.error === 'string' ? json.error : json.error.message;
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
return error.message;
|
|
178
|
+
}
|
|
179
|
+
}
|
package/oclif.manifest.json
CHANGED
|
@@ -721,6 +721,59 @@
|
|
|
721
721
|
"_uninstall.js"
|
|
722
722
|
]
|
|
723
723
|
},
|
|
724
|
+
"app:delete": {
|
|
725
|
+
"aliases": [],
|
|
726
|
+
"args": {},
|
|
727
|
+
"description": "Deletes the app from the store that owns it, to free a slot under the store's app limit.\n\nThe app's versions, configs, functions and its development install in the test environment are removed. Records the app created in the store are not deleted.\n\nAn app can't be deleted while it is installed anywhere else, if it is linked to a partner, or if it was ever published. The command lists what blocks it.\n\nAfter deleting, .swellrc is removed, so the next `swell app push` creates a new app.",
|
|
728
|
+
"examples": [
|
|
729
|
+
"swell app delete",
|
|
730
|
+
"swell app delete --confirm",
|
|
731
|
+
"swell app delete --confirm -y"
|
|
732
|
+
],
|
|
733
|
+
"flags": {
|
|
734
|
+
"app-path": {
|
|
735
|
+
"description": "Path to your app directory",
|
|
736
|
+
"name": "app-path",
|
|
737
|
+
"hasDynamicHelp": false,
|
|
738
|
+
"multiple": false,
|
|
739
|
+
"type": "option"
|
|
740
|
+
},
|
|
741
|
+
"confirm": {
|
|
742
|
+
"description": "confirm deleting the app, instead of being asked",
|
|
743
|
+
"name": "confirm",
|
|
744
|
+
"allowNo": false,
|
|
745
|
+
"type": "boolean"
|
|
746
|
+
},
|
|
747
|
+
"yes": {
|
|
748
|
+
"char": "y",
|
|
749
|
+
"description": "skip all prompts; requires --confirm",
|
|
750
|
+
"name": "yes",
|
|
751
|
+
"allowNo": false,
|
|
752
|
+
"type": "boolean"
|
|
753
|
+
}
|
|
754
|
+
},
|
|
755
|
+
"hasDynamicHelp": false,
|
|
756
|
+
"hiddenAliases": [],
|
|
757
|
+
"id": "app:delete",
|
|
758
|
+
"pluginAlias": "@swell/cli",
|
|
759
|
+
"pluginName": "@swell/cli",
|
|
760
|
+
"pluginType": "core",
|
|
761
|
+
"strict": true,
|
|
762
|
+
"summary": "Delete an app your store owns.",
|
|
763
|
+
"enableJsonFlag": false,
|
|
764
|
+
"requiresSwellConfig": true,
|
|
765
|
+
"delayOrientation": false,
|
|
766
|
+
"orientation": {
|
|
767
|
+
"env": "test"
|
|
768
|
+
},
|
|
769
|
+
"isESM": true,
|
|
770
|
+
"relativePath": [
|
|
771
|
+
"dist",
|
|
772
|
+
"commands",
|
|
773
|
+
"app",
|
|
774
|
+
"delete.js"
|
|
775
|
+
]
|
|
776
|
+
},
|
|
724
777
|
"app:dev": {
|
|
725
778
|
"aliases": [],
|
|
726
779
|
"args": {},
|
|
@@ -3685,5 +3738,5 @@
|
|
|
3685
3738
|
]
|
|
3686
3739
|
}
|
|
3687
3740
|
},
|
|
3688
|
-
"version": "2.9.
|
|
3741
|
+
"version": "2.9.31"
|
|
3689
3742
|
}
|