@barbapapazes/content-creation 0.18.3 → 0.18.5

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 (2) hide show
  1. package/dist/cli.mjs +4 -4
  2. package/package.json +1 -1
package/dist/cli.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import e from"node:process";import{intro as t,isCancel as n,log as r,multiselect as i,outro as a,select as o,text as s}from"@clack/prompts";import{cac as c}from"cac";import{addDays as l,format as u,parse as d,parseISO as f,setHours as ee,setMinutes as te,setSeconds as ne}from"date-fns";import{existsSync as p,mkdirSync as m,readFileSync as h,readdirSync as g,statSync as _,writeFileSync as v}from"node:fs";import{dirname as y,isAbsolute as re,join as b,resolve as x}from"node:path";import S from"gray-matter";import{loadConfig as C}from"c12";import{execFileSync as w}from"node:child_process";import{createHash as T}from"node:crypto";import{fileURLToPath as E}from"node:url";var ie=`0.18.3`;const D={linkedin:{mainFile:`linkedin.md`,additionalFiles:[`linkedin-script.md`]},x:{mainFile:`x.md`},youtube:{mainFile:`youtube-script.md`,additionalFiles:[`youtube-description.md`]},instagram:{mainFile:`instagram-script.md`,additionalFiles:[`instagram-description.md`]}},ae={thematic:[],templatesDir:void 0,templates:{},scheduling:{},calendar:{}};function oe(t,n){return t?re(t)?t:x(n?y(n):e.cwd(),t):n?y(n):e.cwd()}function se(e,t){let n={};if(e?.footerPath){let r=x(t,e.footerPath);p(r)&&(n.footerPath=r)}return n}function O(e,t){let n={};if(e?.templatePath){let r=x(t,e.templatePath);p(r)&&(n.templatePath=r)}return n}async function k(){let{config:t,configFile:n}=await C({name:`content-creation`,defaults:ae,globalRc:!0,dotenv:!0}),r=oe(t.templatesDir,n),i={linkedin:se(t.templates?.linkedin,r),youtube:O(t.templates?.youtube,r),instagram:O(t.templates?.instagram,r)},a={automationEndpoint:t.scheduling?.automationEndpoint||e.env.AUTOMATION_ENDPOINT,cfAccessClientId:t.scheduling?.cfAccessClientId||e.env.CF_ACCESS_CLIENT_ID,cfAccessClientSecret:t.scheduling?.cfAccessClientSecret||e.env.CF_ACCESS_CLIENT_SECRET},o={publicUrl:t.calendar?.publicUrl||e.env.CALENDAR_PUBLIC_URL,token:t.calendar?.token||e.env.CALENDAR_TOKEN};return{thematic:t.thematic||[],templatesDir:t.templatesDir||``,templates:i,scheduling:a,calendar:o}}function A(e){return p(e)?h(e,`utf-8`):``}function ce(t,n,i,a,o=e.cwd()){let s=x(b(o,u(t,`yyyy`),u(t,`MM`),u(t,`dd`)));p(s)||(m(s,{recursive:!0}),r.success(`Created directory: ${s}`));let c=le(i);for(let e of n)j(e,s,c,a);return s}function le(e){return e.hasVideo?{...e,hasImages:!1}:{...e,hasImages:!0}}function j(e,t,n,i){let a=D[e],o=b(t,a.mainFile);if(p(o)?r.info(`${a.mainFile} already exists, skipping`):M(e,o,n,i),a.additionalFiles)for(let o of a.additionalFiles){let a=b(t,o);P(o,e,n)&&(p(a)?r.info(`${o} already exists, skipping`):N(e,a,o,i))}}function M(e,t,n,i){let a={title:n.title};e===`linkedin`&&(n.theme&&(a.theme=n.theme),n.hasVideo&&(a.video=!0),n.hasImages&&(a.images=[null]));let o=``;if(e===`linkedin`){let e=i.templates.linkedin?.footerPath;o=e?A(e):``}v(t,S.stringify(o,a),`utf-8`),r.success(`Created ${t}`)}function N(e,t,n,i){let a=``;if(n.endsWith(`-description.md`)){let t=i.templates[e]?.templatePath;t&&(a=A(t))}v(t,a,`utf-8`),r.success(`Created ${t}`)}function P(e,t,n){return t===`linkedin`&&e===`linkedin-script.md`?n.hasVideo||!1:!0}const F=[`linkedin`,`x`,`youtube`,`instagram`],I={linkedin:`LinkedIn`,x:`X`,youtube:`YouTube`,instagram:`Instagram`};function L(e,t){let n=[],i=D[t].mainFile;try{let t=g(e).filter(t=>_(b(e,t)).isDirectory()&&/^\d{4}$/.test(t));for(let a of t){let t=b(e,a),o=g(t).filter(e=>_(b(t,e)).isDirectory()&&/^\d{2}$/.test(e));for(let e of o){let o=b(t,e),s=g(o).filter(e=>_(b(o,e)).isDirectory()&&/^\d{2}$/.test(e));for(let t of s){let s=x(b(o,t),i);if(!p(s))continue;let c=`${a}-${e}-${t}`,l=new Date(Number(a),Number(e)-1,Number(t));try{let{data:r}=S(h(s,`utf-8`)),o=r.title||`Untitled (${c})`;n.push({path:s,date:l,title:o,relativePath:`${a}/${e}/${t}/${i}`})}catch(e){r.warn(`Error reading ${s}: ${e}`)}}}}}catch(e){return r.error(`Error reading directories: ${e}`),[]}return n.sort((e,t)=>t.date.getTime()-e.date.getTime())}function R(e){switch(e){case`linkedin`:return`linkedin-posts.md`;case`x`:return`x-posts.md`;case`youtube`:return`youtube-videos.md`;case`instagram`:return`instagram-posts.md`}}function z(e){switch(e){case`linkedin`:return`LinkedIn Posts`;case`x`:return`X Posts`;case`youtube`:return`YouTube Videos`;case`instagram`:return`Instagram Posts`}}function B(e,t){let n=L(e,t);if(n.length===0){r.info(`No ${t} posts found`);return}let i=z(t),a=R(t),o=`# ${i}\n\n`;o+=`_Generated on ${new Date().toLocaleDateString()}_\n\n`,o+=`Total posts: ${n.length}\n\n`;for(let e of n){let t=e.date.toLocaleDateString(`en-US`,{year:`numeric`,month:`long`,day:`numeric`});o+=`- [${e.title}](${e.relativePath}) - _${t}_\n`}v(b(e,a),o,`utf-8`),r.success(`Created ${a} with ${n.length} posts`)}function V(t=e.cwd(),n){let r=n||[...F];for(let e of r)B(t,e)}function H(e){return e.toLowerCase().trim().normalize(`NFD`).replace(/[\u0300-\u036F]/g,``).replace(/[^a-z0-9\s-]/g,``).replace(/\s+/g,`-`).replace(/-+/g,`-`).replace(/^-+|-+$/g,``)||`untitled`}function U(t,n,i=e.cwd()){let a=H(t),o=b(x(b(i,`resources`)),a),s=`${n}.md`,c=b(o,s);if(p(c))return r.info(`${s} already exists at: ${c}`),o;m(o,{recursive:!0}),r.success(`Created directory: ${o}`);let l={title:t,url:``,date:``};return v(c,S.stringify(``,l),`utf-8`),r.success(`Created ${s} at: ${c}`),o}function W(t=e.cwd()){let n=[];try{let e=g(t).filter(e=>_(b(t,e)).isDirectory()&&/^\d{4}$/.test(e));for(let r of e){let e=b(t,r),i=g(e).filter(t=>_(b(e,t)).isDirectory()&&/^\d{2}$/.test(t));for(let t of i){let i=b(e,t),a=g(i).filter(e=>_(b(i,e)).isDirectory()&&/^\d{2}$/.test(e));for(let e of a){let a=b(i,e);if(p(b(a,`linkedin.md`))){let i=`${r}-${t}-${e}`;n.push({path:a,date:new Date(i),displayPath:`${r}/${t}/${e}`})}}}}}catch(e){return r.error(`Error reading directories: ${e}`),[]}return n.sort((e,t)=>t.date.getTime()-e.date.getTime()).slice(0,8)}function ue(e){let t=[`.jpg`,`.jpeg`,`.png`],n=[];try{let r=g(e);for(let i of r)if(_(b(e,i)).isFile()){let e=i.toLowerCase().substring(i.lastIndexOf(`.`));t.includes(e)&&n.push(i)}}catch(e){r.error(`Error reading directory: ${e}`)}return n.sort()}function G(e,t){try{let{data:n,content:i}=S(h(e,`utf-8`));n.images=t.length>0?t:[null],v(e,S.stringify(i,n),`utf-8`),r.success(`Updated ${e} with ${t.length} image(s)`)}catch(e){r.error(`Error updating frontmatter: ${e}`)}}async function de(t){if(t.length===0)return r.warn(`No dated folders with linkedin.md found`),null;let i=await o({message:`Select a folder to link images:`,options:t.map(e=>({label:e.displayPath,value:e.path,hint:`Link images in ${e.displayPath}`}))});return n(i)&&(r.error(`Operation cancelled.`),e.exit(0)),t.find(e=>e.path===i)||null}async function fe(t=e.cwd()){let n=W(t);if(n.length===0){r.warn(`No dated folders with linkedin.md found`);return}let i=await de(n);if(!i)return;let a=ue(i.path);if(a.length===0){r.info(`No images found in ${i.displayPath}`),G(b(i.path,`linkedin.md`),[]);return}r.info(`Found ${a.length} image(s): ${a.join(`, `)}`),G(b(i.path,`linkedin.md`),a)}function pe(e){let t=new Date;t.setHours(0,0,0,0);let n=F.flatMap(t=>L(e,t).map(e=>({...e,contentType:t}))).filter(e=>e.date.getTime()>=t.getTime()).sort((e,t)=>{let n=e.date.getTime()-t.date.getTime();return n===0?F.indexOf(e.contentType)-F.indexOf(t.contentType):n});if(n.length===0){r.info(`No upcoming content found from today onward.`);return}r.info(`Found ${n.length} upcoming content item${n.length===1?``:`s`}:`),console.log(``);for(let e of n){let t=u(e.date,`EEEE, MMMM d, yyyy`),n=u(e.date,`yyyy-MM-dd`);console.log(`${t} (${n}) · ${I[e.contentType]} · ${e.title}`),console.log(e.path),console.log(``)}}function me(t){try{let{data:e,content:n}=S(h(t,`utf-8`));if(e.ready===!0){r.warn(`This publication is already marked as ready.`);return}let i={...e,ready:!0};v(t,S.stringify(n,i),`utf-8`),r.success(`Marked publication as ready: ${t}`)}catch(t){r.error(`Failed to mark publication as ready: ${t instanceof Error?t.message:String(t)}`),e.exit(1)}}const K=E(new URL(`../../`,import.meta.url)),q=`content-creation.ics`;function J(e){return e.replace(/\\/g,`\\\\`).replace(/\r\n|\r|\n/g,`\\n`).replace(/;/g,`\\;`).replace(/,/g,`\\,`)}function he(e){if(e.length<=75)return e;let t=[],n=e;for(;n.length>75;)t.push(n.slice(0,75)),n=` ${n.slice(75)}`;return t.push(n),t.join(`\r
3
- `)}function ge(e){return e.toISOString().replace(/[-:]/g,``).replace(/\.\d{3}Z$/,`Z`)}function Y(e){return u(e,`yyyyMMdd`)}function X(e){return e.split(`/`).map(e=>encodeURIComponent(e)).join(`/`)}function _e(e){return`https://github.com/barbapapazes/content-creation/blob/main/${X(e)}`}function ve(e){let t=X(e);return`vscode://file${t.startsWith(`/`)?``:`/`}${t}`}function ye(e){let t=[`GitHub: ${_e(e.relativePath)}`,`VS Code: ${ve(e.path)}`,`Status: ${e.ready?`ready`:`not ready`}`];return e.theme&&t.push(`Theme: ${e.theme}`),typeof e.video==`boolean`&&t.push(`Video planned: ${e.video?`yes`:`no`}`),e.imageCount>0&&t.push(`Images planned: ${e.imageCount}`),t.join(`
4
- `)}function be(e){return`${e.ready?`Ready`:`Not ready`} · ${e.title} · ${I.linkedin}`}function xe(e){return`linkedin-${T(`sha1`).update(e).digest(`hex`)}@barbapapazes`}function Se(e){let t=[`BEGIN:VCALENDAR`,`VERSION:2.0`,`PRODID:-//Barbapapazes//Content Creation LinkedIn Calendar//EN`,`CALSCALE:GREGORIAN`,`METHOD:PUBLISH`,`X-WR-CALNAME:${J(`content-creation.ics`)}`];for(let n of e){let e=ge(new Date(Date.UTC(n.date.getFullYear(),n.date.getMonth(),n.date.getDate()))),r=l(n.date,1);t.push(`BEGIN:VEVENT`,`UID:${xe(n.relativePath)}`,`DTSTAMP:${e}`,`SUMMARY:${J(be(n))}`,`DTSTART;VALUE=DATE:${Y(n.date)}`,`DTEND;VALUE=DATE:${Y(r)}`,`TRANSP:TRANSPARENT`,`DESCRIPTION:${J(ye(n))}`,`END:VEVENT`)}return t.push(`END:VCALENDAR`),`${t.map(he).join(`\r
5
- `)}\r\n`}function Ce(){let t=b(K,`node_modules`,`.bin`,e.platform===`win32`?`wrangler.cmd`:`wrangler`);return p(t)?t:`wrangler`}function we(t){(!t.calendar.publicUrl||!t.calendar.token)&&(r.error(`Calendar publishing configuration is missing. Please set CALENDAR_PUBLIC_URL and CALENDAR_TOKEN in your .env file or config.`),e.exit(1));let n=t.calendar.publicUrl.endsWith(`/`)?t.calendar.publicUrl:`${t.calendar.publicUrl}/`,i=new URL(q,n);return i.searchParams.set(`token`,t.calendar.token),i.toString()}function Te(e){return L(e,`linkedin`).map(e=>{let t={title:e.title};try{t=S(h(e.path,`utf-8`)).data}catch(t){r.warn(`Failed to read LinkedIn metadata from ${e.path}: ${t}`)}return{...e,title:t.title||e.title,ready:t.ready===!0,theme:t.theme,video:t.video,imageCount:t.images?.filter(Boolean).length??0}}).sort((e,t)=>e.date.getTime()-t.date.getTime())}function Ee(t){let n=Ce();try{w(n,[`r2`,`object`,`put`,`content-creation/${q}`,`--pipe`,`--content-type`,`text/calendar; charset=utf-8`,`--remote`],{cwd:K,stdio:[`pipe`,`inherit`,`inherit`],env:e.env,input:t})}catch(t){r.error(`Failed to upload LinkedIn calendar: ${t instanceof Error?t.message:String(t)}`),e.exit(1)}}async function De(e,t){let n=Te(e);if(n.length===0)return r.info(`No LinkedIn publications found. Skipping calendar upload.`),null;let i=we(t);return Ee(Se(n)),r.success(`✓ LinkedIn calendar uploaded to R2 as ${q}`),r.info(`Subscription URL: ${i}`),i}async function Oe(t,n){let i=b(t,`x.md`),{data:a,content:o}=S(h(i,`utf-8`));a.scheduled===!0&&(r.warn(`This content is already scheduled. Skipping.`),e.exit(0)),(!n.scheduling.automationEndpoint||!n.scheduling.cfAccessClientId||!n.scheduling.cfAccessClientSecret)&&(r.error(`Scheduling configuration is missing. Please set AUTOMATION_ENDPOINT, CF_ACCESS_CLIENT_ID, and CF_ACCESS_CLIENT_SECRET in your .env file or config.`),e.exit(1));let s=t.split(`/`),c=s[s.length-1],l=s[s.length-2],f=d(`${s[s.length-3]}-${l}-${c}`,`yyyy-MM-dd`,new Date);f=ee(f,11),f=te(f,0),f=ne(f,0);let p=f.getTime(),m={content:o.trim(),scheduleAt:p};try{let t=await fetch(n.scheduling.automationEndpoint,{method:`POST`,headers:{"Content-Type":`application/json`,Accept:`application/json`,"CF-Access-Client-Id":n.scheduling.cfAccessClientId,"CF-Access-Client-Secret":n.scheduling.cfAccessClientSecret},body:JSON.stringify(m)});if(!t.ok){let n=await t.text();r.error(`Failed to schedule reminder: ${t.status} ${t.statusText}\n${n}`),e.exit(1)}let s=await t.json(),c={...a,scheduled:!0};v(i,S.stringify(o,c),`utf-8`);let l=new Date(s.scheduledAt);r.success(`✓ Reminder scheduled successfully!`),r.info(`Scheduled for: ${u(l,`yyyy-MM-dd HH:mm:ss`)} UTC`)}catch(t){r.error(`Failed to schedule reminder: ${t instanceof Error?t.message:String(t)}`),e.exit(1)}}async function ke(){let t=new Date,i=7;for(;;){let a=await o({message:`When do you want to create content?`,options:Z(t,i)});if(n(a)&&(r.error(`Operation cancelled.`),e.exit(0)),a===`load-more`){i+=7;continue}return f(a)}}function Z(e,t){let n=[];for(let r=0;r<t;r++){let t=l(e,r),i=u(t,`yyyy-MM-dd`),a=u(t,`EEEE`),o=i,s=`Create content for ${a}, ${i}`;r===0?(o=`Today - ${a} (${i})`,s=`Create content for today (${a})`):r===1?(o=`Tomorrow - ${a} (${i})`,s=`Create content for tomorrow (${a})`):(o=`In ${r} days - ${a} (${i})`,s=`Create content for ${a}, ${i}`),n.push({label:o,value:i,hint:s})}return n.push({label:`Show 7 more dates (${t+1}-${t+7} days ahead)`,value:`load-more`,hint:`Extend the list up to ${u(l(e,t+6),`yyyy-MM-dd`)}`}),n}async function Q(){let t=await s({message:`What is the title of your content?`,placeholder:`Enter content title`,validate:e=>{if(!e||e.trim().length===0)return`Title is required`}});return n(t)&&(r.error(`Operation cancelled.`),e.exit(0)),t.trim()}async function Ae(){let t=await o({message:`Is a LinkedIn video planned?`,options:[{label:`Yes`,value:!0,hint:`Add video: true to frontmatter`},{label:`No`,value:!1,hint:`No video metadata`}]});return n(t)&&(r.error(`Operation cancelled.`),e.exit(0)),t}async function je(t){if(t.length===0)return;let i=await o({message:`What is the content theme? (optional)`,options:[{label:`No theme`,value:``,hint:`Leave theme empty`},...t.map(e=>({label:e,value:e,hint:`Use theme: ${e}`}))]});n(i)&&(r.error(`Operation cancelled.`),e.exit(0));let a=i;return a.length>0?a:void 0}async function Me(){let t=await o({message:`What type of resource is this?`,options:[{label:`Article`,value:`article`,hint:`Create article.md`},{label:`Video`,value:`video`,hint:`Create video.md`},{label:`Audio`,value:`audio`,hint:`Create audio.md`},{label:`Tweet`,value:`tweet`,hint:`Create tweet.md`}]});return n(t)&&(r.error(`Operation cancelled.`),e.exit(0)),t}async function Ne(){let t=await i({message:`Which content types do you want to create?`,options:[{label:`LinkedIn`,value:`linkedin`,hint:`Create LinkedIn post and optional script`},{label:`X (Twitter)`,value:`x`,hint:`Create X post`},{label:`YouTube`,value:`youtube`,hint:`Create YouTube script and description`},{label:`Instagram`,value:`instagram`,hint:`Create Instagram script and description`}],required:!0});return n(t)&&(r.error(`Operation cancelled.`),e.exit(0)),t}async function Pe(t){let i=[];p(t)||(r.error(`Base path does not exist: ${t}`),e.exit(1));let a=g(t).filter(e=>_(b(t,e)).isDirectory()&&/^\d{4}$/.test(e));for(let e of a){let n=b(t,e),r=g(n).filter(e=>_(b(n,e)).isDirectory()&&/^\d{2}$/.test(e));for(let t of r){let r=b(n,t),a=g(r).filter(e=>_(b(r,e)).isDirectory()&&/^\d{2}$/.test(e));for(let n of a){let a=b(r,n);if(p(b(a,`x.md`))){let r=`${e}-${t}-${n}`;i.push({path:a,date:r,title:``})}}}}i.length===0&&(r.error(`No x.md files found in dated folders`),e.exit(1)),i.sort((e,t)=>t.date.localeCompare(e.date));let s=await o({message:`Which content do you want to schedule a reminder for?`,options:i.map(e=>({label:e.date,value:e.path,hint:`Schedule reminder for ${e.date}`}))});return n(s)&&(r.error(`Operation cancelled.`),e.exit(0)),s}async function Fe(t){let i=F.flatMap(e=>L(t,e).map(t=>({...t,contentType:e}))).sort((e,t)=>{let n=t.date.getTime()-e.date.getTime();return n===0?F.indexOf(e.contentType)-F.indexOf(t.contentType):n});i.length===0&&(r.error(`No publications found in dated folders`),e.exit(1));let a=await o({message:`Which publication do you want to mark as ready?`,options:i.map(e=>Ie(e))});return n(a)&&(r.error(`Operation cancelled.`),e.exit(0)),a}function Ie(e){return{label:`${u(e.date,`yyyy-MM-dd`)} · ${I[e.contentType]} · ${e.title}`,value:e.path,hint:e.relativePath}}const $=c(`content-creation`);$.command(`[path]`,`Create a dated content directory with content files`).option(`--path <path>`,`Base path for content directory (defaults to current directory)`).action(async(n,r)=>{t(`Content Creation - Create dated content directory`);let i=await k(),o=await Q(),s=await Ne(),c={title:o};s.includes(`linkedin`)&&(c.hasVideo=await Ae(),c.hasImages=!c.hasVideo,i.thematic.length>0&&(c.theme=await je(i.thematic)));let l=await ke();ce(l,s,c,i,n||r?.path||e.cwd());let d=s.join(`, `);a(`✓ Content directory created: ${u(l,`yyyy/MM/dd`)} (${d})`)}),$.command(`link-images [path]`,`Link images to LinkedIn frontmatter in recent folders`).option(`--path <path>`,`Base path for content directory (defaults to current directory)`).action(async(n,r)=>{t(`Content Creation - Link Images to LinkedIn`),await fe(n||r?.path||e.cwd()),a(`✓ Images linked successfully`)}),$.command(`resource [path]`,`Create a resource with article/video/audio markdown file`).option(`--path <path>`,`Base path for resource directory (defaults to current directory)`).action(async(n,r)=>{t(`Content Creation - Create Resource`);let i=await Q(),o=await Me();U(i,o,n||r?.path||e.cwd()),a(`✓ Resource created: resources/${H(i)}/${o}.md`)}),$.command(`create-index [path]`,`Create index files for each content type`).option(`--path <path>`,`Base path for content directory (defaults to current directory)`).action(async(n,r)=>{t(`Content Creation - Create Index Files`),V(n||r?.path||e.cwd()),a(`✓ Index files generated successfully`)}),$.command(`list-upcoming [path]`,`List content scheduled for today or later`).option(`--path <path>`,`Base path for content directory (defaults to current directory)`).action(async(n,r)=>{t(`Content Creation - List Upcoming Content`),pe(n||r?.path||e.cwd()),a(`✓ Upcoming content listed successfully`)}),$.command(`publish-linkedin-calendar [path]`,`Create and upload a LinkedIn publication calendar`).option(`--path <path>`,`Base path for content directory (defaults to current directory)`).action(async(n,r)=>{t(`Content Creation - Publish LinkedIn Calendar`);let i=await k();if(await De(n||r?.path||e.cwd(),i)){a(`✓ LinkedIn calendar published successfully`);return}a(`✓ No LinkedIn content found, calendar was not updated`)}),$.command(`schedule-reminder [path]`,`Schedule a reminder for X content`).option(`--path <path>`,`Base path for content directory (defaults to current directory)`).action(async(n,r)=>{t(`Content Creation - Schedule X Reminder`);let i=await k();await Oe(await Pe(n||r?.path||e.cwd()),i),a(`✓ Reminder scheduled successfully`)}),$.command(`ready [path]`,`Mark a publication as ready`).option(`--path <path>`,`Base path for content directory (defaults to current directory)`).action(async(n,r)=>{t(`Content Creation - Mark Publication as Ready`),me(await Fe(n||r?.path||e.cwd())),a(`✓ Publication marked as ready`)}),$.help(),$.version(ie),$.parse();export{};
2
+ import e from"node:process";import{intro as t,isCancel as n,log as r,multiselect as i,outro as a,select as o,text as s}from"@clack/prompts";import{cac as c}from"cac";import{addDays as l,format as u,parseISO as d,setHours as f,setMinutes as ee,setSeconds as p}from"date-fns";import{existsSync as m,mkdirSync as h,readFileSync as g,readdirSync as _,statSync as v,writeFileSync as y}from"node:fs";import{dirname as b,isAbsolute as te,join as x,resolve as S}from"node:path";import C from"gray-matter";import{loadConfig as ne}from"c12";import{execFileSync as w}from"node:child_process";import{createHash as T}from"node:crypto";import{fileURLToPath as E}from"node:url";var D=`0.18.5`;const O={linkedin:{mainFile:`linkedin.md`,additionalFiles:[`linkedin-script.md`]},x:{mainFile:`x.md`},youtube:{mainFile:`youtube-script.md`,additionalFiles:[`youtube-description.md`]},instagram:{mainFile:`instagram-script.md`,additionalFiles:[`instagram-description.md`]}},re={thematic:[],templatesDir:void 0,templates:{},scheduling:{},calendar:{}};function ie(t,n){return t?te(t)?t:S(n?b(n):e.cwd(),t):n?b(n):e.cwd()}function ae(e,t){let n={};if(e?.footerPath){let r=S(t,e.footerPath);m(r)&&(n.footerPath=r)}return n}function k(e,t){let n={};if(e?.templatePath){let r=S(t,e.templatePath);m(r)&&(n.templatePath=r)}return n}async function A(){let{config:t,configFile:n}=await ne({name:`content-creation`,defaults:re,globalRc:!0,dotenv:!0}),r=ie(t.templatesDir,n),i={linkedin:ae(t.templates?.linkedin,r),youtube:k(t.templates?.youtube,r),instagram:k(t.templates?.instagram,r)},a={automationEndpoint:t.scheduling?.automationEndpoint||e.env.AUTOMATION_ENDPOINT,cfAccessClientId:t.scheduling?.cfAccessClientId||e.env.CF_ACCESS_CLIENT_ID,cfAccessClientSecret:t.scheduling?.cfAccessClientSecret||e.env.CF_ACCESS_CLIENT_SECRET},o={publicUrl:t.calendar?.publicUrl||e.env.CALENDAR_PUBLIC_URL,token:t.calendar?.token||e.env.CALENDAR_TOKEN};return{thematic:t.thematic||[],templatesDir:t.templatesDir||``,templates:i,scheduling:a,calendar:o}}function j(e){return m(e)?g(e,`utf-8`):``}function oe(t,n,i,a,o=e.cwd()){let s=S(x(o,u(t,`yyyy`),u(t,`MM`),u(t,`dd`)));m(s)||(h(s,{recursive:!0}),r.success(`Created directory: ${s}`));let c=se(i);for(let e of n)ce(e,s,c,a);return s}function se(e){return e.hasVideo?{...e,hasImages:!1}:{...e,hasImages:!0}}function ce(e,t,n,i){let a=O[e],o=x(t,a.mainFile);if(m(o)?r.info(`${a.mainFile} already exists, skipping`):le(e,o,n,i),a.additionalFiles)for(let o of a.additionalFiles){let a=x(t,o);M(o,e,n)&&(m(a)?r.info(`${o} already exists, skipping`):ue(e,a,o,i))}}function le(e,t,n,i){let a={title:n.title};e===`linkedin`&&(n.theme&&(a.theme=n.theme),n.hasVideo&&(a.video=!0),n.hasImages&&(a.images=[null]));let o=``;if(e===`linkedin`){let e=i.templates.linkedin?.footerPath;o=e?j(e):``}y(t,C.stringify(o,a),`utf-8`),r.success(`Created ${t}`)}function ue(e,t,n,i){let a=``;if(n.endsWith(`-description.md`)){let t=i.templates[e]?.templatePath;t&&(a=j(t))}y(t,a,`utf-8`),r.success(`Created ${t}`)}function M(e,t,n){return t===`linkedin`&&e===`linkedin-script.md`?n.hasVideo||!1:!0}const N=[`linkedin`,`x`,`youtube`,`instagram`],P={linkedin:`LinkedIn`,x:`X`,youtube:`YouTube`,instagram:`Instagram`};function F(e,t,n){let r=n===`date-asc`?e.date.getTime()-t.date.getTime():t.date.getTime()-e.date.getTime();if(r!==0)return r;let i=N.indexOf(e.contentType)-N.indexOf(t.contentType);return i===0?e.relativePath.localeCompare(t.relativePath):i}function I(e,t={}){let n=[],i=t.contentTypes?.length?t.contentTypes:[...N],a=t.fromDate,o=t.sort||`date-desc`;try{let t=_(e).filter(t=>v(x(e,t)).isDirectory()&&/^\d{4}$/.test(t));for(let o of t){let t=x(e,o),s=_(t).filter(e=>v(x(t,e)).isDirectory()&&/^\d{2}$/.test(e));for(let e of s){let s=x(t,e),c=_(s).filter(e=>v(x(s,e)).isDirectory()&&/^\d{2}$/.test(e));for(let t of c){let c=x(s,t),l=`${o}-${e}-${t}`,u=new Date(Number(o),Number(e)-1,Number(t));if(!(a&&u.getTime()<a.getTime()))for(let a of i){let i=O[a].mainFile,s=S(c,i);if(m(s))try{let{data:r}=C(g(s,`utf-8`)),d=r,f=d.title||`Untitled (${l})`;n.push({contentType:a,path:s,folderPath:c,date:u,title:f,relativePath:`${o}/${e}/${t}/${i}`,frontmatter:d})}catch(e){r.warn(`Error reading ${s}: ${e}`)}}}}}}catch(e){return r.error(`Error reading directories: ${e}`),[]}return n.sort((e,t)=>F(e,t,o))}function L(e){switch(e){case`linkedin`:return`linkedin-posts.md`;case`x`:return`x-posts.md`;case`youtube`:return`youtube-videos.md`;case`instagram`:return`instagram-posts.md`}}function R(e){switch(e){case`linkedin`:return`LinkedIn Posts`;case`x`:return`X Posts`;case`youtube`:return`YouTube Videos`;case`instagram`:return`Instagram Posts`}}function z(e,t){let n=I(e,{contentTypes:[t],sort:`date-desc`});if(n.length===0){r.info(`No ${t} posts found`);return}let i=R(t),a=L(t),o=`# ${i}\n\n`;o+=`_Generated on ${new Date().toLocaleDateString()}_\n\n`,o+=`Total posts: ${n.length}\n\n`;for(let e of n){let t=e.date.toLocaleDateString(`en-US`,{year:`numeric`,month:`long`,day:`numeric`});o+=`- [${e.title}](${e.relativePath}) - _${t}_\n`}y(x(e,a),o,`utf-8`),r.success(`Created ${a} with ${n.length} posts`)}function B(t=e.cwd(),n){let r=n||[...N];for(let e of r)z(t,e)}function V(e){return e.toLowerCase().trim().normalize(`NFD`).replace(/[\u0300-\u036F]/g,``).replace(/[^a-z0-9\s-]/g,``).replace(/\s+/g,`-`).replace(/-+/g,`-`).replace(/^-+|-+$/g,``)||`untitled`}function H(t,n,i=e.cwd()){let a=V(t),o=x(S(x(i,`resources`)),a),s=`${n}.md`,c=x(o,s);if(m(c))return r.info(`${s} already exists at: ${c}`),o;h(o,{recursive:!0}),r.success(`Created directory: ${o}`);let l={title:t,url:``,date:``};return y(c,C.stringify(``,l),`utf-8`),r.success(`Created ${s} at: ${c}`),o}const U=`load-more`;function de(t=e.cwd()){let n=[];try{let e=_(t).filter(e=>v(x(t,e)).isDirectory()&&/^\d{4}$/.test(e));for(let r of e){let e=x(t,r),i=_(e).filter(t=>v(x(e,t)).isDirectory()&&/^\d{2}$/.test(t));for(let t of i){let i=x(e,t),a=_(i).filter(e=>v(x(i,e)).isDirectory()&&/^\d{2}$/.test(e));for(let e of a){let a=x(i,e);if(m(x(a,`linkedin.md`))){let i=`${r}-${t}-${e}`;n.push({path:a,date:new Date(i),displayPath:`${r}/${t}/${e}`})}}}}}catch(e){return r.error(`Error reading directories: ${e}`),[]}return n.sort((e,t)=>t.date.getTime()-e.date.getTime())}function fe(e){let t=[`.jpg`,`.jpeg`,`.png`],n=[];try{let r=_(e);for(let i of r)if(v(x(e,i)).isFile()){let e=i.toLowerCase().substring(i.lastIndexOf(`.`));t.includes(e)&&n.push(i)}}catch(e){r.error(`Error reading directory: ${e}`)}return n.sort()}function W(e,t){try{let{data:n,content:i}=C(g(e,`utf-8`));n.images=t.length>0?t:[null],y(e,C.stringify(i,n),`utf-8`),r.success(`Updated ${e} with ${t.length} image(s)`)}catch(e){r.error(`Error updating frontmatter: ${e}`)}}async function pe(t){if(t.length===0)return r.warn(`No dated folders with linkedin.md found`),null;let i=8;for(;;){let a=await o({message:`Select a folder to link images:`,options:me(t,i)});if(n(a)&&(r.error(`Operation cancelled.`),e.exit(0)),a===U){i+=8;continue}return t.find(e=>e.path===a)||null}}function me(e,t){let n=e.slice(0,t).map(e=>({label:e.displayPath,value:e.path,hint:`Link images in ${e.displayPath}`}));if(t>=e.length)return n;let r=Math.min(t+8,e.length);return[...n,{label:`Show ${r-t} more folders (${t+1}-${r} of ${e.length})`,value:U,hint:`Extend the list to ${e[r-1]?.displayPath}`}]}async function he(t=e.cwd()){let n=de(t);if(n.length===0){r.warn(`No dated folders with linkedin.md found`);return}let i=await pe(n);if(!i)return;let a=fe(i.path);if(a.length===0){r.info(`No images found in ${i.displayPath}`),W(x(i.path,`linkedin.md`),[]);return}r.info(`Found ${a.length} image(s): ${a.join(`, `)}`),W(x(i.path,`linkedin.md`),a)}function ge(e){let t=new Date;t.setHours(0,0,0,0);let n=I(e,{fromDate:t,sort:`date-asc`});if(n.length===0){r.info(`No upcoming publications found from today onward.`);return}r.info(`Found ${n.length} upcoming publication${n.length===1?``:`s`}:`),console.log(``);for(let e of n){let t=u(e.date,`EEEE, MMMM d, yyyy`),n=u(e.date,`yyyy-MM-dd`);console.log(`${t} (${n}) · ${P[e.contentType]} · ${e.title}`),console.log(e.path),console.log(``)}}function _e(t){let n=t.path;try{let{data:e,content:i}=C(g(n,`utf-8`));if(e.ready===!0){r.warn(`This publication is already marked as ready.`);return}let a={...e,ready:!0};y(n,C.stringify(i,a),`utf-8`),r.success(`Publication marked as ready: ${t.relativePath}`)}catch(t){r.error(`Failed to mark publication as ready: ${t instanceof Error?t.message:String(t)}`),e.exit(1)}}const G=E(new URL(`../../`,import.meta.url)),K=`content-creation.ics`;function q(e){return e.replace(/\\/g,`\\\\`).replace(/\r\n|\r|\n/g,`\\n`).replace(/;/g,`\\;`).replace(/,/g,`\\,`)}function ve(e){if(e.length<=75)return e;let t=[],n=e;for(;n.length>75;)t.push(n.slice(0,75)),n=` ${n.slice(75)}`;return t.push(n),t.join(`\r
3
+ `)}function ye(e){return e.toISOString().replace(/[-:]/g,``).replace(/\.\d{3}Z$/,`Z`)}function J(e){return u(e,`yyyyMMdd`)}function Y(e){return e.split(`/`).map(e=>encodeURIComponent(e)).join(`/`)}function be(e){return`https://github.com/barbapapazes/content-creation/blob/main/${Y(e)}`}function xe(e){let t=Y(e);return`vscode://file${t.startsWith(`/`)?``:`/`}${t}`}function Se(e){let t=[`GitHub: ${be(e.relativePath)}`,`VS Code: ${xe(e.path)}`,`Status: ${e.ready?`ready`:`not ready`}`];return e.theme&&t.push(`Theme: ${e.theme}`),typeof e.video==`boolean`&&t.push(`Video planned: ${e.video?`yes`:`no`}`),e.imageCount>0&&t.push(`Images planned: ${e.imageCount}`),t.join(`
4
+ `)}function Ce(e){return`${e.ready?`Ready`:`Not ready`} · ${e.title} · ${P.linkedin}`}function we(e){return`linkedin-${T(`sha1`).update(e).digest(`hex`)}@barbapapazes`}function Te(e){let t=[`BEGIN:VCALENDAR`,`VERSION:2.0`,`PRODID:-//Barbapapazes//Content Creation LinkedIn Calendar//EN`,`CALSCALE:GREGORIAN`,`METHOD:PUBLISH`,`X-WR-CALNAME:${q(`content-creation.ics`)}`];for(let n of e){let e=ye(new Date(Date.UTC(n.date.getFullYear(),n.date.getMonth(),n.date.getDate()))),r=l(n.date,1);t.push(`BEGIN:VEVENT`,`UID:${we(n.relativePath)}`,`DTSTAMP:${e}`,`SUMMARY:${q(Ce(n))}`,`DTSTART;VALUE=DATE:${J(n.date)}`,`DTEND;VALUE=DATE:${J(r)}`,`TRANSP:TRANSPARENT`,`DESCRIPTION:${q(Se(n))}`,`END:VEVENT`)}return t.push(`END:VCALENDAR`),`${t.map(ve).join(`\r
5
+ `)}\r\n`}function Ee(){let t=x(G,`node_modules`,`.bin`,e.platform===`win32`?`wrangler.cmd`:`wrangler`);return m(t)?t:`wrangler`}function De(t){(!t.calendar.publicUrl||!t.calendar.token)&&(r.error(`Calendar publishing configuration is missing. Please set CALENDAR_PUBLIC_URL and CALENDAR_TOKEN in your .env file or config.`),e.exit(1));let n=t.calendar.publicUrl.endsWith(`/`)?t.calendar.publicUrl:`${t.calendar.publicUrl}/`,i=new URL(K,n);return i.searchParams.set(`token`,t.calendar.token),i.toString()}function Oe(e){return I(e,{contentTypes:[`linkedin`],sort:`date-asc`}).map(e=>{let t=e.frontmatter;return{...e,title:t.title||e.title,ready:t.ready===!0,theme:t.theme,video:t.video,imageCount:t.images?.filter(Boolean).length??0}})}function ke(t){let n=Ee();try{w(n,[`r2`,`object`,`put`,`content-creation/${K}`,`--pipe`,`--content-type`,`text/calendar; charset=utf-8`,`--remote`],{cwd:G,stdio:[`pipe`,`inherit`,`inherit`],env:e.env,input:t})}catch(t){r.error(`Failed to upload LinkedIn calendar: ${t instanceof Error?t.message:String(t)}`),e.exit(1)}}async function Ae(e,t){let n=Oe(e);if(n.length===0)return r.info(`No LinkedIn publications found. Skipping calendar upload.`),null;let i=De(t);return ke(Te(n)),r.success(`✓ LinkedIn calendar uploaded to R2 as ${K}`),r.info(`Subscription URL: ${i}`),i}async function X(t,n){let i=t.path,{data:a,content:o}=C(g(i,`utf-8`));a.scheduled===!0&&(r.warn(`This X publication is already scheduled. Skipping.`),e.exit(0)),(!n.scheduling.automationEndpoint||!n.scheduling.cfAccessClientId||!n.scheduling.cfAccessClientSecret)&&(r.error(`Scheduling configuration is missing. Please set AUTOMATION_ENDPOINT, CF_ACCESS_CLIENT_ID, and CF_ACCESS_CLIENT_SECRET in your .env file or config.`),e.exit(1));let s=new Date(t.date);s=f(s,11),s=ee(s,0),s=p(s,0);let c=s.getTime(),l={content:o.trim(),scheduleAt:c};try{let t=await fetch(n.scheduling.automationEndpoint,{method:`POST`,headers:{"Content-Type":`application/json`,Accept:`application/json`,"CF-Access-Client-Id":n.scheduling.cfAccessClientId,"CF-Access-Client-Secret":n.scheduling.cfAccessClientSecret},body:JSON.stringify(l)});if(!t.ok){let n=await t.text();r.error(`Failed to schedule reminder: ${t.status} ${t.statusText}\n${n}`),e.exit(1)}let s=await t.json(),c={...a,scheduled:!0};y(i,C.stringify(o,c),`utf-8`);let d=new Date(s.scheduledAt);r.success(`✓ X publication reminder scheduled`),r.info(`Reminder scheduled for: ${u(d,`yyyy-MM-dd HH:mm:ss`)} UTC`)}catch(t){r.error(`Failed to schedule X publication reminder: ${t instanceof Error?t.message:String(t)}`),e.exit(1)}}async function je(){let t=new Date,i=7;for(;;){let a=await o({message:`When do you want to create content?`,options:Me(t,i)});if(n(a)&&(r.error(`Operation cancelled.`),e.exit(0)),a===`load-more`){i+=7;continue}return d(a)}}function Me(e,t){let n=[];for(let r=0;r<t;r++){let t=l(e,r),i=u(t,`yyyy-MM-dd`),a=u(t,`EEEE`),o=i,s=`Create content for ${a}, ${i}`;r===0?(o=`Today - ${a} (${i})`,s=`Create content for today (${a})`):r===1?(o=`Tomorrow - ${a} (${i})`,s=`Create content for tomorrow (${a})`):(o=`In ${r} days - ${a} (${i})`,s=`Create content for ${a}, ${i}`),n.push({label:o,value:i,hint:s})}return n.push({label:`Show 7 more dates (${t+1}-${t+7} days ahead)`,value:`load-more`,hint:`Extend the list up to ${u(l(e,t+6),`yyyy-MM-dd`)}`}),n}async function Z(){let t=await s({message:`What is the title of your content?`,placeholder:`Enter content title`,validate:e=>{if(!e||e.trim().length===0)return`Title is required`}});return n(t)&&(r.error(`Operation cancelled.`),e.exit(0)),t.trim()}async function Ne(){let t=await o({message:`Is a LinkedIn video planned?`,options:[{label:`Yes`,value:!0,hint:`Add video: true to frontmatter`},{label:`No`,value:!1,hint:`No video metadata`}]});return n(t)&&(r.error(`Operation cancelled.`),e.exit(0)),t}async function Pe(t){if(t.length===0)return;let i=await o({message:`What is the content theme? (optional)`,options:[{label:`No theme`,value:``,hint:`Leave theme empty`},...t.map(e=>({label:e,value:e,hint:`Use theme: ${e}`}))]});n(i)&&(r.error(`Operation cancelled.`),e.exit(0));let a=i;return a.length>0?a:void 0}async function Fe(){let t=await o({message:`What type of resource is this?`,options:[{label:`Article`,value:`article`,hint:`Create article.md`},{label:`Video`,value:`video`,hint:`Create video.md`},{label:`Audio`,value:`audio`,hint:`Create audio.md`},{label:`Tweet`,value:`tweet`,hint:`Create tweet.md`}]});return n(t)&&(r.error(`Operation cancelled.`),e.exit(0)),t}async function Ie(){let t=await i({message:`Which content types do you want to create?`,options:[{label:`LinkedIn`,value:`linkedin`,hint:`Create LinkedIn post and optional script`},{label:`X (Twitter)`,value:`x`,hint:`Create X post`},{label:`YouTube`,value:`youtube`,hint:`Create YouTube script and description`},{label:`Instagram`,value:`instagram`,hint:`Create Instagram script and description`}],required:!0});return n(t)&&(r.error(`Operation cancelled.`),e.exit(0)),t}async function Le(e){return Q({basePath:e,contentTypes:[`x`],message:`Which X publication do you want to schedule a reminder for?`,emptyMessage:`No X publications found in dated folders`,sort:`date-desc`})}async function Re(e){return Q({basePath:e,contentTypes:[...N],message:`Which publication do you want to mark as ready?`,emptyMessage:`No publications found in dated folders`,sort:`date-desc`})}async function Q(t){let i=I(t.basePath,{contentTypes:t.contentTypes,sort:t.sort});i.length===0&&(r.error(t.emptyMessage),e.exit(1));let a=await o({message:t.message,options:i.map(e=>ze(e))});n(a)&&(r.error(`Operation cancelled.`),e.exit(0));let s=i.find(e=>e.path===a);return s||(r.error(`Unable to resolve selected publication: ${a}`),e.exit(1)),s}function ze(e){return{label:`${u(e.date,`yyyy-MM-dd`)} · ${P[e.contentType]} · ${e.title}`,value:e.path,hint:e.relativePath}}const $=c(`content-creation`);$.command(`[path]`,`Create a dated content directory with content files`).option(`--path <path>`,`Base path for content directory (defaults to current directory)`).action(async(n,r)=>{t(`Content Creation - Create dated content directory`);let i=await A(),o=await Z(),s=await Ie(),c={title:o};s.includes(`linkedin`)&&(c.hasVideo=await Ne(),c.hasImages=!c.hasVideo,i.thematic.length>0&&(c.theme=await Pe(i.thematic)));let l=await je();oe(l,s,c,i,n||r?.path||e.cwd());let d=s.join(`, `);a(`✓ Content directory created: ${u(l,`yyyy/MM/dd`)} (${d})`)}),$.command(`link-images [path]`,`Link images to LinkedIn frontmatter in recent folders`).option(`--path <path>`,`Base path for content directory (defaults to current directory)`).action(async(n,r)=>{t(`Content Creation - Link Images to LinkedIn`),await he(n||r?.path||e.cwd()),a(`✓ Images linked successfully`)}),$.command(`resource [path]`,`Create a resource with article/video/audio markdown file`).option(`--path <path>`,`Base path for resource directory (defaults to current directory)`).action(async(n,r)=>{t(`Content Creation - Create Resource`);let i=await Z(),o=await Fe();H(i,o,n||r?.path||e.cwd()),a(`✓ Resource created: resources/${V(i)}/${o}.md`)}),$.command(`create-index [path]`,`Create index files for each content type`).option(`--path <path>`,`Base path for content directory (defaults to current directory)`).action(async(n,r)=>{t(`Content Creation - Create Index Files`),B(n||r?.path||e.cwd()),a(`✓ Index files generated successfully`)}),$.command(`list-upcoming [path]`,`List publications scheduled for today or later`).option(`--path <path>`,`Base path for content directory (defaults to current directory)`).action(async(n,r)=>{t(`Content Creation - List Upcoming Publications`),ge(n||r?.path||e.cwd()),a(`✓ Upcoming publications listed`)}),$.command(`publish-linkedin-calendar [path]`,`Create and upload a LinkedIn publication calendar`).option(`--path <path>`,`Base path for content directory (defaults to current directory)`).action(async(n,r)=>{t(`Content Creation - Publish LinkedIn Calendar`);let i=await A();if(await Ae(n||r?.path||e.cwd(),i)){a(`✓ LinkedIn calendar published successfully`);return}a(`✓ No LinkedIn publications found, calendar was not updated`)}),$.command(`schedule-reminder [path]`,`Schedule a reminder for an X publication`).option(`--path <path>`,`Base path for content directory (defaults to current directory)`).action(async(n,r)=>{t(`Content Creation - Schedule X Publication Reminder`);let i=await A();await X(await Le(n||r?.path||e.cwd()),i),a(`✓ X publication reminder scheduled`)}),$.command(`ready [path]`,`Mark a publication as ready`).option(`--path <path>`,`Base path for content directory (defaults to current directory)`).action(async(n,r)=>{t(`Content Creation - Mark Publication Ready`),_e(await Re(n||r?.path||e.cwd())),a(`✓ Publication marked as ready`)}),$.help(),$.version(D),$.parse();export{};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@barbapapazes/content-creation",
3
3
  "type": "module",
4
- "version": "0.18.3",
4
+ "version": "0.18.5",
5
5
  "author": "Estéban Soubiran <esteban@soubiran.dev>",
6
6
  "license": "MIT",
7
7
  "funding": "https://github.com/sponsors/Barbapapazes",