@swell/cli 2.0.1-alpha.16 → 2.0.1-alpha.18

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.
Files changed (86) hide show
  1. package/dist/app-command.d.ts +1 -1
  2. package/dist/app-command.js +9 -9
  3. package/dist/commands/app/dev.d.ts +16 -0
  4. package/dist/commands/app/dev.js +28 -0
  5. package/dist/commands/app/frontend/deploy.d.ts +15 -0
  6. package/dist/commands/app/frontend/deploy.js +41 -0
  7. package/dist/commands/app/frontend/dev.d.ts +19 -0
  8. package/dist/commands/app/frontend/dev.js +108 -0
  9. package/dist/commands/app/info.js +14 -9
  10. package/dist/commands/app/init.d.ts +12 -0
  11. package/dist/commands/app/init.js +34 -0
  12. package/dist/commands/app/install.d.ts +0 -3
  13. package/dist/commands/app/install.js +12 -21
  14. package/dist/commands/app/pull.d.ts +15 -0
  15. package/dist/commands/app/pull.js +50 -0
  16. package/dist/commands/app/push.d.ts +9 -12
  17. package/dist/commands/app/push.js +57 -131
  18. package/dist/commands/app/release.d.ts +13 -0
  19. package/dist/commands/app/release.js +148 -0
  20. package/dist/commands/app/version.d.ts +5 -4
  21. package/dist/commands/app/version.js +60 -39
  22. package/dist/commands/app/watch.d.ts +2 -4
  23. package/dist/commands/app/watch.js +3 -83
  24. package/dist/commands/create/app.d.ts +35 -11
  25. package/dist/commands/create/app.js +131 -128
  26. package/dist/commands/create/frontend.d.ts +12 -0
  27. package/dist/commands/create/frontend.js +38 -0
  28. package/dist/commands/create/function.d.ts +2 -2
  29. package/dist/commands/create/function.js +2 -2
  30. package/dist/commands/create/notification.d.ts +2 -2
  31. package/dist/commands/create/notification.js +2 -2
  32. package/dist/commands/env.d.ts +12 -0
  33. package/dist/commands/env.js +31 -0
  34. package/dist/commands/login.js +7 -7
  35. package/dist/commands/logs.js +0 -3
  36. package/dist/commands/theme/dev.d.ts +21 -0
  37. package/dist/commands/theme/dev.js +164 -0
  38. package/dist/commands/theme/init.d.ts +9 -0
  39. package/dist/commands/theme/init.js +15 -0
  40. package/dist/commands/theme/pull.d.ts +20 -0
  41. package/dist/commands/theme/pull.js +65 -0
  42. package/dist/commands/theme/push.d.ts +19 -0
  43. package/dist/commands/theme/push.js +89 -0
  44. package/dist/create-app-command.d.ts +20 -0
  45. package/dist/create-app-command.js +253 -0
  46. package/dist/create-collection-command.d.ts +3 -3
  47. package/dist/create-collection-command.js +4 -4
  48. package/dist/{create-command.d.ts → create-config-command.d.ts} +1 -1
  49. package/dist/{create-command.js → create-config-command.js} +3 -5
  50. package/dist/env/local.d.ts +8 -0
  51. package/dist/env/local.js +7 -0
  52. package/dist/env/production.d.ts +8 -0
  53. package/dist/env/production.js +7 -0
  54. package/dist/env/review.d.ts +8 -0
  55. package/dist/env/review.js +7 -0
  56. package/dist/env/staging.d.ts +8 -0
  57. package/dist/env/staging.js +7 -0
  58. package/dist/lib/api.d.ts +1 -0
  59. package/dist/lib/api.js +39 -13
  60. package/dist/lib/app-config.d.ts +12 -5
  61. package/dist/lib/app-config.js +57 -21
  62. package/dist/lib/apps/app-config.d.ts +17 -3
  63. package/dist/lib/apps/app-config.js +194 -66
  64. package/dist/lib/apps/index.d.ts +92 -12
  65. package/dist/lib/apps/index.js +145 -47
  66. package/dist/lib/apps/paths.d.ts +14 -7
  67. package/dist/lib/apps/paths.js +73 -28
  68. package/dist/lib/config.d.ts +9 -2
  69. package/dist/lib/config.js +49 -1
  70. package/dist/lib/constants.d.ts +5 -0
  71. package/dist/lib/constants.js +23 -4
  72. package/dist/lib/create/content.d.ts +1 -1
  73. package/dist/lib/create/index.js +5 -1
  74. package/dist/lib/info.js +2 -2
  75. package/dist/lib/stores.d.ts +5 -3
  76. package/dist/lib/stores.js +7 -5
  77. package/dist/lib/style.d.ts +2 -0
  78. package/dist/lib/style.js +2 -0
  79. package/dist/push-app-command.d.ts +73 -0
  80. package/dist/push-app-command.js +754 -0
  81. package/dist/remote-app-command.d.ts +35 -9
  82. package/dist/remote-app-command.js +417 -159
  83. package/oclif.manifest.json +817 -71
  84. package/package.json +13 -1
  85. package/dist/commands/app/_pull.d.ts +0 -8
  86. package/dist/commands/app/_pull.js +0 -45
