create-playcanvas 0.10.0 → 0.11.0
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/README.md +12 -2
- package/dist/index.mjs +23 -23
- package/package.json +2 -2
- package/templates/_shared/third-person-controller/src/controller.ts +25 -3
- package/templates/_shared/web-lens/README.md +42 -0
- package/templates/_shared/web-lens/package.json +9 -0
- package/templates/_shared/web-lens/scripts/package-lens.mjs +231 -0
- package/templates/_shared/web-lens/src/dodge-game.ts +271 -0
- package/templates/_shared/web-lens/src/lens.css +155 -0
- package/templates/_shared/web-lens/src/lens.ts +91 -0
- package/templates/engine/base/package.json +4 -4
- package/templates/engine/third-person-controller/src/main.ts +10 -5
- package/templates/engine/web-lens/index.html +26 -0
- package/templates/engine/web-lens/src/main.ts +89 -0
- package/templates/engine/web-lens/vite.config.ts +44 -0
- package/templates/index.ts +6 -0
- package/templates/react/base/package.json +7 -7
- package/templates/react/third-person-controller/src/App.tsx +5 -2
- package/templates/react/web-lens/index.html +12 -0
- package/templates/react/web-lens/src/App.tsx +85 -0
- package/templates/react/web-lens/vite.config.ts +46 -0
- package/templates/web-components/base/package.json +4 -4
- package/templates/web-components/third-person-controller/index.html +17 -2
- package/templates/web-components/web-lens/index.html +36 -0
- package/templates/web-components/web-lens/src/main.ts +47 -0
- package/templates/web-components/web-lens/vite.config.ts +44 -0
package/README.md
CHANGED
|
@@ -38,9 +38,19 @@ Every format uses TypeScript and includes Vite, ESLint, Prettier and a productio
|
|
|
38
38
|
|
|
39
39
|
## Starters
|
|
40
40
|
|
|
41
|
-
Choose from
|
|
41
|
+
Choose from 13 starter kits across basics, viewers, games, tools and XR. Run the creator to browse them; `spinning-cube` is the default.
|
|
42
42
|
|
|
43
|
-

