@stacksjs/actions 0.70.75 → 0.70.77
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.d.ts +1 -0
- package/dist/blog.js +98 -52
- package/package.json +17 -17
package/dist/blog.d.ts
CHANGED
|
@@ -10,3 +10,4 @@ export declare function renderBlog(req: Request): Promise<Response | null>;
|
|
|
10
10
|
* (e.g. https://stacksjs.com) — used for absolute feed/sitemap/share URLs.
|
|
11
11
|
*/
|
|
12
12
|
export declare function buildBlog(options?: { outDir: string, baseUrl?: string }): Promise<void>;
|
|
13
|
+
declare type BlogThemeMode = 'colored' | 'light' | 'dark';
|
package/dist/blog.js
CHANGED
|
@@ -1,28 +1,32 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
import"./chunk-v0hqskdk.js";import{existsSync as x,mkdirSync as
|
|
3
|
-
<title>${
|
|
4
|
-
<link>${
|
|
5
|
-
<guid>${
|
|
6
|
-
<pubDate>${
|
|
7
|
-
<description>${
|
|
2
|
+
import"./chunk-v0hqskdk.js";import{existsSync as x,mkdirSync as C,readdirSync as n,readFileSync as y,writeFileSync as c}from"fs";import{homedir as b}from"os";import{join as B}from"path";var M=B(process.cwd(),"content/blog"),E=B(M,".theme.css"),X={title:"The Stacks Blog",description:"Notes from building a full-stack TypeScript framework whose only dependencies are TypeScript and Bun.",siteTitle:"Stacks Blog",author:"The Stacks Team",url:"https://stacksjs.com",nav:[{text:"Blog",link:"/blog"},{text:"Docs",link:"/docs"},{text:"GitHub",link:"https://github.com/stacksjs/stacks"}],themes:["colored","light","dark"],defaultTheme:"colored",colophon:'Built with Stacks \xB7 TypeScript & Bun \xB7 <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."},I=null;function k(){if(!I)I=(async()=>{try{let z=(await import(B(process.cwd(),"config/blog.ts"))).default??{},J={...X,...z};if(!z.siteTitle&&z.title)J.siteTitle=z.title;return J}catch{return X}})();return I}var O=null;async function S(){if(!O)O=(async()=>{let G=[B(b(),"Code/Tools/bunpress/packages/bunpress/dist/src/index.js"),B(process.cwd(),"pantry/@stacksjs/bunpress/dist/src/index.js"),"@stacksjs/bunpress"];for(let z of G)try{if(z.startsWith("@")||x(z))return await import(z)}catch{}return null})();return O}function $(G){return String(G).replace(/[&<>"]/g,(z)=>({"&":"&","<":"<",">":">",'"':"""})[z])}function L(G){return String(G).replace(/[&<>"']/g,(z)=>({"&":"&","<":"<",">":">",'"':""","'":"'"})[z])}function U(G,z){return`${G.replace(/\/$/,"")}/blog/${z}`}function o(G){if(!G)return"";let z=new Date(G);return Number.isNaN(z.getTime())?G:z.toUTCString()}async function i(G){let z=await k(),J=G||z.url,W=A().slice(0,50).map((Z)=>{let V=U(J,Z.slug);return` <item>
|
|
3
|
+
<title>${L(Z.fm.title||Z.slug)}</title>
|
|
4
|
+
<link>${L(V)}</link>
|
|
5
|
+
<guid>${L(V)}</guid>
|
|
6
|
+
<pubDate>${L(o(Z.fm.date))}</pubDate>
|
|
7
|
+
<description>${L(Z.fm.description||"")}</description>
|
|
8
8
|
</item>`}).join(`
|
|
9
9
|
`);return`<?xml version="1.0" encoding="UTF-8"?>
|
|
10
10
|
<rss version="2.0">
|
|
11
11
|
<channel>
|
|
12
|
-
<title
|
|
13
|
-
<link>${
|
|
14
|
-
<description
|
|
15
|
-
${
|
|
12
|
+
<title>${L(z.title)}</title>
|
|
13
|
+
<link>${L(`${J}/blog`)}</link>
|
|
14
|
+
<description>${L(z.description)}</description>
|
|
15
|
+
${W}
|
|
16
16
|
</channel>
|
|
17
|
-
</rss>`}function
|
|
17
|
+
</rss>`}async function r(G){let z=await k(),J=G||z.url;return`<?xml version="1.0" encoding="UTF-8"?>
|
|
18
18
|
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
19
|
-
${[`${
|
|
19
|
+
${[`${J}/blog`,...A().map((W)=>U(J,W.slug))].map((W)=>` <url><loc>${L(W)}</loc></url>`).join(`
|
|
20
20
|
`)}
|
|
21
|
-
</urlset>`}function
|
|
22
|
-
`)){let W
|
|
21
|
+
</urlset>`}function m(G){let z=G.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);if(!z)return{data:{},body:G};let J={},Y=z[1]??"";for(let W of Y.split(`
|
|
22
|
+
`)){let Z=W.indexOf(":");if(Z===-1)continue;let V=W.slice(0,Z).trim(),Q=W.slice(Z+1).trim();if(Q.startsWith('"')&&Q.endsWith('"')||Q.startsWith("'")&&Q.endsWith("'"))Q=Q.slice(1,-1);J[V]=Q}return{data:J,body:z[2]??""}}function h(G){if(!G)return"";let z=new Date(G);if(Number.isNaN(z.getTime()))return G;return`${["January","February","March","April","May","June","July","August","September","October","November","December"][z.getUTCMonth()]} ${z.getUTCDate()}, ${z.getUTCFullYear()}`}var P={colored:{label:"Park theme",glyph:"\uD83C\uDF32"},light:{label:"Light theme",glyph:"\u2600"},dark:{label:"Dark theme",glyph:"\uD83C\uDF19"}};async function F(){let G=await k(),z=G.themes.length?G.themes:X.themes,J=z.includes(G.defaultTheme)?G.defaultTheme:z[0],Y=z.map((Z)=>`<button type="button" data-t="${Z}" title="${P[Z].label}" aria-label="${P[Z].label}" onclick="stxBlogTheme('${Z}')">${P[Z].glyph}</button>`).join(`
|
|
23
|
+
`),W=z.length>1?`<div class="blog-theme-toggle" role="group" aria-label="Theme">
|
|
24
|
+
${Y}
|
|
25
|
+
</div>`:"";return`<script>
|
|
23
26
|
(function () {
|
|
27
|
+
var modes = ${JSON.stringify(z)}
|
|
24
28
|
function ok(theme) {
|
|
25
|
-
return theme
|
|
29
|
+
return modes.indexOf(theme) !== -1
|
|
26
30
|
}
|
|
27
31
|
|
|
28
32
|
var queryTheme = ''
|
|
@@ -37,7 +41,7 @@ ${[`${A}/blog`,...q().map((Q)=>D(A,Q.slug))].map((Q)=>` <url><loc>${B(Q)}</loc>
|
|
|
37
41
|
}
|
|
38
42
|
catch {}
|
|
39
43
|
|
|
40
|
-
var theme = ok(queryTheme) ? queryTheme : (ok(savedTheme) ? savedTheme :
|
|
44
|
+
var theme = ok(queryTheme) ? queryTheme : (ok(savedTheme) ? savedTheme : ${JSON.stringify(J)})
|
|
41
45
|
document.documentElement.setAttribute('data-theme', theme)
|
|
42
46
|
window.stxBlogTheme = function (nextTheme) {
|
|
43
47
|
try {
|
|
@@ -48,55 +52,97 @@ ${[`${A}/blog`,...q().map((Q)=>D(A,Q.slug))].map((Q)=>` <url><loc>${B(Q)}</loc>
|
|
|
48
52
|
}
|
|
49
53
|
})()
|
|
50
54
|
</script>
|
|
51
|
-
|
|
52
|
-
<button type="button" data-t="colored" title="Park (default)" aria-label="Park theme" onclick="stxBlogTheme('colored')">\uD83C\uDF32</button>
|
|
53
|
-
<button type="button" data-t="light" title="Light" aria-label="Light theme" onclick="stxBlogTheme('light')">\u2600</button>
|
|
54
|
-
<button type="button" data-t="dark" title="Dark" aria-label="Dark theme" onclick="stxBlogTheme('dark')">\uD83C\uDF19</button>
|
|
55
|
-
</div>`}function P(){return`<div class="blog-landscape" aria-hidden="true">
|
|
55
|
+
${W}`}async function D(){return`<div class="blog-landscape" aria-hidden="true">
|
|
56
56
|
<span class="ridge"></span>
|
|
57
57
|
<span class="river"></span>
|
|
58
|
-
<p class="colophon"
|
|
59
|
-
</div>`}function
|
|
60
|
-
${
|
|
58
|
+
<p class="colophon">${(await k()).colophon}</p>
|
|
59
|
+
</div>`}async function w(G,z){let J=await k(),Y=x(E)?y(E,"utf-8"):"",W=G.defaultConfig.markdown?.css||"";return{...G.defaultConfig,title:z.title||J.title,description:z.description||J.description,docsDir:M,theme:"vitepress",markdown:{...G.defaultConfig.markdown,css:`${W}
|
|
60
|
+
${Y}`},themeConfig:{siteTitle:J.siteTitle,nav:J.nav}}}function A(){if(!x(M))return[];return n(M).filter((G)=>G.endsWith(".md")).map((G)=>{let{data:z}=m(y(B(M,G),"utf-8"));return{slug:G.replace(/\.md$/,""),fm:z}}).filter((G)=>!!G.fm.title&&!!G.fm.date).sort((G,z)=>{let J=G.fm.featured==="true"?1:0,Y=z.fm.featured==="true"?1:0;if(J!==Y)return Y-J;return(z.fm.date||"").localeCompare(G.fm.date||"")})}async function l(G){return G.wrapInLayout(await F()+`
|
|
61
61
|
<a class="blog-back" href="/blog">\u2190 All posts</a>
|
|
62
62
|
<div class="blog-404">
|
|
63
63
|
<span class="blog-404-art" aria-hidden="true"></span>
|
|
64
|
-
<h1>
|
|
65
|
-
<p>This post
|
|
66
|
-
</div>`+
|
|
64
|
+
<h1>Nothing here</h1>
|
|
65
|
+
<p>This post doesn't exist (or it moved). Head back to <a href="/blog">all posts</a>.</p>
|
|
66
|
+
</div>`+await D(),await w(G,{title:"Not found"}),"/blog","page")}async function H(G,z,J){let Y=await k(),W=B(M,`${z}.md`);if(!x(W))return null;let Z=y(W,"utf-8"),{html:V,frontmatter:Q}=await G.markdownToHtml(Z,M);if(!Q.title||!Q.date)return null;let K=Q.author||Y.author,v=$(K.charAt(0).toUpperCase()),R=`
|
|
67
67
|
<a class="blog-back" href="/blog">\u2190 All posts</a>
|
|
68
68
|
<div class="blog-post-head">
|
|
69
|
-
<h1>${
|
|
69
|
+
<h1>${$(Q.title||z)}</h1>
|
|
70
70
|
<p class="blog-post-meta">
|
|
71
|
-
<span class="author">${
|
|
71
|
+
<span class="author">${$(K)}</span>
|
|
72
72
|
<span class="dot">\xB7</span>
|
|
73
|
-
<time>${
|
|
73
|
+
<time>${$(h(Q.date))}</time>
|
|
74
74
|
</p>
|
|
75
|
-
${
|
|
76
|
-
</div>`,
|
|
77
|
-
<div class="avatar">${
|
|
78
|
-
<div><span class="name">${
|
|
79
|
-
</aside>`:"",
|
|
75
|
+
${Q.poster?`<figure class="blog-post-poster"><img src="${$(Q.poster)}" alt="${$(Q.title||"")}"></figure>`:""}
|
|
76
|
+
</div>`,j=Q.authorBio?`<aside class="blog-author-card">
|
|
77
|
+
<div class="avatar">${v}</div>
|
|
78
|
+
<div><span class="name">${$(K)}</span><p class="bio">${$(Q.authorBio)}</p></div>
|
|
79
|
+
</aside>`:"",_=`${J}/blog/${z}`,g=`
|
|
80
80
|
<div class="blog-share">
|
|
81
81
|
<span class="blog-share-label">Share</span>
|
|
82
|
-
<a href="https://twitter.com/intent/tweet?url=${encodeURIComponent(
|
|
83
|
-
<a href="https://www.linkedin.com/sharing/share-offsite/?url=${encodeURIComponent(
|
|
84
|
-
</div>`,
|
|
82
|
+
<a href="https://twitter.com/intent/tweet?url=${encodeURIComponent(_)}&text=${encodeURIComponent(Q.title||"")}" target="_blank" rel="noreferrer">X / Twitter</a>
|
|
83
|
+
<a href="https://www.linkedin.com/sharing/share-offsite/?url=${encodeURIComponent(_)}" target="_blank" rel="noreferrer">LinkedIn</a>
|
|
84
|
+
</div>`,N=A().filter((q)=>q.slug!==z).slice(0,3),f=N.length?`<nav class="blog-more">
|
|
85
85
|
<h2>More from the blog</h2>
|
|
86
|
-
<ul>${
|
|
87
|
-
</nav>`:"";return
|
|
88
|
-
|
|
89
|
-
<span class="sign" aria-hidden="true"></span>
|
|
90
|
-
<h1>The Stacks Blog</h1>
|
|
91
|
-
<p>Notes from building a full-stack TypeScript framework whose only dependencies are TypeScript and Bun.</p>
|
|
92
|
-
</div>
|
|
93
|
-
<div class="blog-cards">${q().map((K)=>{let W=K.fm.featured==="true",J=K.fm.poster?`<div class="blog-card-media"><img src="${Y(K.fm.poster)}" alt="" loading="lazy"></div>`:"";return`<a class="blog-card${W?" is-featured":""}" href="/blog/${Y(K.slug)}">
|
|
94
|
-
${J}
|
|
86
|
+
<ul>${N.map((q)=>`<li><a href="/blog/${$(q.slug)}">${$(q.fm.title||q.slug)}</a></li>`).join("")}</ul>
|
|
87
|
+
</nav>`:"";return G.wrapInLayout(await F()+R+V+j+g+f+await D(),await w(G,Q),`/blog/${z}`,"page")}async function T(G){let z=await k(),Y=A().map((V)=>{let Q=V.fm.featured==="true",K=V.fm.poster?`<div class="blog-card-media"><img src="${$(V.fm.poster)}" alt="" loading="lazy"></div>`:"";return`<a class="blog-card${Q?" is-featured":""}" href="/blog/${$(V.slug)}">
|
|
88
|
+
${K}
|
|
95
89
|
<div>
|
|
96
|
-
${
|
|
97
|
-
<h2 class="blog-card-title">${
|
|
98
|
-
<p class="blog-card-excerpt">${
|
|
99
|
-
<div class="blog-card-meta">${
|
|
90
|
+
${Q?'<span class="blog-card-flag">Featured</span>':""}
|
|
91
|
+
<h2 class="blog-card-title">${$(V.fm.title||V.slug)}</h2>
|
|
92
|
+
<p class="blog-card-excerpt">${$((V.fm.description||"").slice(0,180))}</p>
|
|
93
|
+
<div class="blog-card-meta">${$(V.fm.author||z.author)} \xB7 ${$(h(V.fm.date))}</div>
|
|
100
94
|
</div>
|
|
101
95
|
</a>`}).join(`
|
|
102
|
-
`)
|
|
96
|
+
`),W=`
|
|
97
|
+
<div class="blog-empty">
|
|
98
|
+
<h2>${$(z.emptyTitle)}</h2>
|
|
99
|
+
<p>${$(z.emptyText)}</p>
|
|
100
|
+
<form class="blog-subscribe" method="POST" action="/api/email/subscribe">
|
|
101
|
+
<input type="email" name="email" placeholder="you@example.com" autocomplete="email" aria-label="Email address" required>
|
|
102
|
+
<input type="hidden" name="source" value="blog-empty">
|
|
103
|
+
<button type="submit">Subscribe</button>
|
|
104
|
+
<p class="blog-form-note" role="status" aria-live="polite"></p>
|
|
105
|
+
</form>
|
|
106
|
+
</div>
|
|
107
|
+
<script>
|
|
108
|
+
(function () {
|
|
109
|
+
var form = document.querySelector('.blog-subscribe')
|
|
110
|
+
if (!form) return
|
|
111
|
+
var note = form.querySelector('.blog-form-note')
|
|
112
|
+
var button = form.querySelector('button[type="submit"]')
|
|
113
|
+
var say = function (text) { if (note) note.textContent = text }
|
|
114
|
+
form.addEventListener('submit', function (event) {
|
|
115
|
+
event.preventDefault()
|
|
116
|
+
var input = form.querySelector('input[name="email"]')
|
|
117
|
+
if (!input || !input.value) { say('Enter an email address first.'); return }
|
|
118
|
+
if (button) button.disabled = true
|
|
119
|
+
say('Subscribing...')
|
|
120
|
+
var body = new URLSearchParams()
|
|
121
|
+
body.set('email', input.value)
|
|
122
|
+
body.set('source', 'blog-empty')
|
|
123
|
+
fetch('/api/email/subscribe', {
|
|
124
|
+
method: 'POST',
|
|
125
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
126
|
+
body: body.toString(),
|
|
127
|
+
})
|
|
128
|
+
.then(function (res) { return res.json().catch(function () { return {} }) })
|
|
129
|
+
.then(function (data) {
|
|
130
|
+
if (data && data.success) {
|
|
131
|
+
say(data.message === 'Already subscribed' ? 'You are already on the list.' : 'Subscribed. Check your inbox to confirm.')
|
|
132
|
+
form.reset()
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
say((data && data.message) || 'Something went wrong. Try again.')
|
|
136
|
+
}
|
|
137
|
+
})
|
|
138
|
+
.catch(function () { say('Network error. Try again in a moment.') })
|
|
139
|
+
.finally(function () { if (button) button.disabled = false })
|
|
140
|
+
})
|
|
141
|
+
})()
|
|
142
|
+
</script>`,Z=`
|
|
143
|
+
<div class="blog-listing-head">
|
|
144
|
+
<span class="sign" aria-hidden="true"></span>
|
|
145
|
+
<h1>${$(z.title)}</h1>
|
|
146
|
+
<p>${$(z.description)}</p>
|
|
147
|
+
</div>
|
|
148
|
+
<div class="blog-cards">${Y||W}</div>`;return G.wrapInLayout(await F()+Z+await D(),await w(G,{title:z.title,description:z.description}),"/blog","page")}async function u(G,z){let J=await k(),Y={...await w(G,{title:J.title}),sitemap:{enabled:!0,baseUrl:`${z}/blog`}};return typeof G.buildRssFeed==="function"?G.buildRssFeed(M,Y,{enabled:!0,title:J.title,description:J.description,maxItems:50}):i(z)}async function d(G,z){let J={...await w(G,{title:"Blog"}),sitemap:{enabled:!0,baseUrl:`${z}/blog`}};return typeof G.buildSitemap==="function"?G.buildSitemap(M,J):r(z)}async function s(G){if(G.method!=="GET"&&G.method!=="HEAD")return null;let z=new URL(G.url),{pathname:J}=z,Y=J==="/blog/feed.xml",W=J==="/blog/sitemap.xml",Z=J==="/blog"||J==="/blog/"||/^\/blog\/[^/.]+$/.test(J);if(!Y&&!W&&!Z)return null;let V=await S();if(!V)return null;let Q=(R,j=200)=>new Response(R,{status:j,headers:{"Content-Type":"text/html; charset=utf-8"}});if(Y)return new Response(await u(V,z.origin),{headers:{"Content-Type":"application/rss+xml; charset=utf-8"}});if(W)return new Response(await d(V,z.origin),{headers:{"Content-Type":"application/xml; charset=utf-8"}});if(J==="/blog"||J==="/blog/")return Q(await T(V));let K=J.replace(/^\/blog\//,"").replace(/\/$/,""),v=await H(V,K,z.origin);return v?Q(v):Q(await l(V),404)}async function e(G={outDir:"dist/blog"}){let z=G.outDir||"dist/blog",J=(G.baseUrl||(await k()).url).replace(/\/$/,""),Y=await S();if(!Y)throw Error("[blog] BunPress not found \u2014 cannot build the blog.");C(z,{recursive:!0});let W=(V,Q)=>{let K=B(z,V);C(B(K,".."),{recursive:!0}),c(K,Q,"utf-8")};W("index.html",await T(Y));let Z=A();for(let V of Z){let Q=await H(Y,V.slug,J);if(Q)W(B(V.slug,"index.html"),Q)}W("feed.xml",await u(Y,J)),W("sitemap.xml",await d(Y,J)),console.log(`[blog] built ${Z.length} post(s) + listing, feed.xml, sitemap.xml \u2192 ${z}`)}export{s as renderBlog,e as buildBlog};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stacksjs/actions",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.70.
|
|
4
|
+
"version": "0.70.77",
|
|
5
5
|
"description": "The Stacks actions.",
|
|
6
6
|
"author": "Chris Breuer",
|
|
7
7
|
"contributors": [
|
|
@@ -64,27 +64,27 @@
|
|
|
64
64
|
"@stacksjs/bumpx": "^0.2.6",
|
|
65
65
|
"@stacksjs/bunpress": "^0.1.12",
|
|
66
66
|
"@stacksjs/logsmith": "^0.2.3",
|
|
67
|
-
"@stacksjs/ts-cloud": "^0.7.
|
|
67
|
+
"@stacksjs/ts-cloud": "^0.7.16",
|
|
68
68
|
"@stacksjs/ts-md": "^0.1.1"
|
|
69
69
|
},
|
|
70
70
|
"devDependencies": {
|
|
71
|
-
"@stacksjs/api": "0.70.
|
|
72
|
-
"@stacksjs/cli": "0.70.
|
|
73
|
-
"@stacksjs/config": "0.70.
|
|
74
|
-
"@stacksjs/database": "0.70.
|
|
71
|
+
"@stacksjs/api": "0.70.77",
|
|
72
|
+
"@stacksjs/cli": "0.70.77",
|
|
73
|
+
"@stacksjs/config": "0.70.77",
|
|
74
|
+
"@stacksjs/database": "0.70.77",
|
|
75
75
|
"@stacksjs/tlsx": "^0.13.2",
|
|
76
76
|
"better-dx": "^0.2.16",
|
|
77
|
-
"@stacksjs/dns": "0.70.
|
|
78
|
-
"@stacksjs/enums": "0.70.
|
|
79
|
-
"@stacksjs/env": "0.70.
|
|
80
|
-
"@stacksjs/error-handling": "0.70.
|
|
81
|
-
"@stacksjs/logging": "0.70.
|
|
82
|
-
"@stacksjs/path": "0.70.
|
|
83
|
-
"@stacksjs/security": "0.70.
|
|
84
|
-
"@stacksjs/storage": "0.70.
|
|
85
|
-
"@stacksjs/strings": "0.70.
|
|
86
|
-
"@stacksjs/utils": "0.70.
|
|
87
|
-
"@stacksjs/validation": "0.70.
|
|
77
|
+
"@stacksjs/dns": "0.70.77",
|
|
78
|
+
"@stacksjs/enums": "0.70.77",
|
|
79
|
+
"@stacksjs/env": "0.70.77",
|
|
80
|
+
"@stacksjs/error-handling": "0.70.77",
|
|
81
|
+
"@stacksjs/logging": "0.70.77",
|
|
82
|
+
"@stacksjs/path": "0.70.77",
|
|
83
|
+
"@stacksjs/security": "0.70.77",
|
|
84
|
+
"@stacksjs/storage": "0.70.77",
|
|
85
|
+
"@stacksjs/strings": "0.70.77",
|
|
86
|
+
"@stacksjs/utils": "0.70.77",
|
|
87
|
+
"@stacksjs/validation": "0.70.77"
|
|
88
88
|
},
|
|
89
89
|
"peerDependencies": {
|
|
90
90
|
"pickier": "^0.1.28"
|