@@ -5,11 +5,14 @@ import Conf from 'conf';
5
5
  import * as fs from 'node:fs';
6
6
  import * as path from 'node:path';
7
7
  const schema = {
8
+ compatibilities: { type: 'object' },
8
9
  description: { type: 'string' },
9
10
  id: { type: 'string' },
10
11
  integrations: { type: 'array' },
11
12
  name: { type: 'string' },
12
13
  permissions: { type: 'array' },
14
+ storefront: { type: 'object' },
15
+ theme: { type: 'object' },
13
16
  type: { type: 'string' },
14
17
  version: { type: 'string' },
15
18
  };
@@ -22,23 +25,23 @@ const APP_FILE_EXT = 'json';
22
25
  * @param dir - the directory to use for the config file path
23
26
  * @returns string
24
27
  */
25
- function appConfigFile(dir = '') {
28
+ function swellConfigFile(dir = '') {
26
29
  if (!dir) {
27
30
  dir = process.cwd();
28
31
  }
29
32
  return path.join(dir, [APP_FILE_NAME, '.', APP_FILE_EXT].join(''));
30
33
  }
31
34
  /**
32
- * Checks if the app config file exists.
35
+ * Checks if the swell config file exists at the given path, default to cwd.
33
36
  *
34
37
  * @param configFilePath - the path to the app config file
35
38
  * @returns boolean
36
39
  * @throws Error
37
40
  */
38
- function appConfigFileExists(configFilePath = '') {
41
+ function swellConfigFileExists(configFilePath = '') {
39
42
  // if the config file path is not provided, try to find it as the default
40
43
  if (!configFilePath) {
41
- configFilePath = appConfigFile();
44
+ configFilePath = swellConfigFile();
42
45
  }
43
46
  try {
44
47
  // eslint-disable-next-line no-bitwise
@@ -60,9 +63,9 @@ function appConfigFileExists(configFilePath = '') {
60
63
  * @returns string
61
64
  * @throws Error
62
65
  */
63
- function appConfigFilePath(dir = '') {
64
- const configFilePath = appConfigFile(dir);
65
- if (appConfigFileExists(configFilePath)) {
66
+ function swellConfigFilePath(dir = '') {
67
+ const configFilePath = swellConfigFile(dir);
68
+ if (swellConfigFileExists(configFilePath)) {
66
69
  return configFilePath;
67
70
  }
68
71
  // if the file doesn't exist, try to find it in the parent directory
@@ -70,10 +73,10 @@ function appConfigFilePath(dir = '') {
70
73
  if (dir && dir === path.parse(dir).root) {
71
74
  return '';
72
75
  }
73
- return appConfigFilePath(path.resolve(dir, '..'));
76
+ return swellConfigFilePath(path.resolve(dir, '..'));
74
77
  }
75
78
  async function writeRcFile(path, data) {
76
- let dataToWrite = '';
79
+ let dataToWrite = '# This file is automatically generated by the Swell CLI\n';
77
80
  for (const [key, value] of Object.entries(data)) {
78
81
  dataToWrite += `${key}=${value}\n`;
79
82
  }
@@ -84,9 +87,15 @@ function readRcFile(path) {
84
87
  const dataFromFile = fs.readFileSync(path, 'utf8');
85
88
  const lines = dataFromFile.split('\n');
86
89
  for (const line of lines) {
87
- const [key, value] = line.split('=');
88
- if (key && value) {
89
- data[key] = value;
90
+ const cleanLine = line.trim();
91
+ if (cleanLine.startsWith('#')) {
92
+ // Ignore comments
93
+ continue;
94
+ }
95
+ const [key, value] = cleanLine.trim().split('=');
96
+ const cleanValue = value && value.trim();
97
+ if (key && cleanValue) {
98
+ data[key] = cleanValue;
90
99
  }
91
100
  }
92
101
  return data;
@@ -95,19 +104,26 @@ function readRcFile(path) {
95
104
  * Returns a new configuration instance in the current working directory or
96
105
  * a parent directory.
97
106
  *
98
- * @param configDirPath - the path to the app config directory. If not provided,
107
+ * @param configDir - the path to the app config directory. If not provided,
99
108
  * the config file will be searched for in the current working directory or
100
109
  * a parent directory.
101
110
  *
102
111
  * @returns Conf
103
112
  */
104
- function config(configDirPath = '') {
105
- const dirPath = configDirPath || path.dirname(appConfigFilePath());
106
- const config = new Conf({
107
- configName: APP_FILE_NAME,
108
- cwd: dirPath,
109
- schema,
110
- });
113
+ function config(configDir = '') {
114
+ const dirPath = configDir || path.dirname(swellConfigFilePath());
115
+ let config;
116
+ try {
117
+ config = new Conf({
118
+ clearInvalidConfig: true,
119
+ configName: APP_FILE_NAME,
120
+ cwd: dirPath,
121
+ schema,
122
+ });
123
+ }
124
+ catch (error) {
125
+ throw new Error(`Error parsing swell.json: ${error.toString()}`);
126
+ }
111
127
  // for convenience, we add custom properties to the Conf object
112
128
  // we prefix them with "sw" to avoid conflicts with Conf's properties
113
129
  config.swDirPath = dirPath;
@@ -121,9 +137,29 @@ function config(configDirPath = '') {
121
137
  */
122
138
  function newConfig(path) {
123
139
  return new Conf({
140
+ clearInvalidConfig: true,
124
141
  configName: APP_FILE_NAME,
125
142
  cwd: `${process.cwd()}${path ? `/${path}` : ''}`,
126
143
  schema,
144
+ serialize,
145
+ });
146
+ }
147
+ /**
148
+ * Returns a new configuration instance at the specified path.
149
+ *
150
+ * @param appPath Path of the current working directory
151
+ * @returns Conf
152
+ */
153
+ function newConfigAtPath(appPath) {
154
+ const dirPath = appPath || path.dirname(swellConfigFilePath());
155
+ return new Conf({
156
+ configName: APP_FILE_NAME,
157
+ cwd: dirPath,
158
+ schema,
159
+ serialize,
127
160
  });
128
161
  }
129
- export { appConfigFileExists, config as default, newConfig, readRcFile, writeRcFile, };
162
+ function serialize(value) {
163
+ return JSON.stringify(value, null, 2);
164
+ }
165
+ export { config as default, newConfig, newConfigAtPath, readRcFile, swellConfigFileExists, writeRcFile, };
@@ -1,3 +1,4 @@
1
+ /// <reference types="node" />
1
2
  import { ConfigType } from './index.js';
2
3
  declare class IgnoringFileError extends Error {
3
4
  constructor(message: string);
@@ -7,6 +8,7 @@ declare class FunctionProcessingError extends Error {
7
8
  constructor(message: string, error: Error);
8
9
  }
9
10
  type AppConfigFile = {
11
+ content_type?: string;
10
12
  data?: string;
11
13
  date_uploaded?: string;
12
14
  ext?: string;
@@ -20,21 +22,33 @@ type AppConfigFile = {
20
22
  * app & the configuration are saved to the API.
21
23
  */
22
24
  declare abstract class AppConfig {
25
+ appConfigId?: string;
26
+ appPath: string;
27
+ byteSize?: number;
23
28
  date_created?: string;
29
+ date_updated?: string;
24
30
  file?: AppConfigFile;
25
- fileData?: string;
31
+ file_path?: string;
32
+ fileContent?: string;
33
+ fileData?: Buffer;
34
+ fileHash?: string;
35
+ filePath: string;
36
+ hash?: string;
26
37
  id?: string;
27
- /** The local path of a remote AppConfig */
28
- localPath?: string;
38
+ mbSize?: number;
29
39
  name?: string;
30
40
  parent_id?: string;
31
41
  slug?: string;
32
42
  values: {
33
43
  [key: string]: any;
34
44
  };
45
+ version?: string;
35
46
  constructor(attrs?: Partial<AppConfig>);
36
47
  static create(attrs?: Partial<AppConfig>): AppConfig;
48
+ isRootConfig(configDir: string): boolean;
37
49
  postData(): Promise<any>;
50
+ prepareFileData(): any;
51
+ abstract hasValues: boolean;
38
52
  /** build the JSON used when making requests (usually push) to the API */
39
53
  protected abstract preparePostData(postData: any): Promise<any>;
40
54
  abstract type: ConfigType;
@@ -1,9 +1,10 @@
1
- /* eslint-disable camelcase */
1
+ import { getEncoding } from 'istextorbinary';
2
2
  import isEmpty from 'lodash/isEmpty.js';
3
+ import { detectFilenameMime } from 'mime-detect';
3
4
  import * as fs from 'node:fs';
4
5
  import * as path from 'node:path';
5
6
  import { bundleFunction } from '../bundle.js';
6
- import { ConfigType } from './index.js';
7
+ import { ConfigPaths, ConfigType, filePathExists, hashFile } from './index.js';
7
8
  class IgnoringFileError extends Error {
8
9
  constructor(message) {
9
10
  super(message);
@@ -23,21 +24,68 @@ class FunctionProcessingError extends Error {
23
24
  * app & the configuration are saved to the API.
24
25
  */
25
26
  class AppConfig {
27
+ // Used when pushing a theme and the app config is considered
28
+ appConfigId;
29
+ // Full local path of remote AppConfig
30
+ appPath;
31
+ // Size of local file in bytes and mb
32
+ byteSize;
26
33
  date_created;
34
+ date_updated;
27
35
  file;
36
+ file_path;
37
+ // UTF8 content of local file
38
+ fileContent;
39
+ // Buffer of local file data
28
40
  fileData;
41
+ fileHash;
42
+ // Relative path of remote AppConfig, same as file_path if defined
43
+ filePath;
44
+ hash;
45
+ // Server-side props
29
46
  id;
30
- /** The local path of a remote AppConfig */
31
- localPath;
47
+ mbSize;
32
48
  name;
33
49
  parent_id;
34
50
  slug;
35
51
  values = {};
52
+ version;
36
53
  constructor(attrs = {}) {
37
54
  Object.assign(this, attrs);
38
- const { localPath } = attrs;
39
- if (localPath) {
40
- this.fileData = fs.readFileSync(localPath, 'utf8');
55
+ let { appPath, file_path, filePath, name, type } = attrs;
56
+ this.appPath = appPath || '';
57
+ this.filePath = filePath || '';
58
+ if (!type || !name) {
59
+ throw new Error('Missing app config type or name.');
60
+ }
61
+ if (!appPath) {
62
+ throw new Error('Missing app path to initialize config.');
63
+ }
64
+ // Make sure file path is set from server prop or type/name
65
+ if (!filePath) {
66
+ if (file_path) {
67
+ filePath = file_path;
68
+ }
69
+ else {
70
+ // Note this shouldn't be necessary unless the config was created before file_path was added
71
+ const typePath = ConfigPaths[String(type).toUpperCase()];
72
+ filePath = `${typePath}/${name}`;
73
+ if (!typePath) {
74
+ throw new Error(`Missing path for app config type: ${type}`);
75
+ }
76
+ }
77
+ this.filePath = filePath;
78
+ }
79
+ if (!appPath.endsWith(filePath)) {
80
+ this.appPath = path.join(appPath, filePath);
81
+ }
82
+ if (filePathExists(this.appPath)) {
83
+ this.fileHash = hashFile(this.appPath);
84
+ this.hash = this.hash || this.fileHash;
85
+ this.fileData = fs.readFileSync(this.appPath);
86
+ this.fileContent = this.fileData.toString('utf8');
87
+ this.byteSize = Buffer.byteLength(this.fileContent);
88
+ this.mbSize = this.byteSize / 1024 / 1024;
41
89
  }
42
90
  }
43
91
  static create(attrs = {}) {
@@ -51,120 +99,200 @@ class AppConfig {
51
99
  case ConfigType.MODEL: {
52
100
  return new AppConfigModel(attrs);
53
101
  }
102
+ case ConfigType.CONTENT: {
103
+ return new AppConfigContent(attrs);
104
+ }
54
105
  case ConfigType.NOTIFICATION: {
55
- // the path can be a tpl file or a json file as we can use the `create`
56
- // function when watching for changes. We need to make sure that we
57
- // always use the json file for AppConfig.
58
- attrs.localPath = AppConfigNotification.actualConfigPath(attrs.localPath);
59
106
  return new AppConfigNotification(attrs);
60
107
  }
108
+ case ConfigType.SETTING: {
109
+ return new AppConfigSetting(attrs);
110
+ }
111
+ case ConfigType.WEBHOOK: {
112
+ return new AppConfigWebhook(attrs);
113
+ }
114
+ case ConfigType.FRONTEND: {
115
+ return new AppConfigFrontend(attrs);
116
+ }
117
+ case ConfigType.THEME: {
118
+ return new AppConfigTheme(attrs);
119
+ }
61
120
  default: {
62
- // the default type is content so we want to make sure
63
- // that we set the expected type
121
+ // the default type is file
64
122
  const defaultConfig = new AppConfigDefault(attrs);
65
123
  defaultConfig.type = attrs.type;
66
124
  return defaultConfig;
67
125
  }
68
126
  }
69
127
  }
128
+ isRootConfig(configDir) {
129
+ return !this.filePath
130
+ ?.replace(new RegExp(`^${configDir}/`), '')
131
+ .includes('/');
132
+ }
70
133
  async postData() {
71
- if (!this.localPath) {
72
- throw new Error('Missing local config path');
73
- }
74
- if (!this.fileData) {
134
+ if (this.fileContent === undefined) {
75
135
  return;
76
136
  }
77
- if (this.localPath.endsWith('.json')) {
137
+ if (this.hasValues && this.filePath.endsWith('.json')) {
78
138
  try {
79
- this.values = JSON.parse(this.fileData);
139
+ this.values = JSON.parse(this.fileContent);
80
140
  }
81
141
  catch {
82
- throw new IgnoringFileError('invalid JSON');
142
+ throw new IgnoringFileError('Invalid JSON');
83
143
  }
84
144
  if (isEmpty(this.values)) {
85
- throw new IgnoringFileError('empty JSON');
145
+ throw new IgnoringFileError('Empty JSON');
86
146
  }
87
147
  }
148
+ // Detect file content type
149
+ const contentType = this.filePath.endsWith('.liquid') || this.filePath.endsWith('.liquidx')
150
+ ? 'application/liquid'
151
+ : detectFilenameMime(this.filePath);
88
152
  const postData = {
153
+ file: {
154
+ content_type: contentType,
155
+ data: this.prepareFileData(),
156
+ },
157
+ file_path: this.filePath,
158
+ hash: this.hash,
159
+ // Name is either configured or the last part of the path without extension
89
160
  name: this.values?.name || this.name,
90
161
  type: this.type,
91
- values: this.values,
92
162
  };
163
+ if (this.hasValues) {
164
+ postData.values = this.values || {};
165
+ }
93
166
  return this.preparePostData(postData);
94
167
  }
168
+ prepareFileData() {
169
+ if (!this.fileData) {
170
+ return null;
171
+ }
172
+ return getEncoding(this.fileData) === 'binary'
173
+ ? { $base64: this.fileData.toString('base64') }
174
+ : this.fileData?.toString('utf8');
175
+ }
95
176
  }
96
177
  class AppConfigDefault extends AppConfig {
97
- type = ConfigType.CONTENT;
178
+ hasValues = false;
179
+ type = ConfigType.FILE;
98
180
  preparePostData(postData) {
99
181
  return postData;
100
182
  }
101
183
  }
102
184
  class AppConfigModel extends AppConfig {
185
+ hasValues = true;
103
186
  type = ConfigType.MODEL;
104
187
  preparePostData(postData) {
105
- postData.values.collection = this.name;
188
+ postData.values.collection =
189
+ postData.values.collection || postData.values.model || this.name;
190
+ return postData;
191
+ }
192
+ }
193
+ class AppConfigContent extends AppConfig {
194
+ hasValues = true;
195
+ type = ConfigType.CONTENT;
196
+ preparePostData(postData) {
197
+ postData.values.collection = postData.values.collection || this.name;
106
198
  return postData;
107
199
  }
108
200
  }
109
201
  class AppConfigNotification extends AppConfig {
202
+ hasValues = true;
110
203
  type = ConfigType.NOTIFICATION;
111
- /**
112
- * The actual config path is the path to the JSON file that is used to
113
- * configure the notification. This function ensures that the path is
114
- * correct.
115
- *
116
- * @param localPath - json or tpl file path
117
- * @returns - json file path
118
- */
119
- static actualConfigPath(localPath) {
120
- return localPath?.replace('.tpl', '.json');
121
- }
122
204
  preparePostData(postData) {
123
- if (!this.localPath) {
124
- throw new Error('Missing local config path');
125
- }
126
- const templateFile = this.localPath.replace('.json', '.tpl');
127
- this.fileData = fs.readFileSync(templateFile, 'utf8');
128
- if (this.fileData) {
129
- postData.values.content = {
130
- html: {
131
- data: this.fileData.replaceAll('\r\n', '\n'),
132
- },
205
+ const isTemplate = this.isRootConfig('notifications') &&
206
+ (this.filePath.endsWith('.tpl') || this.filePath.endsWith('.liquid'));
207
+ if (isTemplate) {
208
+ // Get json config to match collection/model name
209
+ let jsonData;
210
+ try {
211
+ const jsonPath = this.filePath
212
+ .replace('.tpl', '.json')
213
+ .replace('.liquid', '.json');
214
+ jsonData = JSON.parse(fs.readFileSync(jsonPath, 'utf8'));
215
+ postData.values.collection = jsonData?.collection || jsonData?.model;
216
+ }
217
+ catch {
218
+ // noop
219
+ }
220
+ postData.file = {
221
+ content_type: 'text/plain',
222
+ data: this.fileContent?.replaceAll('\r\n', '\n'),
133
223
  };
134
224
  }
135
225
  return postData;
136
226
  }
137
227
  }
228
+ class AppConfigSetting extends AppConfig {
229
+ hasValues = true;
230
+ type = ConfigType.SETTING;
231
+ preparePostData(postData) {
232
+ return postData;
233
+ }
234
+ }
235
+ class AppConfigWebhook extends AppConfig {
236
+ hasValues = true;
237
+ type = ConfigType.WEBHOOK;
238
+ preparePostData(postData) {
239
+ return postData;
240
+ }
241
+ }
138
242
  class AppConfigFunction extends AppConfig {
243
+ hasValues = true;
139
244
  type = ConfigType.FUNCTION;
140
245
  async preparePostData(postData) {
141
- if (!this.localPath) {
142
- throw new Error('Missing local config path');
143
- }
144
- try {
145
- const { code, config } = await bundleFunction(this.localPath);
146
- if (!config) {
147
- throw new IgnoringFileError('Function must export a `config` object.');
246
+ const isFunction = this.isRootConfig('functions') &&
247
+ (this.filePath.endsWith('.js') || this.filePath.endsWith('.ts'));
248
+ if (isFunction) {
249
+ try {
250
+ const { code, config } = await bundleFunction(this.filePath);
251
+ if (!config) {
252
+ throw new IgnoringFileError('Function must export a `config` object.');
253
+ }
254
+ // Save the original file and the bundled version
255
+ postData.file = {
256
+ data: this.prepareFileData(),
257
+ };
258
+ postData.build_file = {
259
+ content_type: 'text/javascript',
260
+ data: code,
261
+ };
262
+ postData.values = config;
263
+ }
264
+ catch (error) {
265
+ throw new FunctionProcessingError(`Unable to compile function ${this.name}`, error);
148
266
  }
149
- const fileName = path.basename(this.localPath);
150
- postData.file = {
151
- data: code,
152
- // we keep the file extension so we can easily identify the file path
153
- // when we need to delete the function
154
- ext: fileName.replace(this.name, ''),
155
- };
156
- postData.values = config;
157
- }
158
- catch (error) {
159
- throw new FunctionProcessingError(`Unable to compile function ${this.name}`, error);
160
267
  }
161
268
  return postData;
162
269
  }
163
270
  }
164
- class AppConfigAsset extends AppConfig {
271
+ // Assets do not get installed but saved as plain files
272
+ class AppConfigAsset extends AppConfigDefault {
273
+ hasValues = false;
165
274
  type = ConfigType.ASSET;
166
- preparePostData(_postData) {
167
- throw new Error('Assets not yet supported');
275
+ }
276
+ class AppConfigFrontend extends AppConfigDefault {
277
+ hasValues = false;
278
+ type = ConfigType.FRONTEND;
279
+ }
280
+ class AppConfigTheme extends AppConfigDefault {
281
+ hasValues = false;
282
+ type = ConfigType.THEME;
283
+ preparePostData(postData) {
284
+ const isConfigJson = this.filePath?.match(/^theme\/config\/[^./]+\.json$/);
285
+ // Files in theme/config/*.json are treated as settings with values
286
+ if (isConfigJson) {
287
+ try {
288
+ const jsonData = JSON.parse(fs.readFileSync(this.filePath, 'utf8'));
289
+ postData.values = jsonData;
290
+ }
291
+ catch {
292
+ // noop
293
+ }
294
+ }
295
+ return postData;
168
296
  }
169
297
  }
170
298
  export { AppConfig, FunctionProcessingError, IgnoringFileError };