|
|
44
|
+
|
|
45
|
+
## Snapchat Web Lenses
|
|
46
|
+
|
|
47
|
+
The `web-lens` starter is a portrait game ready to publish to Snapchat as a [Web Lens](https://developers.snap.com/web-lenses/home), with the [Web Lens API](https://developers.snap.com/web-lenses/web-lens-api) wired up for leaderboards, Bitmoji and storage:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
npm create playcanvas@latest my-lens -- --starter web-lens
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Its `npm run package` script builds the game, checks it against the Web Lens upload rules (size limits, relative paths and no network access) and writes the zip to upload through [My Lenses](https://my-lenses.snapchat.com/).
|
|
44
54
|
|
|
45
55
|
## CLI Options
|
|
46
56
|
|
package/dist/index.mjs
CHANGED
|
@@ -1,51 +1,51 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{styleText as l}from"node:util";import B,{stdin as Le,stdout as he}from"node:process";import Ne from"node:readline";import"node:tty";import $ from"node:fs";import y from"node:path";import{fileURLToPath as De}from"node:url";const We=(()=>{const e=/[\uD800-\uDBFF][\uDC00-\uDFFF]/g;return t=>{let r=0;for(e.lastIndex=0;e.test(t);)r+=1;return t.length-r}})(),Ve=e=>e===12288||e>=65281&&e<=65376||e>=65504&&e<=65510,Ue=e=>e===8987||e===9001||e>=12272&&e<=12287||e>=12289&&e<=12350||e>=12441&&e<=12543||e>=12549&&e<=12591||e>=12593&&e<=12686||e>=12688&&e<=12771||e>=12783&&e<=12830||e>=12832&&e<=12871||e>=12880&&e<=19903||e>=65040&&e<=65049||e>=65072&&e<=65106||e>=65108&&e<=65126||e>=65128&&e<=65131||e>=127488&&e<=127490||e>=127504&&e<=127547||e>=127552&&e<=127560||e>=131072&&e<=196605||e>=196608&&e<=262141,Ge=/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]|\u001b\]8;[^;]*;.*?(?:\u0007|\u001b\u005c)/y,Ke=/[\x00-\x08\x0A-\x1F\x7F-\x9F]{1,1000}/y,me=/(?:(?![\uFF61-\uFF9F\uFF00-\uFFEF])[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}\p{Script=Tangut}]){1,1000}/yu,He=/\t{1,1000}/y,de=new RegExp("[\\u{1F1E6}-\\u{1F1FF}]{2}|\\u{1F3F4}[\\u{E0061}-\\u{E007A}]{2}[\\u{E0030}-\\u{E0039}\\u{E0061}-\\u{E007A}]{1,3}\\u{E007F}|(?:\\p{Emoji}\\uFE0F\\u20E3?|\\p{Emoji_Modifier_Base}\\p{Emoji_Modifier}?|\\p{Emoji_Presentation})(?:\\u200D(?:\\p{Emoji_Modifier_Base}\\p{Emoji_Modifier}?|\\p{Emoji_Presentation}|\\p{Emoji}\\uFE0F\\u20E3?))*","yu"),ze=/(?:[\x20-\x7E\xA0-\xFF](?!\uFE0F)){1,1000}/y,Je=new RegExp("\\p{M}+","gu"),Ye={limit:1/0,ellipsis:""},fe=(e,t={},r={})=>{const i=t.limit??1/0,n=t.ellipsis??"",c=t?.ellipsisWidth??(n?fe(n,Ye,r).width:0),a=0,o=r.controlWidth??0,s=r.tabWidth??8,u=r.emojiWidth??2,d=2,h=r.regularWidth??1,m=r.wideWidth??d,p=[[ze,h],[Ge,a],[Ke,o],[He,s],[de,u],[me,m]];let f=0,b=0,F=e.length,T=0,O=!1,M=F,k=Math.max(0,i-c),w=0,S=0,x=0,E=0;e:for(;;){if(S>w||b>=F&&b>f){const _=e.slice(w,S)||e.slice(f,b);T=0;for(const R of _.replaceAll(Je,"")){const I=R.codePointAt(0)||0;if(Ve(I)?E=d:Ue(I)?E=m:E=h,x+E>k&&(M=Math.min(M,Math.max(w,f)+T)),x+E>i){O=!0;break e}T+=R.length,x+=E}w=S=0}if(b>=F)break e;for(let _=0,R=p.length;_<R;_++){const[I,g]=p[_];if(I.lastIndex=b,I.test(e)){if(T=I===me?We(e.slice(b,I.lastIndex)):I===de?1:I.lastIndex-b,E=T*g,x+E>k&&(M=Math.min(M,b+Math.floor((k-x)/g))),x+E>i){O=!0;break e}x+=E,w=f,S=b,b=f=I.lastIndex;continue e}}b+=1}return{width:O?k:x,index:O?M:F,truncated:O,ellipsed:O&&i>=c}},qe={limit:1/0,ellipsis:"",ellipsisWidth:0},
|
|
3
|
-
`);let d=!1;for(let h=0;h<u.length;h++){const m=u[h];if(i+=m,d)d=!1;else if(d=m>="\uD800"&&m<="\uDBFF",d)continue;if(m===
|
|
2
|
+
import{styleText as l}from"node:util";import B,{stdin as Le,stdout as he}from"node:process";import Ne from"node:readline";import"node:tty";import $ from"node:fs";import y from"node:path";import{fileURLToPath as De}from"node:url";const We=(()=>{const e=/[\uD800-\uDBFF][\uDC00-\uDFFF]/g;return t=>{let r=0;for(e.lastIndex=0;e.test(t);)r+=1;return t.length-r}})(),Ve=e=>e===12288||e>=65281&&e<=65376||e>=65504&&e<=65510,Ue=e=>e===8987||e===9001||e>=12272&&e<=12287||e>=12289&&e<=12350||e>=12441&&e<=12543||e>=12549&&e<=12591||e>=12593&&e<=12686||e>=12688&&e<=12771||e>=12783&&e<=12830||e>=12832&&e<=12871||e>=12880&&e<=19903||e>=65040&&e<=65049||e>=65072&&e<=65106||e>=65108&&e<=65126||e>=65128&&e<=65131||e>=127488&&e<=127490||e>=127504&&e<=127547||e>=127552&&e<=127560||e>=131072&&e<=196605||e>=196608&&e<=262141,Ge=/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]|\u001b\]8;[^;]*;.*?(?:\u0007|\u001b\u005c)/y,Ke=/[\x00-\x08\x0A-\x1F\x7F-\x9F]{1,1000}/y,me=/(?:(?![\uFF61-\uFF9F\uFF00-\uFFEF])[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}\p{Script=Tangut}]){1,1000}/yu,He=/\t{1,1000}/y,de=new RegExp("[\\u{1F1E6}-\\u{1F1FF}]{2}|\\u{1F3F4}[\\u{E0061}-\\u{E007A}]{2}[\\u{E0030}-\\u{E0039}\\u{E0061}-\\u{E007A}]{1,3}\\u{E007F}|(?:\\p{Emoji}\\uFE0F\\u20E3?|\\p{Emoji_Modifier_Base}\\p{Emoji_Modifier}?|\\p{Emoji_Presentation})(?:\\u200D(?:\\p{Emoji_Modifier_Base}\\p{Emoji_Modifier}?|\\p{Emoji_Presentation}|\\p{Emoji}\\uFE0F\\u20E3?))*","yu"),ze=/(?:[\x20-\x7E\xA0-\xFF](?!\uFE0F)){1,1000}/y,Je=new RegExp("\\p{M}+","gu"),Ye={limit:1/0,ellipsis:""},fe=(e,t={},r={})=>{const i=t.limit??1/0,n=t.ellipsis??"",c=t?.ellipsisWidth??(n?fe(n,Ye,r).width:0),a=0,o=r.controlWidth??0,s=r.tabWidth??8,u=r.emojiWidth??2,d=2,h=r.regularWidth??1,m=r.wideWidth??d,p=[[ze,h],[Ge,a],[Ke,o],[He,s],[de,u],[me,m]];let f=0,b=0,F=e.length,T=0,O=!1,M=F,k=Math.max(0,i-c),w=0,S=0,x=0,E=0;e:for(;;){if(S>w||b>=F&&b>f){const _=e.slice(w,S)||e.slice(f,b);T=0;for(const R of _.replaceAll(Je,"")){const I=R.codePointAt(0)||0;if(Ve(I)?E=d:Ue(I)?E=m:E=h,x+E>k&&(M=Math.min(M,Math.max(w,f)+T)),x+E>i){O=!0;break e}T+=R.length,x+=E}w=S=0}if(b>=F)break e;for(let _=0,R=p.length;_<R;_++){const[I,g]=p[_];if(I.lastIndex=b,I.test(e)){if(T=I===me?We(e.slice(b,I.lastIndex)):I===de?1:I.lastIndex-b,E=T*g,x+E>k&&(M=Math.min(M,b+Math.floor((k-x)/g))),x+E>i){O=!0;break e}x+=E,w=f,S=b,b=f=I.lastIndex;continue e}}b+=1}return{width:O?k:x,index:O?M:F,truncated:O,ellipsed:O&&i>=c}},qe={limit:1/0,ellipsis:"",ellipsisWidth:0},D=(e,t={})=>fe(e,qe,t).width,J="\x1B",pe="\x9B",Xe=39,te="\x07",ge="[",Ze="]",be="m",re=`${Ze}8;;`,ye=new RegExp(`(?:\\${ge}(?<code>\\d+)m|\\${re}(?<uri>.*)${te})`,"y"),$e=e=>{if(e>=30&&e<=37||e>=90&&e<=97)return 39;if(e>=40&&e<=47||e>=100&&e<=107)return 49;if(e===1||e===2)return 22;if(e===3)return 23;if(e===4)return 24;if(e===7)return 27;if(e===8)return 28;if(e===9)return 29;if(e===0)return 0},xe=e=>`${J}${ge}${e}${be}`,we=e=>`${J}${re}${e}${te}`,se=(e,t,r)=>{const i=t[Symbol.iterator]();let n=!1,c=!1,a=e.at(-1),o=a===void 0?0:D(a),s=i.next(),u=i.next(),d=0;for(;!s.done;){const h=s.value,m=D(h);o+m<=r?e[e.length-1]+=h:(e.push(h),o=0),(h===J||h===pe)&&(n=!0,c=t.startsWith(re,d+1)),n?c?h===te&&(n=!1,c=!1):h===be&&(n=!1):(o+=m,o===r&&!u.done&&(e.push(""),o=0)),s=u,u=i.next(),d+=h.length}a=e.at(-1),!o&&a!==void 0&&a.length&&e.length>1&&(e[e.length-2]+=e.pop())},Qe=e=>{const t=e.split(" ");let r=t.length;for(;r&&!D(t[r-1]);)r--;return r===t.length?e:t.slice(0,r).join(" ")+t.slice(r).join("")},et=(e,t,r={})=>{if(r.trim!==!1&&e.trim()==="")return"";let i="",n,c;const a=e.split(" ");let o=[""],s=0;for(let h=0;h<a.length;h++){const m=a[h];if(r.trim!==!1){const f=o.at(-1)??"",b=f.trimStart();f.length!==b.length&&(o[o.length-1]=b,s=D(b))}h!==0&&(s>=t&&(r.wordWrap===!1||r.trim===!1)&&(o.push(""),s=0),(s||r.trim===!1)&&(o[o.length-1]+=" ",s++));const p=D(m);if(r.hard&&p>t){const f=t-s,b=1+Math.floor((p-f-1)/t);Math.floor((p-1)/t)<b&&o.push(""),se(o,m,t),s=D(o.at(-1)??"");continue}if(s+p>t&&s&&p){if(r.wordWrap===!1&&s<t){se(o,m,t),s=D(o.at(-1)??"");continue}o.push(""),s=0}if(s+p>t&&r.wordWrap===!1){se(o,m,t),s=D(o.at(-1)??"");continue}o[o.length-1]+=m,s+=p}r.trim!==!1&&(o=o.map(h=>Qe(h)));const u=o.join(`
|
|
3
|
+
`);let d=!1;for(let h=0;h<u.length;h++){const m=u[h];if(i+=m,d)d=!1;else if(d=m>="\uD800"&&m<="\uDBFF",d)continue;if(m===J||m===pe){ye.lastIndex=h+1;const f=ye.exec(u)?.groups;if(f?.code!==void 0){const b=Number.parseFloat(f.code);n=b===Xe?void 0:b}else f?.uri!==void 0&&(c=f.uri.length===0?void 0:f.uri)}if(u[h+1]===`
|
|
4
4
|
`){c&&(i+=we(""));const p=n?$e(n):void 0;n&&p&&(i+=xe(p))}else m===`
|
|
5
|
-
`&&(n&&$e(n)&&(i+=xe(n)),c&&(i+=we(c)))}return i},tt=/\r?\n/;function
|
|
6
|
-
`)}function rt(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var ie,ve;function st(){if(ve)return ie;ve=1;const e="\x1B",t=`${e}[`,r="\x07",i={to(a,o){return o?`${t}${o+1};${a+1}H`:`${t}${a+1}G`},move(a,o){let s="";return a<0?s+=`${t}${-a}D`:a>0&&(s+=`${t}${a}C`),o<0?s+=`${t}${-o}A`:o>0&&(s+=`${t}${o}B`),s},up:(a=1)=>`${t}${a}A`,down:(a=1)=>`${t}${a}B`,forward:(a=1)=>`${t}${a}C`,backward:(a=1)=>`${t}${a}D`,nextLine:(a=1)=>`${t}E`.repeat(a),prevLine:(a=1)=>`${t}F`.repeat(a),left:`${t}G`,hide:`${t}?25l`,show:`${t}?25h`,save:`${e}7`,restore:`${e}8`},n={up:(a=1)=>`${t}S`.repeat(a),down:(a=1)=>`${t}T`.repeat(a)},c={screen:`${t}2J`,up:(a=1)=>`${t}1J`.repeat(a),down:(a=1)=>`${t}J`.repeat(a),line:`${t}2K`,lineEnd:`${t}K`,lineStart:`${t}1K`,lines(a){let o="";for(let s=0;s<a;s++)o+=this.line+(s<a-1?i.up():"");return a&&(o+=i.left),o}};return ie={cursor:i,scroll:n,erase:c,beep:r},ie}var P=st();function
|
|
5
|
+
`&&(n&&$e(n)&&(i+=xe(n)),c&&(i+=we(c)))}return i},tt=/\r?\n/;function Y(e,t,r){return String(e).normalize().split(tt).map(i=>et(i,t,r)).join(`
|
|
6
|
+
`)}function rt(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var ie,ve;function st(){if(ve)return ie;ve=1;const e="\x1B",t=`${e}[`,r="\x07",i={to(a,o){return o?`${t}${o+1};${a+1}H`:`${t}${a+1}G`},move(a,o){let s="";return a<0?s+=`${t}${-a}D`:a>0&&(s+=`${t}${a}C`),o<0?s+=`${t}${-o}A`:o>0&&(s+=`${t}${o}B`),s},up:(a=1)=>`${t}${a}A`,down:(a=1)=>`${t}${a}B`,forward:(a=1)=>`${t}${a}C`,backward:(a=1)=>`${t}${a}D`,nextLine:(a=1)=>`${t}E`.repeat(a),prevLine:(a=1)=>`${t}F`.repeat(a),left:`${t}G`,hide:`${t}?25l`,show:`${t}?25h`,save:`${e}7`,restore:`${e}8`},n={up:(a=1)=>`${t}S`.repeat(a),down:(a=1)=>`${t}T`.repeat(a)},c={screen:`${t}2J`,up:(a=1)=>`${t}1J`.repeat(a),down:(a=1)=>`${t}J`.repeat(a),line:`${t}2K`,lineEnd:`${t}K`,lineStart:`${t}1K`,lines(a){let o="";for(let s=0;s<a;s++)o+=this.line+(s<a-1?i.up():"");return a&&(o+=i.left),o}};return ie={cursor:i,scroll:n,erase:c,beep:r},ie}var P=st();function q(e,t,r){if(!r.some(a=>!a.disabled))return e;const i=e+t,n=Math.max(r.length-1,0),c=i<0?n:i>n?0:i;return r[c]?.disabled?q(c,t<0?-1:1,r):c}const it=["up","down","left","right","space","enter","cancel"],V={actions:new Set(it),aliases:new Map([["k","up"],["j","down"],["h","left"],["l","right"],["","cancel"],["escape","cancel"]]),withGuide:!0};function nt(e){if(e!==void 0)return e;const t=process.env.ACCESSIBLE;return t!==void 0&&t!==""&&t!=="0"&&t!=="false"}function Se(e,t){if(typeof e=="string")return V.aliases.get(e)===t;for(const r of e)if(r!==void 0&&Se(r,t))return!0;return!1}function at(e,t){if(e===t)return;const r=e.split(`
|
|
7
7
|
`),i=t.split(`
|
|
8
|
-
`),n=Math.max(r.length,i.length),c=[];for(let a=0;a<n;a++)r[a]!==i[a]&&c.push(a);return{lines:c,numLinesBefore:r.length,numLinesAfter:i.length,numLines:n}}globalThis.process.platform.startsWith("win");const ne=Symbol("clack:cancel");function
|
|
8
|
+
`),n=Math.max(r.length,i.length),c=[];for(let a=0;a<n;a++)r[a]!==i[a]&&c.push(a);return{lines:c,numLinesBefore:r.length,numLinesAfter:i.length,numLines:n}}globalThis.process.platform.startsWith("win");const ne=Symbol("clack:cancel");function H(e){return e===ne}function X(e,t){const r=e;r.isTTY&&r.setRawMode(t)}const Ee=e=>"columns"in e&&typeof e.columns=="number"?e.columns:80,_e=e=>"rows"in e&&typeof e.rows=="number"?e.rows:20;function ae(e,t,r,i=r,n=r,c){const a=Ee(e??he);return Y(t,a-r.length,{hard:!0,trim:!1}).split(`
|
|
9
9
|
`).map((o,s,u)=>{const d=o;return s===0?`${i}${d}`:s===u.length-1?`${n}${d}`:`${r}${d}`}).join(`
|
|
10
|
-
`)}function ot(e,t){if("~standard"in e){const r=e["~standard"].validate(t);return r instanceof Promise?r.then(i=>i.issues?.at(0)?.message):r.issues?.at(0)?.message}return e(t)}class Ie{input;output;_abortSignal;rl;opts;_render;_track=!1;_prevFrame="";_subscribers=new Map;_cursor=0;state="initial";error="";value;userInput="";get accessible(){return nt(this.opts.accessible)}constructor(t,r=!0){const{input:i=Le,output:n=he,render:c,signal:a,...o}=t;this.opts=o,this.onKeypress=this.onKeypress.bind(this),this.close=this.close.bind(this),this.render=this.render.bind(this),this._render=c.bind(this),this._track=r,this._abortSignal=a,this.input=i,this.output=n}unsubscribe(){this._subscribers.clear()}setSubscriber(t,r){const i=this._subscribers.get(t)??[];i.push(r),this._subscribers.set(t,i)}on(t,r){this.setSubscriber(t,{cb:r})}once(t,r){this.setSubscriber(t,{cb:r,once:!0})}emit(t,...r){const i=this._subscribers.get(t)??[],n=[];for(const c of i)c.cb(...r),c.once&&n.push(()=>i.splice(i.indexOf(c),1));for(const c of n)c()}prompt(){return new Promise(t=>{if(this._abortSignal){if(this._abortSignal.aborted)return this.state="cancel",this.close(),t(ne);this._abortSignal.addEventListener("abort",()=>{this.state="cancel",this.close()},{once:!0})}this.rl=Ne.createInterface({input:this.input,tabSize:2,prompt:"",escapeCodeTimeout:50,terminal:!0}),this.rl.prompt(),this.opts.initialUserInput!==void 0&&this._setUserInput(this.opts.initialUserInput,!0),this.input.on("keypress",this.onKeypress),
|
|
11
|
-
`),
|
|
12
|
-
`).length-1;this.output.write(P.cursor.move(-999,t*-1))}render(){const t=
|
|
10
|
+
`)}function ot(e,t){if("~standard"in e){const r=e["~standard"].validate(t);return r instanceof Promise?r.then(i=>i.issues?.at(0)?.message):r.issues?.at(0)?.message}return e(t)}class Ie{input;output;_abortSignal;rl;opts;_render;_track=!1;_prevFrame="";_subscribers=new Map;_cursor=0;state="initial";error="";value;userInput="";get accessible(){return nt(this.opts.accessible)}constructor(t,r=!0){const{input:i=Le,output:n=he,render:c,signal:a,...o}=t;this.opts=o,this.onKeypress=this.onKeypress.bind(this),this.close=this.close.bind(this),this.render=this.render.bind(this),this._render=c.bind(this),this._track=r,this._abortSignal=a,this.input=i,this.output=n}unsubscribe(){this._subscribers.clear()}setSubscriber(t,r){const i=this._subscribers.get(t)??[];i.push(r),this._subscribers.set(t,i)}on(t,r){this.setSubscriber(t,{cb:r})}once(t,r){this.setSubscriber(t,{cb:r,once:!0})}emit(t,...r){const i=this._subscribers.get(t)??[],n=[];for(const c of i)c.cb(...r),c.once&&n.push(()=>i.splice(i.indexOf(c),1));for(const c of n)c()}prompt(){return new Promise(t=>{if(this._abortSignal){if(this._abortSignal.aborted)return this.state="cancel",this.close(),t(ne);this._abortSignal.addEventListener("abort",()=>{this.state="cancel",this.close()},{once:!0})}this.rl=Ne.createInterface({input:this.input,tabSize:2,prompt:"",escapeCodeTimeout:50,terminal:!0}),this.rl.prompt(),this.opts.initialUserInput!==void 0&&this._setUserInput(this.opts.initialUserInput,!0),this.input.on("keypress",this.onKeypress),X(this.input,!0),this.output.on("resize",this.render),this.render(),this.once("submit",()=>{this.output.write(P.cursor.show),this.output.off("resize",this.render),X(this.input,!1),t(this.value)}),this.once("cancel",()=>{this.output.write(P.cursor.show),this.output.off("resize",this.render),X(this.input,!1),t(ne)})})}_isActionKey(t,r){return t===" "}_shouldSubmit(t,r){return!0}_setValue(t){this.value=t,this.emit("value",this.value)}_setUserInput(t,r){this.userInput=t??"",this.emit("userInput",this.userInput),r&&this._track&&this.rl&&(this.rl.write(this.userInput),this._cursor=this.rl.cursor)}_clearUserInput(){this.rl?.write(null,{ctrl:!0,name:"u"}),this._setUserInput("")}async onKeypress(t,r){if(this.state!=="validating"){if(this._track&&r.name!=="return"&&(r.name&&this._isActionKey(t,r)&&this.rl?.write(null,{ctrl:!0,name:"h"}),this._cursor=this.rl?.cursor??0,this._setUserInput(this.rl?.line)),this.state==="error"&&(this.state="active"),r?.name&&(!this._track&&V.aliases.has(r.name)&&this.emit("cursor",V.aliases.get(r.name)),V.actions.has(r.name)&&this.emit("cursor",r.name)),t&&(t.toLowerCase()==="y"||t.toLowerCase()==="n")&&this.emit("confirm",t.toLowerCase()==="y"),this.emit("key",t,r),r?.name==="return"&&this._shouldSubmit(t,r)){if(this.opts.validate){const i=ot(this.opts.validate,this.value);let n;i instanceof Promise?(this.state="validating",this.render(),n=await i):n=i,n&&(this.error=n instanceof Error?n.message:n,this.state="error",this.rl?.write(this.userInput))}this.state!=="error"&&(this.state="submit")}Se([t,r?.name,r?.sequence],"cancel")&&(this.state="cancel"),(this.state==="submit"||this.state==="cancel")&&this.emit("finalize"),this.render(),(this.state==="submit"||this.state==="cancel")&&this.close()}}close(){this.input.unpipe(),this.input.removeListener("keypress",this.onKeypress),this.output.write(`
|
|
11
|
+
`),X(this.input,!1),this.rl?.close(),this.rl=void 0,this.emit(`${this.state}`,this.value),this.unsubscribe()}restoreCursor(){const t=Y(this._prevFrame,process.stdout.columns,{hard:!0,trim:!1}).split(`
|
|
12
|
+
`).length-1;this.output.write(P.cursor.move(-999,t*-1))}render(){const t=Y(this._render(this)??"",process.stdout.columns,{hard:!0,trim:!1});if(t!==this._prevFrame){if(this.state==="initial")this.output.write(P.cursor.hide);else{const r=at(this._prevFrame,t),i=_e(this.output);if(this.restoreCursor(),r){const n=Math.max(0,r.numLinesAfter-i),c=Math.max(0,r.numLinesBefore-i);let a=r.lines.find(o=>o>=n);if(a===void 0){this._prevFrame=t;return}if(r.lines.length===1){this.output.write(P.cursor.move(0,a-c)),this.output.write(P.erase.lines(1));const o=t.split(`
|
|
13
13
|
`);this.output.write(o[a]),this._prevFrame=t,this.output.write(P.cursor.move(0,o.length-a-1));return}else if(r.lines.length>1){if(n<c)a=n;else{const s=a-c;s>0&&this.output.write(P.cursor.move(0,s))}this.output.write(P.erase.down());const o=t.split(`
|
|
14
14
|
`).slice(a);this.output.write(o.join(`
|
|
15
|
-
`)),this._prevFrame=t;return}}this.output.write(P.erase.down())}this.output.write(t),this.state==="initial"&&(this.state="active"),this._prevFrame=t}}}let ct=class extends Ie{options;cursor=0;get _selectedValue(){return this.options[this.cursor]}changeValue(){const t=this._selectedValue;this.value=t===void 0?void 0:t.value}constructor(t){super(t,!1),this.options=t.options;const r=this.options.findIndex(({value:n})=>n===t.initialValue),i=r===-1?0:r;this.cursor=this.options[i]?.disabled?
|
|
16
|
-
`);F.push(E),T+=E.length}if(T>d){let w=0,S=0,x=T;const E=e-O;let _=d;const R=()=>Ae(F,x,0,E,_),I=()=>Ae(F,x,E+1,F.length,_,!0);p?({lineCount:x,removals:w}=R(),x>_&&(f||(_-=1),{lineCount:x,removals:S}=I())):(f||(_-=1),{lineCount:x,removals:S}=I(),x>_&&(_-=1,{lineCount:x,removals:w}=R())),w>0&&(p=!0,F.splice(0,w)),S>0&&(f=!0,F.splice(F.length-S,S))}const k=[];p&&k.push(u);for(const w of F)for(const S of w)k.push(S);return f&&k.push(u),k};`${l("dim","\u2191/\u2193")}`,`${l("dim","Space:")}`,`${l("dim","Enter:")}`;const
|
|
15
|
+
`)),this._prevFrame=t;return}}this.output.write(P.erase.down())}this.output.write(t),this.state==="initial"&&(this.state="active"),this._prevFrame=t}}}let ct=class extends Ie{options;cursor=0;get _selectedValue(){return this.options[this.cursor]}changeValue(){const t=this._selectedValue;this.value=t===void 0?void 0:t.value}constructor(t){super(t,!1),this.options=t.options;const r=this.options.findIndex(({value:n})=>n===t.initialValue),i=r===-1?0:r;this.cursor=this.options[i]?.disabled?q(i,1,this.options):i,this.changeValue(),this.on("cursor",n=>{switch(n){case"left":case"up":this.cursor=q(this.cursor,-1,this.options);break;case"down":case"right":this.cursor=q(this.cursor,1,this.options);break}this.changeValue()})}};class lt extends Ie{get userInputWithCursor(){if(this.state==="submit")return this.userInput;const t=this.userInput;if(this.cursor>=t.length)return`${this.userInput}\u2588`;const r=t.slice(0,this.cursor),i=t.slice(this.cursor,this.cursor+1),n=t.slice(this.cursor+1);return`${r}${l("inverse",i)}${n}`}get cursor(){return this._cursor}constructor(t){super({...t,initialUserInput:t.initialUserInput??t.initialValue}),this.on("userInput",r=>{this._setValue(r)}),this.on("finalize",()=>{this.value||(this.value=t.defaultValue),this.value===void 0&&(this.value="")})}}function ut(){return B.platform!=="win32"?B.env.TERM!=="linux":!!B.env.CI||!!B.env.WT_SESSION||!!B.env.TERMINUS_SUBLIME||B.env.ConEmuTask==="{cmd::Cmder}"||B.env.TERM_PROGRAM==="Terminus-Sublime"||B.env.TERM_PROGRAM==="vscode"||B.env.TERM==="xterm-256color"||B.env.TERM==="alacritty"||B.env.TERMINAL_EMULATOR==="JetBrains-JediTerm"}const ht=ut(),j=(e,t)=>ht?e:t,Fe=j("\u25C6","*"),mt=j("\u25A0","x"),dt=j("\u25B2","x"),Ce=j("\u25C7","o"),v=j("\u2502","|"),U=j("\u2514","\u2014"),ft=j("\u25CF",">"),Te=j("\u25CB"," "),pt=j("\u25CF","\u2022"),gt=j("\u25C6","*"),bt=j("\u25B2","!"),yt=j("\u25A0","x"),Re=e=>{switch(e){case"initial":case"active":return l("cyan",Fe);case"cancel":return l("red",mt);case"error":return l("yellow",dt);case"submit":return l("green",Ce);case"validating":return l("dim",Fe)}},$t=e=>{switch(e){case"initial":case"active":return l("cyan",v);case"cancel":return l("red",v);case"error":return l("yellow",v);case"submit":return l("green",v)}};function xt(e,t){const r=[`${t?`${l("cyan",v)} `:""}${e.join(" \u2022 ")}`];return t&&r.push(l("cyan",U)),r}const Ae=(e,t,r,i,n,c=!1)=>{let a=t,o=0;if(c)for(let s=i-1;s>=r;s--){const u=e[s];if(u&&(a-=u.length),o++,a<=n)break}else for(let s=r;s<i;s++){const u=e[s];if(u&&(a-=u.length),o++,a<=n)break}return{lineCount:a,removals:o}},wt=({cursor:e,options:t,style:r,output:i=process.stdout,maxItems:n=Number.POSITIVE_INFINITY,columnPadding:c=0,rowPadding:a=4})=>{const o=Ee(i)-c,s=_e(i),u=l("dim","..."),d=Math.max(s-a,0),h=Math.max(Math.min(n,d),5);let m=0;e>=h-3&&(m=Math.max(Math.min(e-h+3,t.length-h),0));let p=h<t.length&&m>0,f=h<t.length&&m+h<t.length;const b=Math.min(m+h,t.length),F=[];let T=0;p&&T++,f&&T++;const O=m+(p?1:0),M=b-(f?1:0);for(let w=O;w<M;w++){const S=t[w],x=S?r(S,w===e):"",E=Y(x,o,{hard:!0,trim:!1}).split(`
|
|
16
|
+
`);F.push(E),T+=E.length}if(T>d){let w=0,S=0,x=T;const E=e-O;let _=d;const R=()=>Ae(F,x,0,E,_),I=()=>Ae(F,x,E+1,F.length,_,!0);p?({lineCount:x,removals:w}=R(),x>_&&(f||(_-=1),{lineCount:x,removals:S}=I())):(f||(_-=1),{lineCount:x,removals:S}=I(),x>_&&(_-=1,{lineCount:x,removals:w}=R())),w>0&&(p=!0,F.splice(0,w)),S>0&&(f=!0,F.splice(F.length-S,S))}const k=[];p&&k.push(u);for(const w of F)for(const S of w)k.push(S);return f&&k.push(u),k};`${l("dim","\u2191/\u2193")}`,`${l("dim","Space:")}`,`${l("dim","Enter:")}`;const W={message:(e=[],{symbol:t=l("gray",v),secondarySymbol:r=l("gray",v),output:i=process.stdout,spacing:n=1,withGuide:c}={})=>{const a=[],o=c??V.withGuide,s=o?r:"",u=o?`${t} `:"",d=o?`${r} `:"";for(let m=0;m<n;m++)a.push(s);const h=Array.isArray(e)?e:e.split(`
|
|
17
17
|
`);if(h.length>0){const[m,...p]=h;m.length>0?a.push(`${u}${m}`):a.push(o?t:"");for(const f of p)f.length>0?a.push(`${d}${f}`):a.push(o?r:"")}i.write(`${a.join(`
|
|
18
18
|
`)}
|
|
19
|
-
`)},info:(e,t)=>{
|
|
19
|
+
`)},info:(e,t)=>{W.message(e,{...t,symbol:l("blue",pt)})},success:(e,t)=>{W.message(e,{...t,symbol:l("green",gt)})},step:(e,t)=>{W.message(e,{...t,symbol:l("green",Ce)})},warn:(e,t)=>{W.message(e,{...t,symbol:l("yellow",bt)})},warning:(e,t)=>{W.warn(e,t)},error:(e,t)=>{W.message(e,{...t,symbol:l("red",yt)})}},vt=(e="",t)=>{const r=process.stdout,i=`${l("gray",U)} `;r.write(`${i}${l("red",e)}
|
|
20
20
|
|
|
21
21
|
`)},St=(e="",t)=>{const r=process.stdout,i=`${l("gray",v)}
|
|
22
|
-
${l("gray",
|
|
22
|
+
${l("gray",U)} `;r.write(`${i}${e}
|
|
23
23
|
|
|
24
|
-
`)},Et=[`${l("dim","\u2191/\u2193")} to navigate`,`${l("dim","Enter:")} confirm`],
|
|
24
|
+
`)},Et=[`${l("dim","\u2191/\u2193")} to navigate`,`${l("dim","Enter:")} confirm`],Z=(e,t)=>e.includes(`
|
|
25
25
|
`)?e.split(`
|
|
26
26
|
`).map(r=>t(r)).join(`
|
|
27
|
-
`):t(e),oe=e=>{const t=(i,n)=>{if(i===void 0)return"";const c=i.label??String(i.value);switch(n){case"disabled":return`${l("gray",Te)} ${
|
|
27
|
+
`):t(e),oe=e=>{const t=(i,n)=>{if(i===void 0)return"";const c=i.label??String(i.value);switch(n){case"disabled":return`${l("gray",Te)} ${Z(c,a=>l("gray",a))}${i.hint?` ${l("dim",`(${i.hint??"disabled"})`)}`:""}`;case"selected":return`${Z(c,a=>l("dim",a))}`;case"active":return`${l("green",ft)} ${c}${i.hint?` ${l("dim",`(${i.hint})`)}`:""}`;case"cancelled":return`${Z(c,a=>l(["strikethrough","dim"],a))}`;default:return`${l("dim",Te)} ${Z(c,a=>l("dim",a))}`}},r=e.showInstructions??!0;return new ct({options:e.options,signal:e.signal,input:e.input,output:e.output,initialValue:e.initialValue,render(){const i=e.withGuide??V.withGuide,n=`${Re(this.state)} `,c=`${$t(this.state)} `,a=ae(e.output,e.message,c,n),o=`${i?`${l("gray",v)}
|
|
28
28
|
`:""}${a}
|
|
29
29
|
`;switch(this.state){case"submit":{const s=i?`${l("gray",v)} `:"",u=ae(e.output,t(this.options[this.cursor],"selected"),s);return`${o}${u}`}case"cancel":{const s=i?`${l("gray",v)} `:"",u=ae(e.output,t(this.options[this.cursor],"cancelled"),s);return`${o}${u}${i?`
|
|
30
30
|
${l("gray",v)}`:""}`}default:{const s=i?`${l("cyan",v)} `:"",u=o.split(`
|
|
31
|
-
`).length,d=r?xt(Et,i):i?[l("cyan",
|
|
31
|
+
`).length,d=r?xt(Et,i):i?[l("cyan",U)]:[],h=d.join(`
|
|
32
32
|
`),m=d.length+1;return`${o}${s}${wt({output:e.output,cursor:this.cursor,options:this.options,maxItems:e.maxItems,columnPadding:s.length,rowPadding:u+m,style:(p,f)=>t(p,p.disabled?"disabled":f?"active":"inactive")}).join(`
|
|
33
33
|
${s}`)}
|
|
34
34
|
${h}
|
|
35
|
-
`}}}}).prompt()};`${l("gray",v)}`;const ke=e=>new lt({validate:e.validate,placeholder:e.placeholder,defaultValue:e.defaultValue,initialValue:e.initialValue,output:e.output,signal:e.signal,input:e.input,render(){const t=e?.withGuide??
|
|
35
|
+
`}}}}).prompt()};`${l("gray",v)}`;const ke=e=>new lt({validate:e.validate,placeholder:e.placeholder,defaultValue:e.defaultValue,initialValue:e.initialValue,output:e.output,signal:e.signal,input:e.input,render(){const t=e?.withGuide??V.withGuide,r=`${`${t?`${l("gray",v)}
|
|
36
36
|
`:""}${Re(this.state)} `}${e.message}
|
|
37
|
-
`,i=e.placeholder&&e.placeholder.length>0?l("inverse",e.placeholder[0])+l("dim",e.placeholder.slice(1)):l(["inverse","hidden"],"_"),n=this.userInput?this.userInputWithCursor:i,c=this.value??"";switch(this.state){case"validating":{const a=t?`${l("cyan",v)} `:"",o=t?l("cyan",
|
|
37
|
+
`,i=e.placeholder&&e.placeholder.length>0?l("inverse",e.placeholder[0])+l("dim",e.placeholder.slice(1)):l(["inverse","hidden"],"_"),n=this.userInput?this.userInputWithCursor:i,c=this.value??"";switch(this.state){case"validating":{const a=t?`${l("cyan",v)} `:"",o=t?l("cyan",U):"",s=l("dim",n),u=l("dim","Validating...");return`${r}${a}${s}
|
|
38
38
|
${o} ${u}
|
|
39
|
-
`}case"error":{const a=this.error?` ${l("yellow",this.error)}`:"",o=t?`${l("yellow",v)} `:"",s=t?l("yellow",
|
|
39
|
+
`}case"error":{const a=this.error?` ${l("yellow",this.error)}`:"",o=t?`${l("yellow",v)} `:"",s=t?l("yellow",U):"";return`${r.trim()}
|
|
40
40
|
${o}${n}
|
|
41
41
|
${s}${a}
|
|
42
42
|
`}case"submit":{const a=c?`${t?" ":""}${l("dim",c)}`:"",o=t?l("gray",v):"";return`${r}${o}${a}`}case"cancel":{const a=c?` ${l(["strikethrough","dim"],c)}`:"",o=t?l("gray",v):"";return`${r}${o}${a}${c.trim()?`
|
|
43
|
-
${o}`:""}`}default:{const a=t?`${l("cyan",v)} `:"",o=t?l("cyan",
|
|
43
|
+
${o}`:""}`}default:{const a=t?`${l("cyan",v)} `:"",o=t?l("cyan",U):"";return`${r}${a}${n}
|
|
44
44
|
${o}
|
|
45
|
-
`}}}}).prompt();function ce(e){return e==null?[]:Array.isArray(e)?e:[e]}function _t(e,t,r,i){var n,c=e[t],a=~i.string.indexOf(t)?r==null||r===!0?"":String(r):typeof r=="boolean"?r:~i.boolean.indexOf(t)?r==="false"?!1:r==="true"||(e._.push((n=+r,n*0===0?n:r)),!!r):(n=+r,n*0===0?n:r);e[t]=c==null?a:Array.isArray(c)?c.concat(a):[c,a]}function It(e,t){e=e||[],t=t||{};var r,i,n,c,a,o={_:[]},s=0,u=0,d=0,h=e.length;const m=t.alias!==void 0,p=t.unknown!==void 0,f=t.default!==void 0;if(t.alias=t.alias||{},t.string=ce(t.string),t.boolean=ce(t.boolean),m)for(r in t.alias)for(i=t.alias[r]=ce(t.alias[r]),s=0;s<i.length;s++)(t.alias[i[s]]=i.concat(r)).splice(s,1);for(s=t.boolean.length;s-- >0;)for(i=t.alias[t.boolean[s]]||[],u=i.length;u-- >0;)t.boolean.push(i[u]);for(s=t.string.length;s-- >0;)for(i=t.alias[t.string[s]]||[],u=i.length;u-- >0;)t.string.push(i[u]);if(f){for(r in t.default)if(c=typeof t.default[r],i=t.alias[r]=t.alias[r]||[],t[c]!==void 0)for(t[c].push(r),s=0;s<i.length;s++)t[c].push(i[s])}const b=p?Object.keys(t.alias):[];for(s=0;s<h;s++){if(n=e[s],n==="--"){o._=o._.concat(e.slice(++s));break}for(u=0;u<n.length&&n.charCodeAt(u)===45;u++);if(u===0)o._.push(n);else if(n.substring(u,u+3)==="no-"){if(c=n.substring(u+3),p&&!~b.indexOf(c))return t.unknown(n);o[c]=!1}else{for(d=u+1;d<n.length&&n.charCodeAt(d)!==61;d++);for(c=n.substring(u,d),a=n.substring(++d)||s+1===h||(""+e[s+1]).charCodeAt(0)===45||e[++s],i=u===2?[c]:c,d=0;d<i.length;d++){if(c=i[d],p&&!~b.indexOf(c))return t.unknown("-".repeat(u)+c);_t(o,c,d+1<i.length||a,t)}}}if(f)for(r in t.default)o[r]===void 0&&(o[r]=t.default[r]);if(m)for(r in o)for(i=t.alias[r]||[];i.length>0;)o[i.shift()]=o[r];return o}var
|
|
45
|
+
`}}}}).prompt();function ce(e){return e==null?[]:Array.isArray(e)?e:[e]}function _t(e,t,r,i){var n,c=e[t],a=~i.string.indexOf(t)?r==null||r===!0?"":String(r):typeof r=="boolean"?r:~i.boolean.indexOf(t)?r==="false"?!1:r==="true"||(e._.push((n=+r,n*0===0?n:r)),!!r):(n=+r,n*0===0?n:r);e[t]=c==null?a:Array.isArray(c)?c.concat(a):[c,a]}function It(e,t){e=e||[],t=t||{};var r,i,n,c,a,o={_:[]},s=0,u=0,d=0,h=e.length;const m=t.alias!==void 0,p=t.unknown!==void 0,f=t.default!==void 0;if(t.alias=t.alias||{},t.string=ce(t.string),t.boolean=ce(t.boolean),m)for(r in t.alias)for(i=t.alias[r]=ce(t.alias[r]),s=0;s<i.length;s++)(t.alias[i[s]]=i.concat(r)).splice(s,1);for(s=t.boolean.length;s-- >0;)for(i=t.alias[t.boolean[s]]||[],u=i.length;u-- >0;)t.boolean.push(i[u]);for(s=t.string.length;s-- >0;)for(i=t.alias[t.string[s]]||[],u=i.length;u-- >0;)t.string.push(i[u]);if(f){for(r in t.default)if(c=typeof t.default[r],i=t.alias[r]=t.alias[r]||[],t[c]!==void 0)for(t[c].push(r),s=0;s<i.length;s++)t[c].push(i[s])}const b=p?Object.keys(t.alias):[];for(s=0;s<h;s++){if(n=e[s],n==="--"){o._=o._.concat(e.slice(++s));break}for(u=0;u<n.length&&n.charCodeAt(u)===45;u++);if(u===0)o._.push(n);else if(n.substring(u,u+3)==="no-"){if(c=n.substring(u+3),p&&!~b.indexOf(c))return t.unknown(n);o[c]=!1}else{for(d=u+1;d<n.length&&n.charCodeAt(d)!==61;d++);for(c=n.substring(u,d),a=n.substring(++d)||s+1===h||(""+e[s+1]).charCodeAt(0)===45||e[++s],i=u===2?[c]:c,d=0;d<i.length;d++){if(c=i[d],p&&!~b.indexOf(c))return t.unknown("-".repeat(u)+c);_t(o,c,d+1<i.length||a,t)}}}if(f)for(r in t.default)o[r]===void 0&&(o[r]=t.default[r]);if(m)for(r in o)for(i=t.alias[r]||[];i.length>0;)o[i.shift()]=o[r];return o}var Q={exports:{}},je;function Ft(){if(je)return Q.exports;je=1;let e=process||{},t=e.argv||[],r=e.env||{},i=!(r.NO_COLOR||t.includes("--no-color"))&&(!!r.FORCE_COLOR||t.includes("--color")||e.platform==="win32"||(e.stdout||{}).isTTY&&r.TERM!=="dumb"||!!r.CI),n=(o,s,u=o)=>d=>{let h=""+d,m=h.indexOf(s,o.length);return~m?o+c(h,s,u,m)+s:o+h+s},c=(o,s,u,d)=>{let h="",m=0;do h+=o.substring(m,d)+u,m=d+s.length,d=o.indexOf(s,m);while(~d);return h+o.substring(m)},a=(o=i)=>{let s=o?n:()=>String;return{isColorSupported:o,reset:s("\x1B[0m","\x1B[0m"),bold:s("\x1B[1m","\x1B[22m","\x1B[22m\x1B[1m"),dim:s("\x1B[2m","\x1B[22m","\x1B[22m\x1B[2m"),italic:s("\x1B[3m","\x1B[23m"),underline:s("\x1B[4m","\x1B[24m"),inverse:s("\x1B[7m","\x1B[27m"),hidden:s("\x1B[8m","\x1B[28m"),strikethrough:s("\x1B[9m","\x1B[29m"),black:s("\x1B[30m","\x1B[39m"),red:s("\x1B[31m","\x1B[39m"),green:s("\x1B[32m","\x1B[39m"),yellow:s("\x1B[33m","\x1B[39m"),blue:s("\x1B[34m","\x1B[39m"),magenta:s("\x1B[35m","\x1B[39m"),cyan:s("\x1B[36m","\x1B[39m"),white:s("\x1B[37m","\x1B[39m"),gray:s("\x1B[90m","\x1B[39m"),bgBlack:s("\x1B[40m","\x1B[49m"),bgRed:s("\x1B[41m","\x1B[49m"),bgGreen:s("\x1B[42m","\x1B[49m"),bgYellow:s("\x1B[43m","\x1B[49m"),bgBlue:s("\x1B[44m","\x1B[49m"),bgMagenta:s("\x1B[45m","\x1B[49m"),bgCyan:s("\x1B[46m","\x1B[49m"),bgWhite:s("\x1B[47m","\x1B[49m"),blackBright:s("\x1B[90m","\x1B[39m"),redBright:s("\x1B[91m","\x1B[39m"),greenBright:s("\x1B[92m","\x1B[39m"),yellowBright:s("\x1B[93m","\x1B[39m"),blueBright:s("\x1B[94m","\x1B[39m"),magentaBright:s("\x1B[95m","\x1B[39m"),cyanBright:s("\x1B[96m","\x1B[39m"),whiteBright:s("\x1B[97m","\x1B[39m"),bgBlackBright:s("\x1B[100m","\x1B[49m"),bgRedBright:s("\x1B[101m","\x1B[49m"),bgGreenBright:s("\x1B[102m","\x1B[49m"),bgYellowBright:s("\x1B[103m","\x1B[49m"),bgBlueBright:s("\x1B[104m","\x1B[49m"),bgMagentaBright:s("\x1B[105m","\x1B[49m"),bgCyanBright:s("\x1B[106m","\x1B[49m"),bgWhiteBright:s("\x1B[107m","\x1B[49m")}};return Q.exports=a(),Q.exports.createColors=a,Q.exports}var Ct=Ft();const Tt=rt(Ct),{cyan:Rt,magenta:At,yellow:kt}=Tt,z=[{name:"engine",display:"Engine",description:"with the PlayCanvas Engine API directly",color:kt},{name:"react",display:"React",description:"with @playcanvas/react",color:Rt},{name:"web-components",display:"Web Components",description:"with @playcanvas/web-components",color:At}],Oe={physics:{engine:["assets"],react:["react"],"web-components":["assets"]}},G=[{name:"spinning-cube",display:"Spinning Cube",category:"Basics",description:"a rotating cube and a light"},{name:"interactive-sphere",display:"Interactive Sphere",category:"Basics",description:"orbit controls, a grid and pointer picking"},{name:"model-viewer",display:"Model Viewer",category:"Viewers",description:"a glTF model, orbit controls and a procedural sky"},{name:"splat-viewer",display:"Splat Viewer",category:"Viewers",description:"a Gaussian splat and orbit controls"},{name:"product-configurator",display:"Product Configurator",category:"Viewers",description:"product variants, materials and orbit controls"},{name:"physics-playground",display:"Physics Playground",category:"Games",description:"rigid bodies, collisions, spawning and reset",features:["physics"]},{name:"first-person-controller",display:"First-Person Controller",category:"Games",description:"first-person movement, mouse look and jumping",features:["physics"]},{name:"third-person-controller",display:"Third-Person Controller",category:"Games",description:"an animated character, follow camera and locomotion",features:["physics"]},{name:"sprite-game",display:"Sprite Game",category:"Games",description:"an orthographic camera, sprite animation and input"},{name:"web-lens",display:"Snapchat Web Lens",category:"Games",description:"a portrait game with Snapchat leaderboards, packaged for upload"},{name:"scene-editor",display:"Scene Editor",category:"Tools",description:"selection and translate, rotate and scale gizmos"},{name:"vr-starter",display:"VR Starter",category:"XR",description:"immersive VR, controllers and locomotion"},{name:"ar-placement",display:"AR Placement",category:"XR",description:"mobile AR, hit testing and object placement"}],jt=async({argFormat:e,yes:t,cancel:r})=>{if(e&&z.some(n=>n.name===e))return e;if(e&&(!process.stdout.isTTY||t))throw new Error(`Unknown format: ${e}. Choose from: ${z.map(n=>n.name).join(", ")}`);if(t)return z[0].name;const i=await oe({message:e?`"${e}" isn't a valid format. Please choose from below: `:"Select a format:",options:z.map(n=>({label:n.color(n.display),value:n.name,hint:n.description}))});return H(i)&&r(),i},Ot=async({argStarter:e,yes:t,cancel:r})=>{if(e&&G.some(n=>n.name===e))return e;if(!process.stdout.isTTY||t){if(!e)return G[0].name;throw new Error(`Unknown starter: ${e}. Choose from: ${G.map(n=>n.name).join(", ")}`)}const i=await oe({message:e?`"${e}" isn't a valid starter. Please choose from below: `:"Select a starter:",initialValue:G[0].name,options:G.map(n=>({label:n.display,value:n.name,hint:`${n.category} \xB7 ${n.description}`}))});return H(i)&&r(),i},Be=e=>/^(?:@[a-z\d\-*~][a-z\d\-*._~]*\/)?[a-z\d\-~][a-z\d\-._~]*$/.test(e),le=e=>e.trim().toLowerCase().replace(/\s+/g,"-").replace(/^[._]/,"").replace(/[^a-z\d\-~]+/g,"-"),Bt=async({targetDir:e,yes:t,cancel:r})=>{let i=y.basename(y.resolve(e));if(!Be(i)){if(t)return le(i);const n=await ke({message:"Package name:",defaultValue:le(i),placeholder:le(i),validate:c=>{if(!c||!Be(c))return"Invalid package.json name"}});if(H(n))return r();i=n}return i},ee=e=>e.trim().replace(/\/+$/g,""),Mt=e=>{const t=$.readdirSync(e);return t.length===0||t.length===1&&t[0]===".git"},Pt=e=>{if($.existsSync(e))for(const t of $.readdirSync(e))t!==".git"&&$.rmSync(y.resolve(e,t),{recursive:!0,force:!0})},Lt=async({argTargetDir:e,defaultTargetDir:t,yes:r,cancel:i})=>{let n=e?ee(String(e)):void 0;if(!n&&r)return t;if(!n){const c=await ke({message:"Project name:",defaultValue:t,placeholder:t,validate:a=>{if(a&&!ee(a))return"Invalid project name"}});H(c)&&i(),n=ee(c)}return n},Nt=async({targetDir:e,argOverwrite:t,yes:r,cancel:i})=>{if($.existsSync(e)&&!Mt(e)){if(r&&!t)throw new Error(`Target directory "${e}" is not empty. Pass --overwrite to remove its contents.`);const n=t?"yes":await oe({message:(e==="."?"Current directory":`Target directory "${e}"`)+" is not empty. Please choose how to proceed:",options:[{label:"Cancel operation",value:"no"},{label:"Remove existing files and continue",value:"yes"},{label:"Ignore files and continue",value:"ignore"}]});if(H(n))return i();switch(n){case"yes":Pt(e);break;case"no":i();return}}},Dt=["scripts","dependencies","devDependencies","peerDependencies","engines"],Me="## Prerequisites",Wt={engine:"src/main.ts",react:"src/App.tsx","web-components":"src/main.ts"},Vt=e=>{const{cwd:t,targetDir:r,format:i,starter:n,packageName:c,skills:a,renameFiles:o}=e,s=y.resolve(t,r);$.mkdirSync(s,{recursive:!0});const u=process.env.npm_config_user_agent?.split("/")[0]??"npm";W.step(`Creating project in ${s}...`);const d=De(import.meta.url),h=y.dirname(d),m=[y.resolve(h,"../templates"),y.resolve(h,"../../templates")].find(g=>$.existsSync(y.join(g,i)));if(!m)throw new Error(`Format directory not found for: ${i}`);const p=y.join(m,i);for(const g of z)for(const A of G){const C=y.join(m,g.name,A.name),N=Wt[g.name];if(!N||!$.existsSync(y.join(C,N)))throw new Error(`Incomplete starter matrix: ${g.name}/${A.name}`);for(const K of A.features??[]){const ue=Oe[K]?.[g.name];if(!ue?.length||ue.some(Pe=>!$.existsSync(y.join(m,"_features",K,Pe))))throw new Error(`Incomplete feature matrix: ${K}/${g.name}`)}}const f=G.find(g=>g.name===n);if(!f)throw new Error(`Starter not found: ${n}`);const b=y.join(m,"_shared",n),F=y.join(p,"base"),T=y.join(p,n),O=(f.features??[]).flatMap(g=>Oe[g][i].map(A=>y.join(m,"_features",g,A))),M=[b,F,...O,T],k=M.map(g=>{const A=y.join(g,"package.json");return $.existsSync(A)?JSON.parse($.readFileSync(A,"utf-8")):{}}),w=[k[1],k[0],...k.slice(2)];if(!$.existsSync(T))throw new Error(`Starter directory not found: ${i}/${n}`);for(const g of M)$.existsSync(g)&&$.cpSync(g,s,{recursive:!0});const S=y.join(b,"README.md"),x=y.join(s,"README.md");$.existsSync(S)&&$.writeFileSync(x,$.readFileSync(x,"utf8").replace(Me,`${$.readFileSync(S,"utf8").trimEnd()}
|
|
46
46
|
|
|
47
|
-
${Me}`));for(const[g,
|
|
48
|
-
`),a){const g=[y.resolve(h,"../skills"),y.resolve(h,"../../skills")].find(
|
|
47
|
+
${Me}`));for(const[g,A]of Object.entries(o)){const C=y.join(s,g);$.existsSync(C)&&$.renameSync(C,y.join(s,A))}const E=y.join(s,"package.json"),_=Object.assign({},...w);for(const g of Dt){const A=Object.assign({},...w.map(N=>N?.[g])),C=Object.entries(A).sort(([N],[K])=>N>K?1:-1);C.length?_[g]=Object.fromEntries(C):delete _[g]}if(_.name=c,$.writeFileSync(E,JSON.stringify(_,null,4)+`
|
|
48
|
+
`),a){const g=[y.resolve(h,"../skills"),y.resolve(h,"../../skills")].find(C=>$.existsSync(C));if(!g)throw new Error("Skills directory not found");const A=$.readdirSync(g,{withFileTypes:!0}).filter(C=>C.isDirectory()).map(C=>C.name);for(const C of[".claude/skills",".agents/skills"])for(const N of A)$.cpSync(y.join(g,N),y.join(s,C,N),{recursive:!0});W.step("Added PlayCanvas agent skills (opt out with --no-skills)")}let R=`Project created. Now run:
|
|
49
49
|
`;const I=y.isAbsolute(r)?s:y.relative(t,s);switch(s!==t&&(R+=`
|
|
50
50
|
cd ${I.includes(" ")?`"${I}"`:I}`),u){case"yarn":R+=`
|
|
51
51
|
yarn`,R+=`
|
|
@@ -63,4 +63,4 @@ Options:
|
|
|
63
63
|
--no-skills omit the PlayCanvas agent skills
|
|
64
64
|
-y, --yes skip the prompts and take the defaults
|
|
65
65
|
-h, --help show help
|
|
66
|
-
`,Kt={_gitignore:".gitignore",_npmrc:".npmrc"},Ht="playcanvas-project",zt=async()=>{const e=L._[0]?
|
|
66
|
+
`,Kt={_gitignore:".gitignore",_npmrc:".npmrc"},Ht="playcanvas-project",zt=async()=>{const e=L._[0]?ee(String(L._[0])):void 0,t=L.format??L.template,r=L.starter??L.boilerplate,i=L.overwrite,n=L.yes;if(L.help){console.log(Gt);return}const c=()=>{vt("Operation cancelled"),process.exit(0)},a=await Lt({argTargetDir:e,defaultTargetDir:Ht,yes:n,cancel:c});await Nt({targetDir:a,argOverwrite:i,yes:n,cancel:c});const o=await Bt({targetDir:a,yes:n,cancel:c}),s=await jt({argFormat:t,yes:n,cancel:c}),u=await Ot({argStarter:r,yes:n,cancel:c}),d=L.skills??!0;Vt({cwd:Ut,targetDir:a,format:s,starter:u,packageName:o,skills:d,renameFiles:Kt})};zt().catch(e=>{console.error(e instanceof Error?e.message:e),process.exitCode=1});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-playcanvas",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "Scaffold a PlayCanvas project",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"playcanvas",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"eslint": "^9.39.5",
|
|
49
49
|
"mri": "^1.2.0",
|
|
50
50
|
"picocolors": "^1.1.1",
|
|
51
|
-
"prettier": "^3.9.
|
|
51
|
+
"prettier": "^3.9.10",
|
|
52
52
|
"unbuild": "^3.6.1"
|
|
53
53
|
},
|
|
54
54
|
"engines": {
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import type { Entity, RigidBodyComponentSystem } from 'playcanvas';
|
|
2
|
-
import { Script } from 'playcanvas';
|
|
2
|
+
import { Script, Vec3 } from 'playcanvas';
|
|
3
3
|
import { ThirdPersonController } from 'playcanvas/scripts/esm/third-person-controller.mjs';
|
|
4
4
|
|
|
5
|
+
/** Ground probes under the player's box collider (half width 0.4): centre, edges and corners */
|
|
6
|
+
const FEET = [-0.38, 0, 0.38].flatMap((x) => [-0.38, 0, 0.38].map((z) => [x, z]));
|
|
7
|
+
|
|
5
8
|
class RobotAnimation extends Script {
|
|
6
9
|
static scriptName = 'robotAnimation';
|
|
7
10
|
|
|
@@ -28,9 +31,10 @@ class RobotAnimation extends Script {
|
|
|
28
31
|
}
|
|
29
32
|
|
|
30
33
|
export const addThirdPersonController = (player: Entity, camera: Entity, model: Entity) => {
|
|
31
|
-
|
|
34
|
+
const sys = player.rigidbody!.system as RigidBodyComponentSystem;
|
|
35
|
+
sys.gravity.set(0, -18, 0);
|
|
32
36
|
if (!player.script) player.addComponent('script');
|
|
33
|
-
player.script!.create(ThirdPersonController, {
|
|
37
|
+
const controller = player.script!.create(ThirdPersonController, {
|
|
34
38
|
properties: {
|
|
35
39
|
camera,
|
|
36
40
|
characterModel: model,
|
|
@@ -41,6 +45,24 @@ export const addThirdPersonController = (player: Entity, camera: Entity, model:
|
|
|
41
45
|
jumpForce: 520,
|
|
42
46
|
speedGround: 60
|
|
43
47
|
}
|
|
48
|
+
})!;
|
|
49
|
+
|
|
50
|
+
// The stock ground check is one ray from the centre, so a player standing on a ledge reads as
|
|
51
|
+
// airborne and can't jump. Count it grounded when any probe under the collider finds a solid
|
|
52
|
+
// surface; triggers (colliders without a rigid body) only stop the camera
|
|
53
|
+
const [from, to] = [new Vec3(), new Vec3()];
|
|
54
|
+
const solid = { filterCallback: (e: Entity) => !!e.rigidbody };
|
|
55
|
+
let grounded = false;
|
|
56
|
+
Object.defineProperty(controller, '_grounded', {
|
|
57
|
+
get: () => grounded,
|
|
58
|
+
set: () => {
|
|
59
|
+
const p = player.getPosition();
|
|
60
|
+
grounded = FEET.some(([x, z]) => {
|
|
61
|
+
from.set(p.x + x, p.y, p.z + z);
|
|
62
|
+
to.set(from.x, p.y - 1.1, from.z);
|
|
63
|
+
return !!sys.raycastFirst(from, to, solid);
|
|
64
|
+
});
|
|
65
|
+
}
|
|
44
66
|
});
|
|
45
67
|
|
|
46
68
|
if (!model.script) model.addComponent('script');
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
## Snapchat Web Lens
|
|
2
|
+
|
|
3
|
+
A portrait, one-thumb dodge game set up to run inside Snapchat as a [Web Lens](https://developers.snap.com/web-lenses/home). It submits each run to the Lens leaderboard, keeps the player's best score in Snapchat storage and shows their Bitmoji, all through the [Web Lens API](https://developers.snap.com/web-lenses/web-lens-api).
|
|
4
|
+
|
|
5
|
+
### Controls
|
|
6
|
+
|
|
7
|
+
Drag left and right to steer the ball around the falling blocks. Tap to start, and tap again after a crash to play another run.
|
|
8
|
+
|
|
9
|
+
### Publishing to Snapchat
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm run package
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
This builds the game into `dist/lens/`, checks it against the [Web Lens upload rules](https://developers.snap.com/web-lenses/requirements) and writes a zip to `dist/` to upload through [My Lenses](https://my-lenses.snapchat.com/). It writes no zip if:
|
|
16
|
+
|
|
17
|
+
- the zip is over 8 MB, or `index.html` is over 1 MB
|
|
18
|
+
- the build refers to one of its own files by an absolute path, such as `/assets/game.js`
|
|
19
|
+
- the build loads anything from another host, such as a script from a CDN or a web font
|
|
20
|
+
- the build uses something the Lens sandbox blocks, such as an `<iframe>` or a web app manifest
|
|
21
|
+
|
|
22
|
+
It warns about any other host your scripts mention, since a Lens can't reach the network. To check the build itself, `npm run start` serves it under the same rules, so a request for anything outside the zip fails in the browser console as it would in Snapchat.
|
|
23
|
+
|
|
24
|
+
### How it fits together
|
|
25
|
+
|
|
26
|
+
- `src/dodge-game.ts` is the game, an engine script that fires a `game:state` app event for the HUD to follow.
|
|
27
|
+
- `src/lens.ts` wraps the Web Lens API. Outside Snapchat the API answers from a built-in stub with canned data, so the game runs in any browser, and every call falls back to playing on without the feature.
|
|
28
|
+
- `src/lens.css` fills the phone screen and shows a 9:16 column on wider screens. It keeps the HUD clear of the controls Snapchat draws over the top of a Lens, and `npm run dev` marks the room they take.
|
|
29
|
+
- `vite.config.ts` makes every URL in the build relative, which a Lens needs.
|
|
30
|
+
|
|
31
|
+
### Make it your own
|
|
32
|
+
|
|
33
|
+
The game is the only part that's specific to Dodge, so replace it and keep the rest:
|
|
34
|
+
|
|
35
|
+
- **Replace** `src/dodge-game.ts` with your game. The HUD follows its `game:state` app event and shows the text in its `CARDS`, so fire the same event from your game, or rework the HUD in `index.html` and `src/main.ts` (`src/App.tsx` in React). Rename Dodge in the page `<title>` too.
|
|
36
|
+
- **Keep** `src/lens.ts`, `src/lens.css`, `vite.config.ts` and `scripts/package-lens.mjs`. None of them depends on the game.
|
|
37
|
+
- **Report each run to Snapchat** by calling `recordScore(score)` from `src/lens.ts` when it ends. That submits the score to the leaderboard, saves a new best to Snapchat storage and resolves the best score. Call `showLeaderboard()` from a button, and `loadBest()` and `loadAvatar()` at startup for the saved best and the player's Bitmoji.
|
|
38
|
+
- **Stick to what a Lens needs:** a portrait layout, a HUD kept inside the safe area with the `--inset-*` properties from `src/lens.css`, a pause when the page is hidden, audio that starts from a tap, and every asset bundled into the build, since a Lens can't reach the network.
|
|
39
|
+
|
|
40
|
+
Lenses run on phones, in portrait. To try the game on yours, run `npm run dev -- --host` and open the network URL it prints.
|
|
41
|
+
|
|
42
|
+
When you're ready to publish, follow Snap's [publishing guide](https://developers.snap.com/web-lenses/publishing): you'll also need a 512 × 512 PNG icon and a 5-second, 720 × 1280 preview video.
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
// Checks a build against the Snapchat Web Lens upload rules, then zips it for My Lenses:
|
|
2
|
+
// https://developers.snap.com/web-lenses/requirements
|
|
3
|
+
//
|
|
4
|
+
// Usage: node scripts/package-lens.mjs [build folder]
|
|
5
|
+
// `npm run package` builds first, then runs this on dist/lens.
|
|
6
|
+
|
|
7
|
+
import fs from 'node:fs';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import zlib from 'node:zlib';
|
|
10
|
+
|
|
11
|
+
const MB = 1024 * 1024;
|
|
12
|
+
const MAX_ZIP = 8 * MB;
|
|
13
|
+
const MAX_INDEX = MB;
|
|
14
|
+
|
|
15
|
+
// tags the Lens sandbox blocks
|
|
16
|
+
const BLOCKED_TAGS = ['base', 'embed', 'iframe', 'object'];
|
|
17
|
+
|
|
18
|
+
// hosts that bundles only mention as text: XML namespaces, and the docs that engine and React
|
|
19
|
+
// error messages link to
|
|
20
|
+
const TEXT_HOSTS = ['www.w3.org', 'github.com', 'react.dev', 'api.playcanvas.com'];
|
|
21
|
+
|
|
22
|
+
const buildDir = path.resolve(process.argv[2] ?? 'dist/lens');
|
|
23
|
+
const { name = 'web-lens' } = JSON.parse(fs.readFileSync('package.json', 'utf8'));
|
|
24
|
+
const zipFile = path.join(path.dirname(buildDir), `${name.replace(/^@/, '').replace('/', '-')}.zip`);
|
|
25
|
+
|
|
26
|
+
if (!fs.existsSync(buildDir)) {
|
|
27
|
+
console.error(`No build found at ${path.relative('.', buildDir)}. Run npm run package to build and package.`);
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const errors = [];
|
|
32
|
+
const warnings = [];
|
|
33
|
+
|
|
34
|
+
const formatSize = (bytes) => (bytes < MB ? `${(bytes / 1024).toFixed(1)} KB` : `${(bytes / MB).toFixed(2)} MB`);
|
|
35
|
+
|
|
36
|
+
// every file in the build, as the forward-slash path it takes in the zip
|
|
37
|
+
const walk = (dir) =>
|
|
38
|
+
fs.readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
|
|
39
|
+
const file = path.join(dir, entry.name);
|
|
40
|
+
return entry.isDirectory() ? walk(file) : [path.relative(buildDir, file).split(path.sep).join('/')];
|
|
41
|
+
});
|
|
42
|
+
const all = walk(buildDir).sort((a, b) => (a === 'index.html' ? -1 : b === 'index.html' ? 1 : a.localeCompare(b)));
|
|
43
|
+
|
|
44
|
+
// source maps only add weight, so leave them out of the zip
|
|
45
|
+
const maps = all.filter((file) => file.endsWith('.map'));
|
|
46
|
+
const files = all.filter((file) => !file.endsWith('.map'));
|
|
47
|
+
if (maps.length) {
|
|
48
|
+
warnings.push(
|
|
49
|
+
`Left ${maps.length} source map(s) out of the zip. Set build.sourcemap to false to stop generating them`
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// the entry point
|
|
54
|
+
const indexSize = files.includes('index.html') ? fs.statSync(path.join(buildDir, 'index.html')).size : 0;
|
|
55
|
+
if (!indexSize) {
|
|
56
|
+
errors.push('index.html must sit at the root of the build');
|
|
57
|
+
} else if (indexSize > MAX_INDEX) {
|
|
58
|
+
errors.push(
|
|
59
|
+
`index.html is ${formatSize(indexSize)}, over the 1 MB limit. Load the bundle from a file rather than inlining it`
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
for (const file of files.filter((f) => f.startsWith('__snap/'))) {
|
|
64
|
+
errors.push(`${file}: __snap/ is reserved for Snapchat, so rename the folder`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// URLs in markup and styles: anything on another host is blocked, and a root-absolute path
|
|
68
|
+
// resolves against Snapchat's origin rather than the Lens, so the game loads a blank screen
|
|
69
|
+
const checkUrl = (file, url) => {
|
|
70
|
+
if (/^(?:[a-z][a-z\d+.-]*:)?\/\//i.test(url)) {
|
|
71
|
+
errors.push(
|
|
72
|
+
`${file}: loads ${url} from another host. A Lens can only load files from its own zip, so bundle it`
|
|
73
|
+
);
|
|
74
|
+
} else if (url.startsWith('/')) {
|
|
75
|
+
errors.push(`${file}: refers to ${url} by an absolute path. Use ./${url.slice(1)}`);
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const checkCss = (file, css) => {
|
|
80
|
+
for (const [, , url] of css.matchAll(/url\(\s*(['"]?)([^'")]+)\1\s*\)/g)) checkUrl(file, url);
|
|
81
|
+
for (const [, , url] of css.matchAll(/@import\s+(['"])([^'"]+)\1/g)) checkUrl(file, url);
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const read = (file) => fs.readFileSync(path.join(buildDir, file), 'utf8');
|
|
85
|
+
|
|
86
|
+
for (const file of files.filter((f) => f.endsWith('.html'))) {
|
|
87
|
+
const html = read(file);
|
|
88
|
+
for (const [, tag, attributes] of html.matchAll(/<([a-z][\w-]*)\b([^>]*)>/gi)) {
|
|
89
|
+
const lower = tag.toLowerCase();
|
|
90
|
+
if (BLOCKED_TAGS.includes(lower)) {
|
|
91
|
+
errors.push(`${file}: the Lens sandbox blocks <${lower}>`);
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
if (lower === 'link' && /\brel\s*=\s*["']?manifest\b/i.test(attributes)) {
|
|
95
|
+
errors.push(`${file}: the Lens sandbox blocks web app manifests`);
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
// links navigate rather than load, so they don't count
|
|
99
|
+
if (lower === 'a') continue;
|
|
100
|
+
|
|
101
|
+
for (const [, attribute, ...values] of attributes.matchAll(
|
|
102
|
+
/\b(src|href|srcset|poster|data)\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'>]+))/gi
|
|
103
|
+
)) {
|
|
104
|
+
const value = values.find((v) => v !== undefined) ?? '';
|
|
105
|
+
const urls =
|
|
106
|
+
attribute.toLowerCase() === 'srcset' ? value.split(',').map((s) => s.trim().split(/\s+/)[0]) : [value];
|
|
107
|
+
for (const url of urls) checkUrl(file, url);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
for (const [, css] of html.matchAll(/<style\b[^>]*>([\s\S]*?)<\/style>/gi)) checkCss(file, css);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
for (const file of files.filter((f) => f.endsWith('.css'))) checkCss(file, read(file));
|
|
114
|
+
|
|
115
|
+
// scripts: a quoted root-absolute path to a file in the build always breaks, while a remote URL
|
|
116
|
+
// may only be text, so it is worth a look rather than a failure
|
|
117
|
+
const escape = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
118
|
+
const absolute = new RegExp(`["'\`(]/(${files.map(escape).join('|')})(?=["'\`)?#])`, 'g');
|
|
119
|
+
for (const file of files.filter((f) => /\.m?js$/.test(f))) {
|
|
120
|
+
const source = read(file);
|
|
121
|
+
for (const target of new Set(Array.from(source.matchAll(absolute), (m) => m[1]))) {
|
|
122
|
+
errors.push(`${file}: refers to /${target} by an absolute path. Use ./${target}`);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const hosts = new Set();
|
|
126
|
+
for (const [url] of source.matchAll(/\b(?:https?|wss?):\/\/[^\s"'`)\\]+/g)) {
|
|
127
|
+
const host = URL.canParse(url) ? new URL(url).host : url;
|
|
128
|
+
if (!TEXT_HOSTS.includes(host)) hosts.add(host);
|
|
129
|
+
}
|
|
130
|
+
if (hosts.size) {
|
|
131
|
+
warnings.push(
|
|
132
|
+
`${file}: mentions ${[...hosts].join(', ')}. A Lens can't reach other hosts, so check none of them is loaded at runtime (npm run start blocks them the way Snapchat does)`
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// a plain zip: deflated entries with forward-slash names and index.html at the root
|
|
138
|
+
const zipEntries = (entries) => {
|
|
139
|
+
const local = [];
|
|
140
|
+
const central = [];
|
|
141
|
+
let offset = 0;
|
|
142
|
+
|
|
143
|
+
for (const entry of entries) {
|
|
144
|
+
const file = path.join(buildDir, entry);
|
|
145
|
+
const data = fs.readFileSync(file);
|
|
146
|
+
const deflated = zlib.deflateRawSync(data, { level: 9 });
|
|
147
|
+
const stored = deflated.length >= data.length;
|
|
148
|
+
const body = stored ? data : deflated;
|
|
149
|
+
const entryName = Buffer.from(entry);
|
|
150
|
+
const crc = zlib.crc32(data);
|
|
151
|
+
|
|
152
|
+
const mtime = fs.statSync(file).mtime;
|
|
153
|
+
const time = (mtime.getHours() << 11) | (mtime.getMinutes() << 5) | (mtime.getSeconds() >> 1);
|
|
154
|
+
const date = ((mtime.getFullYear() - 1980) << 9) | ((mtime.getMonth() + 1) << 5) | mtime.getDate();
|
|
155
|
+
|
|
156
|
+
const header = Buffer.alloc(30);
|
|
157
|
+
header.writeUInt32LE(0x04034b50, 0);
|
|
158
|
+
header.writeUInt16LE(20, 4);
|
|
159
|
+
header.writeUInt16LE(0x0800, 6); // UTF-8 names
|
|
160
|
+
header.writeUInt16LE(stored ? 0 : 8, 8);
|
|
161
|
+
header.writeUInt16LE(time, 10);
|
|
162
|
+
header.writeUInt16LE(date, 12);
|
|
163
|
+
header.writeUInt32LE(crc, 14);
|
|
164
|
+
header.writeUInt32LE(body.length, 18);
|
|
165
|
+
header.writeUInt32LE(data.length, 22);
|
|
166
|
+
header.writeUInt16LE(entryName.length, 26);
|
|
167
|
+
|
|
168
|
+
const record = Buffer.alloc(46);
|
|
169
|
+
record.writeUInt32LE(0x02014b50, 0);
|
|
170
|
+
record.writeUInt16LE((3 << 8) | 20, 4); // made on Unix, so the permissions below apply
|
|
171
|
+
record.writeUInt16LE(20, 6);
|
|
172
|
+
record.writeUInt16LE(0x0800, 8);
|
|
173
|
+
record.writeUInt16LE(stored ? 0 : 8, 10);
|
|
174
|
+
record.writeUInt16LE(time, 12);
|
|
175
|
+
record.writeUInt16LE(date, 14);
|
|
176
|
+
record.writeUInt32LE(crc, 16);
|
|
177
|
+
record.writeUInt32LE(body.length, 20);
|
|
178
|
+
record.writeUInt32LE(data.length, 24);
|
|
179
|
+
record.writeUInt16LE(entryName.length, 28);
|
|
180
|
+
record.writeUInt32LE((0o100644 << 16) >>> 0, 38); // a regular file, rw-r--r--
|
|
181
|
+
record.writeUInt32LE(offset, 42);
|
|
182
|
+
|
|
183
|
+
local.push(header, entryName, body);
|
|
184
|
+
central.push(record, entryName);
|
|
185
|
+
offset += header.length + entryName.length + body.length;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const directory = Buffer.concat(central);
|
|
189
|
+
const end = Buffer.alloc(22);
|
|
190
|
+
end.writeUInt32LE(0x06054b50, 0);
|
|
191
|
+
end.writeUInt16LE(entries.length, 8);
|
|
192
|
+
end.writeUInt16LE(entries.length, 10);
|
|
193
|
+
end.writeUInt32LE(directory.length, 12);
|
|
194
|
+
end.writeUInt32LE(offset, 16);
|
|
195
|
+
|
|
196
|
+
return Buffer.concat([...local, directory, end]);
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
const zip = zipEntries(files);
|
|
200
|
+
if (zip.length > MAX_ZIP) {
|
|
201
|
+
errors.push(
|
|
202
|
+
`The zip is ${formatSize(zip.length)}, over the 8 MB limit. Compress textures and audio, and drop unused assets`
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
for (const warning of warnings) console.warn(`warning: ${warning}`);
|
|
207
|
+
|
|
208
|
+
if (errors.length) {
|
|
209
|
+
for (const error of errors) console.error(`error: ${error}`);
|
|
210
|
+
fs.rmSync(zipFile, { force: true });
|
|
211
|
+
console.error(
|
|
212
|
+
`\nNo zip written. Fix the ${errors.length === 1 ? 'problem' : 'problems'} above and run npm run package again.`
|
|
213
|
+
);
|
|
214
|
+
process.exit(1);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
fs.writeFileSync(zipFile, zip);
|
|
218
|
+
|
|
219
|
+
const largest = files
|
|
220
|
+
.map((file) => ({ file, size: fs.statSync(path.join(buildDir, file)).size }))
|
|
221
|
+
.sort((a, b) => b.size - a.size)
|
|
222
|
+
.slice(0, 5);
|
|
223
|
+
const width = Math.max(...largest.map(({ file }) => file.length));
|
|
224
|
+
|
|
225
|
+
console.log(`\nPackaged ${path.relative('.', zipFile)} for Snapchat Web Lenses`);
|
|
226
|
+
console.log(` zip ${formatSize(zip.length)} of 8 MB (${Math.ceil((zip.length / MAX_ZIP) * 100)}%)`);
|
|
227
|
+
console.log(` index.html ${formatSize(indexSize)} of 1 MB`);
|
|
228
|
+
console.log(` files ${files.length}`);
|
|
229
|
+
console.log('\nLargest files, before compression:');
|
|
230
|
+
for (const { file, size } of largest) console.log(` ${file.padEnd(width)} ${formatSize(size)}`);
|
|
231
|
+
console.log('\nUpload the zip at https://my-lenses.snapchat.com/');
|