@swell/cli 2.0.19 → 2.1.0

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.
@@ -174,14 +174,14 @@ export class AppConfig {
174
174
  : this.fileData?.toString('utf8');
175
175
  }
176
176
  }
177
- class AppConfigDefault extends AppConfig {
177
+ export class AppConfigDefault extends AppConfig {
178
178
  hasValues = false;
179
179
  type = ConfigType.FILE;
180
180
  preparePostData(postData) {
181
181
  return postData;
182
182
  }
183
183
  }
184
- class AppConfigModel extends AppConfig {
184
+ export class AppConfigModel extends AppConfig {
185
185
  hasValues = true;
186
186
  type = ConfigType.MODEL;
187
187
  preparePostData(postData) {
@@ -190,7 +190,7 @@ class AppConfigModel extends AppConfig {
190
190
  return postData;
191
191
  }
192
192
  }
193
- class AppConfigContent extends AppConfig {
193
+ export class AppConfigContent extends AppConfig {
194
194
  hasValues = true;
195
195
  type = ConfigType.CONTENT;
196
196
  preparePostData(postData) {
@@ -198,7 +198,7 @@ class AppConfigContent extends AppConfig {
198
198
  return postData;
199
199
  }
200
200
  }
201
- class AppConfigNotification extends AppConfig {
201
+ export class AppConfigNotification extends AppConfig {
202
202
  hasValues = true;
203
203
  type = ConfigType.NOTIFICATION;
204
204
  preparePostData(postData) {
@@ -225,35 +225,42 @@ class AppConfigNotification extends AppConfig {
225
225
  return postData;
226
226
  }
227
227
  }
228
- class AppConfigSetting extends AppConfig {
228
+ export class AppConfigSetting extends AppConfig {
229
229
  hasValues = true;
230
230
  type = ConfigType.SETTING;
231
231
  preparePostData(postData) {
232
232
  return postData;
233
233
  }
234
234
  }
235
- class AppConfigWebhook extends AppConfig {
235
+ export class AppConfigWebhook extends AppConfig {
236
236
  hasValues = true;
237
237
  type = ConfigType.WEBHOOK;
238
238
  preparePostData(postData) {
239
239
  return postData;
240
240
  }
241
241
  }
242
- class AppConfigFunction extends AppConfig {
242
+ export class AppConfigFunction extends AppConfig {
243
243
  hasValues = true;
244
244
  type = ConfigType.FUNCTION;
245
+ isRootFunction() {
246
+ return (this.isRootConfig('functions') &&
247
+ (this.filePath.endsWith('.js') || this.filePath.endsWith('.ts')));
248
+ }
245
249
  async preparePostData(postData) {
246
- const isFunction = this.isRootConfig('functions') &&
247
- (this.filePath.endsWith('.js') || this.filePath.endsWith('.ts'));
248
- if (isFunction) {
250
+ if (this.isRootFunction()) {
249
251
  try {
252
+ // get file contents and if it's empty ignore
253
+ const fileData = this.prepareFileData();
254
+ if (!fileData) {
255
+ return;
256
+ }
250
257
  const { code, config } = await bundleFunction(this.filePath);
251
258
  if (!config) {
252
259
  throw new IgnoringFileError('Function must export a `config` object.');
253
260
  }
254
261
  // Save the original file and the bundled version
255
262
  postData.file = {
256
- data: this.prepareFileData(),
263
+ data: fileData,
257
264
  };
258
265
  postData.build_file = {
259
266
  content_type: 'text/javascript',
@@ -269,15 +276,15 @@ class AppConfigFunction extends AppConfig {
269
276
  }
270
277
  }
271
278
  // Assets do not get installed but saved as plain files
272
- class AppConfigAsset extends AppConfigDefault {
279
+ export class AppConfigAsset extends AppConfigDefault {
273
280
  hasValues = false;
274
281
  type = ConfigType.ASSET;
275
282
  }
276
- class AppConfigFrontend extends AppConfigDefault {
283
+ export class AppConfigFrontend extends AppConfigDefault {
277
284
  hasValues = false;
278
285
  type = ConfigType.FRONTEND;
279
286
  }
280
- class AppConfigTheme extends AppConfigDefault {
287
+ export class AppConfigTheme extends AppConfigDefault {
281
288
  hasValues = false;
282
289
  type = ConfigType.THEME;
283
290
  preparePostData(postData) {
@@ -117,11 +117,10 @@ export declare enum ConfigInputFields {
117
117
  VERSION = "version"
118
118
  }
119
119
  export interface FrontendProjectType {
120
- buildCommand: string;
121
- configFileName: string;
122
- deployPath: string;
120
+ buildCommand?: string;
123
121
  devCommand: string;
124
- installCommand: string;
122
+ installCommand?: string;
123
+ mainPackage: string;
125
124
  name: string;
126
125
  slug: string;
127
126
  }
@@ -94,39 +94,122 @@ export var ConfigInputFields;
94
94
  ConfigInputFields["VALUES"] = "values";
95
95
  ConfigInputFields["VERSION"] = "version";
96
96
  })(ConfigInputFields || (ConfigInputFields = {}));
97
- export const FrontendProjectTypes = [
98
- {
99
- buildCommand: 'npx @cloudflare/next-on-pages',
100
- configFileName: 'next.config',
101
- deployPath: '.vercel/output/static',
102
- devCommand: 'npx next dev --port ${PORT}',
103
- installCommand: 'npx create-next-app@latest frontend --typescript --use-npm --src-dir --app --eslint --import-alias "@/*" --tailwind',
104
- name: 'Next.js',
105
- slug: 'nextjs',
106
- },
97
+ // Legacy apps (pre-workspace) - Astro only (Proxima, Sunrise)
98
+ // Uses direct commands without workspace prefix
99
+ const LegacyFrontendProjectTypes = [
107
100
  {
108
101
  buildCommand: 'npx astro build',
109
- configFileName: 'astro.config',
110
- deployPath: 'dist',
111
102
  devCommand: 'npx astro dev --port ${PORT}',
112
- installCommand: 'npm create astro@latest frontend -- --install --no-git --yes --skip-houston --typescript strict',
103
+ mainPackage: 'astro',
113
104
  name: 'Astro',
114
105
  slug: 'astro',
115
106
  },
116
107
  ];
117
- const frontendProjectConfigExtensions = ['.mjs', '.js', '.ts', '.cjs'];
108
+ // Modern apps (workspace-based) - All frameworks
109
+ export const FrontendProjectTypes = [
110
+ {
111
+ buildCommand: 'npm exec --workspace=frontend -- astro build',
112
+ devCommand: 'npm exec --workspace=frontend -- astro dev --port ${PORT}',
113
+ installCommand: 'npm create cloudflare@latest -- frontend --framework=astro --deploy=false --git=false -- --no-git --yes --skip-houston --typescript strict',
114
+ mainPackage: 'astro',
115
+ name: 'Astro',
116
+ slug: 'astro',
117
+ },
118
+ {
119
+ buildCommand: 'npm exec --workspace=frontend -- ng build',
120
+ devCommand: 'npm exec --workspace=frontend -- ng serve --port ${PORT}',
121
+ installCommand: 'npm create cloudflare@latest -- frontend --framework=angular --deploy=false --git=false -- --style=sass --zoneless --ai-config=none',
122
+ mainPackage: '@angular/core',
123
+ name: 'Angular',
124
+ slug: 'angular',
125
+ },
126
+ {
127
+ devCommand: 'npm exec --workspace=frontend -- wrangler dev --port ${PORT}',
128
+ installCommand: 'npm create cloudflare@latest -- frontend --framework=hono --deploy=false --git=false',
129
+ mainPackage: 'hono',
130
+ name: 'Hono',
131
+ slug: 'hono',
132
+ },
133
+ {
134
+ buildCommand: 'npm exec --workspace=frontend -- nuxt build',
135
+ devCommand: 'npm exec --workspace=frontend -- nuxt dev --port ${PORT}',
136
+ installCommand: 'npm create cloudflare@latest -- frontend --framework=nuxt --deploy=false --git=false -- --no-modules -f',
137
+ mainPackage: 'nuxt',
138
+ name: 'Nuxt',
139
+ slug: 'nuxt',
140
+ },
141
+ {
142
+ buildCommand: 'npm exec --workspace=frontend -- opennextjs-cloudflare build',
143
+ //devCommand: 'npm exec --workspace=frontend -- opennextjs-cloudflare build && npm exec --workspace=frontend -- opennextjs-cloudflare preview --port=${PORT}',
144
+ devCommand: 'npm exec --workspace=frontend -- next dev --turbopack --port ${PORT}',
145
+ installCommand: 'npm create cloudflare@latest -- frontend --framework=next --deploy=false --git=false -- --typescript --use-npm --src-dir --app --eslint --import-alias "@/*" --tailwind --turbopack',
146
+ mainPackage: 'next',
147
+ name: 'Next.js',
148
+ slug: 'nextjs',
149
+ },
150
+ ];
118
151
  export function getAppSlugId(app) {
119
152
  return toAppId(app.private_id) || app.public_id || app.id;
120
153
  }
154
+ function hasWorkspaceStructure(appPath) {
155
+ // Check if root package.json has workspaces field including 'frontend'
156
+ const rootPkgPath = path.join(appPath, 'package.json');
157
+ if (!filePathExists(rootPkgPath)) {
158
+ return false;
159
+ }
160
+ try {
161
+ const content = fs.readFileSync(rootPkgPath, 'utf-8');
162
+ const pkg = JSON.parse(content);
163
+ return Array.isArray(pkg.workspaces) && pkg.workspaces.includes('frontend');
164
+ }
165
+ catch {
166
+ return false;
167
+ }
168
+ }
121
169
  export function getFrontendProjectType(appPath) {
122
- for (const projectType of FrontendProjectTypes) {
123
- for (const extension of frontendProjectConfigExtensions) {
124
- const configFile = path.join(appPath, 'frontend', `${projectType.configFileName}${extension}`);
125
- if (filePathExists(configFile)) {
126
- return projectType;
170
+ // Detect workspace structure and select appropriate config
171
+ const hasWorkspace = hasWorkspaceStructure(appPath);
172
+ const projectTypes = hasWorkspace
173
+ ? FrontendProjectTypes
174
+ : LegacyFrontendProjectTypes;
175
+ // Try frontend/package.json first (new workspace structure), then root package.json (old structure)
176
+ const pkgPaths = [
177
+ path.join(appPath, 'frontend', 'package.json'),
178
+ path.join(appPath, 'package.json'),
179
+ ];
180
+ for (const pkgPath of pkgPaths) {
181
+ if (!filePathExists(pkgPath)) {
182
+ continue;
183
+ }
184
+ try {
185
+ const content = fs.readFileSync(pkgPath, 'utf-8');
186
+ const pkg = JSON.parse(content);
187
+ for (const projectType of projectTypes) {
188
+ if (pkg.dependencies?.[projectType.mainPackage] ||
189
+ pkg.devDependencies?.[projectType.mainPackage]) {
190
+ // Create a copy to avoid mutating the original
191
+ const detectedType = { ...projectType };
192
+ // If buildCommand not explicitly set in framework definition, detect it
193
+ if (detectedType.buildCommand === undefined) {
194
+ // Check if package.json has a "build" script
195
+ if (pkg.scripts?.build) {
196
+ detectedType.buildCommand = 'npm run build';
197
+ }
198
+ else {
199
+ // No build script found, set to empty string (no build needed)
200
+ detectedType.buildCommand = '';
201
+ }
202
+ }
203
+ return detectedType;
204
+ }
127
205
  }
128
206
  }
207
+ catch {
208
+ // Ignore JSON parse errors, try next path
209
+ continue;
210
+ }
129
211
  }
212
+ return undefined;
130
213
  }
131
214
  export function getConfigTypeFromPath(path) {
132
215
  for (const type in ConfigPaths) {
@@ -11,7 +11,8 @@ export async function bundleFunction(filePath) {
11
11
  metafile: true,
12
12
  platform: 'node',
13
13
  target: ['chrome58'],
14
- write: false, // Do not write the output to a file
14
+ write: false,
15
+ logLevel: 'silent', // Do not output compile errors
15
16
  });
16
17
  const { metafile: { inputs }, outputFiles: [{ text: origCode }], } = buildResult;
17
18
  // TODO: make a separate method to get dependencies for watch command
@@ -1,8 +1,5 @@
1
1
  export declare const API_BASE_URL: any;
2
- export declare const ADMIN_API_BASE_URL: any;
3
- export declare const LOGIN_HOST: any;
4
- export declare const APP_PAGES_HOST: any;
5
- export declare const STOREFRONT_FRONTEND_HOST: any;
2
+ export declare const LOCAL_PROXY_PROVIDER: any;
6
3
  export declare function getAdminApiBaseUrl(storeId: string): any;
7
4
  export declare function getLoginHost(storeId?: string): any;
8
5
  export declare function getAppFrontendHost(storeId: string, installedAppId: string): any;
@@ -4,15 +4,16 @@ import { env } from './config.js';
4
4
  // url for backend api calls
5
5
  export const API_BASE_URL = env.get('API_BASE_URL') || defaultEnv.API_BASE_URL;
6
6
  // url for admin api calls
7
- export const ADMIN_API_BASE_URL = env.get('ADMIN_API_BASE_URL') || defaultEnv.ADMIN_API_BASE_URL;
7
+ const ADMIN_API_BASE_URL = env.get('ADMIN_API_BASE_URL') || defaultEnv.ADMIN_API_BASE_URL;
8
8
  // url host for admin login
9
- export const LOGIN_HOST = env.get('LOGIN_HOST') || defaultEnv.LOGIN_HOST;
9
+ const LOGIN_HOST = env.get('LOGIN_HOST') || defaultEnv.LOGIN_HOST;
10
10
  // url host for app frontend
11
- export const APP_PAGES_HOST = env.get('APP_PAGES_HOST') || defaultEnv.APP_PAGES_HOST;
11
+ const APP_PAGES_HOST = env.get('APP_PAGES_HOST') || defaultEnv.APP_PAGES_HOST;
12
12
  // url host for storefront app frontend
13
- export const STOREFRONT_FRONTEND_HOST = env.get('STOREFRONT_FRONTEND_HOST') ||
13
+ const STOREFRONT_FRONTEND_HOST = env.get('STOREFRONT_FRONTEND_HOST') ||
14
14
  env.get('STOREFRONT_PAGES_HOST') || // Deprecated
15
15
  defaultEnv.STOREFRONT_FRONTEND_HOST;
16
+ export const LOCAL_PROXY_PROVIDER = env.get('LOCAL_PROXY_PROVIDER') || defaultEnv.LOCAL_PROXY_PROVIDER;
16
17
  // helpers to get constants with replacements
17
18
  export function getAdminApiBaseUrl(storeId) {
18
19
  return ADMIN_API_BASE_URL.replace('${STORE_ID}', storeId);
@@ -0,0 +1 @@
1
+ export declare function getProxyUrl(port: number): Promise<string>;
package/dist/lib/proxy.js CHANGED
@@ -1 +1,29 @@
1
- "use strict";
1
+ import localtunnel from 'localtunnel';
2
+ import ngrok from 'ngrok';
3
+ import { LOCAL_PROXY_PROVIDER } from './constants.js';
4
+ export async function getProxyUrl(port) {
5
+ const provider = LOCAL_PROXY_PROVIDER || 'localtunnel';
6
+ try {
7
+ switch (provider) {
8
+ case 'local': {
9
+ return `http://localhost:${port}`;
10
+ }
11
+ case 'ngrok': {
12
+ return await ngrok.connect({
13
+ addr: port,
14
+ region: 'us', // TODO: make it configurable
15
+ });
16
+ }
17
+ // eslint-disable-next-line unicorn/no-useless-switch-case
18
+ case 'localtunnel':
19
+ default: {
20
+ const tunnel = await localtunnel({ port });
21
+ return tunnel.url;
22
+ }
23
+ }
24
+ }
25
+ catch (error) {
26
+ console.log(error);
27
+ throw new Error(`Unable to start tunnel on port ${port} (${provider}): ${error.message}`);
28
+ }
29
+ }
@@ -48,10 +48,28 @@ declare function executeModuleHandler(req: SwellRequest, context: Event): Promis
48
48
  * @returns {boolean}
49
49
  */
50
50
  declare function isOrdinaryObject(val: any): boolean;
51
- declare const origialConsoleLog: {
52
- (...data: any[]): void;
53
- (message?: any, ...optionalParams: any[]): void;
54
- };
51
+ declare namespace originalConsole {
52
+ let log: {
53
+ (...data: any[]): void;
54
+ (message?: any, ...optionalParams: any[]): void;
55
+ };
56
+ let info: {
57
+ (...data: any[]): void;
58
+ (message?: any, ...optionalParams: any[]): void;
59
+ };
60
+ let debug: {
61
+ (...data: any[]): void;
62
+ (message?: any, ...optionalParams: any[]): void;
63
+ };
64
+ let warn: {
65
+ (...data: any[]): void;
66
+ (message?: any, ...optionalParams: any[]): void;
67
+ };
68
+ let error: {
69
+ (...data: any[]): void;
70
+ (message?: any, ...optionalParams: any[]): void;
71
+ };
72
+ }
55
73
  /**
56
74
  * Class representing a Swell request.
57
75
  */
@@ -68,6 +86,7 @@ declare class SwellRequest {
68
86
  logParams: any;
69
87
  apiHost: any;
70
88
  id: any;
89
+ isLocalDev: boolean;
71
90
  swell: SwellAPI;
72
91
  body: {};
73
92
  query: {};
@@ -1,5 +1,11 @@
1
1
  "use strict";
2
- const origialConsoleLog = console.log;
2
+ const originalConsole = {
3
+ log: console.log,
4
+ info: console.info,
5
+ debug: console.debug,
6
+ warn: console.warn,
7
+ error: console.error,
8
+ };
3
9
  addEventListener('fetch', (event) => {
4
10
  event.respondWith(request(event.request, event.env, event));
5
11
  });
@@ -93,6 +99,8 @@ class SwellRequest {
93
99
  this.logParams = this.parseJson(req.headers.get('Swell-Request-Log'));
94
100
  this.apiHost = req.headers.get('Swell-API-Host') || 'https://api.schema.io';
95
101
  this.id = req.headers.get('Swell-Request-ID') || this.logParams?.req_id;
102
+ // Check if the request is from a local development environment
103
+ this.isLocalDev = req.headers.get('Swell-Local-Dev') === 'true';
96
104
  // Swell client
97
105
  this.swell = new SwellAPI(this, context);
98
106
  // URL of the original request
@@ -142,7 +150,7 @@ class SwellRequest {
142
150
  }
143
151
  }
144
152
  log(level, ...line) {
145
- origialConsoleLog(...line);
153
+ originalConsole[level]?.(...line);
146
154
  this._logs.push({
147
155
  date: Date.now(),
148
156
  line: line.map((l) => (l instanceof Error ? l.stack : JSON.stringify(l))),
@@ -150,7 +158,7 @@ class SwellRequest {
150
158
  });
151
159
  }
152
160
  getIngestableLogs(response) {
153
- if (!this.logParams) {
161
+ if (!this.logParams || this.isLocalDev) {
154
162
  return;
155
163
  }
156
164
  if (this.logParams.$start) {
@@ -384,4 +392,3 @@ function isOrdinaryObject(val) {
384
392
  val !== null &&
385
393
  Object.getPrototypeOf(val) === Object.prototype);
386
394
  }
387
- ;
@@ -12,20 +12,22 @@ interface WatchingChange {
12
12
  export declare abstract class PushAppCommand extends RemoteAppCommand {
13
13
  frontendPath: string;
14
14
  logWatchChanges: boolean;
15
- onWatchChange?: (appConfig?: AppConfig, result?: any) => void;
15
+ onWatchChange?: (appConfig?: AppConfig, action?: string, result?: any) => void;
16
16
  watchingChangeQueue: Map<string, WatchingChange>;
17
17
  watchingFiles: Set<string>;
18
18
  watchingIgnoreFilter: GlobbyFilterFunction | null;
19
19
  watchingTimer: NodeJS.Timeout | null;
20
20
  watchListener: (eventType: fs.WatchEventType, filename: null | string) => Promise<void>;
21
- buildFrontend(projectType: FrontendProjectType): Promise<void>;
21
+ protected showFrontendMigrationError(): never;
22
+ buildFrontend(projectType?: FrontendProjectType): Promise<void>;
22
23
  chooseAppToPull(query?: any): Promise<App>;
23
24
  createAppStorefront(hasOtherStorefronts?: boolean): Promise<any>;
24
25
  deployAppFrontend(force?: boolean, log?: boolean): Promise<void>;
25
26
  deployFrontend(projectType: FrontendProjectType): Promise<string>;
26
27
  ensureAppExists(file?: string, shouldCreate?: boolean): Promise<boolean>;
27
28
  ensureLoggedIn(): Promise<true | undefined>;
28
- exec(command: string, onOutput?: (string: string) => any | false): Promise<void>;
29
+ execFrontend(command: string, onOutput?: (string: string) => any | false): Promise<void>;
30
+ exec(command: string, cwd?: string, onOutput?: (string: string) => any | false): Promise<void>;
29
31
  getAllAppStorefronts(params?: {
30
32
  type?: string;
31
33
  }): Promise<any>;
@@ -79,9 +81,11 @@ export declare abstract class PushAppCommand extends RemoteAppCommand {
79
81
  updateFrontendDeployment(currentStore: string, projectType: FrontendProjectType, deploymentUrl: string, deploymentHash: string): Promise<void>;
80
82
  watchForChanges({ logChanges, onChange, syncAll, }?: {
81
83
  logChanges?: boolean;
82
- onChange?: () => void;
84
+ onChange?: (appConfig?: AppConfig, result?: any) => void;
83
85
  syncAll?: boolean;
84
86
  }): Promise<void>;
87
+ startProxyServer(port?: number): Promise<number>;
88
+ updateLocalProxy(proxyUrl: string, storefrontId?: string): Promise<void>;
85
89
  wranglerDeployFrontend(projectType: FrontendProjectType): Promise<string>;
86
90
  private confirmRemoveInstalledApp;
87
91
  }