@swell/cli 2.9.8 → 2.9.9

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.
@@ -17,7 +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) and swell.json.
20
+ This includes the app icon (assets/icon.png), cover image (assets/image.png),
21
+ and swell.json.
21
22
  - If a file is specified, only that file will be pushed to the store.
22
23
  - If a directory is specified, only files in that directory will be pushed.
23
24
 
@@ -11,6 +11,5 @@ export default class AppRelease extends PushAppCommand {
11
11
  };
12
12
  static summary: string;
13
13
  run(): Promise<void>;
14
- validateReleaseDetails(): void;
15
14
  private validateCreateVersion;
16
15
  }
@@ -53,7 +53,7 @@ The app must be connected to a partner account with access to publish.`;
53
53
  }),
54
54
  yes: Flags.boolean({
55
55
  char: 'y',
56
- description: 'skip confirmation prompt',
56
+ description: 'skip confirmation prompts, including unstaging another staged version',
57
57
  }),
58
58
  };
59
59
  static orientation = {
@@ -82,6 +82,20 @@ The app must be connected to a partner account with access to publish.`;
82
82
  if (appVersion.released && !amend) {
83
83
  throw new Error(`Version ${appVersion.version} has already been released. Use --amend to update version details.`);
84
84
  }
85
+ const stagedVersion = this.app.staged_props?.version;
86
+ const hasOtherStagedVersion = !amend && stagedVersion && stagedVersion !== appVersion.version;
87
+ if (hasOtherStagedVersion) {
88
+ this.log(style.funcWarn(`Version ${stagedVersion} is currently staged. Releasing version ${appVersion.version} will unstage it.`));
89
+ if (!yes) {
90
+ const continueUnstage = await confirm({
91
+ default: false,
92
+ message: `Continue?`,
93
+ });
94
+ if (!continueUnstage) {
95
+ return;
96
+ }
97
+ }
98
+ }
85
99
  const releaseData = {
86
100
  description: message || appVersion.description,
87
101
  release_notes: releaseNotesFile ? releaseNotes : appVersion.release_notes,
@@ -108,22 +122,6 @@ The app must be connected to a partner account with access to publish.`;
108
122
  }, spinner);
109
123
  spinner.succeed(`${style.appConfigValue(this.app.name)} release ${appVersion.version} ${amend ? 'updated' : 'released'}.`);
110
124
  }
111
- validateReleaseDetails() {
112
- if (!this.app.description) {
113
- throw new Error('App must have a description');
114
- }
115
- const requiresPreviewImages = this.app.type === 'storefront' &&
116
- (!this.app.kind || this.app.kind === 'shop') &&
117
- this.app.storefront?.theme.provider !== 'app';
118
- if (requiresPreviewImages) {
119
- if (!this.app.preview_image) {
120
- throw new Error('Storefront app must have a preview image to release');
121
- }
122
- }
123
- else if (this.app.type !== 'theme' && !this.app.logo_icon) {
124
- throw new Error('App must have a logo icon to release');
125
- }
126
- }
127
125
  async validateCreateVersion(inputVersion, flags) {
128
126
  let version = inputVersion;
129
127
  if (!version) {
@@ -132,7 +130,7 @@ The app must be connected to a partner account with access to publish.`;
132
130
  throw new Error('No version specified');
133
131
  }
134
132
  }
135
- this.validateReleaseDetails();
133
+ // Mandatory app details are validated server-side when the version is released.
136
134
  let appVersion = await this.getVersion(version);
