@@ -10,6 +10,7 @@ import fetch from 'node-fetch';
1010
1111const readFile = promisify ( fs . readFile ) ;
1212const writeFile = promisify ( fs . writeFile ) ;
13+ const readdir = promisify ( fs . readdir ) ;
1314
1415
1516function escapeData ( s : string ) : string {
@@ -23,6 +24,27 @@ function setOutput(key: string, val: string) {
2324 process . stdout . write ( `::set-output name=${ key } ::${ escapeData ( val ) } ${ EOL } ` ) ;
2425}
2526
27+ async function postToSlack ( slackUrl : string , url : string ) {
28+ await fetch ( slackUrl , {
29+ method : 'POST' ,
30+ headers : {
31+ 'Content-type' : 'application/json' ,
32+ } ,
33+ body : JSON . stringify ( {
34+ text : "SendGrid single send created for newsletter" ,
35+ blocks : [
36+ {
37+ type : "section" ,
38+ text : {
39+ type : "mrkdwn" ,
40+ text : `Newsletter: <${ url } |Single Send> created.`
41+ }
42+ }
43+ ]
44+ } )
45+ } ) ;
46+ }
47+
2648const API_BASE = 'https://api.sendgrid.com/v3' ;
2749type SingleSendParams = {
2850 html : string ,
@@ -40,7 +62,7 @@ async function singleSend(params: SingleSendParams) {
4062 'Content-type' : 'application/json' ,
4163 } ,
4264 body : JSON . stringify ( {
43- name : 'Test Send' ,
65+ name : `Newsletter: ${ params . subject } ` ,
4466 send_at : params . sendAt ?. toISOString ( ) ,
4567 send_to : {
4668 list_ids : [ params . listId ]
@@ -54,17 +76,25 @@ async function singleSend(params: SingleSendParams) {
5476 } ) ;
5577}
5678
79+ type GetSingleSendsParams = {
80+
81+ } ;
82+
83+ async function getSingleSends ( params : GetSingleSendsParams ) {
84+
85+ }
5786
5887type Options = {
5988 apiKey ?: string ,
60- path : string ,
89+ filePath : string ,
6190 output ?: string ,
6291 template ?: string ,
6392 context ?: any ,
6493 listId ?: string ,
6594 suppressionGroupId ?: number ,
6695 siteYaml ?: string ,
6796 subject ?: string ,
97+ slackUrl ?: string ,
6898} ;
6999
70100async function loadTemplate ( path ?: string , options ?: CompileOptions ) {
@@ -134,7 +164,7 @@ async function siteContext(path?: string): Promise<{ [k in string]: any }> {
134164}
135165
136166async function render ( opts : Options ) {
137- const pFile = readFile ( opts . path ) ;
167+ const pFile = readFile ( opts . filePath ) ;
138168 const pTemplate = loadTemplate ( opts . template ) ;
139169 const site = await siteContext ( opts . siteYaml ) ;
140170
@@ -151,7 +181,7 @@ async function render(opts: Options) {
151181 const template = await pTemplate ;
152182 const context : TemplateContext = Object . assign ( {
153183 content : rendered ,
154- post : postContext ( data , opts . path , site ) ,
184+ post : postContext ( data , opts . filePath , site ) ,
155185 site,
156186 } , opts . context ) ;
157187 const text = template ( context ) ;
@@ -197,11 +227,65 @@ async function run(options: Options) {
197227
198228 setOutput ( 'send_date' , sendAt . toISOString ( ) ) ;
199229 setOutput ( 'single_send_url' , url ) ;
230+
231+ return {
232+ sendAt,
233+ url
234+ } ;
200235 } else {
201236 console . log ( text ) ;
202237 }
203238}
204239
240+ type PathFilter = ( path : string ) => boolean ;
241+ type RunOptions = Omit < Options , 'filePath' > & {
242+ source : { file : string } | { dir : string } ,
243+ filter ?: PathFilter ,
244+ } ;
245+
246+ function dateFilter ( after : number ) {
247+ return ( path : string ) => {
248+ const ctx = contextFromPath ( path ) ;
249+ return ctx . date . getTime ( ) > after ;
250+ } ;
251+ }
252+
253+ async function toFileList ( source : RunOptions [ 'source' ] , filter ?: PathFilter ) {
254+ if ( 'file' in source )
255+ return [ source . file ] ;
256+
257+ const paths = await readdir ( source . dir )
258+ . then ( names => names . map ( n => path . join ( source . dir , n ) ) ) ;
259+
260+ return filter ?
261+ paths . filter ( filter ) : paths ;
262+ }
263+
264+ async function runAll ( options : RunOptions ) {
265+ const posts = await toFileList ( options . source , options . filter ) ;
266+ const urls : string [ ] = [ ] ;
267+ const promises : Promise < any > [ ] = [ ] ;
268+
269+ if ( ! posts . length ) {
270+ console . log ( 'No posts to send.' ) ;
271+ return ;
272+ }
273+
274+ for ( const post of posts ) {
275+ const result = await run ( {
276+ ...options ,
277+ filePath : post
278+ } ) ;
279+
280+ if ( result ?. url && options . slackUrl ) {
281+ promises . push ( postToSlack ( options . slackUrl , result . url ) ) ;
282+ }
283+ urls . push ( result . url ) ;
284+ }
285+
286+ await Promise . all ( urls ) ;
287+ }
288+
205289async function runAction ( ) {
206290 const {
207291 INPUT_SENDGRID_LIST_ID : listId ,
@@ -213,38 +297,52 @@ async function runAction() {
213297 INPUT_SUPPRESSION_GROUP_ID : suppressionGroupId ,
214298 INPUT_SITE_YAML : siteYaml ,
215299 INPUT_SUBJECT_FORMAT : subject = '%s' ,
300+ INPUT_POSTS_DIR : postsDir ,
301+ INPUT_SLACK_URL : slackUrl ,
302+ TODAY_OVERRIDE : today ,
216303 } = process . env ;
217304
218- if ( ! path ) {
305+ if ( ! ( path || postsDir ) ) {
219306 console . error (
220- 'Missing required environment variable INPUT_TEXT_PATH '
307+ 'Either INPUT_TEXT_PATH or INPUT_POSTS_DIR must be non-empty '
221308 ) ;
222309 process . exit ( 1 ) ;
223310 }
224311
225- await run ( {
312+ await runAll ( {
226313 apiKey,
227- path,
314+ source : path ? { file : path } : { dir : postsDir } ,
228315 template,
229316 output : outPath ,
230317 context : context ? JSON . parse ( context ) : { } ,
231318 listId : listId ,
232319 suppressionGroupId : suppressionGroupId ? parseInt ( suppressionGroupId ) : undefined ,
233320 siteYaml,
234321 subject,
322+ slackUrl,
323+ filter : dateFilter ( today ? new Date ( today ) . getTime ( ) : Date . now ( ) ) ,
235324 } ) ;
236325}
237326
238327async function testRun ( ) {
328+ // const apiKey = 'REAS-yuff0naum!krar';
239329 process . env [ 'INPUT_SENDGRID_LIST_ID' ] = "559adb5e-7164-4ac8-bbb5-1398d4ff0df9" ;
240330 // process.env['INPUT_SENDGRID_API_KEY'] = apiKey;
241- process . env [ 'INPUT_TEXT_PATH' ] = __dirname + '/../../../../_posts/2021-11-16-communications-lead.md' ;
331+ // process.env['INPUT_TEXT_PATH'] = __dirname + '/../../../../_posts/2021-11-16-communications-lead.md';
332+ process . env [ 'INPUT_POSTS_DIR' ] = __dirname + '/../../../../_posts' ;
242333 process . env [ 'INPUT_TEMPLATE_PATH' ] = __dirname + '/../../../workflows/newsletter_template.html' ;
243334 process . env [ 'INPUT_CONTEXT' ] = `{}` ;
244335 process . env [ 'INPUT_SUPPRESSION_GROUP_ID' ] = '17889' ;
245336 process . env [ 'INPUT_SITE_YAML' ] = __dirname + '/../../../../_config.yml' ;
337+ process . env [ 'INPUT_SLACK_URL' ] = 'https://hooks.slack.com/services/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' ;
338+ process . env [ 'TODAY_OVERRIDE' ] = '2022-01-10' ;
246339
247340 await runAction ( ) ;
248341}
249342
250- runAction ( ) ;
343+
344+ if ( process . env [ 'NODE_ENV' ] === 'test' )
345+ testRun ( ) ;
346+ else
347+ runAction ( ) ;
348+
0 commit comments