@swell/cli 2.5.2 → 2.5.4

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.
@@ -69,12 +69,12 @@ export default class AppDev extends PushAppCommand {
69
69
  this.saveCurrentStorefront();
70
70
  const spinner = ora();
71
71
  spinner.start(`Starting app dev server...\n`);
72
- const serverPort = await this.startProxyServer(port);
73
- await this.startAppFunctionServer(spinner, serverPort);
74
- await this.runAppFrontendDevIfApplicable(frontendPort);
75
72
  // Register cleanup handlers to clear local proxy on exit
76
73
  process.on('SIGINT', this.cleanupOnExit.bind(this));
77
74
  process.on('SIGTERM', this.cleanupOnExit.bind(this));
75
+ const serverPort = await this.startProxyServer(port);
76
+ await this.startAppFunctionServer(spinner, serverPort);
77
+ await this.runAppFrontendDevIfApplicable(frontendPort);
78
78
  }
79
79
  async cleanupOnExit() {
80
80
  if (this.isCleaningUp)
@@ -86,7 +86,9 @@ export default class AppDev extends PushAppCommand {
86
86
  catch {
87
87
  // Ignore errors during cleanup
88
88
  }
89
- // Note: proxy.ts signal handlers will call process.exit()
89
+ this.log();
90
+ // eslint-disable-next-line no-process-exit
91
+ process.exit();
90
92
  }
91
93
  async runAppFrontendDevIfApplicable(frontendPort) {
92
94
  const projectType = this.getFrontendProjectType(false);
@@ -98,7 +100,12 @@ export default class AppDev extends PushAppCommand {
98
100
  // Store frontend port for function router to use for fallback routing
99
101
  this.frontendPort = proxyPort;
100
102
  this.argv.push('--proxy-port', String(proxyPort));
101
- this.config.runCommand('app:frontend:dev', this.argv);
103
+ this.config.runCommand('app:frontend:dev', this.argv).catch((error) => {
104
+ // SIGTERM/SIGINT during dev server shutdown is expected
105
+ if (error?.signal === 'SIGTERM' || error?.signal === 'SIGINT')
106
+ return;
107
+ this.error(error);
108
+ });
102
109
  return proxyPort;
103
110
  }
104
111
  }
@@ -302,10 +309,24 @@ ENVIRONMENT = "development"
302
309
  async startAppFunctionServer(spinner, serverPort) {
303
310
  // Get all functions in this app
304
311
  const functions = await this.getAppFunctions();
312
+ const projectType = this.getFrontendProjectType(false);
313
+ const shouldLogPreviewUrl = this.app.type === 'storefront' && !projectType;
305
314
  if (functions.length === 0) {
315
+ if (!projectType && this.app.type !== 'storefront') {
316
+ spinner.stop();
317
+ this.log(`No functions or frontend detected. Exiting.\n`);
318
+ await this.cleanupOnExit();
319
+ return;
320
+ }
306
321
  // Create routing server to proxy requests to frontend even without functions
307
322
  await this.createFunctionRouter(serverPort);
308
- spinner.stop();
323
+ if (shouldLogPreviewUrl) {
324
+ spinner.succeed(`App dev server running on port ${serverPort}\n`);
325
+ this.logLocalPreviewUrl();
326
+ }
327
+ else {
328
+ spinner.stop();
329
+ }
309
330
  return;
310
331
  }
311
332
  // Create TMP directory for wrangler configs and bundled functions
@@ -322,6 +343,9 @@ ENVIRONMENT = "development"
322
343
  this.log(`${style.appConfigName(`Functions:`)}`);
323
344
  await this.logAllFunctions(functions);
324
345
  this.log(`${style.success('→')} Call functions at: http://localhost:${serverPort}/<function-name>\n`);
346
+ if (shouldLogPreviewUrl) {
347
+ this.logLocalPreviewUrl();
348
+ }
325
349
  }
326
350
  async startFunctionServers(functions) {
327
351
  const functionStatus = new Map();
@@ -15,7 +15,6 @@ export default class AppFrontendDev extends PushAppCommand {
15
15
  env: string;
16
16
  };
17
17
  static summary: string;
18
- logAppLocalUrl(storeId: string, sessionId: string): void;
19
18
  run(): Promise<void>;
20
19
  private execFrontendProject;
21
20
  }
@@ -1,7 +1,5 @@
1
1
  import { Flags } from '@oclif/core';
2
2
  import { CUSTOM_FRAMEWORK_SLUG, getManualDevHint, getProjectCommands, } from '../../../lib/apps/index.js';
3
- import { default as localConfig } from '../../../lib/config.js';
4
- import style from '../../../lib/style.js';
5
3
  import { PushAppCommand } from '../../../push-app-command.js';
6
4
  import AppDev from '../dev.js';
7
5
  export default class AppFrontendDev extends PushAppCommand {
@@ -37,12 +35,6 @@ export default class AppFrontendDev extends PushAppCommand {
37
35
  env: 'test',
38
36
  };
39
37
  static summary = `Run a frontend app in dev mode from your local machine.`;
40
- logAppLocalUrl(storeId, sessionId) {
41
- const frontendAppRoute = this.app.type === 'storefront' && this.app.storefront?.external
42
- ? `/${this.app.private_id}`
43
- : '';
44
- this.log(`View it at ${style.link(`${this.localFrontendUrl(storeId, sessionId)}${frontendAppRoute}`)}\n`);
45
- }
46
38
  async run() {
47
39
  const { flags } = await this.parse(AppFrontendDev);
48
40
  const { port, 'proxy-port': proxyPort, 'app-dev': isAppDev } = flags;
@@ -66,7 +58,12 @@ export default class AppFrontendDev extends PushAppCommand {
66
58
  const serverPort = isAppDev && proxyPort
67
59
  ? proxyPort
68
60
  : await this.startProxyServer(proxyPort || port);
69
- await this.execFrontendProject(serverPort, isAppDev);
61
+ await this.execFrontendProject(serverPort, isAppDev).catch((error) => {
62
+ // SIGTERM/SIGINT during dev server shutdown is expected
63
+ if (error?.signal === 'SIGTERM' || error?.signal === 'SIGINT')
64
+ return;
65
+ throw error;
66
+ });
70
67
  }
71
68
  async execFrontendProject(serverPort, isAppDev) {
72
69
  const projectType = this.getFrontendProjectType(!isAppDev);
@@ -76,13 +73,11 @@ export default class AppFrontendDev extends PushAppCommand {
76
73
  // Manual mode for unrecognized frameworks
77
74
  if (projectType.slug === CUSTOM_FRAMEWORK_SLUG) {
78
75
  const hintCommand = getManualDevHint(this.appPath, serverPort);
79
- const currentStore = localConfig.getDefaultStore();
80
- const sessionId = localConfig.getSessionId(currentStore);
81
76
  this.log(`Detected a frontend project but could not identify the framework.`);
82
77
  this.log(`Start your dev server manually on port ${serverPort}:\n`);
83
78
  this.log(` cd frontend && ${hintCommand}\n`);
84
79
  this.log(`The Swell tunnel is ready and will route traffic once your server is running.\n`);
85
- this.logAppLocalUrl(currentStore, sessionId);
80
+ this.logLocalPreviewUrl();
86
81
  if (!isAppDev) {
87
82
  this.watchForChanges();
88
83
  }
@@ -92,7 +87,6 @@ export default class AppFrontendDev extends PushAppCommand {
92
87
  }
93
88
  // Get commands transformed for the detected package manager
94
89
  const { devCommand } = getProjectCommands(this.appPath, projectType);
95
- const currentStore = localConfig.getDefaultStore();
96
90
  let serverReadyDetected = false;
97
91
  await this.execFrontend(devCommand.replace('${PORT}', String(serverPort)), (string) => {
98
92
  // Shared: Hide Vite noise output common across frameworks
@@ -117,8 +111,7 @@ export default class AppFrontendDev extends PushAppCommand {
117
111
  (/✓ ready in \d+/i.test(string) || /✓ starting/i.test(string))) {
118
112
  serverReadyDetected = true;
119
113
  this.log(`Started ${projectType.name} dev server on port ${serverPort}.\n`);
120
- const sessionId = localConfig.getSessionId(currentStore);
121
- setTimeout(() => this.logAppLocalUrl(currentStore, sessionId), 100);
114
+ setTimeout(() => this.logLocalPreviewUrl(), 100);
122
115
  if (!isAppDev) {
123
116
  this.watchForChanges();
124
117
  }
@@ -135,8 +128,7 @@ export default class AppFrontendDev extends PushAppCommand {
135
128
  /watching for file changes/i.test(string)) {
136
129
  serverReadyDetected = true;
137
130
  this.log(`Started ${projectType.name} dev server on port ${serverPort}.\n`);
138
- const sessionId = localConfig.getSessionId(currentStore);
139
- setTimeout(() => this.logAppLocalUrl(currentStore, sessionId), 100);
131
+ setTimeout(() => this.logLocalPreviewUrl(), 100);
140
132
  if (!isAppDev) {
141
133
  this.watchForChanges();
142
134
  }
@@ -154,8 +146,7 @@ export default class AppFrontendDev extends PushAppCommand {
154
146
  /watch mode enabled/i.test(string))) {
155
147
  serverReadyDetected = true;
156
148
  this.log(`Started ${projectType.name} dev server on port ${serverPort}.\n`);
157
- const sessionId = localConfig.getSessionId(currentStore);
158
- setTimeout(() => this.logAppLocalUrl(currentStore, sessionId), 100);
149
+ setTimeout(() => this.logLocalPreviewUrl(), 100);
159
150
  if (!isAppDev) {
160
151
  this.watchForChanges();
161
152
  }
@@ -171,8 +162,7 @@ export default class AppFrontendDev extends PushAppCommand {
171
162
  if (!serverReadyDetected) {
172
163
  serverReadyDetected = true;
173
164
  this.log(`Started ${projectType.name} dev server on port ${serverPort}.\n`);
174
- const sessionId = localConfig.getSessionId(currentStore);
175
- setTimeout(() => this.logAppLocalUrl(currentStore, sessionId), 100);
165
+ setTimeout(() => this.logLocalPreviewUrl(), 100);
176
166
  if (!isAppDev) {
177
167
  this.watchForChanges();
178
168
  }
@@ -184,8 +174,7 @@ export default class AppFrontendDev extends PushAppCommand {
184
174
  /ready on http:\/\/localhost:\d+/i.test(string)) {
185
175
  serverReadyDetected = true;
186
176
  this.log(`Started ${projectType.name} dev server on port ${serverPort}.\n`);
187
- const sessionId = localConfig.getSessionId(currentStore);
188
- setTimeout(() => this.logAppLocalUrl(currentStore, sessionId), 100);
177
+ setTimeout(() => this.logLocalPreviewUrl(), 100);
189
178
  if (!isAppDev) {
190
179
  this.watchForChanges();
191
180
  }
@@ -203,8 +192,7 @@ export default class AppFrontendDev extends PushAppCommand {
203
192
  if (!serverReadyDetected && /nuxt \d+\.\d+\.\d+/i.test(string)) {
204
193
  serverReadyDetected = true;
205
194
  this.log(`Started ${projectType.name} dev server on port ${serverPort}.\n`);
206
- const sessionId = localConfig.getSessionId(currentStore);
207
- setTimeout(() => this.logAppLocalUrl(currentStore, sessionId), 100);
195
+ setTimeout(() => this.logLocalPreviewUrl(), 100);
208
196
  if (!isAppDev) {
209
197
  this.watchForChanges();
210
198
  }
@@ -37,5 +37,14 @@ export declare abstract class CreateAppCommand extends SwellCommand {
37
37
  };
38
38
  setupPackage(name: string, config: any, pkg: PackageManager): Promise<void>;
39
39
  tryPackageSetup(name: string, config: any, pkg: PackageManager): Promise<void>;
40
+ /**
41
+ * Fix wrangler.jsonc `main` field when it points to a build artifact that
42
+ * doesn't exist at dev time. The @cloudflare/vite-plugin validates this path
43
+ * at startup and crashes during `astro dev` if it can't resolve it.
44
+ *
45
+ * Only replaces the exact legacy value that C3 currently scaffolds.
46
+ * Once C3 updates its template, this becomes a no-op.
47
+ */
48
+ private fixAstroWranglerEntrypoint;
40
49
  private addFrontendAllowedHosts;
41
50
  }
@@ -14,7 +14,7 @@ import style from './lib/style.js';
14
14
  import { SwellCommand } from './swell-command.js';
15
15
  const execAsync = promisify(exec);
16
16
  /** Allowed hosts for tunnel providers used in local development */
17
- const TUNNEL_ALLOWED_HOSTS = ['.ngrok.app', '.loca.lt', '.trycloudflare.com'];
17
+ const TUNNEL_ALLOWED_HOSTS = ['.trycloudflare.com', '.swell.store'];
18
18
  /**
19
19
  * Find the closing brace position for a config function call (e.g., defineConfig, defineNuxtConfig).
20
20
  * Uses brace counting to find the matching `}` for the opening `{`.
@@ -477,12 +477,37 @@ export class CreateAppCommand extends SwellCommand {
477
477
  this.log(`There was an error setting up the package manager: ${error.message}`);
478
478
  }
479
479
  }
480
+ /**
481
+ * Fix wrangler.jsonc `main` field when it points to a build artifact that
482
+ * doesn't exist at dev time. The @cloudflare/vite-plugin validates this path
483
+ * at startup and crashes during `astro dev` if it can't resolve it.
484
+ *
485
+ * Only replaces the exact legacy value that C3 currently scaffolds.
486
+ * Once C3 updates its template, this becomes a no-op.
487
+ */
488
+ async fixAstroWranglerEntrypoint(configPath) {
489
+ const filePath = path.join(configPath, 'frontend', 'wrangler.jsonc');
490
+ let content;
491
+ try {
492
+ content = await fs.readFile(filePath, 'utf8');
493
+ }
494
+ catch {
495
+ return;
496
+ }
497
+ const legacy = '"main": "./dist/_worker.js/index.js"';
498
+ if (!content.includes(legacy)) {
499
+ return;
500
+ }
501
+ const updated = content.replace(legacy, '"main": "@astrojs/cloudflare/entrypoints/server"');
502
+ await fs.writeFile(filePath, updated, 'utf8');
503
+ }
480
504
  async addFrontendAllowedHosts(projectType, configPath) {
481
505
  // Configure allowedHosts for frameworks using Vite dev server
482
506
  // This allows tunneling services (ngrok, localtunnel) to proxy requests to the local dev environment
483
507
  switch (projectType.slug) {
484
508
  case 'astro': {
485
509
  await this.addAllowedHostsToAstro(configPath);
510
+ await this.fixAstroWranglerEntrypoint(configPath);
486
511
  break;
487
512
  }
488
513
  case 'angular': {
@@ -368,7 +368,9 @@ export function batchAppFilesByType(configs) {
368
368
  for (const configType of ConfigTypeBatchOrder) {
369
369
  const configsByType = sorted.filter((config) => config.type === configType);
370
370
  const label = configType === ConfigType.FILE ? 'App files' : titleize(configType);
371
- const concurrency = configType === ConfigType.NOTIFICATION || configType === ConfigType.ASSET
371
+ const concurrency = configType === ConfigType.NOTIFICATION ||
372
+ configType === ConfigType.ASSET ||
373
+ configType === ConfigType.SETTING
372
374
  ? 1
373
375
  : PUSH_CONCURRENCY;
374
376
  batches.push({
@@ -58,18 +58,19 @@ export class ThemeSync {
58
58
  proxyReq.setHeader('swell-request-id', `${Date.now()}`);
59
59
  });
60
60
  this.proxy.on('proxyRes', (proxyRes, req, res) => {
61
- let body = Buffer.from('');
61
+ const chunks = [];
62
62
  proxyRes.on('data', (chunk) => {
63
- body = Buffer.concat([body, chunk]);
63
+ chunks.push(chunk);
64
64
  });
65
65
  // Preserve headers
66
66
  for (let i = 0; i < proxyRes.rawHeaders.length; i += 2) {
67
67
  const header = proxyRes.rawHeaders[i];
68
68
  res.setHeader(header, proxyRes.rawHeaders[i + 1]);
69
69
  }
70
- // Use utf8 encoding for the response
71
- res.setHeader('Content-Encoding', 'utf8');
70
+ res.removeHeader('Content-Encoding');
72
71
  proxyRes.on('end', async () => {
72
+ const body = Buffer.concat(chunks);
73
+ chunks.length = 0;
73
74
  const html = await this.parseRequestBody(proxyRes, body);
74
75
  if (this.shouldInjectScript(proxyRes, html)) {
75
76
  const modifiedHtml = this.injectScript(html, `localhost:${this.options.port}`);
@@ -11,6 +11,7 @@ interface WatchingChange {
11
11
  }
12
12
  export declare abstract class PushAppCommand extends RemoteAppCommand {
13
13
  frontendPath: string;
14
+ hasLoggedLocalPreviewUrl: boolean;
14
15
  logWatchChanges: boolean;
15
16
  onWatchChange?: (appConfig?: AppConfig, action?: string, result?: any) => void;
16
17
  watchingChangeQueue: Map<string, WatchingChange>;
@@ -78,6 +79,7 @@ export declare abstract class PushAppCommand extends RemoteAppCommand {
78
79
  resolveAppPath(appId?: string, targetPath?: string): string;
79
80
  runWatchChangeQueue(): Promise<void>;
80
81
  saveCurrentStorefront(): void;
82
+ logLocalPreviewUrl(): void;
81
83
  setWatchingFiles(): Promise<void>;
82
84
  startProxyServer(port?: number): Promise<number>;
83
85
  updateFrontendDeployment(currentStore: string, projectType: FrontendProjectType, deploymentUrl: string, deploymentHash: string): Promise<void>;
@@ -20,6 +20,7 @@ const WATCH_WINDOW_MS = 100;
20
20
  const DEV_SERVER_FALLBACK_PORT = 3000;
21
21
  export class PushAppCommand extends RemoteAppCommand {
22
22
  frontendPath = '';
23
+ hasLoggedLocalPreviewUrl = false;
23
24
  logWatchChanges = true;
24
25
  onWatchChange;
25
26
  watchingChangeQueue = new Map();
@@ -778,6 +779,18 @@ export class PushAppCommand extends RemoteAppCommand {
778
779
  localConfig.setDefaultStorefront(this.appPath, this.storefront.id, this.api.envId);
779
780
  }
780
781
  }
782
+ logLocalPreviewUrl() {
783
+ if (this.hasLoggedLocalPreviewUrl) {
784
+ return;
785
+ }
786
+ const currentStore = localConfig.getDefaultStore();
787
+ const sessionId = localConfig.getSessionId(currentStore);
788
+ const frontendAppRoute = this.app.type === 'storefront' && this.app.storefront?.external
789
+ ? `/${this.app.private_id}`
790
+ : '';
791
+ this.log(`View it at ${style.link(`${this.localFrontendUrl(currentStore, sessionId)}${frontendAppRoute}`)}\n`);
792
+ this.hasLoggedLocalPreviewUrl = true;
793
+ }
781
794
  async setWatchingFiles() {
782
795
  this.watchingFiles = new Set(await globAllFilesByPath(this.appPath));
783
796
  }
@@ -2969,5 +2969,5 @@
2969
2969
  ]
2970
2970
  }
2971
2971
  },
2972
- "version": "2.5.2"
2972
+ "version": "2.5.4"
2973
2973
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swell/cli",
3
- "version": "2.5.2",
3
+ "version": "2.5.4",
4
4
  "type": "module",
5
5
  "description": "Swell's command line interface/utility",
6
6
  "keywords": [