@stacksjs/actions 0.75.134 → 0.75.136

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/dist/blog.js CHANGED
@@ -1,4 +1,4 @@
1
- import{existsSync,mkdirSync,readdirSync,readFileSync,writeFileSync}from"node:fs";import{homedir}from"node:os";import{join}from"node:path";import{articleCss,articleScript,renderPostHtml}from"@stacksjs/cms/article";import{usableOrigin}from"./site-origin";export{usableOrigin};const CONTENT_DIR=join(process.cwd(),"content/blog"),THEME_FILE=join(CONTENT_DIR,".theme.css");function blogDefaults(){const appName=(process.env.APP_NAME||"").trim(),appUrl=(process.env.APP_URL||"").trim().replace(/\/+$/,""),title=appName?`${appName} Blog`:"Blog";return{title,description:"",siteTitle:title,author:appName,url:appUrl&&!/^https?:\/\//.test(appUrl)?`https://${appUrl}`:appUrl,nav:[{text:"Home",link:"/"},{text:"Blog",link:"/blog"}],themes:["colored","light","dark"],defaultTheme:"colored",colophon:'<a href="/blog/feed.xml">RSS</a>',emptyTitle:"No posts yet",emptyText:"The first one is in the works. Leave your email and it will land in your inbox the moment it ships."}}let sitePromise=null;function site(){if(!sitePromise)sitePromise=(async()=>{try{const cfg=(await import(join(process.cwd(),"config/blog.ts"))).default??{},merged={...blogDefaults(),...cfg};if(!cfg.siteTitle&&cfg.title)merged.siteTitle=cfg.title;return merged}catch{return blogDefaults()}})();return sitePromise}let bunpressPromise=null;async function loadBunPress(){if(!bunpressPromise)bunpressPromise=(async()=>{const candidates=[join(homedir(),"Code/Tools/bunpress/packages/bunpress/dist/src/index.js"),join(process.cwd(),"pantry/@stacksjs/bunpress/dist/src/index.js")].filter((entry)=>existsSync(entry));try{candidates.push(Bun.resolveSync("@stacksjs/bunpress",import.meta.dir))}catch{}for(const entry of new Set(candidates))try{return await import(entry)}catch(cause){const detail=cause instanceof Error?cause.message:String(cause);throw Error(`[blog] Failed to import BunPress from ${entry}: ${detail}`,{cause})}return null})();return bunpressPromise}function escapeHtml(s){return String(s).replace(/[&<>"]/g,(c)=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;"})[c])}function escapeXml(s){return String(s).replace(/[&<>"']/g,(c)=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&apos;"})[c])}function postUrl(baseUrl,slug){return`${baseUrl.replace(/\/$/,"")}/blog/${slug}`}function rssDate(date){if(!date)return"";const parsed=new Date(date);return Number.isNaN(parsed.getTime())?date:parsed.toUTCString()}async function fallbackFeedXml(baseUrl){const cfg=await site(),origin=usableOrigin(baseUrl)||usableOrigin(cfg.url)||cfg.url,items=listPosts().slice(0,50).map((p)=>{const url=postUrl(origin,p.slug);return` <item>
1
+ import{existsSync,mkdirSync,readFileSync,writeFileSync}from"node:fs";import{homedir}from"node:os";import{join}from"node:path";import{articleCss,articleScript,renderPostHtml}from"@stacksjs/cms/article";import{loadPosts}from"@stacksjs/cms/blog";import{usableOrigin}from"./site-origin";export{usableOrigin};const CONTENT_DIR=join(process.cwd(),"content/blog"),THEME_FILE=join(CONTENT_DIR,".theme.css");function blogDefaults(){const appName=(process.env.APP_NAME||"").trim(),appUrl=(process.env.APP_URL||"").trim().replace(/\/+$/,""),title=appName?`${appName} Blog`:"Blog";return{title,description:"",siteTitle:title,author:appName,url:appUrl&&!/^https?:\/\//.test(appUrl)?`https://${appUrl}`:appUrl,nav:[{text:"Home",link:"/"},{text:"Blog",link:"/blog"}],themes:["colored","light","dark"],defaultTheme:"colored",colophon:'<a href="/blog/feed.xml">RSS</a>',emptyTitle:"No posts yet",emptyText:"The first one is in the works. Leave your email and it will land in your inbox the moment it ships."}}let sitePromise=null;function site(){if(!sitePromise)sitePromise=(async()=>{try{const cfg=(await import(join(process.cwd(),"config/blog.ts"))).default??{},merged={...blogDefaults(),...cfg};if(!cfg.siteTitle&&cfg.title)merged.siteTitle=cfg.title;return merged}catch{return blogDefaults()}})();return sitePromise}let bunpressPromise=null;async function loadBunPress(){if(!bunpressPromise)bunpressPromise=(async()=>{const candidates=[join(homedir(),"Code/Tools/bunpress/packages/bunpress/dist/src/index.js"),join(process.cwd(),"pantry/@stacksjs/bunpress/dist/src/index.js")].filter((entry)=>existsSync(entry));try{candidates.push(Bun.resolveSync("@stacksjs/bunpress",import.meta.dir))}catch{}for(const entry of new Set(candidates))try{return await import(entry)}catch(cause){const detail=cause instanceof Error?cause.message:String(cause);throw Error(`[blog] Failed to import BunPress from ${entry}: ${detail}`,{cause})}return null})();return bunpressPromise}function escapeHtml(s){return String(s).replace(/[&<>"]/g,(c)=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;"})[c])}function escapeXml(s){return String(s).replace(/[&<>"']/g,(c)=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&apos;"})[c])}function postUrl(baseUrl,slug){return`${baseUrl.replace(/\/$/,"")}/blog/${slug}`}function rssDate(date){if(!date)return"";const parsed=new Date(date);return Number.isNaN(parsed.getTime())?date:parsed.toUTCString()}async function fallbackFeedXml(baseUrl){const cfg=await site(),origin=usableOrigin(baseUrl)||usableOrigin(cfg.url)||cfg.url,items=listPosts().slice(0,50).map((p)=>{const url=postUrl(origin,p.slug);return` <item>
2
2
  <title>${escapeXml(p.fm.title||p.slug)}</title>
3
3
  <link>${escapeXml(url)}</link>
4
4
  <guid>${escapeXml(url)}</guid>
@@ -18,8 +18,7 @@ ${items}
18
18
  <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
19
19
  ${[`${origin}/blog`,...listPosts().map((p)=>postUrl(origin,p.slug))].map((url)=>` <url><loc>${escapeXml(url)}</loc></url>`).join(`
20
20
  `)}
21
- </urlset>`}function parseFrontmatter(md){const m=md.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);if(!m)return{data:{},body:md};const data={},frontmatter=m[1]??"";for(const line of frontmatter.split(`
22
- `)){const i=line.indexOf(":");if(i===-1)continue;const key=line.slice(0,i).trim();let val=line.slice(i+1).trim();if(val.startsWith('"')&&val.endsWith('"')||val.startsWith("'")&&val.endsWith("'"))val=val.slice(1,-1);data[key]=val}return{data,body:m[2]??""}}function formatDate(d){if(!d)return"";const date=new Date(d);if(Number.isNaN(date.getTime()))return d;return`${["January","February","March","April","May","June","July","August","September","October","November","December"][date.getUTCMonth()]} ${date.getUTCDate()}, ${date.getUTCFullYear()}`}const THEME_BUTTONS={colored:{label:"Glacier theme",glyph:"\uD83C\uDF32"},light:{label:"Light theme",glyph:"\u2600"},dark:{label:"Dark theme",glyph:"\uD83C\uDF19"}};async function blogChrome(){const cfg=await site(),modes=cfg.themes.length?cfg.themes:blogDefaults().themes,fallback=modes.includes(cfg.defaultTheme)?cfg.defaultTheme:modes[0],buttons=modes.map((m)=>`<button type="button" data-t="${m}" title="${THEME_BUTTONS[m].label}" aria-label="${THEME_BUTTONS[m].label}" onclick="stxBlogTheme('${m}')">${THEME_BUTTONS[m].glyph}</button>`).join(`
21
+ </urlset>`}function formatDate(d){if(!d)return"";const date=new Date(d);if(Number.isNaN(date.getTime()))return d;return`${["January","February","March","April","May","June","July","August","September","October","November","December"][date.getUTCMonth()]} ${date.getUTCDate()}, ${date.getUTCFullYear()}`}const THEME_BUTTONS={colored:{label:"Glacier theme",glyph:"\uD83C\uDF32"},light:{label:"Light theme",glyph:"\u2600"},dark:{label:"Dark theme",glyph:"\uD83C\uDF19"}};async function blogChrome(){const cfg=await site(),modes=cfg.themes.length?cfg.themes:blogDefaults().themes,fallback=modes.includes(cfg.defaultTheme)?cfg.defaultTheme:modes[0],buttons=modes.map((m)=>`<button type="button" data-t="${m}" title="${THEME_BUTTONS[m].label}" aria-label="${THEME_BUTTONS[m].label}" onclick="stxBlogTheme('${m}')">${THEME_BUTTONS[m].glyph}</button>`).join(`
23
22
  `),toggle=modes.length>1?`<div class="blog-theme-toggle" role="group" aria-label="Theme">
24
23
  ${buttons}
25
24
  </div>`:"";return`<script>
@@ -69,7 +68,7 @@ ${[`${origin}/blog`,...listPosts().map((p)=>postUrl(origin,p.slug))].map((url)=>
69
68
  <p class="colophon">${(await site()).colophon}</p>
70
69
  </footer>`}async function blogConfig(bp,fm){const cfg=await site(),themeCss=existsSync(THEME_FILE)?readFileSync(THEME_FILE,"utf-8"):"",baseCss=bp.defaultConfig.markdown?.css||"";return{...bp.defaultConfig,title:fm.title||cfg.title,description:fm.description||cfg.description,docsDir:CONTENT_DIR,theme:"bun",markdown:{...bp.defaultConfig.markdown,css:`${baseCss}
71
70
  ${articleCss()}
72
- ${themeCss}`},themeConfig:{siteTitle:cfg.siteTitle,nav:cfg.nav}}}export function blogSitemapPaths(contentDir=CONTENT_DIR){const posts=listPosts(contentDir);if(posts.length===0)return[];return["/blog",...posts.map((post)=>`/blog/${post.slug}`)]}function listPosts(contentDir=CONTENT_DIR){if(!existsSync(contentDir))return[];return readdirSync(contentDir).filter((f)=>f.endsWith(".md")).map((f)=>{const{data}=parseFrontmatter(readFileSync(join(contentDir,f),"utf-8"));return{slug:f.replace(/\.md$/,""),fm:data}}).filter((p)=>!!p.fm.title&&!!p.fm.date&&p.fm.draft!=="true").sort((a,b)=>{const fa=a.fm.featured==="true"?1:0,fb=b.fm.featured==="true"?1:0;if(fa!==fb)return fb-fa;return(b.fm.date||"").localeCompare(a.fm.date||"")})}async function notFoundHtml(bp){const body=`
71
+ ${themeCss}`},themeConfig:{siteTitle:cfg.siteTitle,nav:cfg.nav}}}export function blogSitemapPaths(contentDir=CONTENT_DIR){const posts=listPosts(contentDir);if(posts.length===0)return[];return["/blog",...posts.map((post)=>`/blog/${post.slug}`)]}function listPosts(contentDir=CONTENT_DIR){return loadPosts({dir:contentDir}).map((post)=>({slug:post.slug,fm:post.frontmatter}))}async function notFoundHtml(bp){const body=`
73
72
  <a class="blog-back" href="/blog">\u2190 All posts</a>
74
73
  <div class="blog-404">
75
74
  <p class="blog-kicker">404</p>
package/dist/build/dmg.js CHANGED
@@ -1,31 +1 @@
1
- import{cpSync,existsSync,mkdirSync,mkdtempSync,readdirSync,readFileSync,rmSync,statSync,symlinkSync,writeFileSync}from"node:fs";import{tmpdir}from"node:os";import{basename,dirname,join}from"node:path";import process from"node:process";import{log,runCommand}from"@stacksjs/cli";import{appPath,projectPath,publicPath,resourcesPath,storagePath}from"@stacksjs/path";import{bundleDirectoryFor,codesignArgs,describeRuntimeDuplication,dmgVolumeName,looksLikeBunExecutable,renderUserlandPlistEntries}from"@stacksjs/desktop-build";import{runBuildStep}from"./run-build-step";if(process.platform!=="darwin")throw Error("DMG packaging only runs on macOS");const desktopDist=storagePath("framework/desktop-dist"),manifestPath=join(desktopDist,"desktop.json");if(!existsSync(manifestPath))throw Error("Run `buddy build:desktop` before packaging a DMG");const manifest=JSON.parse(readFileSync(manifestPath,"utf8")),ownsLauncher=manifest.launcher==="userland",appName=process.env.DESKTOP_APP_NAME||manifest.title||process.env.APP_NAME||"Stacks",bundleId=process.env.DESKTOP_BUNDLE_ID||`sh.stacks.${appName.toLowerCase().replace(/[^a-z0-9]+/g,"-")}`,version=process.env.DESKTOP_APP_VERSION||"0.0.0",signingIdentity=process.env.DESKTOP_SIGNING_IDENTITY,notaryProfile=process.env.DESKTOP_NOTARY_PROFILE,outputDir=storagePath("framework/desktop-dmg");if(existsSync(outputDir))rmSync(outputDir,{recursive:!0});mkdirSync(outputDir,{recursive:!0});const staging=mkdtempSync(join(tmpdir(),"stacks-dmg-")),scratch=mkdtempSync(join(tmpdir(),"stacks-dmg-work-"));process.on("exit",()=>{rmSync(staging,{recursive:!0,force:!0});rmSync(scratch,{recursive:!0,force:!0})});const appDir=join(staging,`${appName}.app`),macosDir=join(appDir,"Contents/MacOS"),resourcesDir=join(appDir,"Contents/Resources");mkdirSync(macosDir,{recursive:!0});mkdirSync(resourcesDir,{recursive:!0});const builtLauncher="stacks-desktop",launcherName=appName,bundledFiles=readdirSync(desktopDist).filter((name)=>statSync(join(desktopDist,name)).isFile());for(const file of bundledFiles){const target=file===builtLauncher?launcherName:file,directory=bundleDirectoryFor(join(desktopDist,file))==="MacOS"?macosDir:resourcesDir;await runBuildStep(["cp",join(desktopDist,file),join(directory,target)],{cwd:projectPath(),describe:`Copying ${file} into the bundle`})}if(!existsSync(join(macosDir,launcherName)))throw Error(`build:desktop did not produce ${builtLauncher}`);const runtimes=readdirSync(macosDir).filter((name)=>statSync(join(macosDir,name)).isFile()).filter((name)=>looksLikeBunExecutable(join(macosDir,name))).map((name)=>({name,bytes:statSync(join(macosDir,name)).size})),duplication=describeRuntimeDuplication(runtimes);if(duplication)log.warn(duplication);const userlandResources=appPath("Desktop/Resources");if(existsSync(userlandResources))cpSync(userlandResources,resourcesDir,{recursive:!0});async function buildIcon(){const source=[resourcesPath("assets/images/app-icon.png"),publicPath("images/app-icon.png"),publicPath("apple-touch-icon.png")].find((candidate)=>existsSync(candidate));if(!source)return;const iconset=join(scratch,"icon.iconset");mkdirSync(iconset,{recursive:!0});for(const size of[16,32,128,256,512]){await runCommand(["sips","-z",String(size),String(size),source,"--out",join(iconset,`icon_${size}x${size}.png`)],{cwd:scratch,silent:!0});await runCommand(["sips","-z",String(size*2),String(size*2),source,"--out",join(iconset,`icon_${size}x${size}@2x.png`)],{cwd:scratch,silent:!0})}const icns=join(resourcesDir,"AppIcon.icns");if((await runCommand(["iconutil","-c","icns",iconset,"-o",icns],{cwd:scratch})).isErr||!existsSync(icns)){log.warn(`Could not build an .icns from ${source}; the bundle will use the default icon.`);return}return"AppIcon"}const iconFile=await buildIcon();function readUserlandPlist(){const declared=appPath("Desktop/Info.plist.json");if(!existsSync(declared))return{};try{return JSON.parse(readFileSync(declared,"utf8"))}catch(error){throw Error(`app/Desktop/Info.plist.json is not valid JSON: ${error instanceof Error?error.message:error}`)}}const declaredPlist=readUserlandPlist(),{xml:extraPlistEntries,ignored:ignoredPlistKeys}=renderUserlandPlistEntries(declaredPlist);if(ignoredPlistKeys.length>0)log.info(`Ignoring reserved Info.plist keys from app/Desktop/Info.plist.json: ${ignoredPlistKeys.join(", ")}`);function overriddenByApp(key){return Object.hasOwn(declaredPlist,key)}writeFileSync(join(appDir,"Contents/Info.plist"),`<?xml version="1.0" encoding="UTF-8"?>
2
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
- <plist version="1.0">
4
- <dict>
5
- <key>CFBundleName</key><string>${appName}</string>
6
- <key>CFBundleDisplayName</key><string>${appName}</string>
7
- <key>CFBundleIdentifier</key><string>${bundleId}</string>
8
- <key>CFBundleVersion</key><string>${version}</string>
9
- <key>CFBundleShortVersionString</key><string>${version}</string>
10
- <key>CFBundleExecutable</key><string>${launcherName}</string>
11
- <key>CFBundlePackageType</key><string>APPL</string>
12
- <key>CFBundleInfoDictionaryVersion</key><string>6.0</string>
13
- ${overriddenByApp("LSMinimumSystemVersion")?"":` <key>LSMinimumSystemVersion</key><string>11.0</string>
14
- `}${overriddenByApp("NSHighResolutionCapable")?"":` <key>NSHighResolutionCapable</key><true/>
15
- `}${overriddenByApp("NSAppTransportSecurity")?"":`${ownsLauncher?` <!-- The window talks to something this app started on loopback, so an
16
- exception for 127.0.0.1 is all it needs. NSAllowsArbitraryLoads would
17
- additionally permit every unencrypted host on the internet. -->
18
- <key>NSAppTransportSecurity</key>
19
- <dict>
20
- <key>NSAllowsLocalNetworking</key><true/>
21
- <key>NSExceptionDomains</key>
22
- <dict>
23
- <key>127.0.0.1</key>
24
- <dict><key>NSExceptionAllowsInsecureHTTPLoads</key><true/></dict>
25
- </dict>
26
- </dict>`:` <!-- The window loads a remote URL, so the app has to be allowed to reach it. -->
27
- <key>NSAppTransportSecurity</key><dict><key>NSAllowsArbitraryLoads</key><true/></dict>`}`}${iconFile?`
28
- <key>CFBundleIconFile</key><string>${iconFile}</string>`:""}${extraPlistEntries}
29
- </dict>
30
- </plist>
31
- `);if(signingIdentity){const nested=readdirSync(macosDir).filter((name)=>name!==launcherName).map((name)=>join(macosDir,name)),declaredEntitlements=appPath("Desktop/Entitlements.plist"),entitlements=existsSync(declaredEntitlements)?declaredEntitlements:void 0;for(const target of[...nested,join(macosDir,launcherName),appDir])await runBuildStep(codesignArgs({identity:signingIdentity,target,entitlements}),{cwd:staging,describe:`Signing ${basename(target)}`})}async function notarize(target,what){if(!signingIdentity)throw Error("DESKTOP_NOTARY_PROFILE needs DESKTOP_SIGNING_IDENTITY: Apple only notarizes signed code.");const upload=target.endsWith(".app")?join(scratch,`${basename(target)}.zip`):target;if(upload!==target)await runBuildStep(["ditto","-c","-k","--keepParent",target,upload],{cwd:scratch,describe:`Zipping ${basename(target)} for notarization`});log.info(`Notarizing ${what} (Apple usually answers within a few minutes)...`);const submitted=Bun.spawnSync(["xcrun","notarytool","submit",upload,"--keychain-profile",notaryProfile,"--wait"],{stderr:"pipe"}),report=`${submitted.stdout.toString()}${submitted.stderr.toString()}`,status=report.match(/status: (\w[\w ]*)/g)?.pop()?.replace("status: ","");if(submitted.exitCode!==0||status!=="Accepted"){console.error(report.trim());const id=report.match(/id: ([0-9a-f-]{36})/)?.[1];throw Error(`Notarization of ${what} ${status??"failed"}${id?` - see \`xcrun notarytool log ${id} --keychain-profile ${notaryProfile}\``:""}`)}await runBuildStep(["xcrun","stapler","staple",target],{cwd:dirname(target),describe:`Stapling the notarization ticket to ${what}`})}if(notaryProfile)await notarize(appDir,basename(appDir));symlinkSync("/Applications",join(staging,"Applications"));const dmgPath=join(outputDir,`${appName}-${version}.dmg`),volumeName=dmgVolumeName({appName,version,override:process.env.DESKTOP_VOLUME_NAME});if(volumeName===appName)log.warn(`DESKTOP_VOLUME_NAME is the app's own name, so the bundle stages at /Volumes/${appName}/${appName}.app. That path is refused on a Mac where ${appName} is already installed; drop the variable to get "${appName} ${version}".`);const created=await runCommand(["hdiutil","create","-volname",volumeName,"-srcfolder",staging,"-ov","-format","UDZO",dmgPath],{cwd:staging});if(created.isErr)throw Error(`hdiutil failed to create ${dmgPath}`);rmSync(staging,{recursive:!0,force:!0});rmSync(scratch,{recursive:!0,force:!0});if(signingIdentity)await runBuildStep(["codesign","--force","--timestamp","--sign",signingIdentity,dmgPath],{cwd:outputDir,describe:"Signing the DMG"});if(notaryProfile){await notarize(dmgPath,"the DMG");log.success("Notarized and stapled")}log.success(`Built ${dmgPath}`);if(!signingIdentity)log.info("Unsigned build: Gatekeeper blocks the first launch. Right-click the app and choose Open, or set DESKTOP_SIGNING_IDENTITY and DESKTOP_NOTARY_PROFILE before distributing.");else if(!notaryProfile)log.info("Signed but not notarized: other Macs still warn on first open. Set DESKTOP_NOTARY_PROFILE to notarize.");
1
+ import{cpSync,existsSync,mkdirSync,mkdtempSync,readdirSync,readFileSync,rmSync,statSync,symlinkSync,writeFileSync}from"node:fs";import{tmpdir}from"node:os";import{basename,dirname,join}from"node:path";import process from"node:process";import{log,parseOptions,runCommand}from"@stacksjs/cli";import{appPath,projectPath,publicPath,resourcesPath,storagePath}from"@stacksjs/path";import{bundleDirectoryFor,codesignArgs,describeRuntimeDuplication,looksLikeBunExecutable,renderInfoPlist,resolveDmgPackaging}from"@stacksjs/desktop-build";import{runBuildStep}from"./run-build-step";if(process.platform!=="darwin")throw Error("DMG packaging only runs on macOS");const desktopDist=storagePath("framework/desktop-dist"),manifestPath=join(desktopDist,"desktop.json");if(!existsSync(manifestPath)){log.info("No desktop build found. Building it first (`build:desktop`).");await import("./desktop")}if(!existsSync(manifestPath))throw Error(`build:desktop ran but left no manifest at ${manifestPath}`);const manifest=JSON.parse(readFileSync(manifestPath,"utf8")),ownsLauncher=manifest.launcher==="userland";function declaredProjectVersion(){const packageJson=projectPath("package.json");if(!existsSync(packageJson))return;try{return JSON.parse(readFileSync(packageJson,"utf8")).version}catch{return}}const{appName,bundleId,version,volumeName,signingIdentity,notaryProfile}=resolveDmgPackaging({flags:parseOptions(),env:process.env,manifestTitle:manifest.title,declaredVersion:declaredProjectVersion()}),outputDir=storagePath("framework/desktop-dmg");if(existsSync(outputDir))rmSync(outputDir,{recursive:!0});mkdirSync(outputDir,{recursive:!0});const staging=mkdtempSync(join(tmpdir(),"stacks-dmg-")),scratch=mkdtempSync(join(tmpdir(),"stacks-dmg-work-"));process.on("exit",()=>{rmSync(staging,{recursive:!0,force:!0});rmSync(scratch,{recursive:!0,force:!0})});const appDir=join(staging,`${appName}.app`),macosDir=join(appDir,"Contents/MacOS"),resourcesDir=join(appDir,"Contents/Resources");mkdirSync(macosDir,{recursive:!0});mkdirSync(resourcesDir,{recursive:!0});const builtLauncher="stacks-desktop",launcherName=appName,bundledFiles=readdirSync(desktopDist).filter((name)=>statSync(join(desktopDist,name)).isFile());for(const file of bundledFiles){const target=file===builtLauncher?launcherName:file,directory=bundleDirectoryFor(join(desktopDist,file))==="MacOS"?macosDir:resourcesDir;await runBuildStep(["cp",join(desktopDist,file),join(directory,target)],{cwd:projectPath(),describe:`Copying ${file} into the bundle`})}if(!existsSync(join(macosDir,launcherName)))throw Error(`build:desktop did not produce ${builtLauncher}`);const runtimes=readdirSync(macosDir).filter((name)=>statSync(join(macosDir,name)).isFile()).filter((name)=>looksLikeBunExecutable(join(macosDir,name))).map((name)=>({name,bytes:statSync(join(macosDir,name)).size})),duplication=describeRuntimeDuplication(runtimes);if(duplication)log.warn(duplication);const userlandResources=appPath("Desktop/Resources");if(existsSync(userlandResources))cpSync(userlandResources,resourcesDir,{recursive:!0});async function buildIcon(){const source=[resourcesPath("assets/images/app-icon.png"),publicPath("images/app-icon.png"),publicPath("apple-touch-icon.png")].find((candidate)=>existsSync(candidate));if(!source)return;const iconset=join(scratch,"icon.iconset");mkdirSync(iconset,{recursive:!0});for(const size of[16,32,128,256,512]){await runCommand(["sips","-z",String(size),String(size),source,"--out",join(iconset,`icon_${size}x${size}.png`)],{cwd:scratch,silent:!0});await runCommand(["sips","-z",String(size*2),String(size*2),source,"--out",join(iconset,`icon_${size}x${size}@2x.png`)],{cwd:scratch,silent:!0})}const icns=join(resourcesDir,"AppIcon.icns");if((await runCommand(["iconutil","-c","icns",iconset,"-o",icns],{cwd:scratch})).isErr||!existsSync(icns)){log.warn(`Could not build an .icns from ${source}; the bundle will use the default icon.`);return}return"AppIcon"}const iconFile=await buildIcon();function readUserlandPlist(){const declared=appPath("Desktop/Info.plist.json");if(!existsSync(declared))return{};try{return JSON.parse(readFileSync(declared,"utf8"))}catch(error){throw Error(`app/Desktop/Info.plist.json is not valid JSON: ${error instanceof Error?error.message:error}`)}}const declaredPlist=readUserlandPlist(),{xml:infoPlist,ignored:ignoredPlistKeys}=renderInfoPlist({appName,bundleId,version,executable:launcherName,iconFile,ownsLauncher,declared:declaredPlist});if(ignoredPlistKeys.length>0)log.info(`Ignoring reserved Info.plist keys from app/Desktop/Info.plist.json: ${ignoredPlistKeys.join(", ")}`);const infoPlistPath=join(appDir,"Contents/Info.plist");writeFileSync(infoPlistPath,infoPlist);const linted=Bun.spawnSync(["plutil","-lint",infoPlistPath],{stdout:"pipe",stderr:"pipe"});if(linted.exitCode!==0)throw Error(`Generated an Info.plist that macOS cannot read: ${`${linted.stdout.toString()}${linted.stderr.toString()}`.trim()}`);if(signingIdentity){const nested=readdirSync(macosDir).filter((name)=>name!==launcherName).map((name)=>join(macosDir,name)),declaredEntitlements=appPath("Desktop/Entitlements.plist"),entitlements=existsSync(declaredEntitlements)?declaredEntitlements:void 0;for(const target of[...nested,join(macosDir,launcherName),appDir])await runBuildStep(codesignArgs({identity:signingIdentity,target,entitlements}),{cwd:staging,describe:`Signing ${basename(target)}`})}async function notarize(target,what){if(!signingIdentity)throw Error("A notary profile needs a signing identity (--signing-identity or DESKTOP_SIGNING_IDENTITY): Apple only notarizes signed code.");const upload=target.endsWith(".app")?join(scratch,`${basename(target)}.zip`):target;if(upload!==target)await runBuildStep(["ditto","-c","-k","--keepParent",target,upload],{cwd:scratch,describe:`Zipping ${basename(target)} for notarization`});log.info(`Notarizing ${what} (Apple usually answers within a few minutes)...`);const submitted=Bun.spawnSync(["xcrun","notarytool","submit",upload,"--keychain-profile",notaryProfile,"--wait"],{stderr:"pipe"}),report=`${submitted.stdout.toString()}${submitted.stderr.toString()}`,status=report.match(/status: (\w[\w ]*)/g)?.pop()?.replace("status: ","");if(submitted.exitCode!==0||status!=="Accepted"){console.error(report.trim());const id=report.match(/id: ([0-9a-f-]{36})/)?.[1];throw Error(`Notarization of ${what} ${status??"failed"}${id?` - see \`xcrun notarytool log ${id} --keychain-profile ${notaryProfile}\``:""}`)}await runBuildStep(["xcrun","stapler","staple",target],{cwd:dirname(target),describe:`Stapling the notarization ticket to ${what}`})}if(notaryProfile)await notarize(appDir,basename(appDir));symlinkSync("/Applications",join(staging,"Applications"));const dmgPath=join(outputDir,`${appName}-${version}.dmg`);if(volumeName===appName)log.warn(`The volume name is the app's own name, so the bundle stages at /Volumes/${appName}/${appName}.app. That path is refused on a Mac where ${appName} is already installed; drop --volume-name / DESKTOP_VOLUME_NAME to get "${appName} ${version}".`);const created=await runCommand(["hdiutil","create","-volname",volumeName,"-srcfolder",staging,"-ov","-format","UDZO",dmgPath],{cwd:staging});if(created.isErr)throw Error(`hdiutil failed to create ${dmgPath}`);rmSync(staging,{recursive:!0,force:!0});rmSync(scratch,{recursive:!0,force:!0});if(signingIdentity)await runBuildStep(["codesign","--force","--timestamp","--sign",signingIdentity,dmgPath],{cwd:outputDir,describe:"Signing the DMG"});if(notaryProfile){await notarize(dmgPath,"the DMG");log.success("Notarized and stapled")}log.success(`Built ${dmgPath}`);if(!signingIdentity)log.info("Unsigned build: Gatekeeper blocks the first launch. Right-click the app and choose Open, or pass --signing-identity and --notary-profile before distributing.");else if(!notaryProfile)log.info("Signed but not notarized: other Macs still warn on first open. Pass --notary-profile to notarize.");
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@stacksjs/actions",
3
3
  "type": "module",
4
4
  "sideEffects": false,
5
- "version": "0.75.134",
5
+ "version": "0.75.136",
6
6
  "description": "The Stacks actions.",
7
7
  "author": "Chris Breuer",
8
8
  "contributors": [
@@ -66,54 +66,54 @@
66
66
  "prepublishOnly": "bun run build"
67
67
  },
68
68
  "dependencies": {
69
- "@stacksjs/action-runner": "0.75.134",
70
- "@stacksjs/ai": "0.75.134",
71
- "@stacksjs/analytics": "0.75.134",
72
- "@stacksjs/api": "0.75.134",
73
- "@stacksjs/auth": "0.75.134",
74
- "@stacksjs/build": "0.75.134",
69
+ "@stacksjs/action-runner": "0.75.136",
70
+ "@stacksjs/ai": "0.75.136",
71
+ "@stacksjs/analytics": "0.75.136",
72
+ "@stacksjs/api": "0.75.136",
73
+ "@stacksjs/auth": "0.75.136",
74
+ "@stacksjs/build": "0.75.136",
75
75
  "@stacksjs/bumpx": "^0.2.21",
76
76
  "@stacksjs/bunpress": "^0.2.15",
77
- "@stacksjs/cli": "0.75.134",
78
- "@stacksjs/cloud": "0.75.134",
79
- "@stacksjs/cms": "0.75.134",
80
- "@stacksjs/components": "^0.2.440",
81
- "@stacksjs/config": "0.75.134",
82
- "@stacksjs/database": "0.75.134",
83
- "@stacksjs/desktop-build": "0.75.134",
84
- "@stacksjs/dns": "0.75.134",
85
- "@stacksjs/enums": "0.75.134",
86
- "@stacksjs/env": "0.75.134",
87
- "@stacksjs/error-handling": "0.75.134",
77
+ "@stacksjs/cli": "0.75.136",
78
+ "@stacksjs/cloud": "0.75.136",
79
+ "@stacksjs/cms": "0.75.136",
80
+ "@stacksjs/components": "^0.2.445",
81
+ "@stacksjs/config": "0.75.136",
82
+ "@stacksjs/database": "0.75.136",
83
+ "@stacksjs/desktop-build": "0.75.136",
84
+ "@stacksjs/dns": "0.75.136",
85
+ "@stacksjs/enums": "0.75.136",
86
+ "@stacksjs/env": "0.75.136",
87
+ "@stacksjs/error-handling": "0.75.136",
88
88
  "@stacksjs/gitit": "^0.2.5",
89
- "@stacksjs/i18n": "0.75.134",
90
- "@stacksjs/image": "0.75.134",
91
- "@stacksjs/logging": "0.75.134",
89
+ "@stacksjs/i18n": "0.75.136",
90
+ "@stacksjs/image": "0.75.136",
91
+ "@stacksjs/logging": "0.75.136",
92
92
  "@stacksjs/logsmith": "^0.2.11",
93
- "@stacksjs/mobile": "0.75.134",
94
- "@stacksjs/orm": "0.75.134",
95
- "@stacksjs/path": "0.75.134",
96
- "@stacksjs/payments": "0.75.134",
97
- "@stacksjs/queue": "0.75.134",
98
- "@stacksjs/registry": "0.75.134",
99
- "@stacksjs/router": "0.75.134",
93
+ "@stacksjs/mobile": "0.75.136",
94
+ "@stacksjs/orm": "0.75.136",
95
+ "@stacksjs/path": "0.75.136",
96
+ "@stacksjs/payments": "0.75.136",
97
+ "@stacksjs/queue": "0.75.136",
98
+ "@stacksjs/registry": "0.75.136",
99
+ "@stacksjs/router": "0.75.136",
100
100
  "@stacksjs/rpx": "^0.11.54",
101
- "@stacksjs/scheduler": "0.75.134",
102
- "@stacksjs/search-engine": "0.75.134",
103
- "@stacksjs/security": "0.75.134",
104
- "@stacksjs/server": "0.75.134",
105
- "@stacksjs/sites": "0.75.134",
106
- "@stacksjs/storage": "0.75.134",
107
- "@stacksjs/strings": "0.75.134",
108
- "@stacksjs/stx": "^0.2.440",
101
+ "@stacksjs/scheduler": "0.75.136",
102
+ "@stacksjs/search-engine": "0.75.136",
103
+ "@stacksjs/security": "0.75.136",
104
+ "@stacksjs/server": "0.75.136",
105
+ "@stacksjs/sites": "0.75.136",
106
+ "@stacksjs/storage": "0.75.136",
107
+ "@stacksjs/strings": "0.75.136",
108
+ "@stacksjs/stx": "^0.2.445",
109
109
  "@stacksjs/tlsx": "^0.13.19",
110
110
  "@stacksjs/ts-cloud": "^0.16.52",
111
111
  "@stacksjs/ts-md": "^0.1.1",
112
- "@stacksjs/tunnel": "0.75.134",
113
- "@stacksjs/types": "0.75.134",
114
- "@stacksjs/utils": "0.75.134",
115
- "@stacksjs/whois": "0.75.134",
116
- "bun-plugin-stx": "^0.2.440"
112
+ "@stacksjs/tunnel": "0.75.136",
113
+ "@stacksjs/types": "0.75.136",
114
+ "@stacksjs/utils": "0.75.136",
115
+ "@stacksjs/whois": "0.75.136",
116
+ "bun-plugin-stx": "^0.2.445"
117
117
  },
118
118
  "peerDependencies": {
119
119
  "craft-native": ">=0.0.127",
@@ -128,7 +128,7 @@
128
128
  }
129
129
  },
130
130
  "devDependencies": {
131
- "@stacksjs/validation": "0.75.134",
131
+ "@stacksjs/validation": "0.75.136",
132
132
  "better-dx": "^0.2.24"
133
133
  }
134
134
  }