@swell/cli 1.0.2 → 2.0.1-alpha.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.
Files changed (105) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +1 -1
  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 +89 -42
  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,25 @@
1
+ import { CreateCommand } from '../../create-command.js';
2
+ import { ConfigType } from '../../lib/apps/index.js';
3
+ export default class CreateNotification extends CreateCommand {
4
+ static args: {
5
+ model: import("@oclif/core/lib/interfaces/parser.js").Arg<string | undefined, Record<string, unknown>>;
6
+ name: import("@oclif/core/lib/interfaces/parser.js").Arg<string | undefined, Record<string, unknown>>;
7
+ };
8
+ static description: string;
9
+ static examples: {
10
+ command: string;
11
+ description: string;
12
+ }[];
13
+ static flags: {
14
+ description: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
15
+ subject: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
16
+ event: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
17
+ admin: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
18
+ once: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
19
+ overwrite: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
20
+ };
21
+ static summary: string;
22
+ createType: ConfigType;
23
+ run(): Promise<void>;
24
+ private writeNotificationFile;
25
+ }
@@ -0,0 +1,170 @@
1
+ import { input, select } from '@inquirer/prompts';
2
+ import { Args, Flags } from '@oclif/core';
3
+ import { singularize, capitalize } from 'inflection';
4
+ import { CreateCommand } from '../../create-command.js';
5
+ import { ConfigType } from '../../lib/apps/index.js';
6
+ import { toFileName, toNotificationLabel } from '../../lib/create/index.js';
7
+ export default class CreateNotification extends CreateCommand {
8
+ static args = {
9
+ model: Args.string({
10
+ description: 'data model to trigger the notification by',
11
+ }),
12
+ name: Args.string({
13
+ description: 'internal name for the notification',
14
+ }),
15
+ };
16
+ static description = `This command assists in generating notifications configuration files. This CLI command will prompt for values when flags are not present.`;
17
+ static examples = [
18
+ {
19
+ command: 'swell create notification',
20
+ description: 'Create notification configuration interactively.',
21
+ },
22
+ {
23
+ command: 'swell create notification subscriptions confirmed --event subscription.created --once --s "Your subscription has been confirmed" -d "Send a confirmation email when subscription is created"',
24
+ description: 'Specify the model, name, event, subject, and description.',
25
+ },
26
+ {
27
+ command: 'swell create notification orders paid --event paid"',
28
+ description: 'Specify the event to trigger the notification.',
29
+ },
30
+ ];
31
+ static flags = {
32
+ description: Flags.string({
33
+ char: 'd',
34
+ default: '',
35
+ description: 'notification description',
36
+ }),
37
+ subject: Flags.string({
38
+ char: 's',
39
+ default: '',
40
+ description: 'notification email subject',
41
+ }),
42
+ event: Flags.string({
43
+ char: 'e',
44
+ default: '',
45
+ description: 'data model event to trigger the notification event, i.e. created',
46
+ }),
47
+ admin: Flags.boolean({
48
+ char: 'a',
49
+ description: 'indicate when the notification is sent to store admins only',
50
+ }),
51
+ once: Flags.boolean({
52
+ char: 'o',
53
+ description: 'indicate the notification should be sent only once when conditions are met, instead of repeating',
54
+ }),
55
+ overwrite: Flags.boolean({
56
+ description: 'overwrite existing notification configuration file',
57
+ }),
58
+ };
59
+ static summary = 'Initialize notification files and configuration with properties in the notifications folder.';
60
+ createType = ConfigType.NOTIFICATION;
61
+ async run() {
62
+ const { args, flags } = await this.parse(CreateNotification);
63
+ if (args.model && args.name) {
64
+ await this.writeNotificationFile({
65
+ admin: flags.admin,
66
+ description: flags.description,
67
+ event: flags.event,
68
+ model: args.model,
69
+ name: `${singularize(args.model)}-${args.name.replace(singularize(args.model), '')}`,
70
+ subject: flags.subject,
71
+ once: flags.once,
72
+ }, false);
73
+ }
74
+ else {
75
+ let admin = flags.admin;
76
+ if (admin === undefined) {
77
+ const choice = await await select({
78
+ message: 'Who will be notified?',
79
+ choices: [
80
+ { name: 'Admins', value: 'admin' },
81
+ { name: 'Customers', value: 'customer' },
82
+ ],
83
+ });
84
+ if (choice === 'admin') {
85
+ admin = true;
86
+ }
87
+ }
88
+ const collections = await this.getCollectionOptions();
89
+ const model = await select({
90
+ message: 'Which model will trigger the notification?',
91
+ choices: [
92
+ ...collections.map((collectionName) => ({
93
+ name: collectionName,
94
+ value: collectionName,
95
+ })),
96
+ ],
97
+ pageSize: 25,
98
+ loop: false,
99
+ });
100
+ const event = await input({
101
+ message: 'Event to trigger the notification',
102
+ default: 'created',
103
+ });
104
+ const answers = {
105
+ name: await input({
106
+ message: 'Name of the notification',
107
+ default: `${singularize(model)}-${event}`,
108
+ }),
109
+ subject: await input({
110
+ message: 'Subject of the notification email',
111
+ default: `Something happened with ${model}`,
112
+ }),
113
+ description: await input({
114
+ message: 'Describe what the notification does',
115
+ default: `Notify ${admin ? 'admin' : 'customer'} when ${singularize(model)}...`,
116
+ }),
117
+ once: await select({
118
+ message: 'When should we send this notification?',
119
+ choices: [
120
+ {
121
+ name: 'Every time conditions are met',
122
+ value: false,
123
+ },
124
+ {
125
+ name: 'Only the first time conditions are met',
126
+ value: true,
127
+ },
128
+ ],
129
+ }),
130
+ };
131
+ const { description, name, subject, once } = answers;
132
+ await this.writeNotificationFile({ admin, description, event, model, name, subject, once }, false);
133
+ }
134
+ }
135
+ async writeNotificationFile({ admin, description, event, model, name, subject, once, }, overwrite) {
136
+ const label = [name].map((val) => capitalize(val).trim()).join(' ');
137
+ const fileName = toFileName(label);
138
+ const noteLabel = toNotificationLabel(name);
139
+ const fileBody = {
140
+ collection: model,
141
+ label: noteLabel,
142
+ subject: subject || `${noteLabel}...`,
143
+ event: event || 'created',
144
+ conditions: {},
145
+ method: 'email',
146
+ };
147
+ fileBody.description = description;
148
+ if (admin) {
149
+ fileBody.admin = true;
150
+ }
151
+ else {
152
+ fileBody.contact = 'account.email';
153
+ }
154
+ // Note since we are using events, we don't need 'new' option
155
+ if (!once) {
156
+ fileBody.repeat = true;
157
+ }
158
+ fileBody.query = {
159
+ expand: [],
160
+ };
161
+ fileBody.sample = {};
162
+ const jsonCreated = await this.createFile({
163
+ fileBody,
164
+ fileName,
165
+ }, overwrite);
166
+ if (jsonCreated) {
167
+ await this.createFile({ extension: 'tpl', fileBody: '\n', fileName }, overwrite, false);
168
+ }
169
+ }
170
+ }
@@ -0,0 +1,22 @@
1
+ import { Config } from '@oclif/core';
2
+ import { SwellCommand } from '../swell-command.js';
3
+ export default class Login extends SwellCommand {
4
+ static description: string;
5
+ static flags: {
6
+ store: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
7
+ force: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
8
+ };
9
+ static summary: string;
10
+ constructor(argv: string[], config: Config);
11
+ run(): Promise<void>;
12
+ /**
13
+ * Use the /user/switch API request to either get the session id for the
14
+ * current store or create a session id for the given store id.
15
+ *
16
+ * @param sessionId - The session id for the current store
17
+ * @param storeId - The store id to get the session id for
18
+ *
19
+ * @returns void
20
+ */
21
+ private getAndSetStoreConfig;
22
+ }
@@ -0,0 +1,77 @@
1
+ import { Flags } from '@oclif/core';
2
+ import config from '../lib/config.js';
3
+ import { getSessionIdOrLoginInBrowser } from '../lib/sessions.js';
4
+ import { modifyDefaultStore } from '../lib/stores.js';
5
+ import style from '../lib/style.js';
6
+ import { SwellCommand } from '../swell-command.js';
7
+ export default class Login extends SwellCommand {
8
+ static description = `
9
+ If you are not already logged in, this command will open your browser to the
10
+ Swell Admin login page, otherwise it will show you the stores you still
11
+ have available for logging in to.
12
+ `;
13
+ static flags = {
14
+ store: Flags.string({
15
+ char: 's',
16
+ description: 'store to login to',
17
+ }),
18
+ force: Flags.boolean({
19
+ char: 'f',
20
+ description: 'show all available stores and switch the current session if needed',
21
+ }),
22
+ };
23
+ static summary = 'Login to your Swell user account.';
24
+ constructor(argv, config) {
25
+ super(argv, config);
26
+ this.requiresAuth = false;
27
+ }
28
+ async run() {
29
+ const { flags } = await this.parse(Login);
30
+ const { force, store } = flags;
31
+ let currentStoreId = config.getDefaultStore();
32
+ let user;
33
+ if (currentStoreId && !force) {
34
+ try {
35
+ user = await this.api.get({ adminPath: 'user' });
36
+ config.setUser(user);
37
+ }
38
+ catch (err) {
39
+ // noop
40
+ }
41
+ }
42
+ if (!user) {
43
+ const [sessionId, storeId] = await getSessionIdOrLoginInBrowser(store);
44
+ this.api.setStoreEnv(storeId);
45
+ user = await this.api.get({ adminPath: 'user' }, { sessionId });
46
+ await this.getAndSetStoreConfig({ sessionId, storeId });
47
+ await modifyDefaultStore({ currentStoreId, storeId });
48
+ config.setUser(user);
49
+ this.log(`You are now logged in to ${style.storeId(storeId)} as ${style.currentUser(user.username)}.`);
50
+ process.exit(0);
51
+ }
52
+ this.log(`You are already logged in as ${style.currentUser(user.username)}.`);
53
+ this.log([
54
+ `Use ${style.command('swell switch')}`,
55
+ 'to login to another account.',
56
+ ].join(' '));
57
+ }
58
+ /**
59
+ * Use the /user/switch API request to either get the session id for the
60
+ * current store or create a session id for the given store id.
61
+ *
62
+ * @param sessionId - The session id for the current store
63
+ * @param storeId - The store id to get the session id for
64
+ *
65
+ * @returns void
66
+ */
67
+ async getAndSetStoreConfig({ sessionId, storeId, }) {
68
+ const switchResponse = await this.api.post({ adminPath: '/user/switch' },
69
+ // eslint-disable-next-line camelcase
70
+ { body: { client_id: storeId }, sessionId });
71
+ // ensure we have the attribute names as we expect before saving
72
+ switchResponse.sessionId = switchResponse.session_id;
73
+ switchResponse.storeId = switchResponse.client_id;
74
+ switchResponse.name = switchResponse.client_name;
75
+ config.setStore(switchResponse);
76
+ }
77
+ }
@@ -0,0 +1,8 @@
1
+ import { SwellCommand } from '../swell-command.js';
2
+ export default class Logout extends SwellCommand {
3
+ static description: string;
4
+ static flags: {
5
+ local: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
6
+ };
7
+ run(): Promise<void>;
8
+ }
@@ -0,0 +1,45 @@
1
+ import { confirm } from '@inquirer/prompts';
2
+ import { Flags } from '@oclif/core';
3
+ import { inflect } from 'inflection';
4
+ import config from '../lib/config.js';
5
+ import style from '../lib/style.js';
6
+ import { SwellCommand } from '../swell-command.js';
7
+ export default class Logout extends SwellCommand {
8
+ static description = 'Log out of all stores available in your current session.';
9
+ static flags = {
10
+ local: Flags.boolean({
11
+ description: 'clear local authentication data',
12
+ }),
13
+ };
14
+ async run() {
15
+ const { flags } = await this.parse(Logout);
16
+ const user = config.getUser();
17
+ const storesCount = (config.get('stores') || []).length;
18
+ if (!config.getDefaultStore()) {
19
+ config.clear();
20
+ this.log('You are not logged in.');
21
+ return;
22
+ }
23
+ const continueLogout = await confirm({
24
+ message: `You are logged in as ${style.currentUser(user.email)} ${style.dim(`(${storesCount} ${inflect('stores', storesCount)})`)}. Are you sure you want to log out?`,
25
+ });
26
+ if (!continueLogout) {
27
+ return;
28
+ }
29
+ if (flags.local) {
30
+ config.clear();
31
+ this.log('Cleared local authentication data.');
32
+ return;
33
+ }
34
+ // logout from remote
35
+ const response = await this.api.post({ adminPath: 'user/logout' }, {});
36
+ if (response === null || response.success) {
37
+ // clear locally stored config
38
+ config.clear();
39
+ this.log(`Logged out from ${style.currentUser(user.email)}.`);
40
+ }
41
+ else {
42
+ this.logError('Logout failed.');
43
+ }
44
+ }
45
+ }
@@ -0,0 +1,23 @@
1
+ import { SwellCommand } from '../swell-command.js';
2
+ export default class Logs extends SwellCommand {
3
+ static examples: string[];
4
+ static flags: {
5
+ after: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
6
+ app: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
7
+ before: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
8
+ columns: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
9
+ env: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
10
+ follow: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
11
+ number: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<number, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
12
+ output: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
13
+ pretty: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
14
+ search: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
15
+ status: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
16
+ type: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string[] | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
17
+ };
18
+ static summary: string;
19
+ private output;
20
+ run(): Promise<void>;
21
+ private getLogs;
22
+ private getLogsAndWriteToStream;
23
+ }
@@ -0,0 +1,207 @@
1
+ import { Flags } from '@oclif/core';
2
+ import { LineOutput, LoggedItem, TableOutput } from '../lib/logs/index.js';
3
+ import { SwellCommand } from '../swell-command.js';
4
+ // the columns available to display in the table
5
+ const OUTPUT_COLUMNS = [
6
+ 'app',
7
+ 'data',
8
+ 'date',
9
+ 'env',
10
+ 'ip',
11
+ 'request',
12
+ 'req',
13
+ 'status',
14
+ 'time',
15
+ 'type',
16
+ ];
17
+ // the default columns to display in the table
18
+ const OUTPUT_COLUMNS_DEFAULTS = ['date', 'request', 'data', 'status', 'time'];
19
+ // the interval to poll the API when following logs
20
+ const FOLLOW_POLLING_INTERVAL = 1000 * 2; // 2 seconds
21
+ /**
22
+ * Builds the request options to send to the API based on the flags passed
23
+ * to the command.
24
+ *
25
+ * @param flags the flags passed to the command
26
+ * @returns the request options to send to the API
27
+ */
28
+ function buildLogRequestBody(flags) {
29
+ const reqOptions = { where: {} };
30
+ const andConditions = [];
31
+ reqOptions.limit = flags.number;
32
+ if (flags.search) {
33
+ reqOptions.search = flags.search;
34
+ }
35
+ // some filters are simple and can be mapped directly to the API
36
+ const logFilters = [
37
+ { filter: 'app_id', flag: 'app', operator: '$in' },
38
+ { filter: 'message.status', flag: 'status', operator: '$in' },
39
+ { filter: 'message.type', flag: 'type', operator: '$in' },
40
+ ];
41
+ // map the flags to the API query
42
+ for (const filter of logFilters) {
43
+ if (flags[filter.flag]) {
44
+ andConditions.push({
45
+ [filter.filter]: filter.operator
46
+ ? { [filter.operator]: flags[filter.flag] }
47
+ : flags[filter.flag],
48
+ });
49
+ }
50
+ }
51
+ const { after, before, env, startPolling } = flags;
52
+ if (env) {
53
+ andConditions.push({ environment_id: env });
54
+ }
55
+ else {
56
+ // Default to test env
57
+ andConditions.push({ environment_id: 'test' });
58
+ }
59
+ // if we are polling, we want to get logs after the last date we received
60
+ if (startPolling) {
61
+ andConditions.push({ date: { $gt: startPolling } });
62
+ }
63
+ else {
64
+ if (after) {
65
+ if (typeof after === 'object' && after !== null) {
66
+ andConditions.push({ date: after });
67
+ }
68
+ else {
69
+ andConditions.push({ date: { $gte: after } });
70
+ }
71
+ }
72
+ if (before) {
73
+ andConditions.push({ date: { $lte: before } });
74
+ }
75
+ }
76
+ // if there are any conditions, add them to the request options
77
+ // otherwise, the server will complain for an empty $and array
78
+ if (andConditions.length > 0) {
79
+ reqOptions.where.$and = andConditions;
80
+ }
81
+ return reqOptions;
82
+ }
83
+ export default class Logs extends SwellCommand {
84
+ static examples = [
85
+ 'swell logs',
86
+ 'swell logs -f -p -n 10 -c date,req,data',
87
+ 'swell logs -s accounts',
88
+ 'swell logs --after 2023-08-25T16:01:02.697Z -c date',
89
+ 'swell logs --after 2023-08-27',
90
+ ];
91
+ static flags = {
92
+ after: Flags.string({
93
+ description: 'expected is Javascript Date Time String Format: YYYY-MM-DDTHH:mm:ss.sssZ',
94
+ parse: async (value) => new Date(value).toISOString(),
95
+ summary: 'show logs created on or after this date/time (utc)',
96
+ }),
97
+ // filters
98
+ app: Flags.string({
99
+ description: 'filter logs by app id',
100
+ }),
101
+ before: Flags.string({
102
+ description: 'expected is Javascript Date Time String Format: YYYY-MM-DDTHH:mm:ss.sssZ',
103
+ parse: async (value) => new Date(value).toISOString(),
104
+ summary: 'show logs created on or before this date/time (utc)',
105
+ }),
106
+ columns: Flags.string({
107
+ char: 'c',
108
+ default: OUTPUT_COLUMNS_DEFAULTS.join(','),
109
+ description: `available columns: ${OUTPUT_COLUMNS.join(', ')}`,
110
+ // validate the columns requested for display
111
+ async parse(value) {
112
+ const flagColumns = value.split(/\s*,\s*/g);
113
+ for (const column of flagColumns) {
114
+ if (!OUTPUT_COLUMNS.includes(column)) {
115
+ throw new Error(`Invalid column: ${column}`);
116
+ }
117
+ }
118
+ return flagColumns.join(',');
119
+ },
120
+ summary: 'comma separated list of columns to display',
121
+ }),
122
+ env: Flags.string({
123
+ description: 'filter logs by environment id',
124
+ }),
125
+ // control / output
126
+ follow: Flags.boolean({
127
+ char: 'f',
128
+ default: false,
129
+ description: 'stream logs in real time',
130
+ exclusive: ['before'],
131
+ }),
132
+ number: Flags.integer({
133
+ char: 'n',
134
+ default: 100,
135
+ description: 'number of log lines to return',
136
+ }),
137
+ output: Flags.string({
138
+ char: 'o',
139
+ default: 'line',
140
+ description: 'how to display the logs',
141
+ options: ['table', 'line'],
142
+ }),
143
+ pretty: Flags.boolean({
144
+ char: 'p',
145
+ default: false,
146
+ description: 'note that this flag will take more space in the terminal and require more time to load',
147
+ summary: 'pretty print json data',
148
+ }),
149
+ search: Flags.string({
150
+ char: 's',
151
+ description: 'search logs by keyword',
152
+ }),
153
+ status: Flags.string({
154
+ description: 'filter logs by request status',
155
+ }),
156
+ type: Flags.string({
157
+ description: 'filter logs by type',
158
+ multiple: true,
159
+ options: ['api', 'function', 'webhook'],
160
+ }),
161
+ };
162
+ static summary = 'Output or stream store logs to the terminal.';
163
+ output;
164
+ async run() {
165
+ const { flags } = await this.parse(Logs);
166
+ // indentify the columns to display
167
+ const columns = flags.columns.split(',');
168
+ // create the output stream
169
+ this.output =
170
+ flags.output === 'table'
171
+ ? new TableOutput(columns, flags.pretty)
172
+ : new LineOutput(columns, flags.pretty);
173
+ // first run for getting the logs
174
+ // we keep track of the last date we received so we can get logs after that
175
+ flags.startPolling = await this.getLogsAndWriteToStream(flags);
176
+ // if following, poll the API every FOLLOW_POLLING_INTERVAL seconds and
177
+ // write new logs to the stream
178
+ if (flags.follow) {
179
+ setInterval(async () => {
180
+ // when polling, we want to get logs after the last date we received
181
+ flags.startPolling = await this.getLogsAndWriteToStream(flags);
182
+ }, FOLLOW_POLLING_INTERVAL);
183
+ }
184
+ }
185
+ async getLogs(flags) {
186
+ const body = buildLogRequestBody(flags);
187
+ this.debug('getLogs body', body);
188
+ const response = await this.api.post({ adminPath: `/data/$get/:logs` }, { body });
189
+ return response?.results?.reverse() || [];
190
+ }
191
+ async getLogsAndWriteToStream(flags) {
192
+ const logs = await this.getLogs(flags);
193
+ let lastDate = '';
194
+ if (!this.output) {
195
+ this.error('Output stream not available');
196
+ }
197
+ if (logs.length > 0) {
198
+ lastDate = logs.at(-1).date;
199
+ for (const log of logs) {
200
+ this.output.write(this.output.prepareData(new LoggedItem(log)));
201
+ }
202
+ }
203
+ // if the user is following logs, we want to return the last date we
204
+ // received
205
+ return lastDate || flags.startPolling;
206
+ }
207
+ }
@@ -0,0 +1,10 @@
1
+ import { SwellCommand } from '../swell-command.js';
2
+ export default class Switch extends SwellCommand {
3
+ static args: {
4
+ 'store-id': import("@oclif/core/lib/interfaces/parser.js").Arg<string | undefined, Record<string, unknown>>;
5
+ };
6
+ static description: string;
7
+ static examples: import("@oclif/core").Command.Example[];
8
+ static summary: string;
9
+ run(): Promise<void>;
10
+ }
@@ -0,0 +1,41 @@
1
+ import { Args } from '@oclif/core';
2
+ import { CLIError } from '@oclif/errors';
3
+ import config from '../lib/config.js';
4
+ import { selectLoggedInStoreId } from '../lib/stores.js';
5
+ import style from '../lib/style.js';
6
+ import { SwellCommand } from '../swell-command.js';
7
+ export default class Switch extends SwellCommand {
8
+ static args = {
9
+ 'store-id': Args.string({
10
+ description: 'store id you want to switch to',
11
+ }),
12
+ };
13
+ static description = `
14
+ If you have access to more than one store, this command will
15
+ define the store that all other commands will apply to by default.
16
+ `;
17
+ static examples = [
18
+ ...SwellCommand.examples,
19
+ '<%= config.bin %> <%= command.id %> <store-id>',
20
+ ];
21
+ static summary = 'Switch your default current store.';
22
+ async run() {
23
+ const { args } = await this.parse(Switch);
24
+ let storeId = args['store-id'];
25
+ if (!storeId) {
26
+ storeId = await selectLoggedInStoreId();
27
+ // Login to a different account
28
+ if (storeId === '') {
29
+ await this.config.runCommand('login', ['--force']);
30
+ return;
31
+ }
32
+ }
33
+ if (storeId) {
34
+ config.setDefaultStore(storeId);
35
+ this.log(`Current store is now ${style.storeId(config.getDefaultStore())}.`);
36
+ }
37
+ else {
38
+ throw new CLIError('Could not switch stores.');
39
+ }
40
+ }
41
+ }
@@ -0,0 +1,7 @@
1
+ import { Config } from '@oclif/core';
2
+ import { SwellCommand } from '../swell-command.js';
3
+ export default class Whoami extends SwellCommand {
4
+ static description: string;
5
+ constructor(argv: string[], config: Config);
6
+ run(): Promise<void>;
7
+ }
@@ -0,0 +1,24 @@
1
+ import config from '../lib/config.js';
2
+ import { showStores } from '../lib/stores.js';
3
+ import style from '../lib/style.js';
4
+ import { SwellCommand } from '../swell-command.js';
5
+ export default class Whoami extends SwellCommand {
6
+ static description = `Display the current logged-in user and available stores.`;
7
+ constructor(argv, config) {
8
+ super(argv, config);
9
+ this.requiresAuth = false;
10
+ }
11
+ async run() {
12
+ const currentStoreId = config.getDefaultStore();
13
+ const loggedInStores = config.get('stores') || [];
14
+ const currentUser = config.get('user');
15
+ if (loggedInStores.length > 0 && currentUser?.name) {
16
+ this.log(`Logged in as: ${style.currentUser(currentUser.email)}\n`);
17
+ const storesOutput = showStores(loggedInStores, currentStoreId);
18
+ this.log(`Your stores:\n${storesOutput}`);
19
+ }
20
+ else {
21
+ this.log(`Please login to list current stores.`);
22
+ }
23
+ }
24
+ }