@swell/cli 2.5.0 → 2.5.1

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.
@@ -8,6 +8,7 @@ export default class AppDev extends PushAppCommand {
8
8
  'frontend-port': import("@oclif/core/lib/interfaces/parser.js").OptionFlag<number | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
9
9
  'storefront-id': import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
10
10
  'storefront-select': import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
11
+ yes: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
11
12
  };
12
13
  static orientation: {
13
14
  env: string;
@@ -37,6 +37,11 @@ export default class AppDev extends PushAppCommand {
37
37
  default: false,
38
38
  description: 'for storefront apps, prompt to select a storefront to preview',
39
39
  }),
40
+ // Declared here so oclif accepts -y; passed through to app:frontend:dev via this.argv
41
+ yes: Flags.boolean({
42
+ char: 'y',
43
+ description: 'skip prompts (non-interactive mode)',
44
+ }),
40
45
  };
41
46
  static orientation = {
42
47
  env: 'test',
@@ -6,6 +6,7 @@ export default class AppFrontendDeploy extends PushAppCommand {
6
6
  force: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
7
7
  'storefront-id': import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
8
8
  'storefront-select': import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
9
+ yes: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
9
10
  };
10
11
  static orientation: {
11
12
  env: string;
@@ -22,6 +22,10 @@ the app's deployment URL and push the updated frontend files.`;
22
22
  default: false,
23
23
  description: 'prompt to select a storefront to preview deployment with',
24
24
  }),
25
+ yes: Flags.boolean({
26
+ char: 'y',
27
+ description: 'skip prompts (non-interactive mode)',
28
+ }),
25
29
  };
