@swell/cli 2.0.20 → 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.
@@ -1,26 +1,21 @@
1
1
  import { Flags } from '@oclif/core';
2
- import getPort, { portNumbers } from 'get-port';
3
- import ngrok from 'ngrok';
4
- import ora from 'ora';
5
2
  import { default as localConfig } from '../../../lib/config.js';
6
3
  import style from '../../../lib/style.js';
7
4
  import { PushAppCommand } from '../../../push-app-command.js';
8
- const FALLBACK_PORT = 3000;
5
+ import AppDev from '../dev.js';
9
6
  export default class AppFrontendDev extends PushAppCommand {
10
7
  static examples = [
11
8
  'swell app frontend dev',
12
9
  'swell app frontend dev --no-push',
13
- 'swell app frontend dev --port 3000',
10
+ 'swell app frontend dev --proxy-port 3000',
14
11
  'swell app frontend dev --storefront-select',
15
12
  'swell app frontend dev --storefront-id <id>',
16
13
  ];
17
14
  static flags = {
18
- 'no-push': Flags.boolean({
19
- description: 'skip pushing app files initially',
20
- }),
21
- port: Flags.integer({
22
- char: 'p',
23
- description: 'override the default port to run the app locally',
15
+ ...AppDev.flags,
16
+ 'proxy-port': Flags.integer({
17
+ description: 'specify the port for an existing frontend proxy',
18
+ default: 3001,
24
19
  }),
25
20
  'storefront-id': Flags.string({
26
21
  description: 'identify a storefront to preview with and push theme files to',
@@ -29,25 +24,23 @@ export default class AppFrontendDev extends PushAppCommand {
29
24
  default: false,
30
25
  description: 'prompt to select a storefront to preview with',
31
26
  }),
27
+ 'app-dev': Flags.boolean({
28
+ description: 'indicates frontend app is running in app dev mode',
29
+ default: true,
30
+ }),
32
31
  };
33
32
  static orientation = {
34
33
  env: 'test',
35
34
  };
36
35
  static summary = `Run a frontend app in dev mode from your local machine.`;
37
- logAppLocalUrl(storeId, sessionId) {
38
- const frontendAppRoute = this.app.type === 'storefront' && this.app.storefront?.external
39
- ? `/${this.app.private_id}`
40
- : '';
41
- this.log(`View it at ${style.link(`${this.localFrontendUrl(storeId, sessionId)}${frontendAppRoute}`)}\n`);
42
- }
43
36
  async run() {
44
37
  const { flags } = await this.parse(AppFrontendDev);
45
- const { port } = flags;
38
+ const { port, 'proxy-port': proxyPort, 'app-dev': isAppDev } = flags;
46
39
  const noPush = flags['no-push'];
47
40
  if (!(await this.ensureAppExists(undefined, false))) {
48
41
  return;
49
42
  }
50
- if (!noPush) {
43
+ if (!noPush && !isAppDev) {
51
44
  await this.pushAppConfigs();
52
45
  }
53
46
  if (this.app.type === 'storefront') {
@@ -55,54 +48,124 @@ export default class AppFrontendDev extends PushAppCommand {
55
48
  this.logStorefrontConnected();
56
49
  }
57
50
  this.saveCurrentStorefront();
58
- const projectType = this.getFrontendProjectType();
59
- await this.startDevServer(projectType, port);
51
+ if (!isAppDev) {
52
+ this.log(`Starting app dev server...\n`);
53
+ }
54
+ const serverPort = await this.startProxyServer(proxyPort || port);
55
+ await this.execFrontendProject(serverPort, isAppDev);
60
56
  }
61
- async startDevServer(projectType, port) {
57
+ async execFrontendProject(serverPort, isAppDev) {
58
+ const projectType = this.getFrontendProjectType(!isAppDev);
59
+ if (!projectType) {
60
+ return;
61
+ }
62
62
  const currentStore = localConfig.getDefaultStore();
63
- const spinner = ora();
64
- // Find an open port starting at 3000
65
- const freePort = port ||
66
- (await getPort({ port: portNumbers(3000, 3100) })) ||
67
- FALLBACK_PORT;
68
- this.log(`Starting ${projectType.name} dev server...\n`);
69
- // Start ngrok proxy
70
- const proxyUrl = await ngrok.connect({
71
- addr: freePort,
72
- region: 'us', // TODO: make it configurable
73
- });
74
- await this.updateLocalProxy(proxyUrl, spinner, this.storefront?.id);
75
- return this.exec(projectType.devCommand.replace('${PORT}', String(freePort)), (string) => {
76
- // Hide useless astro warning about image optimization
77
- if (string.includes('The current configuration does not support image optimization')) {
78
- return false;
63
+ let serverReadyDetected = false;
64
+ await this.execFrontend(projectType.devCommand.replace('${PORT}', String(serverPort)), (string) => {
65
+ // Framework-specific output handling and ready detection
66
+ if (projectType.slug === 'nextjs') {
67
+ // 1. NOISE FILTER: Hide unwanted output
68
+ if (string.includes('Using vars defined in .dev.vars')) {
69
+ return false;
70
+ }
71
+ // 2. URL FILTER: Hide localhost/network URLs (confusing - users should use Swell tunnel)
72
+ if (/- Local:\s+http:\/\//i.test(string) ||
73
+ /- Network:\s+http:\/\//i.test(string) ||
74
+ /Ready on http:\/\/localhost/i.test(string)) {
75
+ return false;
76
+ }
77
+ // 3. READY DETECTION: Detect server ready, show our message
78
+ if (!serverReadyDetected) {
79
+ if (/✓ Ready in \d+/i.test(string) ||
80
+ /✓ Starting/i.test(string)) {
81
+ serverReadyDetected = true;
82
+ this.log(`Started ${projectType.name} dev server on port ${serverPort}.\n`);
83
+ const sessionId = localConfig.getSessionId(currentStore);
84
+ setTimeout(() => this.logAppLocalUrl(currentStore, sessionId), 100);
85
+ if (!isAppDev) {
86
+ this.watchForChanges();
87
+ }
88
+ }
89
+ }
79
90
  }
80
- // Hide useless astro warning about .dev.vars
81
- if (string.includes('There is no `.dev.vars` file')) {
82
- return false;
91
+ else if (projectType.slug === 'astro') {
92
+ // 2. URL FILTER: Hide localhost/network URLs (confusing - users should use Swell tunnel)
93
+ if (string.includes('┃ Local') ||
94
+ string.includes('┃ Network')) {
95
+ return false;
96
+ }
97
+ // 3. READY DETECTION: Detect server ready, show our message
98
+ if (!serverReadyDetected && /watching for file changes/i.test(string)) {
99
+ serverReadyDetected = true;
100
+ this.log(`Started ${projectType.name} dev server on port ${serverPort}.\n`);
101
+ const sessionId = localConfig.getSessionId(currentStore);
102
+ setTimeout(() => this.logAppLocalUrl(currentStore, sessionId), 100);
103
+ if (!isAppDev) {
104
+ this.watchForChanges();
105
+ }
106
+ }
83
107
  }
84
- // Hide astro localhost URL/network as it can confuse the user
85
- if (string.includes('┃ Local') || string.includes('┃ Network')) {
86
- return false;
108
+ else if (projectType.slug === 'angular') {
109
+ // 1. NOISE FILTER: Hide unwanted output
110
+ if (string.includes('press h + enter to show help')) {
111
+ return false;
112
+ }
113
+ // 2. URL FILTER: Hide localhost/network URLs (confusing - users should use Swell tunnel)
114
+ if (/➜\s+Local:\s+http:\/\//i.test(string)) {
115
+ return false;
116
+ }
117
+ // 3. READY DETECTION: Detect server ready, show our message
118
+ if (!serverReadyDetected) {
119
+ if (/Application bundle generation complete/i.test(string) ||
120
+ /Watch mode enabled/i.test(string)) {
121
+ serverReadyDetected = true;
122
+ this.log(`Started ${projectType.name} dev server on port ${serverPort}.\n`);
123
+ const sessionId = localConfig.getSessionId(currentStore);
124
+ setTimeout(() => this.logAppLocalUrl(currentStore, sessionId), 100);
125
+ if (!isAppDev) {
126
+ this.watchForChanges();
127
+ }
128
+ }
129
+ }
87
130
  }
88
- // Started, show app url (TODO for nextjs)
89
- if (string.includes('watching for file changes')) {
90
- spinner.succeed(`Started ${projectType.name} dev server on port ${freePort}.\n`);
91
- const sessionId = localConfig.getSessionId(currentStore);
92
- setTimeout(() => this.logAppLocalUrl(currentStore, sessionId), 100);
93
- this.watchForChanges();
131
+ else if (projectType.slug === 'hono') {
132
+ // 2. URL FILTER: Hide localhost URLs (confusing - users should use Swell tunnel)
133
+ // Note: For Hono, the ready signal IS the URL line, so we handle both in section 3
134
+ // 3. READY DETECTION: Detect server ready, show our message, hide the URL
135
+ if (!serverReadyDetected && /Ready on http:\/\/localhost:\d+/i.test(string)) {
136
+ serverReadyDetected = true;
137
+ this.log(`Started ${projectType.name} dev server on port ${serverPort}.\n`);
138
+ const sessionId = localConfig.getSessionId(currentStore);
139
+ setTimeout(() => this.logAppLocalUrl(currentStore, sessionId), 100);
140
+ if (!isAppDev) {
141
+ this.watchForChanges();
142
+ }
143
+ return false; // Hide the "Ready on localhost" line
144
+ }
145
+ }
146
+ else if (projectType.slug === 'nuxt') {
147
+ // 2. URL FILTER: Hide localhost/network URLs (confusing - users should use Swell tunnel)
148
+ if (/➜ Local:\s+http:\/\//i.test(string) ||
149
+ /➜ Network:/i.test(string)) {
150
+ return false;
151
+ }
152
+ // 3. READY DETECTION: Detect server ready, show our message
153
+ if (!serverReadyDetected && /Nuxt \d+\.\d+\.\d+/i.test(string)) {
154
+ serverReadyDetected = true;
155
+ this.log(`Started ${projectType.name} dev server on port ${serverPort}.\n`);
156
+ const sessionId = localConfig.getSessionId(currentStore);
157
+ setTimeout(() => this.logAppLocalUrl(currentStore, sessionId), 100);
158
+ if (!isAppDev) {
159
+ this.watchForChanges();
160
+ }
161
+ }
94
162
  }
95
163
  });
96
164
  }
97
- async updateLocalProxy(proxyUrl, spinner, storefrontId) {
98
- const storefront = this.app.type === 'storefront' &&
99
- (await this.getAppStorefront({ storefront_id: storefrontId }));
100
- await this.handleRequestErrors(async () => this.api.put({ adminPath: `/client/apps/${this.app.id}/local-proxy` }, {
101
- body: {
102
- proxy_url: proxyUrl,
103
- storefront_id: storefront?.id || null,
104
- storefront_slug: storefront?.slug || null,
105
- },
106
- }), () => spinner.fail('Error starting local proxy'));
165
+ logAppLocalUrl(storeId, sessionId) {
166
+ const frontendAppRoute = this.app.type === 'storefront' && this.app.storefront?.external
167
+ ? `/${this.app.private_id}`
168
+ : '';
169
+ this.log(`View it at ${style.link(`${this.localFrontendUrl(storeId, sessionId)}${frontendAppRoute}`)}\n`);
107
170
  }
108
171
  }
@@ -7,6 +7,10 @@ export declare abstract class CreateAppCommand extends SwellCommand {
7
7
  };
8
8
  private devApi;
9
9
  constructor(argv: string[], config: any);
10
+ addAllowedHostsToAstro(configPath: string): Promise<void>;
11
+ addAllowedHostsToAngular(configPath: string): Promise<void>;
12
+ addAllowedHostsToNuxt(configPath: string): Promise<void>;
13
+ private addFrontendAllowedHosts;
10
14
  createAppConfigFolders(swellConfig: any): Promise<void>;
11
15
  createFrontendApp(swellConfig: any, flags: any, directCreate?: boolean, kind?: string): Promise<boolean>;
12
16
  createStorefrontApp(swellConfig: any, flags: any, directCreate?: boolean): Promise<boolean>;
@@ -10,6 +10,7 @@ import Api from './lib/api.js';
10
10
  import { FrontendProjectTypes, getAllConfigPaths, writeFile, writeJsonFile, } from './lib/apps/index.js';
11
11
  import style from './lib/style.js';
12
12
  import { SwellCommand } from './swell-command.js';
13
+ import fs from 'node:fs/promises';
13
14
  const execAsync = promisify(exec);
14
15
  export class CreateAppCommand extends SwellCommand {
15
16
  static baseFlags = {
@@ -30,6 +31,134 @@ export class CreateAppCommand extends SwellCommand {
30
31
  super(argv, config);
31
32
  this.devApi = new Api(undefined, 'test');
32
33
  }
34
+ async addAllowedHostsToAstro(configPath) {
35
+ const astroConfigPath = path.join(configPath, 'frontend', 'astro.config.mjs');
36
+ let content;
37
+ try {
38
+ content = await fs.readFile(astroConfigPath, 'utf-8');
39
+ }
40
+ catch {
41
+ return;
42
+ }
43
+ if (content.includes('allowedHosts')) {
44
+ return;
45
+ }
46
+ const lines = content.split('\n');
47
+ let insertIndex = -1;
48
+ for (let i = 0; i < lines.length; i++) {
49
+ if (lines[i].includes('defineConfig({')) {
50
+ insertIndex = i + 1;
51
+ break;
52
+ }
53
+ }
54
+ if (insertIndex === -1) {
55
+ return;
56
+ }
57
+ // Detect indentation from next line or use default
58
+ const nextLine = lines[insertIndex];
59
+ const indentMatch = nextLine?.match(/^(\s+)/);
60
+ const baseIndent = indentMatch ? indentMatch[1] : ' ';
61
+ const viteConfig = [
62
+ `${baseIndent}vite: {`,
63
+ `${baseIndent} server: {`,
64
+ `${baseIndent} allowedHosts: ['.ngrok.app', '.loca.lt'],`,
65
+ `${baseIndent} },`,
66
+ `${baseIndent}},`,
67
+ ];
68
+ lines.splice(insertIndex, 0, ...viteConfig);
69
+ await fs.writeFile(astroConfigPath, lines.join('\n'), 'utf-8');
70
+ }
71
+ async addAllowedHostsToAngular(configPath) {
72
+ const angularJsonPath = path.join(configPath, 'frontend', 'angular.json');
73
+ let content;
74
+ try {
75
+ content = await fs.readFile(angularJsonPath, 'utf-8');
76
+ }
77
+ catch {
78
+ return;
79
+ }
80
+ let config;
81
+ try {
82
+ config = JSON.parse(content);
83
+ }
84
+ catch {
85
+ return;
86
+ }
87
+ const projects = config.projects;
88
+ if (!projects || typeof projects !== 'object') {
89
+ return;
90
+ }
91
+ const projectNames = Object.keys(projects);
92
+ if (projectNames.length === 0) {
93
+ return;
94
+ }
95
+ // Use first project (typically only one exists in new apps)
96
+ const projectName = projectNames[0];
97
+ const project = projects[projectName];
98
+ if (!project?.architect?.serve) {
99
+ return;
100
+ }
101
+ if (!project.architect.serve.options) {
102
+ project.architect.serve.options = {};
103
+ }
104
+ if (project.architect.serve.options.allowedHosts) {
105
+ return;
106
+ }
107
+ project.architect.serve.options.allowedHosts = ['.ngrok.app', '.loca.lt'];
108
+ await fs.writeFile(angularJsonPath, JSON.stringify(config, null, 2), 'utf-8');
109
+ }
110
+ async addAllowedHostsToNuxt(configPath) {
111
+ const nuxtConfigPath = path.join(configPath, 'frontend', 'nuxt.config.ts');
112
+ let content;
113
+ try {
114
+ content = await fs.readFile(nuxtConfigPath, 'utf-8');
115
+ }
116
+ catch {
117
+ return;
118
+ }
119
+ if (content.includes('allowedHosts')) {
120
+ return;
121
+ }
122
+ const lines = content.split('\n');
123
+ let insertIndex = -1;
124
+ for (let i = 0; i < lines.length; i++) {
125
+ if (lines[i].includes('defineNuxtConfig({')) {
126
+ insertIndex = i + 1;
127
+ break;
128
+ }
129
+ }
130
+ if (insertIndex === -1) {
131
+ return;
132
+ }
133
+ const nextLine = lines[insertIndex];
134
+ const indentMatch = nextLine?.match(/^(\s+)/);
135
+ const baseIndent = indentMatch ? indentMatch[1] : ' ';
136
+ const viteConfig = [
137
+ `${baseIndent}vite: {`,
138
+ `${baseIndent} server: {`,
139
+ `${baseIndent} allowedHosts: ['.ngrok.app', '.loca.lt'],`,
140
+ `${baseIndent} },`,
141
+ `${baseIndent}},`,
142
+ ];
143
+ lines.splice(insertIndex, 0, ...viteConfig);
144
+ await fs.writeFile(nuxtConfigPath, lines.join('\n'), 'utf-8');
145
+ }
146
+ async addFrontendAllowedHosts(projectType, configPath) {
147
+ // Configure allowedHosts for frameworks using Vite dev server
148
+ // This allows tunneling services (ngrok, localtunnel) to proxy requests to the local dev environment
149
+ switch (projectType.slug) {
150
+ case 'astro':
151
+ await this.addAllowedHostsToAstro(configPath);
152
+ break;
153
+ case 'angular':
154
+ await this.addAllowedHostsToAngular(configPath);
155
+ break;
156
+ case 'nuxt':
157
+ await this.addAllowedHostsToNuxt(configPath);
158
+ break;
159
+ // Other frameworks don't require allowedHosts configuration
160
+ }
161
+ }
33
162
  async createAppConfigFolders(swellConfig) {
34
163
  for (const type of getAllConfigPaths(swellConfig.get('type'))) {
35
164
  // Create a config folder
@@ -67,6 +196,9 @@ export class CreateAppCommand extends SwellCommand {
67
196
  if (!projectType) {
68
197
  this.error(`Could not find project type: ${frameworkType}`);
69
198
  }
199
+ if (!projectType.installCommand) {
200
+ this.error(`Project type ${projectType.name} cannot be installed (legacy type)`);
201
+ }
70
202
  this.log();
71
203
  spinner.start(`Creating ${projectType?.name} frontend app (this may take a while)...`);
72
204
  try {
@@ -88,6 +220,31 @@ export class CreateAppCommand extends SwellCommand {
88
220
  this.log();
89
221
  return false;
90
222
  }
223
+ // Ensure frontend package.json has correct name for workspace
224
+ const frontendPkgPath = path.join(configPath, 'frontend', 'package.json');
225
+ try {
226
+ const pkgContent = await fs.readFile(frontendPkgPath, 'utf-8');
227
+ const pkg = JSON.parse(pkgContent);
228
+ if (pkg.name !== 'frontend') {
229
+ pkg.name = 'frontend';
230
+ await fs.writeFile(frontendPkgPath, JSON.stringify(pkg, null, 2), 'utf-8');
231
+ }
232
+ }
233
+ catch {
234
+ // Ignore if package.json doesn't exist or can't be read
235
+ }
236
+ await this.addFrontendAllowedHosts(projectType, configPath);
237
+ // Re-run npm install at root to properly initialize workspace structure
238
+ // (removes frontend/package-lock.json, hoists dependencies, creates root lock file)
239
+ spinner.start('Initializing workspace...');
240
+ try {
241
+ const execAsync = promisify(exec);
242
+ await execAsync('npm install', { cwd: configPath });
243
+ spinner.succeed('Workspace initialized');
244
+ }
245
+ catch (error) {
246
+ spinner.warn('Failed to initialize workspace');
247
+ }
91
248
  spinner.succeed(`${projectType?.name} initialized app in ${configPath}/frontend/`);
92
249
  if (directCreate) {
93
250
  this.log();
@@ -212,6 +369,9 @@ export class CreateAppCommand extends SwellCommand {
212
369
  const configPath = path.dirname(config.path);
213
370
  const packageJson = {
214
371
  description: config.get('description'),
372
+ // Include workspace even if frontend doesn't exist yet
373
+ // npm tolerates missing workspace directories without errors
374
+ workspaces: ['frontend'],
215
375
  devDependencies: {
216
376
  '@swell/app-types': '^1.0.5',
217
377
  },
@@ -226,7 +386,7 @@ export class CreateAppCommand extends SwellCommand {
226
386
  target: 'esnext',
227
387
  types: ['@swell/app-types'],
228
388
  },
229
- exclude: ['node_modules'],
389
+ exclude: ['node_modules', 'frontend'],
230
390
  include: ['**/*.ts'],
231
391
  };
232
392
  await writeJsonFile(path.join(configPath, 'package.json'), packageJson);
@@ -4,5 +4,6 @@ declare namespace _default {
4
4
  let LOGIN_HOST: string;
5
5
  let APP_PAGES_HOST: string;
6
6
  let STOREFRONT_FRONTEND_HOST: string;
7
+ let LOCAL_PROXY_PROVIDER: string;
7
8
  }
8
9
  export default _default;
package/dist/env/local.js CHANGED
@@ -4,4 +4,5 @@ export default {
4
4
  LOGIN_HOST: 'http://${STORE_ID}.swell.test:3000',
5
5
  APP_PAGES_HOST: 'http://${STORE_ID}--${APP_ID}--${ENV}.swell.test:4001',
6
6
  STOREFRONT_FRONTEND_HOST: 'http://${BRANCH_PREFIX}${STOREFRONT_SLUG}.swell.test:4001',
7
+ LOCAL_PROXY_PROVIDER: '',
7
8
  };
@@ -4,5 +4,6 @@ declare namespace _default {
4
4
  let LOGIN_HOST: string;
5
5
  let APP_PAGES_HOST: string;
6
6
  let STOREFRONT_FRONTEND_HOST: string;
7
+ let LOCAL_PROXY_PROVIDER: string;
7
8
  }
8
9
  export default _default;
@@ -4,4 +4,5 @@ export default {
4
4
  LOGIN_HOST: 'https://${STORE_ID}.swell.store',
5
5
  APP_PAGES_HOST: 'https://${STORE_ID}--${APP_ID}--${ENV}.swell.store',
6
6
  STOREFRONT_FRONTEND_HOST: 'https://${BRANCH_PREFIX}${STOREFRONT_SLUG}.swell.store',
7
+ LOCAL_PROXY_PROVIDER: '',
7
8
  };
@@ -4,5 +4,6 @@ declare namespace _default {
4
4
  let LOGIN_HOST: string;
5
5
  let APP_PAGES_HOST: string;
6
6
  let STOREFRONT_FRONTEND_HOST: string;
7
+ let LOCAL_PROXY_PROVIDER: string;
7
8
  }
8
9
  export default _default;
@@ -4,4 +4,5 @@ export default {
4
4
  LOGIN_HOST: 'https://${REVIEW_DOMAIN}.rav2.swell.store',
5
5
  APP_PAGES_HOST: 'https://${STORE_ID}--${APP_ID}--${ENV}.${REVIEW_DOMAIN}.rav2.swell.store',
6
6
  STOREFRONT_FRONTEND_HOST: 'https://${BRANCH_PREFIX}${STOREFRONT_SLUG}.${REVIEW_DOMAIN}.rav2.swell.store',
7
+ LOCAL_PROXY_PROVIDER: '',
7
8
  };
@@ -4,5 +4,6 @@ declare namespace _default {
4
4
  let LOGIN_HOST: string;
5
5
  let APP_PAGES_HOST: string;
6
6
  let STOREFRONT_FRONTEND_HOST: string;
7
+ let LOCAL_PROXY_PROVIDER: string;
7
8
  }
8
9
  export default _default;
@@ -4,4 +4,5 @@ export default {
4
4
  LOGIN_HOST: 'https://staging.swell.store',
5
5
  APP_PAGES_HOST: 'https://${STORE_ID}--${APP_ID}--${ENV}.staging.swell.store',
6
6
  STOREFRONT_FRONTEND_HOST: 'https://${BRANCH_PREFIX}${STOREFRONT_SLUG}.staging.swell.store',
7
+ LOCAL_PROXY_PROVIDER: '',
7
8
  };
@@ -53,4 +53,53 @@ export declare abstract class AppConfig {
53
53
  protected abstract preparePostData(postData: any): Promise<any>;
54
54
  abstract type: ConfigType;
55
55
  }
56
+ export declare class AppConfigDefault extends AppConfig {
57
+ hasValues: boolean;
58
+ type: ConfigType;
59
+ preparePostData(postData: any): Promise<any>;
60
+ }
61
+ export declare class AppConfigModel extends AppConfig {
62
+ hasValues: boolean;
63
+ type: ConfigType;
64
+ preparePostData(postData: any): Promise<any>;
65
+ }
66
+ export declare class AppConfigContent extends AppConfig {
67
+ hasValues: boolean;
68
+ type: ConfigType;
69
+ preparePostData(postData: any): Promise<any>;
70
+ }
71
+ export declare class AppConfigNotification extends AppConfig {
72
+ hasValues: boolean;
73
+ type: ConfigType;
74
+ preparePostData(postData: any): Promise<any>;
75
+ }
76
+ export declare class AppConfigSetting extends AppConfig {
77
+ hasValues: boolean;
78
+ type: ConfigType;
79
+ preparePostData(postData: any): Promise<any>;
80
+ }
81
+ export declare class AppConfigWebhook extends AppConfig {
82
+ hasValues: boolean;
83
+ type: ConfigType;
84
+ preparePostData(postData: any): Promise<any>;
85
+ }
86
+ export declare class AppConfigFunction extends AppConfig {
87
+ hasValues: boolean;
88
+ type: ConfigType;
89
+ isRootFunction(): boolean;
90
+ preparePostData(postData: any): Promise<any>;
91
+ }
92
+ export declare class AppConfigAsset extends AppConfigDefault {
93
+ hasValues: boolean;
94
+ type: ConfigType;
95
+ }
96
+ export declare class AppConfigFrontend extends AppConfigDefault {
97
+ hasValues: boolean;
98
+ type: ConfigType;
99
+ }
100
+ export declare class AppConfigTheme extends AppConfigDefault {
101
+ hasValues: boolean;
102
+ type: ConfigType;
103
+ preparePostData(postData: any): Promise<any>;
104
+ }
56
105
  export {};
@@ -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) {