@swell/cli 1.0.2 → 2.0.1-alpha.2

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 (105) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +2 -140
  3. package/bin/dev.cmd +3 -0
  4. package/bin/dev.js +7 -0
  5. package/bin/run.cmd +3 -0
  6. package/bin/run.js +7 -0
  7. package/dist/app-command.d.ts +22 -0
  8. package/dist/app-command.js +45 -0
  9. package/dist/commands/app/_pull.d.ts +8 -0
  10. package/dist/commands/app/_pull.js +45 -0
  11. package/dist/commands/app/_uninstall.d.ts +12 -0
  12. package/dist/commands/app/_uninstall.js +45 -0
  13. package/dist/commands/app/info.d.ts +15 -0
  14. package/dist/commands/app/info.js +92 -0
  15. package/dist/commands/app/install.d.ts +17 -0
  16. package/dist/commands/app/install.js +177 -0
  17. package/dist/commands/app/push.d.ts +17 -0
  18. package/dist/commands/app/push.js +158 -0
  19. package/dist/commands/app/version.d.ts +18 -0
  20. package/dist/commands/app/version.js +156 -0
  21. package/dist/commands/app/watch.d.ts +15 -0
  22. package/dist/commands/app/watch.js +110 -0
  23. package/dist/commands/create/app.d.ts +21 -0
  24. package/dist/commands/create/app.js +210 -0
  25. package/dist/commands/create/content.d.ts +19 -0
  26. package/dist/commands/create/content.js +87 -0
  27. package/dist/commands/create/function.d.ts +25 -0
  28. package/dist/commands/create/function.js +191 -0
  29. package/dist/commands/create/index.d.ts +6 -0
  30. package/dist/commands/create/index.js +34 -0
  31. package/dist/commands/create/model.d.ts +19 -0
  32. package/dist/commands/create/model.js +87 -0
  33. package/dist/commands/create/notification.d.ts +25 -0
  34. package/dist/commands/create/notification.js +170 -0
  35. package/dist/commands/login.d.ts +22 -0
  36. package/dist/commands/login.js +77 -0
  37. package/dist/commands/logout.d.ts +8 -0
  38. package/dist/commands/logout.js +45 -0
  39. package/dist/commands/logs.d.ts +23 -0
  40. package/dist/commands/logs.js +207 -0
  41. package/dist/commands/switch.d.ts +10 -0
  42. package/dist/commands/switch.js +41 -0
  43. package/dist/commands/whoami.d.ts +7 -0
  44. package/dist/commands/whoami.js +24 -0
  45. package/dist/create-collection-command.d.ts +26 -0
  46. package/dist/create-collection-command.js +97 -0
  47. package/dist/create-command.d.ts +16 -0
  48. package/dist/create-command.js +59 -0
  49. package/dist/index.d.ts +1 -0
  50. package/dist/index.js +1 -0
  51. package/dist/lib/api.d.ts +13 -0
  52. package/dist/lib/api.js +117 -0
  53. package/dist/lib/app-config.d.ts +29 -0
  54. package/dist/lib/app-config.js +127 -0
  55. package/dist/lib/apps/app-config.d.ts +42 -0
  56. package/dist/lib/apps/app-config.js +167 -0
  57. package/dist/lib/apps/index.d.ts +67 -0
  58. package/dist/lib/apps/index.js +167 -0
  59. package/dist/lib/apps/paths.d.ts +18 -0
  60. package/dist/lib/apps/paths.js +54 -0
  61. package/dist/lib/bundle.d.ts +1 -0
  62. package/dist/lib/bundle.js +40 -0
  63. package/dist/lib/config.d.ts +37 -0
  64. package/dist/lib/config.js +131 -0
  65. package/dist/lib/constants.d.ts +5 -0
  66. package/dist/lib/constants.js +17 -0
  67. package/dist/lib/create/content.d.ts +23 -0
  68. package/dist/lib/create/content.js +40 -0
  69. package/dist/lib/create/function.d.ts +17 -0
  70. package/dist/lib/create/function.js +36 -0
  71. package/dist/lib/create/index.d.ts +13 -0
  72. package/dist/lib/create/index.js +20 -0
  73. package/dist/lib/create/model.d.ts +22 -0
  74. package/dist/lib/create/model.js +30 -0
  75. package/dist/lib/create/notification.d.ts +25 -0
  76. package/dist/lib/create/notification.js +1 -0
  77. package/dist/lib/info.d.ts +38 -0
  78. package/dist/lib/info.js +91 -0
  79. package/dist/lib/logs/index.d.ts +23 -0
  80. package/dist/lib/logs/index.js +88 -0
  81. package/dist/lib/logs/line-output.d.ts +5 -0
  82. package/dist/lib/logs/line-output.js +13 -0
  83. package/dist/lib/logs/output.d.ts +7 -0
  84. package/dist/lib/logs/output.js +8 -0
  85. package/dist/lib/logs/table-output.d.ts +11 -0
  86. package/dist/lib/logs/table-output.js +128 -0
  87. package/dist/lib/sessions.d.ts +9 -0
  88. package/dist/lib/sessions.js +110 -0
  89. package/dist/lib/stores.d.ts +63 -0
  90. package/dist/lib/stores.js +173 -0
  91. package/dist/lib/style.d.ts +24 -0
  92. package/dist/lib/style.js +29 -0
  93. package/dist/lib/swell-function-wrapper.d.ts +88 -0
  94. package/dist/lib/swell-function-wrapper.js +302 -0
  95. package/dist/remote-app-command.d.ts +36 -0
  96. package/dist/remote-app-command.js +367 -0
  97. package/dist/swell-command.d.ts +21 -0
  98. package/dist/swell-command.js +43 -0
  99. package/oclif.manifest.json +1085 -0
  100. package/package.json +81 -43
  101. package/LICENSE +0 -674
  102. package/dist/bin/multipart-parser-85ef9626.js +0 -456
  103. package/dist/bin/swell-0f71f07d.js +0 -30670
  104. package/dist/bin/swell.js +0 -24
  105. package/dist/man/swell.1 +0 -85
