@technomoron/mail-magic-client 1.0.23
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/CHANGES +4 -0
- package/README.md +102 -0
- package/dist/cjs/mail-magic-client.d.ts +82 -0
- package/dist/cjs/mail-magic-client.js +281 -0
- package/dist/cli-env.js +55 -0
- package/dist/cli-helpers.js +405 -0
- package/dist/cli.js +307 -0
- package/dist/esm/mail-magic-client.js +276 -0
- package/dist/mail-magic-client.js +281 -0
- package/dist/preprocess.js +310 -0
- package/package.json +69 -0
package/CHANGES
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# mail-magic-client
|
|
2
|
+
|
|
3
|
+
Client library and CLI for the mail-magic server.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @technomoron/mail-magic-client
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Client Usage
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import TemplateClient from '@technomoron/mail-magic-client';
|
|
15
|
+
|
|
16
|
+
const client = new TemplateClient('http://localhost:3000', 'username:token');
|
|
17
|
+
|
|
18
|
+
await client.storeTxTemplate({
|
|
19
|
+
domain: 'example.test',
|
|
20
|
+
name: 'welcome',
|
|
21
|
+
sender: 'App <noreply@example.test>',
|
|
22
|
+
subject: 'Welcome',
|
|
23
|
+
template: '<p>Hello {{ name }}</p>'
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
await client.sendTxMessage({
|
|
27
|
+
domain: 'example.test',
|
|
28
|
+
name: 'welcome',
|
|
29
|
+
rcpt: 'user@example.test',
|
|
30
|
+
vars: { name: 'Sam' }
|
|
31
|
+
});
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## CLI
|
|
35
|
+
|
|
36
|
+
The package ships `mm-cli`.
|
|
37
|
+
|
|
38
|
+
### .mmcli-env
|
|
39
|
+
|
|
40
|
+
Create `.mmcli-env` in your working directory to set defaults:
|
|
41
|
+
|
|
42
|
+
```ini
|
|
43
|
+
MMCLI_API=http://localhost:3000
|
|
44
|
+
MMCLI_TOKEN=username:token
|
|
45
|
+
# or, split token:
|
|
46
|
+
MMCLI_USERNAME=username
|
|
47
|
+
MMCLI_PASSWORD=token
|
|
48
|
+
MMCLI_DOMAIN=example.test
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Template Commands
|
|
52
|
+
|
|
53
|
+
Compile a template locally:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
mm-cli compile --input ./templates --output ./templates-dist
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Push a single transactional template (compile + upload):
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
mm-cli push --template tx-template/en/welcome --domain example.test --input ./templates
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Dry-run a single template upload:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
mm-cli push --template tx-template/en/welcome --domain example.test --input ./templates --dry-run
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Push an entire config-style directory:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
mm-cli push-dir --input ./data --domain example.test
|
|
75
|
+
mm-cli push-dir --input ./data --domain example.test --dry-run
|
|
76
|
+
mm-cli push-dir --input ./data --domain example.test --skip-assets
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Asset Uploads
|
|
80
|
+
|
|
81
|
+
Upload stand-alone domain assets:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
mm-cli assets --file ./logo.png --domain example.test
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Dry-run an asset upload:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
mm-cli assets --file ./logo.png --domain example.test --dry-run
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Upload assets scoped to a template:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
mm-cli assets --file ./hero.png --domain example.test --template-type tx --template welcome --locale en --path images
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Notes
|
|
100
|
+
|
|
101
|
+
- `push-dir` expects a `init-data.json` and domain folders that match the server config layout.
|
|
102
|
+
- Asset uploads use the server endpoint `POST /api/v1/assets`.
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
interface templateData {
|
|
2
|
+
template: string;
|
|
3
|
+
domain: string;
|
|
4
|
+
sender?: string;
|
|
5
|
+
name?: string;
|
|
6
|
+
subject?: string;
|
|
7
|
+
locale?: string;
|
|
8
|
+
part?: boolean;
|
|
9
|
+
}
|
|
10
|
+
interface formTemplateData {
|
|
11
|
+
idname: string;
|
|
12
|
+
domain: string;
|
|
13
|
+
template: string;
|
|
14
|
+
sender: string;
|
|
15
|
+
recipient: string;
|
|
16
|
+
subject?: string;
|
|
17
|
+
locale?: string;
|
|
18
|
+
secret?: string;
|
|
19
|
+
}
|
|
20
|
+
interface sendTemplateData {
|
|
21
|
+
name: string;
|
|
22
|
+
rcpt: string;
|
|
23
|
+
domain: string;
|
|
24
|
+
locale?: string;
|
|
25
|
+
vars?: object;
|
|
26
|
+
replyTo?: string;
|
|
27
|
+
headers?: Record<string, string>;
|
|
28
|
+
attachments?: AttachmentInput[];
|
|
29
|
+
}
|
|
30
|
+
interface sendFormData {
|
|
31
|
+
formid: string;
|
|
32
|
+
secret?: string;
|
|
33
|
+
recipient?: string;
|
|
34
|
+
domain?: string;
|
|
35
|
+
locale?: string;
|
|
36
|
+
vars?: object;
|
|
37
|
+
replyTo?: string;
|
|
38
|
+
fields?: Record<string, unknown>;
|
|
39
|
+
attachments?: AttachmentInput[];
|
|
40
|
+
}
|
|
41
|
+
type AttachmentInput = {
|
|
42
|
+
path: string;
|
|
43
|
+
filename?: string;
|
|
44
|
+
contentType?: string;
|
|
45
|
+
field?: string;
|
|
46
|
+
};
|
|
47
|
+
type UploadAssetInput = string | AttachmentInput;
|
|
48
|
+
interface uploadAssetsData {
|
|
49
|
+
domain: string;
|
|
50
|
+
files: UploadAssetInput[];
|
|
51
|
+
templateType?: 'tx' | 'form';
|
|
52
|
+
template?: string;
|
|
53
|
+
locale?: string;
|
|
54
|
+
path?: string;
|
|
55
|
+
}
|
|
56
|
+
declare class templateClient {
|
|
57
|
+
private baseURL;
|
|
58
|
+
private apiKey;
|
|
59
|
+
constructor(baseURL: string, apiKey: string);
|
|
60
|
+
request<T>(method: 'GET' | 'POST' | 'PUT' | 'DELETE', command: string, body?: any): Promise<T>;
|
|
61
|
+
get<T>(command: string): Promise<T>;
|
|
62
|
+
post<T>(command: string, body: any): Promise<T>;
|
|
63
|
+
put<T>(command: string, body: any): Promise<T>;
|
|
64
|
+
delete<T>(command: string, body?: any): Promise<T>;
|
|
65
|
+
validateEmails(list: string): {
|
|
66
|
+
valid: string[];
|
|
67
|
+
invalid: string[];
|
|
68
|
+
};
|
|
69
|
+
private validateTemplate;
|
|
70
|
+
private validateSender;
|
|
71
|
+
private createAttachmentPayload;
|
|
72
|
+
private appendFields;
|
|
73
|
+
private postFormData;
|
|
74
|
+
storeTemplate(td: templateData): Promise<any>;
|
|
75
|
+
sendTemplate(std: sendTemplateData): Promise<any>;
|
|
76
|
+
storeTxTemplate(td: templateData): Promise<any>;
|
|
77
|
+
sendTxMessage(std: sendTemplateData): Promise<any>;
|
|
78
|
+
storeFormTemplate(data: formTemplateData): Promise<any>;
|
|
79
|
+
sendFormMessage(data: sendFormData): Promise<any>;
|
|
80
|
+
uploadAssets(data: uploadAssetsData): Promise<any>;
|
|
81
|
+
}
|
|
82
|
+
export default templateClient;
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const fs_1 = __importDefault(require("fs"));
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
const email_addresses_1 = __importDefault(require("email-addresses"));
|
|
9
|
+
const nunjucks_1 = __importDefault(require("nunjucks"));
|
|
10
|
+
class templateClient {
|
|
11
|
+
constructor(baseURL, apiKey) {
|
|
12
|
+
this.baseURL = baseURL;
|
|
13
|
+
this.apiKey = apiKey;
|
|
14
|
+
if (!apiKey || !baseURL) {
|
|
15
|
+
throw new Error('Apikey/api-url required');
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
async request(method, command, body) {
|
|
19
|
+
const url = `${this.baseURL}${command}`;
|
|
20
|
+
const options = {
|
|
21
|
+
method,
|
|
22
|
+
headers: {
|
|
23
|
+
'Content-Type': 'application/json',
|
|
24
|
+
Authorization: `Bearer apikey-${this.apiKey}`
|
|
25
|
+
},
|
|
26
|
+
body: body ? JSON.stringify(body) : '{}'
|
|
27
|
+
};
|
|
28
|
+
// console.log(JSON.stringify({ options, url }));
|
|
29
|
+
const response = await fetch(url, options);
|
|
30
|
+
const j = await response.json();
|
|
31
|
+
if (response.ok) {
|
|
32
|
+
return j;
|
|
33
|
+
}
|
|
34
|
+
// console.log(JSON.stringify(j, undefined, 2));
|
|
35
|
+
if (j && j.message) {
|
|
36
|
+
throw new Error(`FETCH FAILED: ${response.status} ${j.message}`);
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
throw new Error(`FETCH FAILED: ${response.status} ${response.statusText}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
async get(command) {
|
|
43
|
+
return this.request('GET', command);
|
|
44
|
+
}
|
|
45
|
+
async post(command, body) {
|
|
46
|
+
return this.request('POST', command, body);
|
|
47
|
+
}
|
|
48
|
+
async put(command, body) {
|
|
49
|
+
return this.request('PUT', command, body);
|
|
50
|
+
}
|
|
51
|
+
async delete(command, body) {
|
|
52
|
+
return this.request('DELETE', command, body);
|
|
53
|
+
}
|
|
54
|
+
validateEmails(list) {
|
|
55
|
+
const valid = [], invalid = [];
|
|
56
|
+
const emails = list
|
|
57
|
+
.split(',')
|
|
58
|
+
.map((email) => email.trim())
|
|
59
|
+
.filter((email) => email !== '');
|
|
60
|
+
emails.forEach((email) => {
|
|
61
|
+
const parsed = email_addresses_1.default.parseOneAddress(email);
|
|
62
|
+
if (parsed && parsed.address) {
|
|
63
|
+
valid.push(parsed.address);
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
invalid.push(email);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
return { valid, invalid };
|
|
70
|
+
}
|
|
71
|
+
validateTemplate(template) {
|
|
72
|
+
try {
|
|
73
|
+
const env = new nunjucks_1.default.Environment(new nunjucks_1.default.FileSystemLoader(['./templates']));
|
|
74
|
+
const t = env.renderString(template, {});
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
if (error instanceof Error) {
|
|
78
|
+
throw new Error(`Template validation failed: ${error.message}`);
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
throw new Error('Template validation failed with an unknown error');
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
validateSender(sender) {
|
|
86
|
+
const exp = /^[^<>]+<[^<>]+@[^<>]+\.[^<>]+>$/;
|
|
87
|
+
if (!exp.test(sender)) {
|
|
88
|
+
throw new Error('Invalid sender format. Expected "Name <email@example.com>"');
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
createAttachmentPayload(attachments) {
|
|
92
|
+
const formData = new FormData();
|
|
93
|
+
const usedFields = [];
|
|
94
|
+
for (const attachment of attachments) {
|
|
95
|
+
if (!attachment?.path) {
|
|
96
|
+
throw new Error('Attachment path is required');
|
|
97
|
+
}
|
|
98
|
+
const raw = fs_1.default.readFileSync(attachment.path);
|
|
99
|
+
const filename = attachment.filename || path_1.default.basename(attachment.path);
|
|
100
|
+
const blob = new Blob([raw], attachment.contentType ? { type: attachment.contentType } : undefined);
|
|
101
|
+
const field = attachment.field || 'attachment';
|
|
102
|
+
formData.append(field, blob, filename);
|
|
103
|
+
usedFields.push(field);
|
|
104
|
+
}
|
|
105
|
+
return { formData, usedFields };
|
|
106
|
+
}
|
|
107
|
+
appendFields(formData, fields) {
|
|
108
|
+
for (const [key, value] of Object.entries(fields)) {
|
|
109
|
+
if (value === undefined || value === null) {
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
if (typeof value === 'string') {
|
|
113
|
+
formData.append(key, value);
|
|
114
|
+
}
|
|
115
|
+
else if (typeof value === 'number' || typeof value === 'boolean') {
|
|
116
|
+
formData.append(key, String(value));
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
formData.append(key, JSON.stringify(value));
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
async postFormData(command, formData) {
|
|
124
|
+
const url = `${this.baseURL}${command}`;
|
|
125
|
+
const response = await fetch(url, {
|
|
126
|
+
method: 'POST',
|
|
127
|
+
headers: {
|
|
128
|
+
Authorization: `Bearer apikey-${this.apiKey}`
|
|
129
|
+
},
|
|
130
|
+
body: formData
|
|
131
|
+
});
|
|
132
|
+
const j = await response.json();
|
|
133
|
+
if (response.ok) {
|
|
134
|
+
return j;
|
|
135
|
+
}
|
|
136
|
+
if (j && j.message) {
|
|
137
|
+
throw new Error(`FETCH FAILED: ${response.status} ${j.message}`);
|
|
138
|
+
}
|
|
139
|
+
throw new Error(`FETCH FAILED: ${response.status} ${response.statusText}`);
|
|
140
|
+
}
|
|
141
|
+
async storeTemplate(td) {
|
|
142
|
+
if (!td.template) {
|
|
143
|
+
throw new Error('No template data provided');
|
|
144
|
+
}
|
|
145
|
+
this.validateTemplate(td.template);
|
|
146
|
+
if (td.sender) {
|
|
147
|
+
this.validateSender(td.sender);
|
|
148
|
+
}
|
|
149
|
+
return this.storeTxTemplate(td);
|
|
150
|
+
}
|
|
151
|
+
async sendTemplate(std) {
|
|
152
|
+
if (!std.name || !std.rcpt) {
|
|
153
|
+
throw new Error('Invalid request body; name/rcpt required');
|
|
154
|
+
}
|
|
155
|
+
return this.sendTxMessage(std);
|
|
156
|
+
}
|
|
157
|
+
async storeTxTemplate(td) {
|
|
158
|
+
if (!td.template) {
|
|
159
|
+
throw new Error('No template data provided');
|
|
160
|
+
}
|
|
161
|
+
this.validateTemplate(td.template);
|
|
162
|
+
if (td.sender) {
|
|
163
|
+
this.validateSender(td.sender);
|
|
164
|
+
}
|
|
165
|
+
return this.post('/api/v1/tx/template', td);
|
|
166
|
+
}
|
|
167
|
+
async sendTxMessage(std) {
|
|
168
|
+
if (!std.name || !std.rcpt) {
|
|
169
|
+
throw new Error('Invalid request body; name/rcpt required');
|
|
170
|
+
}
|
|
171
|
+
const { valid, invalid } = this.validateEmails(std.rcpt);
|
|
172
|
+
if (invalid.length > 0) {
|
|
173
|
+
throw new Error('Invalid email address(es): ' + invalid.join(','));
|
|
174
|
+
}
|
|
175
|
+
// this.validateTemplate(template);
|
|
176
|
+
const body = {
|
|
177
|
+
name: std.name,
|
|
178
|
+
rcpt: std.rcpt,
|
|
179
|
+
domain: std.domain || '',
|
|
180
|
+
locale: std.locale || '',
|
|
181
|
+
vars: std.vars || {},
|
|
182
|
+
replyTo: std.replyTo,
|
|
183
|
+
headers: std.headers
|
|
184
|
+
};
|
|
185
|
+
// console.log(JSON.stringify(body, undefined, 2));
|
|
186
|
+
if (std.attachments && std.attachments.length > 0) {
|
|
187
|
+
if (std.headers) {
|
|
188
|
+
throw new Error('Headers are not supported with attachment uploads');
|
|
189
|
+
}
|
|
190
|
+
const { formData } = this.createAttachmentPayload(std.attachments);
|
|
191
|
+
this.appendFields(formData, {
|
|
192
|
+
name: std.name,
|
|
193
|
+
rcpt: std.rcpt,
|
|
194
|
+
domain: std.domain || '',
|
|
195
|
+
locale: std.locale || '',
|
|
196
|
+
vars: JSON.stringify(std.vars || {}),
|
|
197
|
+
replyTo: std.replyTo
|
|
198
|
+
});
|
|
199
|
+
return this.postFormData('/api/v1/tx/message', formData);
|
|
200
|
+
}
|
|
201
|
+
return this.post('/api/v1/tx/message', body);
|
|
202
|
+
}
|
|
203
|
+
async storeFormTemplate(data) {
|
|
204
|
+
if (!data.template) {
|
|
205
|
+
throw new Error('No template data provided');
|
|
206
|
+
}
|
|
207
|
+
if (!data.idname) {
|
|
208
|
+
throw new Error('Missing form identifier');
|
|
209
|
+
}
|
|
210
|
+
if (!data.sender) {
|
|
211
|
+
throw new Error('Missing sender address');
|
|
212
|
+
}
|
|
213
|
+
if (!data.recipient) {
|
|
214
|
+
throw new Error('Missing recipient address');
|
|
215
|
+
}
|
|
216
|
+
this.validateTemplate(data.template);
|
|
217
|
+
this.validateSender(data.sender);
|
|
218
|
+
return this.post('/api/v1/form/template', data);
|
|
219
|
+
}
|
|
220
|
+
async sendFormMessage(data) {
|
|
221
|
+
if (!data.formid) {
|
|
222
|
+
throw new Error('Invalid request body; formid required');
|
|
223
|
+
}
|
|
224
|
+
const fields = data.fields || {};
|
|
225
|
+
const baseFields = {
|
|
226
|
+
formid: data.formid,
|
|
227
|
+
secret: data.secret,
|
|
228
|
+
recipient: data.recipient,
|
|
229
|
+
domain: data.domain,
|
|
230
|
+
locale: data.locale,
|
|
231
|
+
vars: data.vars || {},
|
|
232
|
+
replyTo: data.replyTo,
|
|
233
|
+
...fields
|
|
234
|
+
};
|
|
235
|
+
if (data.attachments && data.attachments.length > 0) {
|
|
236
|
+
const { formData } = this.createAttachmentPayload(data.attachments);
|
|
237
|
+
this.appendFields(formData, {
|
|
238
|
+
formid: data.formid,
|
|
239
|
+
secret: data.secret,
|
|
240
|
+
recipient: data.recipient,
|
|
241
|
+
domain: data.domain,
|
|
242
|
+
locale: data.locale,
|
|
243
|
+
vars: JSON.stringify(data.vars || {}),
|
|
244
|
+
replyTo: data.replyTo
|
|
245
|
+
});
|
|
246
|
+
this.appendFields(formData, fields);
|
|
247
|
+
return this.postFormData('/api/v1/form/message', formData);
|
|
248
|
+
}
|
|
249
|
+
return this.post('/api/v1/form/message', baseFields);
|
|
250
|
+
}
|
|
251
|
+
async uploadAssets(data) {
|
|
252
|
+
if (!data.domain) {
|
|
253
|
+
throw new Error('domain is required');
|
|
254
|
+
}
|
|
255
|
+
if (!data.files || data.files.length === 0) {
|
|
256
|
+
throw new Error('At least one asset file is required');
|
|
257
|
+
}
|
|
258
|
+
if (data.templateType && !data.template) {
|
|
259
|
+
throw new Error('template is required when templateType is provided');
|
|
260
|
+
}
|
|
261
|
+
if (data.template && !data.templateType) {
|
|
262
|
+
throw new Error('templateType is required when template is provided');
|
|
263
|
+
}
|
|
264
|
+
const attachments = data.files.map((input) => {
|
|
265
|
+
if (typeof input === 'string') {
|
|
266
|
+
return { path: input, field: 'asset' };
|
|
267
|
+
}
|
|
268
|
+
return { ...input, field: input.field || 'asset' };
|
|
269
|
+
});
|
|
270
|
+
const { formData } = this.createAttachmentPayload(attachments);
|
|
271
|
+
this.appendFields(formData, {
|
|
272
|
+
domain: data.domain,
|
|
273
|
+
templateType: data.templateType,
|
|
274
|
+
template: data.template,
|
|
275
|
+
locale: data.locale,
|
|
276
|
+
path: data.path
|
|
277
|
+
});
|
|
278
|
+
return this.postFormData('/api/v1/assets', formData);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
exports.default = templateClient;
|
package/dist/cli-env.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.loadCliEnv = loadCliEnv;
|
|
7
|
+
exports.resolveToken = resolveToken;
|
|
8
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
9
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
+
function stripQuotes(value) {
|
|
11
|
+
const trimmed = value.trim();
|
|
12
|
+
if ((trimmed.startsWith('"') && trimmed.endsWith('"')) || (trimmed.startsWith("'") && trimmed.endsWith("'"))) {
|
|
13
|
+
return trimmed.slice(1, -1);
|
|
14
|
+
}
|
|
15
|
+
return trimmed;
|
|
16
|
+
}
|
|
17
|
+
function loadCliEnv(cwd = process.cwd()) {
|
|
18
|
+
const envPath = node_path_1.default.join(cwd, '.mmcli-env');
|
|
19
|
+
if (!node_fs_1.default.existsSync(envPath)) {
|
|
20
|
+
return {};
|
|
21
|
+
}
|
|
22
|
+
const raw = node_fs_1.default.readFileSync(envPath, 'utf8');
|
|
23
|
+
const values = {};
|
|
24
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
25
|
+
const trimmed = line.trim();
|
|
26
|
+
if (!trimmed || trimmed.startsWith('#')) {
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
const index = trimmed.indexOf('=');
|
|
30
|
+
if (index === -1) {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
const key = trimmed.slice(0, index).trim();
|
|
34
|
+
const value = stripQuotes(trimmed.slice(index + 1));
|
|
35
|
+
if (key) {
|
|
36
|
+
values[key] = value;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
api: values.MMCLI_API || values.API,
|
|
41
|
+
token: values.MMCLI_TOKEN,
|
|
42
|
+
username: values.MMCLI_USERNAME || values.MMCLI_USER,
|
|
43
|
+
password: values.MMCLI_PASSWORD || values.MMCLI_PASS,
|
|
44
|
+
domain: values.MMCLI_DOMAIN
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function resolveToken(defaults) {
|
|
48
|
+
if (defaults.token) {
|
|
49
|
+
return defaults.token;
|
|
50
|
+
}
|
|
51
|
+
if (defaults.username && defaults.password) {
|
|
52
|
+
return `${defaults.username}:${defaults.password}`;
|
|
53
|
+
}
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|