postal-mime 2.0.0 → 2.0.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/.prettierrc.cjs +8 -0
- package/CHANGELOG.md +15 -0
- package/README.md +0 -2
- package/package.json +4 -4
- package/postal-mime.d.ts +1 -1
- package/src/address-parser.js +321 -0
- package/src/base64-decoder.js +50 -0
- package/src/decode-strings.js +268 -0
- package/src/html-entities.js +2236 -0
- package/src/mime-node.js +271 -0
- package/src/package.json +3 -0
- package/src/pass-through-decoder.js +17 -0
- package/src/postal-mime.js +395 -0
- package/src/qp-decoder.js +96 -0
- package/src/text-format.js +334 -0
- package/.github/workflows/release.yaml +0 -37
- /package/{.eslintrc.js → .eslintrc.cjs} +0 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { blobToArrayBuffer } from './decode-strings.js';
|
|
2
|
+
|
|
3
|
+
export default class QPDecoder {
|
|
4
|
+
constructor(opts) {
|
|
5
|
+
opts = opts || {};
|
|
6
|
+
|
|
7
|
+
this.decoder = opts.decoder || new TextDecoder();
|
|
8
|
+
|
|
9
|
+
this.maxChunkSize = 100 * 1024;
|
|
10
|
+
|
|
11
|
+
this.remainder = '';
|
|
12
|
+
|
|
13
|
+
this.chunks = [];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
decodeQPBytes(encodedBytes) {
|
|
17
|
+
let buf = new ArrayBuffer(encodedBytes.length);
|
|
18
|
+
let dataView = new DataView(buf);
|
|
19
|
+
for (let i = 0, len = encodedBytes.length; i < len; i++) {
|
|
20
|
+
dataView.setUint8(i, parseInt(encodedBytes[i], 16));
|
|
21
|
+
}
|
|
22
|
+
return buf;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
decodeChunks(str) {
|
|
26
|
+
// unwrap newlines
|
|
27
|
+
str = str.replace(/=\r?\n/g, '');
|
|
28
|
+
|
|
29
|
+
let list = str.split(/(?==)/);
|
|
30
|
+
let encodedBytes = [];
|
|
31
|
+
for (let part of list) {
|
|
32
|
+
if (part.charAt(0) !== '=') {
|
|
33
|
+
if (encodedBytes.length) {
|
|
34
|
+
this.chunks.push(this.decodeQPBytes(encodedBytes));
|
|
35
|
+
encodedBytes = [];
|
|
36
|
+
}
|
|
37
|
+
this.chunks.push(part);
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (part.length === 3) {
|
|
42
|
+
encodedBytes.push(part.substr(1));
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (part.length > 3) {
|
|
47
|
+
encodedBytes.push(part.substr(1, 2));
|
|
48
|
+
this.chunks.push(this.decodeQPBytes(encodedBytes));
|
|
49
|
+
encodedBytes = [];
|
|
50
|
+
|
|
51
|
+
part = part.substr(3);
|
|
52
|
+
this.chunks.push(part);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (encodedBytes.length) {
|
|
56
|
+
this.chunks.push(this.decodeQPBytes(encodedBytes));
|
|
57
|
+
encodedBytes = [];
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
update(buffer) {
|
|
62
|
+
// expect full lines, so add line terminator as well
|
|
63
|
+
let str = this.decoder.decode(buffer) + '\n';
|
|
64
|
+
|
|
65
|
+
str = this.remainder + str;
|
|
66
|
+
|
|
67
|
+
if (str.length < this.maxChunkSize) {
|
|
68
|
+
this.remainder = str;
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
this.remainder = '';
|
|
73
|
+
|
|
74
|
+
let partialEnding = str.match(/=[a-fA-F0-9]?$/);
|
|
75
|
+
if (partialEnding) {
|
|
76
|
+
if (partialEnding.index === 0) {
|
|
77
|
+
this.remainder = str;
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
this.remainder = str.substr(partialEnding.index);
|
|
81
|
+
str = str.substr(0, partialEnding.index);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
this.decodeChunks(str);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
finalize() {
|
|
88
|
+
if (this.remainder.length) {
|
|
89
|
+
this.decodeChunks(this.remainder);
|
|
90
|
+
this.remainder = '';
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// convert an array of arraybuffers into a blob and then back into a single arraybuffer
|
|
94
|
+
return blobToArrayBuffer(new Blob(this.chunks, { type: 'application/octet-stream' }));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
import htmlEntities from './html-entities.js';
|
|
2
|
+
|
|
3
|
+
export function decodeHTMLEntities(str) {
|
|
4
|
+
return str.replace(/&(#\d+|#x[a-f0-9]+|[a-z]+\d*);?/gi, (match, entity) => {
|
|
5
|
+
if (typeof htmlEntities[match] === 'string') {
|
|
6
|
+
return htmlEntities[match];
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
if (entity.charAt(0) !== '#' || match.charAt(match.length - 1) !== ';') {
|
|
10
|
+
// keep as is, invalid or unknown sequence
|
|
11
|
+
return match;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
let codePoint;
|
|
15
|
+
if (entity.charAt(1) === 'x') {
|
|
16
|
+
// hex
|
|
17
|
+
codePoint = parseInt(entity.substr(2), 16);
|
|
18
|
+
} else {
|
|
19
|
+
// dec
|
|
20
|
+
codePoint = parseInt(entity.substr(1), 10);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
var output = '';
|
|
24
|
+
|
|
25
|
+
if ((codePoint >= 0xd800 && codePoint <= 0xdfff) || codePoint > 0x10ffff) {
|
|
26
|
+
// Invalid range, return a replacement character instead
|
|
27
|
+
return '\uFFFD';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (codePoint > 0xffff) {
|
|
31
|
+
codePoint -= 0x10000;
|
|
32
|
+
output += String.fromCharCode(((codePoint >>> 10) & 0x3ff) | 0xd800);
|
|
33
|
+
codePoint = 0xdc00 | (codePoint & 0x3ff);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
output += String.fromCharCode(codePoint);
|
|
37
|
+
|
|
38
|
+
return output;
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function escapeHtml(str) {
|
|
43
|
+
return str.trim().replace(/[<>"'?&]/g, c => {
|
|
44
|
+
let hex = c.charCodeAt(0).toString(16);
|
|
45
|
+
if (hex.length < 2) {
|
|
46
|
+
hex = '0' + hex;
|
|
47
|
+
}
|
|
48
|
+
return '&#x' + hex.toUpperCase() + ';';
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function textToHtml(str) {
|
|
53
|
+
let html = escapeHtml(str).replace(/\n/g, '<br />');
|
|
54
|
+
return '<div>' + html + '</div>';
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function htmlToText(str) {
|
|
58
|
+
str = str
|
|
59
|
+
// we can't process tags on multiple lines so remove newlines first
|
|
60
|
+
.replace(/\r?\n/g, '\u0001')
|
|
61
|
+
.replace(/<\!\-\-.*?\-\->/gi, ' ')
|
|
62
|
+
|
|
63
|
+
.replace(/<br\b[^>]*>/gi, '\n')
|
|
64
|
+
.replace(/<\/?(p|div|table|tr|td|th)\b[^>]*>/gi, '\n\n')
|
|
65
|
+
.replace(/<script\b[^>]*>.*?<\/script\b[^>]*>/gi, ' ')
|
|
66
|
+
.replace(/^.*<body\b[^>]*>/i, '')
|
|
67
|
+
.replace(/^.*<\/head\b[^>]*>/i, '')
|
|
68
|
+
.replace(/^.*<\!doctype\b[^>]*>/i, '')
|
|
69
|
+
.replace(/<\/body\b[^>]*>.*$/i, '')
|
|
70
|
+
.replace(/<\/html\b[^>]*>.*$/i, '')
|
|
71
|
+
|
|
72
|
+
.replace(/<a\b[^>]*href\s*=\s*["']?([^\s"']+)[^>]*>/gi, ' ($1) ')
|
|
73
|
+
|
|
74
|
+
.replace(/<\/?(span|em|i|strong|b|u|a)\b[^>]*>/gi, '')
|
|
75
|
+
|
|
76
|
+
.replace(/<li\b[^>]*>[\n\u0001\s]*/gi, '* ')
|
|
77
|
+
|
|
78
|
+
.replace(/<hr\b[^>]*>/g, '\n-------------\n')
|
|
79
|
+
|
|
80
|
+
.replace(/<[^>]*>/g, ' ')
|
|
81
|
+
|
|
82
|
+
// convert linebreak placeholders back to newlines
|
|
83
|
+
.replace(/\u0001/g, '\n')
|
|
84
|
+
|
|
85
|
+
.replace(/[ \t]+/g, ' ')
|
|
86
|
+
|
|
87
|
+
.replace(/^\s+$/gm, '')
|
|
88
|
+
|
|
89
|
+
.replace(/\n\n+/g, '\n\n')
|
|
90
|
+
.replace(/^\n+/, '\n')
|
|
91
|
+
.replace(/\n+$/, '\n');
|
|
92
|
+
|
|
93
|
+
str = decodeHTMLEntities(str);
|
|
94
|
+
|
|
95
|
+
return str;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function formatTextAddress(address) {
|
|
99
|
+
return []
|
|
100
|
+
.concat(address.name || [])
|
|
101
|
+
.concat(address.name ? `<${address.address}>` : address.address)
|
|
102
|
+
.join(' ');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function formatTextAddresses(addresses) {
|
|
106
|
+
let parts = [];
|
|
107
|
+
|
|
108
|
+
let processAddress = (address, partCounter) => {
|
|
109
|
+
if (partCounter) {
|
|
110
|
+
parts.push(', ');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (address.group) {
|
|
114
|
+
let groupStart = `${address.name}:`;
|
|
115
|
+
let groupEnd = `;`;
|
|
116
|
+
|
|
117
|
+
parts.push(groupStart);
|
|
118
|
+
address.group.forEach(processAddress);
|
|
119
|
+
parts.push(groupEnd);
|
|
120
|
+
} else {
|
|
121
|
+
parts.push(formatTextAddress(address));
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
addresses.forEach(processAddress);
|
|
126
|
+
|
|
127
|
+
return parts.join('');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function formatHtmlAddress(address) {
|
|
131
|
+
return `<a href="mailto:${escapeHtml(address.address)}" class="postal-email-address">${escapeHtml(address.name || `<${address.address}>`)}</a>`;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function formatHtmlAddresses(addresses) {
|
|
135
|
+
let parts = [];
|
|
136
|
+
|
|
137
|
+
let processAddress = (address, partCounter) => {
|
|
138
|
+
if (partCounter) {
|
|
139
|
+
parts.push('<span class="postal-email-address-separator">, </span>');
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (address.group) {
|
|
143
|
+
let groupStart = `<span class="postal-email-address-group">${escapeHtml(address.name)}:</span>`;
|
|
144
|
+
let groupEnd = `<span class="postal-email-address-group">;</span>`;
|
|
145
|
+
|
|
146
|
+
parts.push(groupStart);
|
|
147
|
+
address.group.forEach(processAddress);
|
|
148
|
+
parts.push(groupEnd);
|
|
149
|
+
} else {
|
|
150
|
+
parts.push(formatHtmlAddress(address));
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
addresses.forEach(processAddress);
|
|
155
|
+
|
|
156
|
+
return parts.join(' ');
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function foldLines(str, lineLength, afterSpace) {
|
|
160
|
+
str = (str || '').toString();
|
|
161
|
+
lineLength = lineLength || 76;
|
|
162
|
+
|
|
163
|
+
let pos = 0,
|
|
164
|
+
len = str.length,
|
|
165
|
+
result = '',
|
|
166
|
+
line,
|
|
167
|
+
match;
|
|
168
|
+
|
|
169
|
+
while (pos < len) {
|
|
170
|
+
line = str.substr(pos, lineLength);
|
|
171
|
+
if (line.length < lineLength) {
|
|
172
|
+
result += line;
|
|
173
|
+
break;
|
|
174
|
+
}
|
|
175
|
+
if ((match = line.match(/^[^\n\r]*(\r?\n|\r)/))) {
|
|
176
|
+
line = match[0];
|
|
177
|
+
result += line;
|
|
178
|
+
pos += line.length;
|
|
179
|
+
continue;
|
|
180
|
+
} else if ((match = line.match(/(\s+)[^\s]*$/)) && match[0].length - (afterSpace ? (match[1] || '').length : 0) < line.length) {
|
|
181
|
+
line = line.substr(0, line.length - (match[0].length - (afterSpace ? (match[1] || '').length : 0)));
|
|
182
|
+
} else if ((match = str.substr(pos + line.length).match(/^[^\s]+(\s*)/))) {
|
|
183
|
+
line = line + match[0].substr(0, match[0].length - (!afterSpace ? (match[1] || '').length : 0));
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
result += line;
|
|
187
|
+
pos += line.length;
|
|
188
|
+
if (pos < len) {
|
|
189
|
+
result += '\r\n';
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return result;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export function formatTextHeader(message) {
|
|
197
|
+
let rows = [];
|
|
198
|
+
|
|
199
|
+
if (message.from) {
|
|
200
|
+
rows.push({ key: 'From', val: formatTextAddress(message.from) });
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (message.subject) {
|
|
204
|
+
rows.push({ key: 'Subject', val: message.subject });
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (message.date) {
|
|
208
|
+
let dateOptions = {
|
|
209
|
+
year: 'numeric',
|
|
210
|
+
month: 'numeric',
|
|
211
|
+
day: 'numeric',
|
|
212
|
+
hour: 'numeric',
|
|
213
|
+
minute: 'numeric',
|
|
214
|
+
second: 'numeric',
|
|
215
|
+
hour12: false
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
let dateStr = typeof Intl === 'undefined' ? message.date : new Intl.DateTimeFormat('default', dateOptions).format(new Date(message.date));
|
|
219
|
+
|
|
220
|
+
rows.push({ key: 'Date', val: dateStr });
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (message.to && message.to.length) {
|
|
224
|
+
rows.push({ key: 'To', val: formatTextAddresses(message.to) });
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (message.cc && message.cc.length) {
|
|
228
|
+
rows.push({ key: 'Cc', val: formatTextAddresses(message.cc) });
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (message.bcc && message.bcc.length) {
|
|
232
|
+
rows.push({ key: 'Bcc', val: formatTextAddresses(message.bcc) });
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Align keys and values by adding space between these two
|
|
236
|
+
// Also make sure that the separator line is as long as the longest line
|
|
237
|
+
// Should end up with something like this:
|
|
238
|
+
/*
|
|
239
|
+
-----------------------------
|
|
240
|
+
From: xx xx <xxx@xxx.com>
|
|
241
|
+
Subject: Example Subject
|
|
242
|
+
Date: 16/02/2021, 02:57:06
|
|
243
|
+
To: not@found.com
|
|
244
|
+
-----------------------------
|
|
245
|
+
*/
|
|
246
|
+
|
|
247
|
+
let maxKeyLength = rows
|
|
248
|
+
.map(r => r.key.length)
|
|
249
|
+
.reduce((acc, cur) => {
|
|
250
|
+
return cur > acc ? cur : acc;
|
|
251
|
+
}, 0);
|
|
252
|
+
|
|
253
|
+
rows = rows.flatMap(row => {
|
|
254
|
+
let sepLen = maxKeyLength - row.key.length;
|
|
255
|
+
let prefix = `${row.key}: ${' '.repeat(sepLen)}`;
|
|
256
|
+
let emptyPrefix = `${' '.repeat(row.key.length + 1)} ${' '.repeat(sepLen)}`;
|
|
257
|
+
|
|
258
|
+
let foldedLines = foldLines(row.val, 80, true)
|
|
259
|
+
.split(/\r?\n/)
|
|
260
|
+
.map(line => line.trim());
|
|
261
|
+
|
|
262
|
+
return foldedLines.map((line, i) => `${i ? emptyPrefix : prefix}${line}`);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
let maxLineLength = rows
|
|
266
|
+
.map(r => r.length)
|
|
267
|
+
.reduce((acc, cur) => {
|
|
268
|
+
return cur > acc ? cur : acc;
|
|
269
|
+
}, 0);
|
|
270
|
+
|
|
271
|
+
let lineMarker = '-'.repeat(maxLineLength);
|
|
272
|
+
|
|
273
|
+
let template = `
|
|
274
|
+
${lineMarker}
|
|
275
|
+
${rows.join('\n')}
|
|
276
|
+
${lineMarker}
|
|
277
|
+
`;
|
|
278
|
+
|
|
279
|
+
return template;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
export function formatHtmlHeader(message) {
|
|
283
|
+
let rows = [];
|
|
284
|
+
|
|
285
|
+
if (message.from) {
|
|
286
|
+
rows.push(`<div class="postal-email-header-key">From</div><div class="postal-email-header-value">${formatHtmlAddress(message.from)}</div>`);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (message.subject) {
|
|
290
|
+
rows.push(
|
|
291
|
+
`<div class="postal-email-header-key">Subject</div><div class="postal-email-header-value postal-email-header-subject">${escapeHtml(
|
|
292
|
+
message.subject
|
|
293
|
+
)}</div>`
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (message.date) {
|
|
298
|
+
let dateOptions = {
|
|
299
|
+
year: 'numeric',
|
|
300
|
+
month: 'numeric',
|
|
301
|
+
day: 'numeric',
|
|
302
|
+
hour: 'numeric',
|
|
303
|
+
minute: 'numeric',
|
|
304
|
+
second: 'numeric',
|
|
305
|
+
hour12: false
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
let dateStr = typeof Intl === 'undefined' ? message.date : new Intl.DateTimeFormat('default', dateOptions).format(new Date(message.date));
|
|
309
|
+
|
|
310
|
+
rows.push(
|
|
311
|
+
`<div class="postal-email-header-key">Date</div><div class="postal-email-header-value postal-email-header-date" data-date="${escapeHtml(
|
|
312
|
+
message.date
|
|
313
|
+
)}">${escapeHtml(dateStr)}</div>`
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (message.to && message.to.length) {
|
|
318
|
+
rows.push(`<div class="postal-email-header-key">To</div><div class="postal-email-header-value">${formatHtmlAddresses(message.to)}</div>`);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
if (message.cc && message.cc.length) {
|
|
322
|
+
rows.push(`<div class="postal-email-header-key">Cc</div><div class="postal-email-header-value">${formatHtmlAddresses(message.cc)}</div>`);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (message.bcc && message.bcc.length) {
|
|
326
|
+
rows.push(`<div class="postal-email-header-key">Bcc</div><div class="postal-email-header-value">${formatHtmlAddresses(message.bcc)}</div>`);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
let template = `<div class="postal-email-header">${rows.length ? '<div class="postal-email-header-row">' : ''}${rows.join(
|
|
330
|
+
'</div>\n<div class="postal-email-header-row">'
|
|
331
|
+
)}${rows.length ? '</div>' : ''}</div>`;
|
|
332
|
+
|
|
333
|
+
return template;
|
|
334
|
+
}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
on:
|
|
2
|
-
push:
|
|
3
|
-
branches:
|
|
4
|
-
- master
|
|
5
|
-
|
|
6
|
-
permissions:
|
|
7
|
-
contents: write
|
|
8
|
-
pull-requests: write
|
|
9
|
-
id-token: write
|
|
10
|
-
|
|
11
|
-
name: release
|
|
12
|
-
jobs:
|
|
13
|
-
release-please:
|
|
14
|
-
runs-on: ubuntu-latest
|
|
15
|
-
steps:
|
|
16
|
-
- uses: google-github-actions/release-please-action@v3
|
|
17
|
-
id: release
|
|
18
|
-
with:
|
|
19
|
-
release-type: node
|
|
20
|
-
package-name: ${{vars.NPM_MODULE_NAME}}
|
|
21
|
-
pull-request-title-pattern: 'chore${scope}: release ${version} [skip-ci]'
|
|
22
|
-
# The logic below handles the npm publication:
|
|
23
|
-
- uses: actions/checkout@v3
|
|
24
|
-
# these if statements ensure that a publication only occurs when
|
|
25
|
-
# a new release is created:
|
|
26
|
-
if: ${{ steps.release.outputs.release_created }}
|
|
27
|
-
- uses: actions/setup-node@v3
|
|
28
|
-
with:
|
|
29
|
-
node-version: 18
|
|
30
|
-
registry-url: 'https://registry.npmjs.org'
|
|
31
|
-
if: ${{ steps.release.outputs.release_created }}
|
|
32
|
-
- run: npm ci
|
|
33
|
-
if: ${{ steps.release.outputs.release_created }}
|
|
34
|
-
- run: npm publish --provenance --access public
|
|
35
|
-
env:
|
|
36
|
-
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
|
|
37
|
-
if: ${{ steps.release.outputs.release_created }}
|
|
File without changes
|