@@ -0,0 +1,17 @@
1
+ import { env } from './config.js';
2
+ // note: env values stored in ~/.swell/env.json
3
+ // url for backend api calls
4
+ export const API_BASE_URL = env.get('API_BASE_URL') || 'https://api.swell.store';
5
+ // url for admin api calls
6
+ export const ADMIN_API_BASE_URL =
7
+ // eslint-disable-next-line no-template-curly-in-string
8
+ env.get('ADMIN_API_BASE_URL') || 'https://${STORE_ID}.swell.store/admin/api';
9
+ // url host for admin login
10
+ export const LOGIN_HOST = env.get('LOGIN_HOST') || 'https://${STORE_ID}.swell.store';
11
+ // helpers to get constants with replacements
12
+ export function getAdminApiBaseUrl(storeId) {
13
+ return ADMIN_API_BASE_URL.replace('${STORE_ID}', storeId);
14
+ }
15
+ export function getLoginHost(storeId) {
16
+ return LOGIN_HOST.replace('${STORE_ID}', storeId || 'app');
17
+ }
@@ -0,0 +1,23 @@
1
+ interface CreateContentFileBody {
2
+ collection: string;
3
+ label?: string;
4
+ description?: string;
5
+ fields?: ContentFieldProps[];
6
+ views?: ContentViewProps[];
7
+ }
8
+ interface ContentViewProps {
9
+ fields: [];
10
+ id: ContentView;
11
+ nav?: Record<string, any>;
12
+ tabs: [];
13
+ }
14
+ type ContentView = 'edit' | 'list' | 'new';
15
+ type ContentType = 'asset' | 'basic_html' | 'blocks' | 'boolean' | 'category_lookup' | 'checkbox' | 'checkboxes' | 'collection' | 'color' | 'currency' | 'customer_lookup' | 'date' | 'datetime' | 'document' | 'dropdown' | 'email' | 'field_group' | 'generic_lookup' | 'icon' | 'image' | 'long_text' | 'lookup' | 'markdown' | 'number' | 'percent' | 'phone' | 'product_lookup' | 'radio' | 'rich_html' | 'select' | 'short_text' | 'slider' | 'slug' | 'tags' | 'text' | 'textarea' | 'time' | 'toggle' | 'url' | 'variant_lookup' | 'video';
16
+ interface ContentFieldProps {
17
+ id: string;
18
+ label: string;
19
+ type?: ContentType;
20
+ }
21
+ declare const parseViews: (views: string[]) => ContentViewProps[];
22
+ declare const parseFields: (fields: string[]) => ContentFieldProps[];
23
+ export { CreateContentFileBody, parseFields, parseViews };
@@ -0,0 +1,40 @@
1
+ import { titleize } from 'inflection';
2
+ const parseViews = (views) => {
3
+ if (views.length === 0) {
4
+ return [];
5
+ }
6
+ const viewResponse = [];
7
+ for (const viewId of views) {
8
+ viewResponse.push({
9
+ id: viewId,
10
+ ...(viewId === 'list' ? { nav: {} } : {}),
11
+ tabs: [],
12
+ fields: [],
13
+ });
14
+ }
15
+ return viewResponse;
16
+ };
17
+ const parseFields = (fields) => {
18
+ if (fields.length === 0) {
19
+ return [];
20
+ }
21
+ const fieldResponse = [];
22
+ for (const fieldPair of fields) {
23
+ if (fieldPair.includes(':')) {
24
+ const [id, type] = fieldPair.split(':');
25
+ fieldResponse.push({
26
+ id,
27
+ label: titleize(id),
28
+ type,
29
+ });
30
+ }
31
+ else {
32
+ fieldResponse.push({
33
+ id: fieldPair,
34
+ label: titleize(fieldPair),
35
+ });
36
+ }
37
+ }
38
+ return fieldResponse;
39
+ };
40
+ export { parseFields, parseViews };
@@ -0,0 +1,17 @@
1
+ type FunctionType = 'cron' | 'model' | 'route';
2
+ type FunctionLanguage = 'js' | 'ts';
3
+ interface CreateFunctionArgs {
4
+ functionName: string;
5
+ trigger: FunctionType;
6
+ }
7
+ interface WriteFunctionFileParams {
8
+ description: string;
9
+ events: string;
10
+ route: string;
11
+ functionName: string;
12
+ language: FunctionLanguage;
13
+ schedule: string;
14
+ trigger: FunctionType;
15
+ }
16
+ declare const getFunctionTemplate: ({ description, events, route, schedule, language, trigger, }: WriteFunctionFileParams) => string;
17
+ export { CreateFunctionArgs, FunctionLanguage, FunctionType, WriteFunctionFileParams, getFunctionTemplate, };
@@ -0,0 +1,36 @@
1
+ const getFunctionTemplate = ({ description, events, route, schedule, language, trigger, }) => {
2
+ let typeAppend = ``;
3
+ if (trigger === 'model') {
4
+ const eventsList = events
5
+ .split(/\s*,\s*/g)
6
+ .filter((e) => e)
7
+ .map((eventName) => `'${eventName}'`)
8
+ .join(', ');
9
+ typeAppend = `,
10
+ model: {
11
+ events: [${eventsList}],
12
+ conditions: {},
13
+ },`;
14
+ }
15
+ if (trigger === 'route') {
16
+ typeAppend = `,
17
+ route: {
18
+ public: ${route === 'public' || !route ? 'true' : 'false'}
19
+ },`;
20
+ }
21
+ if (trigger === 'cron') {
22
+ typeAppend = `,
23
+ cron: {
24
+ schedule: '${schedule}',
25
+ },`;
26
+ }
27
+ return `export const config${language === 'ts' ? ': SwellConfig' : ''} = {
28
+ description: '${description}'${typeAppend}
29
+ };
30
+
31
+ export default async function (req${language === 'ts' ? ': SwellRequest' : ''}) {
32
+ const { swell, store, settings, data${trigger === 'route' ? ', session' : ''} } = req;
33
+ }
34
+ `;
35
+ };
36
+ export { getFunctionTemplate, };
@@ -0,0 +1,13 @@
1
+ interface CreateFileParams {
2
+ extension?: string;
3
+ fileBody: Record<any, any> | string;
4
+ fileName: string;
5
+ }
6
+ declare const toFileName: (value: string) => string;
7
+ declare const toAppName: (value: string) => string;
8
+ declare const toAppId: (value: string) => string;
9
+ declare const toFunctionFileName: (value: string) => string;
10
+ declare const toCollectionName: (value: string) => string;
11
+ declare const toCollectionLabel: (value: string) => string;
12
+ declare const toNotificationLabel: (value: string) => string;
13
+ export { CreateFileParams, toFileName, toAppName, toAppId, toFunctionFileName, toCollectionName, toCollectionLabel, toNotificationLabel, };
@@ -0,0 +1,20 @@
1
+ import { dasherize, pluralize, underscore, titleize } from 'inflection';
2
+ // Example: 'My Thing' => 'my-thing'
3
+ const toFileName = (value) => dasherize(underscore(value.split('.')[0].replace(' ', '_'))).toLowerCase();
4
+ // Example: 'my-things' => 'My Things'
5
+ const toAppName = (value) => pluralize(titleize(value).replace('-', ' '));
6
+ // Example: 'My App' => 'my_app'
7
+ const toAppId = (value) => value
8
+ .replace(/[^a-z0-9]+/gi, '_')
9
+ .replace(/^\_|\_$/g, '')
10
+ .toLowerCase();
11
+ // Example: 'My Thing' => 'my-thing'
12
+ // And: 'myThing' => 'myThing'
13
+ const toFunctionFileName = (value) => value.split('.')[0].replace(' ', '-');
14
+ // Example: 'My Thing' => 'my-things'
15
+ const toCollectionName = (value) => pluralize(value.replaceAll(/[^A-Za-z]/g, '-').replace(' ', '')).toLowerCase();
16
+ // Example: 'my-things' => 'My Things'
17
+ const toCollectionLabel = (value) => pluralize(titleize(value).replace('-', ' '));
18
+ // Example: 'order-receipt' => 'Order Receipt'
19
+ const toNotificationLabel = (value) => titleize(value).replace('-', ' ');
20
+ export { toFileName, toAppName, toAppId, toFunctionFileName, toCollectionName, toCollectionLabel, toNotificationLabel, };
@@ -0,0 +1,22 @@
1
+ interface CreateModelFileBody {
2
+ label?: string;
3
+ collection?: string;
4
+ description?: string;
5
+ fields?: ModelFieldResponse;
6
+ events?: ModelEventObject;
7
+ }
8
+ interface ModelEventObject {
9
+ types?: ModelEvent[];
10
+ }
11
+ interface ModelEvent {
12
+ id: ModelEventID;
13
+ }
14
+ type ModelEventID = 'created' | 'deleted' | 'updated';
15
+ type ModelFieldResponse = Record<string, ModelFieldTypeObject>;
16
+ type FieldType = 'collection' | 'currency' | 'image' | 'int' | 'link' | 'string';
17
+ interface ModelFieldTypeObject {
18
+ type: FieldType;
19
+ }
20
+ declare const parseFields: (fields: string[]) => ModelFieldResponse;
21
+ declare const parseEvents: (events: string[]) => ModelEventObject;
22
+ export { CreateModelFileBody, parseEvents, parseFields };
@@ -0,0 +1,30 @@
1
+ const parseFields = (fields) => {
2
+ if (fields.length === 0) {
3
+ return {};
4
+ }
5
+ const fieldResponse = {};
6
+ for (const fieldPair of fields) {
7
+ if (fieldPair.includes(':')) {
8
+ const [id, type] = fieldPair.split(':');
9
+ fieldResponse[id] = {
10
+ type,
11
+ };
12
+ }
13
+ }
14
+ return fieldResponse;
15
+ };
16
+ const parseEvents = (events) => {
17
+ if (events.length === 0) {
18
+ return {};
19
+ }
20
+ const eventTypes = [];
21
+ for (const event of events) {
22
+ eventTypes.push({
23
+ id: event,
24
+ });
25
+ }
26
+ return {
27
+ types: eventTypes,
28
+ };
29
+ };
30
+ export { parseEvents, parseFields };
@@ -0,0 +1,25 @@
1
+ interface WriteNotificationsFileParams {
2
+ description: string;
3
+ event: string;
4
+ model: string;
5
+ name: string;
6
+ subject: string;
7
+ admin?: boolean;
8
+ once?: boolean;
9
+ }
10
+ interface CreateNotificationFileBody {
11
+ admin?: boolean;
12
+ collection: string;
13
+ conditions: Record<any, any>;
14
+ contact?: string;
15
+ description?: string;
16
+ event: string;
17
+ label: string;
18
+ method: string;
19
+ new?: boolean;
20
+ query?: Record<any, any>;
21
+ repeat?: boolean;
22
+ sample?: Record<any, any>;
23
+ subject: string;
24
+ }
25
+ export { CreateNotificationFileBody, WriteNotificationsFileParams };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Helper module for the `info` command. Encapsulates the logic for
3
+ * displaying the app's information.
4
+ */
5
+ import { App, AppVersion } from './apps';
6
+ interface FieldDefinition {
7
+ label: string;
8
+ transform?: (_arg: any) => Promise<string> | string;
9
+ value?: any;
10
+ }
11
+ export declare class FieldDefinitions {
12
+ app: App;
13
+ versions: AppVersion[];
14
+ constructor(app: App, versions: AppVersion[]);
15
+ get client(): FieldDefinition;
16
+ get dateCreated(): FieldDefinition;
17
+ get dateUpdated(): FieldDefinition;
18
+ get description(): FieldDefinition;
19
+ get installed(): FieldDefinition;
20
+ get name(): FieldDefinition;
21
+ get publicId(): FieldDefinition;
22
+ get published(): FieldDefinition;
23
+ get version(): FieldDefinition;
24
+ /**
25
+ * Returns a FieldDefinition object for the given field.
26
+ *
27
+ * The function uses the field name to find its value in the app object. If
28
+ * the FieldDefinition has a transform function for the required field,
29
+ * it will be called to transform the value before returning the
30
+ * FieldDefinition.
31
+ *
32
+ * @param field The field to get the definition for
33
+ *
34
+ * @returns The FieldDefinition object
35
+ */
36
+ getFieldDefinition(field: string): FieldDefinition;
37
+ }
38
+ export {};
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Helper module for the `info` command. Encapsulates the logic for
3
+ * displaying the app's information.
4
+ */
5
+ import { camelize } from 'inflection';
6
+ import style from './style.js';
7
+ function transformDate(date) {
8
+ return date ? new Date(date).toUTCString() : '';
9
+ }
10
+ function transformBoolean(bool) {
11
+ return bool ? 'Yes' : 'No';
12
+ }
13
+ export class FieldDefinitions {
14
+ app;
15
+ versions;
16
+ constructor(app, versions) {
17
+ this.app = app;
18
+ this.versions = versions;
19
+ }
20
+ get client() {
21
+ return {
22
+ label: 'Test store',
23
+ transform: (client) => `${client.name} (${style.storeId(client.id)})`,
24
+ };
25
+ }
26
+ get dateCreated() {
27
+ return { label: 'Created at', transform: transformDate };
28
+ }
29
+ get dateUpdated() {
30
+ return { label: 'Updated at', transform: transformDate };
31
+ }
32
+ get description() {
33
+ return { label: 'Description' };
34
+ }
35
+ get installed() {
36
+ return { label: 'Installed', transform: transformBoolean };
37
+ }
38
+ get name() {
39
+ return { label: 'Name' };
40
+ }
41
+ get publicId() {
42
+ return {
43
+ label: 'Public ID',
44
+ transform: (publicId) => {
45
+ if (publicId)
46
+ return publicId;
47
+ // replace _ as the first char any number of times in private_id
48
+ return this.app.private_id?.replace(/^_+/, '') || '';
49
+ },
50
+ };
51
+ }
52
+ get published() {
53
+ return { label: 'Published', transform: transformBoolean };
54
+ }
55
+ get version() {
56
+ return {
57
+ label: 'Version',
58
+ transform: (_version) => {
59
+ if (this.versions.length === 0) {
60
+ return 'N/A';
61
+ }
62
+ return [
63
+ style.appConfigValue(this.versions[0].version),
64
+ '(latest)',
65
+ this.versions.length > 1 ? `| ${this.versions.length} (total)` : '',
66
+ ].join(' ');
67
+ },
68
+ };
69
+ }
70
+ /**
71
+ * Returns a FieldDefinition object for the given field.
72
+ *
73
+ * The function uses the field name to find its value in the app object. If
74
+ * the FieldDefinition has a transform function for the required field,
75
+ * it will be called to transform the value before returning the
76
+ * FieldDefinition.
77
+ *
78
+ * @param field The field to get the definition for
79
+ *
80
+ * @returns The FieldDefinition object
81
+ */
82
+ getFieldDefinition(field) {
83
+ const fieldDef = this[camelize(field, true)];
84
+ let value = this.app[field];
85
+ if (fieldDef.transform) {
86
+ value = fieldDef.transform(value);
87
+ }
88
+ fieldDef.value = value || '';
89
+ return fieldDef;
90
+ }
91
+ }
@@ -0,0 +1,23 @@
1
+ export { LineOutput } from './line-output.js';
2
+ export { TableOutput } from './table-output.js';
3
+ /**
4
+ * A log item.
5
+ *
6
+ * A common class for all log types that handles the mapping of log data to the
7
+ * columns. This mapping is to then be used by all output options consistently.
8
+ */
9
+ export declare class LoggedItem implements LogItem {
10
+ app: string;
11
+ data: string;
12
+ date: string;
13
+ env: string;
14
+ ip: string;
15
+ req: string;
16
+ request: string;
17
+ status: string;
18
+ time: string;
19
+ type: string;
20
+ constructor(log: any);
21
+ private prepareRequest;
22
+ private prepareRequestFunction;
23
+ }
@@ -0,0 +1,88 @@
1
+ import style from '../style.js';
2
+ export { LineOutput } from './line-output.js';
3
+ export { TableOutput } from './table-output.js';
4
+ /**
5
+ * A log item.
6
+ *
7
+ * A common class for all log types that handles the mapping of log data to the
8
+ * columns. This mapping is to then be used by all output options consistently.
9
+ */
10
+ export class LoggedItem {
11
+ app;
12
+ data;
13
+ date;
14
+ env;
15
+ ip;
16
+ req;
17
+ request;
18
+ status;
19
+ time;
20
+ type;
21
+ constructor(log) {
22
+ const logMessage = log.message;
23
+ this.app = style.logApp(log.$app_id || log.app_id);
24
+ this.data = logMessage?.data || null;
25
+ this.date = style.logDate(log.date.replaceAll(/T|Z/g, ' ').trim());
26
+ this.env = log.environment_id;
27
+ this.ip = log.ip;
28
+ this.request = this.prepareRequest(logMessage);
29
+ this.req = log.req_id;
30
+ this.status = style.logStatus(logMessage?.status);
31
+ this.time = `${log.time}ms`;
32
+ this.type = logMessage?.type;
33
+ }
34
+ prepareRequest(logMessage) {
35
+ let request = '';
36
+ if (!logMessage) {
37
+ return request;
38
+ }
39
+ const { url } = logMessage;
40
+ switch (logMessage.type) {
41
+ case 'function': {
42
+ request = this.prepareRequestFunction(logMessage);
43
+ break;
44
+ }
45
+ case 'webhook': {
46
+ const { event } = logMessage;
47
+ request = `Webhook ${event.type} > ${style.logMethod('POST')} ${url}`;
48
+ break;
49
+ }
50
+ case 'api': {
51
+ request = [
52
+ style.logMethod(logMessage.method.toUpperCase()),
53
+ logMessage.url,
54
+ ].join(' ');
55
+ break;
56
+ }
57
+ default: {
58
+ request = logMessage.type;
59
+ break;
60
+ }
61
+ }
62
+ return request;
63
+ }
64
+ prepareRequestFunction(logMessage) {
65
+ const { event } = logMessage;
66
+ const { logs, method, name } = logMessage;
67
+ const logLines = logs
68
+ ?.map((l) => {
69
+ const line = l.line?.join('\n');
70
+ switch (l.level) {
71
+ case 'debug': {
72
+ return style.funcDebug(line);
73
+ }
74
+ case 'warn': {
75
+ return style.funcWarn(line);
76
+ }
77
+ case 'error': {
78
+ return style.funcError(line);
79
+ }
80
+ default: {
81
+ return style.funcInfo(line);
82
+ }
83
+ }
84
+ })
85
+ .join('\n');
86
+ return `Function ${event ? `${event.type} >` : ''} ${style.logMethod(method.toUpperCase())} /${name} ${logLines ? `\n${logLines}` : ''}`;
87
+ }
88
+ }
@@ -0,0 +1,5 @@
1
+ import { Output } from './output.js';
2
+ export declare class LineOutput extends Output {
3
+ prepareData(log: LogItem): string[];
4
+ write(row: string[]): void;
5
+ }
@@ -0,0 +1,13 @@
1
+ import { Output } from './output.js';
2
+ const COLUMN_NAMES_TO_HIDE = new Set(['date', 'request']);
3
+ export class LineOutput extends Output {
4
+ prepareData(log) {
5
+ return this.columns.map((c) => {
6
+ const logValue = log[c];
7
+ return COLUMN_NAMES_TO_HIDE.has(c) ? logValue : `${c}: ${logValue}`;
8
+ });
9
+ }
10
+ write(row) {
11
+ console.log(row.join(' - '));
12
+ }
13
+ }
@@ -0,0 +1,7 @@
1
+ export declare abstract class Output {
2
+ protected columns: string[];
3
+ protected pretty: boolean;
4
+ constructor(columns: string[], pretty: boolean);
5
+ abstract prepareData(log: LogItem): string[];
6
+ abstract write(row: string[]): void;
7
+ }
@@ -0,0 +1,8 @@
1
+ export class Output {
2
+ columns;
3
+ pretty = false;
4
+ constructor(columns, pretty) {
5
+ this.columns = columns;
6
+ this.pretty = pretty;
7
+ }
8
+ }
@@ -0,0 +1,11 @@
1
+ import { Output } from './output.js';
2
+ export declare class TableOutput extends Output {
3
+ private stream;
4
+ constructor(columns: string[], pretty: boolean);
5
+ prepareData(log: LogItem): string[];
6
+ write(row: string[]): void;
7
+ private computeExtraWidths;
8
+ private displayOptions;
9
+ private prepareColumnDisplay;
10
+ private writeHeader;
11
+ }
@@ -0,0 +1,128 @@
1
+ import { titleize } from 'inflection';
2
+ import { createStream, getBorderCharacters, } from 'table';
3
+ import { Output } from './output.js';
4
+ // the terminal width used in column display
5
+ // leave 5% for padding
6
+ const MAX_WIDTH = Math.floor((process.stdout.columns || 80) * 0.95);
7
+ // the default column width
8
+ const DEFAULT_COL_WIDTH = 10;
9
+ export class TableOutput extends Output {
10
+ stream;
11
+ constructor(columns, pretty) {
12
+ super(columns, pretty);
13
+ const extraWidths = this.computeExtraWidths();
14
+ const config = {
15
+ border: getBorderCharacters('void'),
16
+ columnCount: columns.length,
17
+ columnDefault: { width: DEFAULT_COL_WIDTH },
18
+ columns: columns.map((c) => this.prepareColumnDisplay(c, extraWidths[c])),
19
+ drawVerticalLine: () => false,
20
+ };
21
+ // create the output stream and write the header
22
+ this.stream = createStream(config);
23
+ this.writeHeader();
24
+ }
25
+ prepareData(log) {
26
+ return this.columns.map((c) => {
27
+ // for each column, get the value from the log item and prepare it for
28
+ // display in the table, i.e. go through TableLoggedItem before returning
29
+ const tableLog = new TableLoggedItem(log, this.pretty);
30
+ const preparedLog = tableLog.prepareData();
31
+ return preparedLog[c];
32
+ });
33
+ }
34
+ write(row) {
35
+ this.stream.write(row);
36
+ }
37
+ computeExtraWidths() {
38
+ const extraWidths = {};
39
+ // compute the initial width of the table
40
+ // eslint-disable-next-line unicorn/no-array-reduce
41
+ const initialWidth = this.columns.reduce((acc, col) => {
42
+ const display = this.displayOptions(col);
43
+ return acc + (display.width || display.minWidth || DEFAULT_COL_WIDTH);
44
+ }, 0);
45
+ const availableWidth = MAX_WIDTH - initialWidth;
46
+ const minWidthCols = this.columns.filter((col) => this.displayOptions(col).minWidth);
47
+ // if there is extra width available, split it across all columns that have
48
+ // minWidth
49
+ if (availableWidth > 0 && minWidthCols.length > 0) {
50
+ const extraWidthPerCol = Math.floor(availableWidth / minWidthCols.length);
51
+ for (const col of minWidthCols) {
52
+ extraWidths[col] = extraWidthPerCol;
53
+ }
54
+ }
55
+ return extraWidths;
56
+ }
57
+ displayOptions(column) {
58
+ // an extension of the column def in https://www.npmjs.com/package/table
59
+ // for config.columns
60
+ const columns = {
61
+ data: {
62
+ minWidth: 40,
63
+ wrapWord: !this.pretty,
64
+ },
65
+ date: {
66
+ title: 'Date UTC',
67
+ width: 24,
68
+ },
69
+ req: {
70
+ width: 24,
71
+ },
72
+ request: {
73
+ minWidth: 40,
74
+ },
75
+ status: {
76
+ width: 6,
77
+ },
78
+ time: {
79
+ width: 6,
80
+ },
81
+ };
82
+ return columns[column];
83
+ }
84
+ prepareColumnDisplay(column, extraWidth) {
85
+ const display = this.displayOptions(column);
86
+ const { minWidth, wrapWord } = display;
87
+ let { width } = display;
88
+ if (minWidth) {
89
+ width = minWidth;
90
+ }
91
+ if (width && extraWidth) {
92
+ width += extraWidth;
93
+ }
94
+ return { width, wrapWord };
95
+ }
96
+ writeHeader() {
97
+ this.stream.write(this.columns.map((c) => this.displayOptions(c)?.title || titleize(c)));
98
+ }
99
+ }
100
+ /**
101
+ * A layer between the log item and the table output that handles the mapping of
102
+ * log data to the columns specifically for the table output.
103
+ */
104
+ class TableLoggedItem {
105
+ logItem;
106
+ pretty = false;
107
+ constructor(logItem, pretty) {
108
+ this.logItem = logItem;
109
+ this.pretty = pretty;
110
+ }
111
+ prepareData() {
112
+ this.logItem.data = this.prepareLogItemData();
113
+ return this.logItem;
114
+ }
115
+ prepareLogItemData() {
116
+ let logData = this.logItem.data;
117
+ try {
118
+ if (this.pretty && typeof logData === 'string') {
119
+ logData = JSON.parse(logData);
120
+ logData = JSON.stringify(logData, null, 2);
121
+ }
122
+ }
123
+ catch {
124
+ // do nothing, JSON parsing errors
125
+ }
126
+ return logData;
127
+ }
128
+ }