@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.
- package/LICENSE.md +21 -0
- package/README.md +2 -140
- package/bin/dev.cmd +3 -0
- package/bin/dev.js +7 -0
- package/bin/run.cmd +3 -0
- package/bin/run.js +7 -0
- package/dist/app-command.d.ts +22 -0
- package/dist/app-command.js +45 -0
- package/dist/commands/app/_pull.d.ts +8 -0
- package/dist/commands/app/_pull.js +45 -0
- package/dist/commands/app/_uninstall.d.ts +12 -0
- package/dist/commands/app/_uninstall.js +45 -0
- package/dist/commands/app/info.d.ts +15 -0
- package/dist/commands/app/info.js +92 -0
- package/dist/commands/app/install.d.ts +17 -0
- package/dist/commands/app/install.js +177 -0
- package/dist/commands/app/push.d.ts +17 -0
- package/dist/commands/app/push.js +158 -0
- package/dist/commands/app/version.d.ts +18 -0
- package/dist/commands/app/version.js +156 -0
- package/dist/commands/app/watch.d.ts +15 -0
- package/dist/commands/app/watch.js +110 -0
- package/dist/commands/create/app.d.ts +21 -0
- package/dist/commands/create/app.js +210 -0
- package/dist/commands/create/content.d.ts +19 -0
- package/dist/commands/create/content.js +87 -0
- package/dist/commands/create/function.d.ts +25 -0
- package/dist/commands/create/function.js +191 -0
- package/dist/commands/create/index.d.ts +6 -0
- package/dist/commands/create/index.js +34 -0
- package/dist/commands/create/model.d.ts +19 -0
- package/dist/commands/create/model.js +87 -0
- package/dist/commands/create/notification.d.ts +25 -0
- package/dist/commands/create/notification.js +170 -0
- package/dist/commands/login.d.ts +22 -0
- package/dist/commands/login.js +77 -0
- package/dist/commands/logout.d.ts +8 -0
- package/dist/commands/logout.js +45 -0
- package/dist/commands/logs.d.ts +23 -0
- package/dist/commands/logs.js +207 -0
- package/dist/commands/switch.d.ts +10 -0
- package/dist/commands/switch.js +41 -0
- package/dist/commands/whoami.d.ts +7 -0
- package/dist/commands/whoami.js +24 -0
- package/dist/create-collection-command.d.ts +26 -0
- package/dist/create-collection-command.js +97 -0
- package/dist/create-command.d.ts +16 -0
- package/dist/create-command.js +59 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/lib/api.d.ts +13 -0
- package/dist/lib/api.js +117 -0
- package/dist/lib/app-config.d.ts +29 -0
- package/dist/lib/app-config.js +127 -0
- package/dist/lib/apps/app-config.d.ts +42 -0
- package/dist/lib/apps/app-config.js +167 -0
- package/dist/lib/apps/index.d.ts +67 -0
- package/dist/lib/apps/index.js +167 -0
- package/dist/lib/apps/paths.d.ts +18 -0
- package/dist/lib/apps/paths.js +54 -0
- package/dist/lib/bundle.d.ts +1 -0
- package/dist/lib/bundle.js +40 -0
- package/dist/lib/config.d.ts +37 -0
- package/dist/lib/config.js +131 -0
- package/dist/lib/constants.d.ts +5 -0
- package/dist/lib/constants.js +17 -0
- package/dist/lib/create/content.d.ts +23 -0
- package/dist/lib/create/content.js +40 -0
- package/dist/lib/create/function.d.ts +17 -0
- package/dist/lib/create/function.js +36 -0
- package/dist/lib/create/index.d.ts +13 -0
- package/dist/lib/create/index.js +20 -0
- package/dist/lib/create/model.d.ts +22 -0
- package/dist/lib/create/model.js +30 -0
- package/dist/lib/create/notification.d.ts +25 -0
- package/dist/lib/create/notification.js +1 -0
- package/dist/lib/info.d.ts +38 -0
- package/dist/lib/info.js +91 -0
- package/dist/lib/logs/index.d.ts +23 -0
- package/dist/lib/logs/index.js +88 -0
- package/dist/lib/logs/line-output.d.ts +5 -0
- package/dist/lib/logs/line-output.js +13 -0
- package/dist/lib/logs/output.d.ts +7 -0
- package/dist/lib/logs/output.js +8 -0
- package/dist/lib/logs/table-output.d.ts +11 -0
- package/dist/lib/logs/table-output.js +128 -0
- package/dist/lib/sessions.d.ts +9 -0
- package/dist/lib/sessions.js +110 -0
- package/dist/lib/stores.d.ts +63 -0
- package/dist/lib/stores.js +173 -0
- package/dist/lib/style.d.ts +24 -0
- package/dist/lib/style.js +29 -0
- package/dist/lib/swell-function-wrapper.d.ts +88 -0
- package/dist/lib/swell-function-wrapper.js +302 -0
- package/dist/remote-app-command.d.ts +36 -0
- package/dist/remote-app-command.js +367 -0
- package/dist/swell-command.d.ts +21 -0
- package/dist/swell-command.js +43 -0
- package/oclif.manifest.json +1085 -0
- package/package.json +81 -43
- package/LICENSE +0 -674
- package/dist/bin/multipart-parser-85ef9626.js +0 -456
- package/dist/bin/swell-0f71f07d.js +0 -30670
- package/dist/bin/swell.js +0 -24
- 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,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,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
|
+
}
|