137
135
  if (!appVersion) {
138
136
  if (flags.amend) {
@@ -12,6 +12,9 @@ const schema = {
12
12
  integrations: { type: 'array' },
13
13
  name: { type: 'string' },
14
14
  permissions: { type: 'array' },
15
+ price: { type: 'number', minimum: 0 },
16
+ price_external: { type: 'boolean' },
17
+ price_interval: { type: 'string', enum: ['once', 'monthly'] },
15
18
  storefront: { type: 'object' },
16
19
  theme: { type: 'object' },
17
20
  type: { type: 'string' },
@@ -16,9 +16,12 @@ export interface App {
16
16
  [key: string]: string;
17
17
  };
18
18
  configs?: AppConfig[];
19
+ cover_image?: unknown;
19
20
  date_created?: string;
20
21
  date_updated?: string;
22
+ demo_url?: string;
21
23
  description?: string;
24
+ documentation_url?: string;
22
25
  extensions?: any[];
23
26
  features?: string[];
24
27
  frontend?: {
@@ -29,6 +32,7 @@ export interface App {
29
32
  service?: string;
30
33
  url?: string;
31
34
  };
35
+ full_description?: string;
32
36
  highlights?: Array<{
33
37
  id: string;
34
38
  image_src?: unknown;
@@ -45,10 +49,19 @@ export interface App {
45
49
  owned?: boolean;
46
50
  preview_image?: unknown;
47
51
  preview_mobile_image?: unknown;
52
+ price?: number;
53
+ price_external?: boolean;
54
+ price_interval?: 'once' | 'monthly';
55
+ preview_video_url?: string;
48
56
  private_id?: string;
49
57
  public_id?: string;
50
58
  published?: boolean;
51
59
  purchase_options?: string[];
60
+ repository_url?: string;
61
+ staged_props?: {
62
+ [key: string]: any;
63
+ version?: string;
64
+ };
52
65
  storefront?: {
53
66
  [key: string]: any;
54
67
  };
@@ -60,6 +73,8 @@ export interface App {
60
73
  preview_image?: unknown;
61
74
  preview_mobile_image?: unknown;
62
75
  }>;
76
+ support_email?: string;
77
+ support_url?: string;
63
78
  theme?: {
64
79
  [key: string]: any;
65
80
  };
@@ -74,6 +89,9 @@ export declare enum SwellJsonFields {
74
89
  KIND = "kind",
75
90
  NAME = "name",
76
91
  PERMISSIONS = "permissions",
92
+ PRICE = "price",
93
+ PRICE_EXTERNAL = "price_external",
94
+ PRICE_INTERVAL = "price_interval",
77
95
  STOREFRONT = "storefront",
78
96
  THEME = "theme",
79
97
  TYPE = "type",
@@ -15,6 +15,9 @@ export var SwellJsonFields;
15
15
  SwellJsonFields["KIND"] = "kind";
16
16
  SwellJsonFields["NAME"] = "name";
17
17
  SwellJsonFields["PERMISSIONS"] = "permissions";
18
+ SwellJsonFields["PRICE"] = "price";
19
+ SwellJsonFields["PRICE_EXTERNAL"] = "price_external";
20
+ SwellJsonFields["PRICE_INTERVAL"] = "price_interval";
18
21
  SwellJsonFields["STOREFRONT"] = "storefront";
19
22
  SwellJsonFields["THEME"] = "theme";
20
23
  SwellJsonFields["TYPE"] = "type";
@@ -26,7 +26,7 @@ export declare abstract class PushAppCommand extends RemoteAppCommand {
26
26
  createAppStorefront(hasOtherStorefronts?: boolean, nonInteractive?: boolean): Promise<any>;
27
27
  deployAppFrontend(force?: boolean, log?: boolean): Promise<void>;
28
28
  deployFrontend(projectType: FrontendProjectType): Promise<string>;
29
- ensureAppExists(file?: string, shouldCreate?: boolean): Promise<boolean>;
29
+ ensureAppExists(file?: string, shouldCreate?: boolean, force?: boolean): Promise<boolean>;
30
30
  ensureLoggedIn(): Promise<void>;
31
31
  exec(command: string, cwd?: string, onOutput?: (string: string) => any | false): Promise<void>;
32
32
  execFrontend(command: string, onOutput?: (string: string) => any | false): Promise<void>;
@@ -282,7 +282,7 @@ export class PushAppCommand extends RemoteAppCommand {
282
282
  this.error(`Error deploying frontend: ${error.message}`);
283
283
  }
284
284
  }
285
- async ensureAppExists(file, shouldCreate = true) {
285
+ async ensureAppExists(file, shouldCreate = true, force = false) {
286
286
  const klass = this.ctor;
287
287
  const currentStore = localConfig.getDefaultStore();
288
288
  await this.ensureLoggedIn();
@@ -311,14 +311,14 @@ export class PushAppCommand extends RemoteAppCommand {
311
311
  // If app exists but owned by another client, create a new instance
312
312
  if (app?.client_id && app.client_id !== currentStore) {
313
313
  // Create a new app
314
- this.app = await this.getCreateUpdateApp();
314
+ this.app = await this.getCreateUpdateApp(undefined, force);
315
315
  if (!this.app) {
316
316
  return false;
317
317
  }
318
318
  }
319
319
  else if (!file) {
320
320
  // Update existing app if not targeting a file
321
- this.app = await this.getCreateUpdateApp(app);
321
+ this.app = await this.getCreateUpdateApp(app, force);
322
322
  }
323
323
  return true;
324
324
  }
@@ -24,7 +24,7 @@ export declare abstract class RemoteAppCommand extends AppCommand {
24
24
  protected getApp(id?: string): Promise<App>;
25
25
  protected getAppWithConfig(id?: string): Promise<App>;
26
26
  getConfigData(configId: string): Promise<any>;
27
- protected getCreateUpdateApp(updateApp?: App | null): Promise<App>;
27
+ protected getCreateUpdateApp(updateApp?: App | null, force?: boolean): Promise<App>;
28
28
  protected getInstalledApp(): Promise<any>;
29
29
  protected getLocalAppConfigs(): Promise<AppConfig[]>;
30
30
  protected getVersion(version?: null | string | undefined): Promise<AppVersion>;
@@ -172,7 +172,7 @@ export class RemoteAppCommand extends AppCommand {
172
172
  adminPath: `/apps/${this.app.id}/configs/${configId}/data`,
173
173
  }, { query: { ...(themeId ? { theme_id: themeId } : undefined) } });
174
174
  }
175
- async getCreateUpdateApp(updateApp) {
175
+ async getCreateUpdateApp(updateApp, force = false) {
176
176
  const spinner = ora();
177
177
  let sourceApp;
178
178
  try {
@@ -195,6 +195,9 @@ export class RemoteAppCommand extends AppCommand {
195
195
  ? { private_id: this.swellConfig.store.id }
196
196
  : undefined),
197
197
  ...(sourceApp?.id ? { source_id: sourceApp.id } : undefined),
198
+ // Overwrite name/description/logo_icon on the server
199
+ // instead of only filling them in when empty
200
+ ...(force ? { $force_meta: true } : undefined),
198
201
  };
199
202
  const appLabel = this.app.type === 'theme' ? 'theme' : 'app';
200
203
  let app = updateApp || {};
@@ -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) 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/",
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), cover image (assets/image.png),\n 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",
@@ -1285,7 +1285,7 @@
1285
1285
  },
1286
1286
  "yes": {
1287
1287
  "char": "y",
1288
- "description": "skip confirmation prompt",
1288
+ "description": "skip confirmation prompts, including unstaging another staged version",
1289
1289
  "name": "yes",
1290
1290
  "allowNo": false,
1291
1291
  "type": "boolean"
@@ -3594,5 +3594,5 @@
3594
3594
  ]
3595
3595
  }
3596
3596
  },
3597
- "version": "2.9.8"
3597
+ "version": "2.9.9"
3598
3598
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swell/cli",
3
- "version": "2.9.8",
3
+ "version": "2.9.9",
4
4
  "type": "module",
5
5
  "description": "Swell's command line interface/utility",
6
6
  "keywords": [