@swell/cli 2.5.2 → 2.5.3

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.
@@ -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);
@@ -302,10 +304,24 @@ ENVIRONMENT = "development"
302
304
  async startAppFunctionServer(spinner, serverPort) {
303
305
  // Get all functions in this app
304
306
  const functions = await this.getAppFunctions();
307
+ const projectType = this.getFrontendProjectType(false);
308
+ const shouldLogPreviewUrl = this.app.type === 'storefront' && !projectType;
305
309
  if (functions.length === 0) {
310
+ if (!projectType && this.app.type !== 'storefront') {
311
+ spinner.stop();
312
+ this.log(`No functions or frontend detected. Exiting.\n`);
313
+ await this.cleanupOnExit();
314
+ return;
315
+ }
306
316
  // Create routing server to proxy requests to frontend even without functions
307
317
  await this.createFunctionRouter(serverPort);
308
- spinner.stop();
318
+ if (shouldLogPreviewUrl) {
319
+ spinner.succeed(`App dev server running on port ${serverPort}\n`);
320
+ this.logLocalPreviewUrl();
321
+ }
322
+ else {
323
+ spinner.stop();
324
+ }
309
325
  return;
310
326
  }
311
327
  // Create TMP directory for wrangler configs and bundled functions
@@ -322,6 +338,9 @@ ENVIRONMENT = "development"
322
338
  this.log(`${style.appConfigName(`Functions:`)}`);
323
339
  await this.logAllFunctions(functions);
324
340
  this.log(`${style.success('→')} Call functions at: http://localhost:${serverPort}/<function-name>\n`);
341
+ if (shouldLogPreviewUrl) {
342
+ this.logLocalPreviewUrl();
343
+ }
325
344
  }
326
345
  async startFunctionServers(functions) {
327
346
  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;
@@ -76,13 +68,11 @@ export default class AppFrontendDev extends PushAppCommand {
76
68
  // Manual mode for unrecognized frameworks
77
69
  if (projectType.slug === CUSTOM_FRAMEWORK_SLUG) {
78
70
  const hintCommand = getManualDevHint(this.appPath, serverPort);
79
- const currentStore = localConfig.getDefaultStore();
80
- const sessionId = localConfig.getSessionId(currentStore);
81
71
  this.log(`Detected a frontend project but could not identify the framework.`);
82
72
  this.log(`Start your dev server manually on port ${serverPort}:\n`);
83
73
  this.log(` cd frontend && ${hintCommand}\n`);
84
74
  this.log(`The Swell tunnel is ready and will route traffic once your server is running.\n`);
85
- this.logAppLocalUrl(currentStore, sessionId);
75
+ this.logLocalPreviewUrl();
86
76
  if (!isAppDev) {
87
77
  this.watchForChanges();
88
78
  }
@@ -92,7 +82,6 @@ export default class AppFrontendDev extends PushAppCommand {
92
82
  }
93
83
  // Get commands transformed for the detected package manager
94
84
  const { devCommand } = getProjectCommands(this.appPath, projectType);
95
- const currentStore = localConfig.getDefaultStore();
96
85
  let serverReadyDetected = false;
97
86
  await this.execFrontend(devCommand.replace('${PORT}', String(serverPort)), (string) => {
98
87
  // Shared: Hide Vite noise output common across frameworks
@@ -117,8 +106,7 @@ export default class AppFrontendDev extends PushAppCommand {
117
106
  (/✓ ready in \d+/i.test(string) || /✓ starting/i.test(string))) {
118
107
  serverReadyDetected = true;
119
108
  this.log(`Started ${projectType.name} dev server on port ${serverPort}.\n`);
120
- const sessionId = localConfig.getSessionId(currentStore);
121
- setTimeout(() => this.logAppLocalUrl(currentStore, sessionId), 100);
109
+ setTimeout(() => this.logLocalPreviewUrl(), 100);
122
110
  if (!isAppDev) {
123
111
  this.watchForChanges();
124
112
  }
@@ -135,8 +123,7 @@ export default class AppFrontendDev extends PushAppCommand {
135
123
  /watching for file changes/i.test(string)) {
136
124
  serverReadyDetected = true;
137
125
  this.log(`Started ${projectType.name} dev server on port ${serverPort}.\n`);
138
- const sessionId = localConfig.getSessionId(currentStore);
139
- setTimeout(() => this.logAppLocalUrl(currentStore, sessionId), 100);
126
+ setTimeout(() => this.logLocalPreviewUrl(), 100);
140
127
  if (!isAppDev) {
141
128
  this.watchForChanges();
142
129
  }
@@ -154,8 +141,7 @@ export default class AppFrontendDev extends PushAppCommand {
154
141
  /watch mode enabled/i.test(string))) {
155
142
  serverReadyDetected = true;
156
143
  this.log(`Started ${projectType.name} dev server on port ${serverPort}.\n`);
157
- const sessionId = localConfig.getSessionId(currentStore);
158
- setTimeout(() => this.logAppLocalUrl(currentStore, sessionId), 100);
144
+ setTimeout(() => this.logLocalPreviewUrl(), 100);
159
145
  if (!isAppDev) {
160
146
  this.watchForChanges();
161
147
  }
@@ -171,8 +157,7 @@ export default class AppFrontendDev extends PushAppCommand {
171
157
  if (!serverReadyDetected) {
172
158
  serverReadyDetected = true;
173
159
  this.log(`Started ${projectType.name} dev server on port ${serverPort}.\n`);
174
- const sessionId = localConfig.getSessionId(currentStore);
175
- setTimeout(() => this.logAppLocalUrl(currentStore, sessionId), 100);
160
+ setTimeout(() => this.logLocalPreviewUrl(), 100);
176
161
  if (!isAppDev) {
177
162
  this.watchForChanges();
178
163
  }
@@ -184,8 +169,7 @@ export default class AppFrontendDev extends PushAppCommand {
184
169
  /ready on http:\/\/localhost:\d+/i.test(string)) {
185
170
  serverReadyDetected = true;
186
171
  this.log(`Started ${projectType.name} dev server on port ${serverPort}.\n`);
187
- const sessionId = localConfig.getSessionId(currentStore);
188
- setTimeout(() => this.logAppLocalUrl(currentStore, sessionId), 100);
172
+ setTimeout(() => this.logLocalPreviewUrl(), 100);
189
173
  if (!isAppDev) {
190
174
  this.watchForChanges();
191
175
  }
@@ -203,8 +187,7 @@ export default class AppFrontendDev extends PushAppCommand {
203
187
  if (!serverReadyDetected && /nuxt \d+\.\d+\.\d+/i.test(string)) {
204
188
  serverReadyDetected = true;
205
189
  this.log(`Started ${projectType.name} dev server on port ${serverPort}.\n`);
206
- const sessionId = localConfig.getSessionId(currentStore);
207
- setTimeout(() => this.logAppLocalUrl(currentStore, sessionId), 100);
190
+ setTimeout(() => this.logLocalPreviewUrl(), 100);
208
191
  if (!isAppDev) {
209
192
  this.watchForChanges();
210
193
  }
@@ -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.3"
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.3",
4
4
  "type": "module",
5
5
  "description": "Swell's command line interface/utility",
6
6
  "keywords": [