26
30
  static orientation = {
27
31
  env: 'test',
@@ -2,10 +2,11 @@ import { PushAppCommand } from '../../../push-app-command.js';
2
2
  export default class AppFrontendDev extends PushAppCommand {
3
3
  static examples: string[];
4
4
  static flags: {
5
- 'proxy-port': import("@oclif/core/lib/interfaces/parser.js").OptionFlag<number, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
5
+ 'proxy-port': import("@oclif/core/lib/interfaces/parser.js").OptionFlag<number | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
6
6
  'storefront-id': import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
7
7
  'storefront-select': import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
8
8
  'app-dev': import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
9
+ yes: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
9
10
  'no-push': import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
10
11
  port: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<number | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
11
12
  'frontend-port': import("@oclif/core/lib/interfaces/parser.js").OptionFlag<number | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
@@ -15,8 +15,7 @@ export default class AppFrontendDev extends PushAppCommand {
15
15
  static flags = {
16
16
  ...AppDev.flags,
17
17
  'proxy-port': Flags.integer({
18
- description: 'specify the port for an existing frontend proxy',
19
- default: 3001,
18
+ description: 'specify the port for the frontend proxy',
20
19
  }),
21
20
  'storefront-id': Flags.string({
22
21
  description: 'identify a storefront to preview with and push theme files to',
@@ -29,6 +28,10 @@ export default class AppFrontendDev extends PushAppCommand {
29
28
  description: 'indicates frontend app is running in app dev mode',
30
29
  default: false,
31
30
  }),
31
+ yes: Flags.boolean({
32
+ char: 'y',
33
+ description: 'skip prompts (non-interactive mode)',
34
+ }),
32
35
  };
33
36
  static orientation = {
34
37
  env: 'test',
@@ -92,6 +95,10 @@ export default class AppFrontendDev extends PushAppCommand {
92
95
  const currentStore = localConfig.getDefaultStore();
93
96
  let serverReadyDetected = false;
94
97
  await this.execFrontend(devCommand.replace('${PORT}', String(serverPort)), (string) => {
98
+ // Shared: Hide Vite noise output common across frameworks
99
+ if (/press h \+ enter to show help/i.test(string)) {
100
+ return false;
101
+ }
95
102
  // Framework-specific output handling and ready detection
96
103
  switch (projectType.slug) {
97
104
  case 'nextjs': {
@@ -137,10 +144,6 @@ export default class AppFrontendDev extends PushAppCommand {
137
144
  break;
138
145
  }
139
146
  case 'angular': {
140
- // 1. NOISE FILTER: Hide unwanted output
141
- if (string.includes('press h + enter to show help')) {
142
- return false;
143
- }
144
147
  // 2. URL FILTER: Hide localhost/network URLs (confusing - users should use Swell tunnel)
145
148
  if (/➜\s+local:\s+http:\/\//i.test(string)) {
146
149
  return false;
@@ -160,9 +163,23 @@ export default class AppFrontendDev extends PushAppCommand {
160
163
  break;
161
164
  }
162
165
  case 'hono': {
163
- // 2. URL FILTER: Hide localhost URLs (confusing - users should use Swell tunnel)
164
- // Note: For Hono, the ready signal IS the URL line, so we handle both in section 3
165
- // 3. READY DETECTION: Detect server ready, show our message, hide the URL
166
+ // 2. URL FILTER: Hide localhost/network/debug URLs (confusing - users should use Swell tunnel)
167
+ // Vite-based Hono apps output "➜ Local: http://localhost:PORT/"
168
+ if (/➜\s+local:\s+http:\/\//i.test(string) ||
169
+ /➜\s+network:/i.test(string) ||
170
+ /➜\s+debug:\s+http:\/\//i.test(string)) {
171
+ if (!serverReadyDetected) {
172
+ serverReadyDetected = true;
173
+ this.log(`Started ${projectType.name} dev server on port ${serverPort}.\n`);
174
+ const sessionId = localConfig.getSessionId(currentStore);
175
+ setTimeout(() => this.logAppLocalUrl(currentStore, sessionId), 100);
176
+ if (!isAppDev) {
177
+ this.watchForChanges();
178
+ }
179
+ }
180
+ return false;
181
+ }
182
+ // 3. READY DETECTION: Native Hono dev server outputs "ready on http://localhost:PORT"
166
183
  if (!serverReadyDetected &&
167
184
  /ready on http:\/\/localhost:\d+/i.test(string)) {
168
185
  serverReadyDetected = true;
@@ -172,7 +189,7 @@ export default class AppFrontendDev extends PushAppCommand {
172
189
  if (!isAppDev) {
173
190
  this.watchForChanges();
174
191
  }
175
- return false; // Hide the "Ready on localhost" line
192
+ return false;
176
193
  }
177
194
  break;
178
195
  }
@@ -7,9 +7,7 @@ export default class AppPush extends PushAppCommand {
7
7
  static description: string;
8
8
  static examples: string[];
9
9
  static flags: any;
10
- static orientation: {
11
- env: string;
12
- };
10
+ static orientation: AppCommandOrientation;
13
11
  static summary: string;
14
12
  resolvePushPaths(file: string, appPathFlag?: string): {
15
13
  filePath: string;
@@ -9,6 +9,7 @@ export default class AppThemeDev extends PushAppCommand {
9
9
  'storefront-id': import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
10
10
  'storefront-select': import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
11
11
  env: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
12
+ yes: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
12
13
  };
13
14
  static summary: string;
14
15
  appType: string;
@@ -41,6 +41,10 @@ export default class AppThemeDev extends PushAppCommand {
41
41
  char: 'e',
42
42
  description: 'target environment to push to, defaults to live',
43
43
  }),
44
+ yes: Flags.boolean({
45
+ char: 'y',
46
+ description: 'skip prompts (non-interactive mode)',
47
+ }),
44
48
  };
45
49
  static summary = `Run a theme in dev mode from your local machine.`;
46
50
  appType = 'theme';
@@ -11,7 +11,9 @@ export default class AppThemePush extends AppPush {
11
11
  'storefront-select': import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
12
12
  env: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
13
13
  'sync-app': import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
14
+ yes: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
14
15
  };
16
+ static orientation: AppCommandOrientation;
15
17
  static summary: string;
16
18
  appType: string;
17
19
  run(): Promise<void>;
@@ -53,7 +53,13 @@ ${Object.values(ThemeConfigPaths)
53
53
  default: false,
54
54
  description: 'push app files in addition to theme files',
55
55
  }),
56
+ yes: Flags.boolean({
57
+ char: 'y',
58
+ description: 'skip prompts (non-interactive mode)',
59
+ }),
56
60
  };
61
+ // No fixed env — respect saved storefront env (test or live)
62
+ static orientation = {};
57
63
  static summary = 'Push local files to your Swell theme.';
58
64
  appType = 'theme';
59
65
  async run() {
@@ -77,7 +83,7 @@ ${Object.values(ThemeConfigPaths)
77
83
  this.themeSyncApp = true;
78
84
  }
79
85
  await this.getStorefrontToPush(flags);
80
- await this.logOrientation();
86
+ await this.logOrientation({ env: this.api.envId || 'live' });
81
87
  if (file) {
82
88
  const { filePath, relativePath } = this.resolvePushPaths(file);
83
89
  // Push individual files if not referring to the whole app path
@@ -135,7 +135,7 @@ export const FrontendProjectTypes = [
135
135
  slug: 'angular',
136
136
  },
137
137
  {
138
- devCommand: 'npx wrangler dev --port ${PORT}',
138
+ devCommand: 'npm run dev -- --port ${PORT}',
139
139
  installCommand: 'npm create cloudflare@latest -- frontend --framework=hono --deploy=false --git=false',
140
140
  mainPackage: 'hono',
141
141
  name: 'Hono',
@@ -7,12 +7,13 @@ import prodEnv from '../env/production.js';
7
7
  import reviewEnv from '../env/review.js';
8
8
  import stagingEnv from '../env/staging.js';
9
9
  import style from './style.js';
10
+ const swellHome = process.env.SWELL_CONFIG_HOME || homedir();
10
11
  const configPath = process.env.NODE_ENV === 'test'
11
12
  ? path.resolve(process.cwd(), 'test', '.swell', 'config.json')
12
- : path.resolve(homedir(), '.swell', 'config.json');
13
+ : path.resolve(swellHome, '.swell', 'config.json');
13
14
  const configDir = process.env.NODE_ENV === 'test'
14
15
  ? path.resolve(process.cwd(), 'test', 'fixtures')
15
- : homedir();
16
+ : swellHome;
16
17
  const configStore = new Configstore('swell', {
17
18
  // user: {...},
18
19
  // defaultStore: '<store_id>',
@@ -21,7 +21,7 @@ export declare abstract class PushAppCommand extends RemoteAppCommand {
21
21
  protected showFrontendMigrationError(): never;
22
22
  buildFrontend(projectType?: FrontendProjectType): Promise<void>;
23
23
  chooseAppToPull(query?: any): Promise<App>;
24
- createAppStorefront(hasOtherStorefronts?: boolean): Promise<any>;
24
+ createAppStorefront(hasOtherStorefronts?: boolean, nonInteractive?: boolean): Promise<any>;
25
25
  deployAppFrontend(force?: boolean, log?: boolean): Promise<void>;
26
26
  deployFrontend(projectType: FrontendProjectType): Promise<string>;
27
27
  ensureAppExists(file?: string, shouldCreate?: boolean): Promise<boolean>;
@@ -59,6 +59,7 @@ export declare abstract class PushAppCommand extends RemoteAppCommand {
59
59
  env?: string;
60
60
  'storefront-id'?: string;
61
61
  'storefront-select'?: boolean;
62
+ yes?: boolean;
62
63
  }): Promise<any>;
63
64
  setStorefrontEnv(flags: {
64
65
  env?: string;
@@ -148,7 +148,7 @@ export class PushAppCommand extends RemoteAppCommand {
148
148
  this.log();
149
149
  return app;
150
150
  }
151
- async createAppStorefront(hasOtherStorefronts = true) {
151
+ async createAppStorefront(hasOtherStorefronts = true, nonInteractive = false) {
152
152
  let appId;
153
153
  let themeAppId;
154
154
  if (this.app.type === 'theme') {
@@ -163,6 +163,12 @@ export class PushAppCommand extends RemoteAppCommand {
163
163
  if (compatibleStorefrontApps.length === 0) {
164
164
  this.error('No compatible storefront apps found for this theme. Install a new storefront app first.');
165
165
  }
166
+ if (compatibleStorefrontApps.length > 1 && nonInteractive) {
167
+ const appList = compatibleStorefrontApps
168
+ .map((app) => ` ${app.id} (${app.name})`)
169
+ .join('\n');
170
+ this.error(`Multiple compatible storefront apps found. Run without -y to select one interactively:\n${appList}`);
171
+ }
166
172
  const targetApp = compatibleStorefrontApps.length === 1
167
173
  ? compatibleStorefrontApps[0]
168
174
  : await select({
@@ -177,37 +183,49 @@ export class PushAppCommand extends RemoteAppCommand {
177
183
  }
178
184
  else if (this.app.type === 'storefront') {
179
185
  appId = this.app.id;
180
- const themeApps = await this.handleRequestErrors(async () => this.api.get({
181
- adminPath: `/apps?type=theme`,
182
- }));
183
- const compatibleThemeApps = themeApps.results.filter((app) => app.installed &&
184
- Object.keys(app.compatibilities || {}).includes(this.app.private_id));
185
- if (compatibleThemeApps.length === 0) {
186
- this.error('No compatible theme apps found for this storefront. Install a new theme app first.');
187
- }
188
- let targetApp;
189
- if (compatibleThemeApps.length === 1) {
190
- targetApp = compatibleThemeApps[0];
186
+ const requiresTheme = this.app.storefront?.theme?.provider === 'app';
187
+ if (requiresTheme) {
188
+ const themeApps = await this.handleRequestErrors(async () => this.api.get({
189
+ adminPath: `/apps?type=theme`,
190
+ }));
191
+ const compatibleThemeApps = themeApps.results.filter((app) => app.installed &&
192
+ Object.keys(app.compatibilities || {}).includes(this.app.private_id));
193
+ if (compatibleThemeApps.length === 0) {
194
+ this.error('No compatible theme apps found for this storefront. Install a new theme app first.');
195
+ }
196
+ let targetApp;
197
+ if (compatibleThemeApps.length === 1) {
198
+ targetApp = compatibleThemeApps[0];
199
+ }
200
+ else if (nonInteractive) {
201
+ const appList = compatibleThemeApps
202
+ .map((app) => ` ${app.id} (${app.name})`)
203
+ .join('\n');
204
+ this.error(`Multiple compatible theme apps found. Run without -y to select one interactively:\n${appList}`);
205
+ }
206
+ else {
207
+ targetApp = await select({
208
+ choices: compatibleThemeApps.map((app) => ({
209
+ name: `${style.appConfigValue(app.name)} v${app.version}`,
210
+ value: app,
211
+ })),
212
+ message: 'Choose a theme app',
213
+ });
214
+ this.log();
215
+ }
216
+ themeAppId = targetApp.id;
217
+ this.log(`${style.success('✔')} Using ${style.appConfigValue(targetApp.name)} v${targetApp.version}\n`);
191
218
  }
192
219
  else {
193
- targetApp = await select({
194
- choices: compatibleThemeApps.map((app) => ({
195
- name: `${style.appConfigValue(app.name)} v${app.version}`,
196
- value: app,
197
- })),
198
- message: 'Choose a theme app',
199
- });
200
- this.log();
220
+ this.log(`${style.success('✔')} Creating storefront without theme app\n`);
201
221
  }
202
- themeAppId = targetApp.id;
203
- this.log(`${style.success('✔')} Using ${style.appConfigValue(targetApp.name)} v${targetApp.version}\n`);
204
222
  }
205
223
  else {
206
224
  this.error('App type not supported for storefront creation');
207
225
  }
208
226
  let name;
209
227
  // Leave name blank when syncing theme app to avoid blocking push
210
- if (hasOtherStorefronts && !this.themeSyncApp) {
228
+ if (hasOtherStorefronts && !this.themeSyncApp && !nonInteractive) {
211
229
  name = await input({
212
230
  default: this.app.name,
213
231
  message: 'Name your storefront',
@@ -512,6 +530,7 @@ export class PushAppCommand extends RemoteAppCommand {
512
530
  return this.storefront;
513
531
  }
514
532
  async getStorefrontToPush(flags = {}) {
533
+ const nonInteractive = flags.yes || false;
515
534
  const targetStorefront = flags['storefront-id'];
516
535
  const defaultStorefront = localConfig.getDefaultStorefront(this.appPath);
517
536
  let storefrontId = targetStorefront || defaultStorefront?.id;
@@ -526,22 +545,37 @@ export class PushAppCommand extends RemoteAppCommand {
526
545
  // Choose storefront if multiple storefronts exist
527
546
  // Except if we are syncing a theme app, then force create new storefront if not found
528
547
  if (storefronts.count > 0 && !this.themeSyncApp) {
529
- storefrontId = await select({
530
- choices: [
531
- ...storefronts.results.map((storefront) => ({
532
- name: `${style.appConfigValue(storefront.name)} (${storefront.id}) ${storefront.primary ? '[primary]' : ''}`,
533
- value: storefront.id,
534
- })),
535
- {
536
- name: 'Create new storefront',
537
- value: null,
538
- },
539
- ],
540
- message: 'Choose a storefront',
541
- });
548
+ if (nonInteractive) {
549
+ if (storefronts.count === 1) {
550
+ const sf = storefronts.results[0];
551
+ storefrontId = sf.id;
552
+ this.log(`${style.success('✔')} Using storefront ${style.appConfigValue(sf.name)} (${sf.id})\n`);
553
+ }
554
+ else {
555
+ const sfList = storefronts.results
556
+ .map((sf) => ` ${sf.id} (${sf.name})${sf.primary ? ' [primary]' : ''}`)
557
+ .join('\n');
558
+ this.error(`Multiple storefronts found. Specify one with --storefront-id:\n${sfList}`);
559
+ }
560
+ }
561
+ else {
562
+ storefrontId = await select({
563
+ choices: [
564
+ ...storefronts.results.map((storefront) => ({
565
+ name: `${style.appConfigValue(storefront.name)} (${storefront.id}) ${storefront.primary ? '[primary]' : ''}`,
566
+ value: storefront.id,
567
+ })),
568
+ {
569
+ name: 'Create new storefront',
570
+ value: null,
571
+ },
572
+ ],
573
+ message: 'Choose a storefront',
574
+ });
575
+ }
542
576
  }
543
577
  if (!storefrontId) {
544
- this.storefront = await this.createAppStorefront(storefronts.count > 0);
578
+ this.storefront = await this.createAppStorefront(storefronts.count > 0, nonInteractive);
545
579
  }
546
580
  }
547
581
  if (storefrontId) {
@@ -555,7 +589,7 @@ export class PushAppCommand extends RemoteAppCommand {
555
589
  }
556
590
  this.log(`Storefront ${style.appConfigValue(storefrontId)} not found.\n`);
557
591
  localConfig.setDefaultStorefront(this.appPath, '');
558
- return this.getStorefrontToPush();
592
+ return this.getStorefrontToPush({ yes: nonInteractive });
559
593
  }
560
594
  }
561
595
  this.app = await this.getApp(this.app.id);
@@ -727,6 +727,13 @@
727
727
  "name": "storefront-select",
728
728
  "allowNo": false,
729
729
  "type": "boolean"
730
+ },
731
+ "yes": {
732
+ "char": "y",
733
+ "description": "skip prompts (non-interactive mode)",
734
+ "name": "yes",
735
+ "allowNo": false,
736
+ "type": "boolean"
730
737
  }
731
738
  },
732
739
  "hasDynamicHelp": false,
@@ -2483,6 +2490,13 @@
2483
2490
  "hasDynamicHelp": false,
2484
2491
  "multiple": false,
2485
2492
  "type": "option"
2493
+ },
2494
+ "yes": {
2495
+ "char": "y",
2496
+ "description": "skip prompts (non-interactive mode)",
2497
+ "name": "yes",
2498
+ "allowNo": false,
2499
+ "type": "boolean"
2486
2500
  }
2487
2501
  },
2488
2502
  "hasDynamicHelp": false,
@@ -2770,6 +2784,13 @@
2770
2784
  "name": "sync-app",
2771
2785
  "allowNo": false,
2772
2786
  "type": "boolean"
2787
+ },
2788
+ "yes": {
2789
+ "char": "y",
2790
+ "description": "skip prompts (non-interactive mode)",
2791
+ "name": "yes",
2792
+ "allowNo": false,
2793
+ "type": "boolean"
2773
2794
  }
2774
2795
  },
2775
2796
  "hasDynamicHelp": false,
@@ -2780,9 +2801,7 @@
2780
2801
  "pluginType": "core",
2781
2802
  "summary": "Push local files to your Swell theme.",
2782
2803
  "delayOrientation": true,
2783
- "orientation": {
2784
- "env": "test"
2785
- },
2804
+ "orientation": {},
2786
2805
  "isESM": true,
2787
2806
  "relativePath": [
2788
2807
  "dist",
@@ -2827,6 +2846,13 @@
2827
2846
  "name": "storefront-select",
2828
2847
  "allowNo": false,
2829
2848
  "type": "boolean"
2849
+ },
2850
+ "yes": {
2851
+ "char": "y",
2852
+ "description": "skip prompts (non-interactive mode)",
2853
+ "name": "yes",
2854
+ "allowNo": false,
2855
+ "type": "boolean"
2830
2856
  }
2831
2857
  },
2832
2858
  "hasDynamicHelp": false,
@@ -2901,10 +2927,16 @@
2901
2927
  "allowNo": false,
2902
2928
  "type": "boolean"
2903
2929
  },
2930
+ "yes": {
2931
+ "char": "y",
2932
+ "description": "skip prompts (non-interactive mode)",
2933
+ "name": "yes",
2934
+ "allowNo": false,
2935
+ "type": "boolean"
2936
+ },
2904
2937
  "proxy-port": {
2905
- "description": "specify the port for an existing frontend proxy",
2938
+ "description": "specify the port for the frontend proxy",
2906
2939
  "name": "proxy-port",
2907
- "default": 3001,
2908
2940
  "hasDynamicHelp": false,
2909
2941
  "multiple": false,
2910
2942
  "type": "option"
@@ -2937,5 +2969,5 @@
2937
2969
  ]
2938
2970
  }
2939
2971
  },
2940
- "version": "2.5.0"
2972
+ "version": "2.5.1"
2941
2973
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swell/cli",
3
- "version": "2.5.0",
3
+ "version": "2.5.1",
4
4
  "type": "module",
5
5
  "description": "Swell's command line interface/utility",
6
6
  "keywords": [