create-playcanvas 0.11.1 → 0.13.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 CHANGED
@@ -50,7 +50,7 @@ The `web-lens` starter is a portrait game ready to publish to Snapchat as a [Web
50
50
  npm create playcanvas@latest my-lens -- --starter web-lens
51
51
  ```
52
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/).
53
+ Its `npm run package` script builds the game, checks it against the Web Lens upload rules (size limits, relative paths, no network access and no camera or microphone) and writes the zip to upload through [My Lenses](https://my-lenses.snapchat.com/).
54
54
 
55
55
  ## CLI Options
56
56
 
@@ -71,9 +71,11 @@ npm create playcanvas@latest my-game -- -f react -y
71
71
 
72
72
  `--yes` takes `playcanvas-project`, the `engine` format and the `spinning-cube` starter for anything you don't pass. It never deletes files, so a non-empty target directory still needs `--overwrite`. A run without a terminal, such as one started by an AI agent or in CI, takes the same defaults, so it never waits on a prompt. The previous `--template` and `--boilerplate` long flags remain accepted for compatibility.
73
73
 
74
- ## Agent skills
74
+ ## Coding agents
75
75
 
76
- Every project includes [`@playcanvas/skills`](https://github.com/playcanvas/skills) so AI coding agents such as Claude Code, Codex and Cursor pick up PlayCanvas-specific workflows automatically, with no install step. They are copied into `.claude/skills/` and `.agents/skills/`. Pass `--no-skills` to leave them out.
76
+ Every project includes an `AGENTS.md` that gives AI coding agents such as Claude Code, Codex and Cursor its commands, layout and conventions, and how to check a change. Its `CLAUDE.md` imports it, since Claude Code skips `AGENTS.md` when a `CLAUDE.md` exists in the project or a folder above it.
77
+
78
+ Every project also includes [`@playcanvas/skills`](https://github.com/playcanvas/skills), so those agents pick up PlayCanvas-specific workflows automatically, with no install step. They are copied into `.claude/skills/` and `.agents/skills/`. Pass `--no-skills` to leave them out.
77
79
 
78
80
  ## Development
79
81
 
@@ -91,13 +93,15 @@ Contributions are welcome. Please open an issue before proposing a new format, s
91
93
  A project is assembled from the layers under `templates/`, in this order. A file in a later layer replaces the same file in an earlier one, and each layer's `package.json` is merged into those before it.
92
94
 
93
95
  1. `_common/`: files every project gets
94
- 2. `<format>/base/`: the format's tooling and README, a default `index.html` for Engine and React, and React's `src/main.tsx`
95
- 3. `_shared/<starter>/`: the starter's format-independent code and assets. Its `README.md` is inserted into the base README above `## Prerequisites`
96
+ 2. `<format>/base/`: the format's tooling, README and `AGENTS.md`, a default `index.html` for Engine and React, and React's `src/main.tsx`
97
+ 3. `_shared/<starter>/`: the starter's format-independent code and assets. Its `README.md` is inserted into the base README above `## Prerequisites`, and an optional `AGENTS.md`, for what an agent must know about the starter, into the base `AGENTS.md` above `## Checking your work`
96
98
  4. `_features/<feature>/<layer>/`: what each of the starter's `features` needs in that format, as mapped by `FEATURES` in `templates/index.ts`
97
99
  5. `<format>/<starter>/`: the starter's scene in that format
98
100
 
99
101
  To add a starter, list it in `STARTERS` in `templates/index.ts`, add `_shared/<starter>/README.md`, and add its scene to every format: `engine/<starter>/src/main.ts`, `react/<starter>/src/App.tsx`, and `web-components/<starter>/index.html` with `src/main.ts`. Import `./starter.css` for the shared page and panel styles, and override a base file only when the starter needs something different. `npm test` checks that the starter exists in every format, and CI scaffolds and builds it in each one.
100
102
 
103
+ Write commands in a README or `AGENTS.md` for npm, as `npm install`, `npm run <script>` and `npm run <script> -- <args>`. The scaffolder rewrites them for the package manager that created the project, and leaves out the `## Agent skills` section of both when run with `--no-skills`.
104
+
101
105
  ## License
102
106
 
103
107
  [MIT](LICENSE)
package/dist/index.mjs CHANGED
@@ -1,57 +1,56 @@
1
1
  #!/usr/bin/env node
2
- import{styleText as c}from"node:util";import k,{stdin as Ae,stdout as ae}from"node:process";import ke from"node:readline";import"node:tty";import w from"node:fs";import x from"node:path";import{fileURLToPath as je}from"node:url";const Oe=(()=>{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}})(),Be=e=>e===12288||e>=65281&&e<=65376||e>=65504&&e<=65510,Le=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,Me=/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]|\u001b\]8;[^;]*;.*?(?:\u0007|\u001b\u005c)/y,Pe=/[\x00-\x08\x0A-\x1F\x7F-\x9F]{1,1000}/y,oe=/(?:(?![\uFF61-\uFF9F\uFF00-\uFFEF])[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}\p{Script=Tangut}]){1,1000}/yu,Ne=/\t{1,1000}/y,le=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"),De=/(?:[\x20-\x7E\xA0-\xFF](?!\uFE0F)){1,1000}/y,We=new RegExp("\\p{M}+","gu"),Ve={limit:1/0,ellipsis:""},ce=(e,t={},r={})=>{const i=t.limit??1/0,n=t.ellipsis??"",l=t?.ellipsisWidth??(n?ce(n,Ve,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,b=[[De,h],[Me,a],[Pe,o],[Ne,s],[le,u],[oe,m]];let g=0,$=0,_=e.length,F=0,T=!1,R=_,I=Math.max(0,i-l),v=0,f=0,y=0,p=0;e:for(;;){if(f>v||$>=_&&$>g){const S=e.slice(v,f)||e.slice(g,$);F=0;for(const B of S.replaceAll(We,"")){const C=B.codePointAt(0)||0;if(Be(C)?p=d:Le(C)?p=m:p=h,y+p>I&&(R=Math.min(R,Math.max(v,g)+F)),y+p>i){T=!0;break e}F+=B.length,y+=p}v=f=0}if($>=_)break e;for(let S=0,B=b.length;S<B;S++){const[C,ne]=b[S];if(C.lastIndex=$,C.test(e)){if(F=C===oe?Oe(e.slice($,C.lastIndex)):C===le?1:C.lastIndex-$,p=F*ne,y+p>I&&(R=Math.min(R,$+Math.floor((I-y)/ne))),y+p>i){T=!0;break e}y+=p,v=g,f=$,$=g=C.lastIndex;continue e}}$+=1}return{width:T?I:y,index:T?R:_,truncated:T,ellipsed:T&&i>=l}},Ue={limit:1/0,ellipsis:"",ellipsisWidth:0},M=(e,t={})=>ce(e,Ue,t).width,V="\x1B",ue="\x9B",Ge=39,q="\x07",he="[",Ke="]",me="m",X=`${Ke}8;;`,de=new RegExp(`(?:\\${he}(?<code>\\d+)m|\\${X}(?<uri>.*)${q})`,"y"),fe=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},pe=e=>`${V}${he}${e}${me}`,ge=e=>`${V}${X}${e}${q}`,Z=(e,t,r)=>{const i=t[Symbol.iterator]();let n=!1,l=!1,a=e.at(-1),o=a===void 0?0:M(a),s=i.next(),u=i.next(),d=0;for(;!s.done;){const h=s.value,m=M(h);o+m<=r?e[e.length-1]+=h:(e.push(h),o=0),(h===V||h===ue)&&(n=!0,l=t.startsWith(X,d+1)),n?l?h===q&&(n=!1,l=!1):h===me&&(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())},He=e=>{const t=e.split(" ");let r=t.length;for(;r&&!M(t[r-1]);)r--;return r===t.length?e:t.slice(0,r).join(" ")+t.slice(r).join("")},ze=(e,t,r={})=>{if(r.trim!==!1&&e.trim()==="")return"";let i="",n,l;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 g=o.at(-1)??"",$=g.trimStart();g.length!==$.length&&(o[o.length-1]=$,s=M($))}h!==0&&(s>=t&&(r.wordWrap===!1||r.trim===!1)&&(o.push(""),s=0),(s||r.trim===!1)&&(o[o.length-1]+=" ",s++));const b=M(m);if(r.hard&&b>t){const g=t-s,$=1+Math.floor((b-g-1)/t);Math.floor((b-1)/t)<$&&o.push(""),Z(o,m,t),s=M(o.at(-1)??"");continue}if(s+b>t&&s&&b){if(r.wordWrap===!1&&s<t){Z(o,m,t),s=M(o.at(-1)??"");continue}o.push(""),s=0}if(s+b>t&&r.wordWrap===!1){Z(o,m,t),s=M(o.at(-1)??"");continue}o[o.length-1]+=m,s+=b}r.trim!==!1&&(o=o.map(h=>He(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===V||m===ue){de.lastIndex=h+1;const g=de.exec(u)?.groups;if(g?.code!==void 0){const $=Number.parseFloat(g.code);n=$===Ge?void 0:$}else g?.uri!==void 0&&(l=g.uri.length===0?void 0:g.uri)}if(u[h+1]===`
4
- `){l&&(i+=ge(""));const b=n?fe(n):void 0;n&&b&&(i+=pe(b))}else m===`
5
- `&&(n&&fe(n)&&(i+=pe(n)),l&&(i+=ge(l)))}return i},Je=/\r?\n/;function U(e,t,r){return String(e).normalize().split(Je).map(i=>ze(i,t,r)).join(`
6
- `)}function Ye(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var Q,be;function qe(){if(be)return Q;be=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)},l={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 Q={cursor:i,scroll:n,erase:l,beep:r},Q}var O=qe();function G(e,t,r){if(!r.some(a=>!a.disabled))return e;const i=e+t,n=Math.max(r.length-1,0),l=i<0?n:i>n?0:i;return r[l]?.disabled?G(l,t<0?-1:1,r):l}const Xe=["up","down","left","right","space","enter","cancel"],P={actions:new Set(Xe),aliases:new Map([["k","up"],["j","down"],["h","left"],["l","right"],["","cancel"],["escape","cancel"]]),withGuide:!0};function Ze(e){if(e!==void 0)return e;const t=process.env.ACCESSIBLE;return t!==void 0&&t!==""&&t!=="0"&&t!=="false"}function ye(e,t){if(typeof e=="string")return P.aliases.get(e)===t;for(const r of e)if(r!==void 0&&ye(r,t))return!0;return!1}function Qe(e,t){if(e===t)return;const r=e.split(`
2
+ import{styleText as c}from"node:util";import j,{stdin as je,stdout as le}from"node:process";import Oe from"node:readline";import"node:tty";import w from"node:fs";import $ from"node:path";import{fileURLToPath as Le}from"node:url";const Pe=(()=>{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}})(),Be=e=>e===12288||e>=65281&&e<=65376||e>=65504&&e<=65510,Me=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,Ne=/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]|\u001b\]8;[^;]*;.*?(?:\u0007|\u001b\u005c)/y,De=/[\x00-\x08\x0A-\x1F\x7F-\x9F]{1,1000}/y,ce=/(?:(?![\uFF61-\uFF9F\uFF00-\uFFEF])[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}\p{Script=Tangut}]){1,1000}/yu,We=/\t{1,1000}/y,ue=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"),Ve=/(?:[\x20-\x7E\xA0-\xFF](?!\uFE0F)){1,1000}/y,Ue=new RegExp("\\p{M}+","gu"),Ge={limit:1/0,ellipsis:""},he=(e,t={},r={})=>{const i=t.limit??1/0,n=t.ellipsis??"",l=t?.ellipsisWidth??(n?he(n,Ge,r).width:0),a=0,o=r.controlWidth??0,s=r.tabWidth??8,u=r.emojiWidth??2,m=2,h=r.regularWidth??1,d=r.wideWidth??m,y=[[Ve,h],[Ne,a],[De,o],[We,s],[ue,u],[ce,d]];let f=0,b=0,F=e.length,T=0,R=!1,L=F,k=Math.max(0,i-l),v=0,S=0,x=0,E=0;e:for(;;){if(S>v||b>=F&&b>f){const p=e.slice(v,S)||e.slice(f,b);T=0;for(const _ of p.replaceAll(Ue,"")){const g=_.codePointAt(0)||0;if(Be(g)?E=m:Me(g)?E=d:E=h,x+E>k&&(L=Math.min(L,Math.max(v,f)+T)),x+E>i){R=!0;break e}T+=_.length,x+=E}v=S=0}if(b>=F)break e;for(let p=0,_=y.length;p<_;p++){const[g,C]=y[p];if(g.lastIndex=b,g.test(e)){if(T=g===ce?Pe(e.slice(b,g.lastIndex)):g===ue?1:g.lastIndex-b,E=T*C,x+E>k&&(L=Math.min(L,b+Math.floor((k-x)/C))),x+E>i){R=!0;break e}x+=E,v=f,S=b,b=f=g.lastIndex;continue e}}b+=1}return{width:R?k:x,index:R?L:F,truncated:R,ellipsed:R&&i>=l}},Ke={limit:1/0,ellipsis:"",ellipsisWidth:0},M=(e,t={})=>he(e,Ke,t).width,G="\x1B",me="\x9B",He=39,Q="\x07",de="[",ze="]",pe="m",ee=`${ze}8;;`,fe=new RegExp(`(?:\\${de}(?<code>\\d+)m|\\${ee}(?<uri>.*)${Q})`,"y"),ge=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},be=e=>`${G}${de}${e}${pe}`,ye=e=>`${G}${ee}${e}${Q}`,te=(e,t,r)=>{const i=t[Symbol.iterator]();let n=!1,l=!1,a=e.at(-1),o=a===void 0?0:M(a),s=i.next(),u=i.next(),m=0;for(;!s.done;){const h=s.value,d=M(h);o+d<=r?e[e.length-1]+=h:(e.push(h),o=0),(h===G||h===me)&&(n=!0,l=t.startsWith(ee,m+1)),n?l?h===Q&&(n=!1,l=!1):h===pe&&(n=!1):(o+=d,o===r&&!u.done&&(e.push(""),o=0)),s=u,u=i.next(),m+=h.length}a=e.at(-1),!o&&a!==void 0&&a.length&&e.length>1&&(e[e.length-2]+=e.pop())},Je=e=>{const t=e.split(" ");let r=t.length;for(;r&&!M(t[r-1]);)r--;return r===t.length?e:t.slice(0,r).join(" ")+t.slice(r).join("")},Ye=(e,t,r={})=>{if(r.trim!==!1&&e.trim()==="")return"";let i="",n,l;const a=e.split(" ");let o=[""],s=0;for(let h=0;h<a.length;h++){const d=a[h];if(r.trim!==!1){const f=o.at(-1)??"",b=f.trimStart();f.length!==b.length&&(o[o.length-1]=b,s=M(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 y=M(d);if(r.hard&&y>t){const f=t-s,b=1+Math.floor((y-f-1)/t);Math.floor((y-1)/t)<b&&o.push(""),te(o,d,t),s=M(o.at(-1)??"");continue}if(s+y>t&&s&&y){if(r.wordWrap===!1&&s<t){te(o,d,t),s=M(o.at(-1)??"");continue}o.push(""),s=0}if(s+y>t&&r.wordWrap===!1){te(o,d,t),s=M(o.at(-1)??"");continue}o[o.length-1]+=d,s+=y}r.trim!==!1&&(o=o.map(h=>Je(h)));const u=o.join(`
3
+ `);let m=!1;for(let h=0;h<u.length;h++){const d=u[h];if(i+=d,m)m=!1;else if(m=d>="\uD800"&&d<="\uDBFF",m)continue;if(d===G||d===me){fe.lastIndex=h+1;const f=fe.exec(u)?.groups;if(f?.code!==void 0){const b=Number.parseFloat(f.code);n=b===He?void 0:b}else f?.uri!==void 0&&(l=f.uri.length===0?void 0:f.uri)}if(u[h+1]===`
4
+ `){l&&(i+=ye(""));const y=n?ge(n):void 0;n&&y&&(i+=be(y))}else d===`
5
+ `&&(n&&ge(n)&&(i+=be(n)),l&&(i+=ye(l)))}return i},qe=/\r?\n/;function K(e,t,r){return String(e).normalize().split(qe).map(i=>Ye(i,t,r)).join(`
6
+ `)}function Xe(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var re,$e;function Ze(){if($e)return re;$e=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)},l={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 re={cursor:i,scroll:n,erase:l,beep:r},re}var P=Ze();function H(e,t,r){if(!r.some(a=>!a.disabled))return e;const i=e+t,n=Math.max(r.length-1,0),l=i<0?n:i>n?0:i;return r[l]?.disabled?H(l,t<0?-1:1,r):l}const Qe=["up","down","left","right","space","enter","cancel"],N={actions:new Set(Qe),aliases:new Map([["k","up"],["j","down"],["h","left"],["l","right"],["","cancel"],["escape","cancel"]]),withGuide:!0};function et(e){if(e!==void 0)return e;const t=process.env.ACCESSIBLE;return t!==void 0&&t!==""&&t!=="0"&&t!=="false"}function xe(e,t){if(typeof e=="string")return N.aliases.get(e)===t;for(const r of e)if(r!==void 0&&xe(r,t))return!0;return!1}function tt(e,t){if(e===t)return;const r=e.split(`
7
7
  `),i=t.split(`
8
- `),n=Math.max(r.length,i.length),l=[];for(let a=0;a<n;a++)r[a]!==i[a]&&l.push(a);return{lines:l,numLinesBefore:r.length,numLinesAfter:i.length,numLines:n}}globalThis.process.platform.startsWith("win");const ee=Symbol("clack:cancel");function W(e){return e===ee}function K(e,t){const r=e;r.isTTY&&r.setRawMode(t)}const $e=e=>"columns"in e&&typeof e.columns=="number"?e.columns:80,xe=e=>"rows"in e&&typeof e.rows=="number"?e.rows:20;function te(e,t,r,i=r,n=r,l){const a=$e(e??ae);return U(t,a-r.length,{hard:!0,trim:!1}).split(`
9
- `).map((o,s,u)=>{const d=o;return s===0?`${i}${d}`:s===u.length-1?`${n}${d}`:`${r}${d}`}).join(`
10
- `)}function et(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 we{input;output;_abortSignal;rl;opts;_render;_track=!1;_prevFrame="";_subscribers=new Map;_cursor=0;state="initial";error="";value;userInput="";get accessible(){return Ze(this.opts.accessible)}constructor(t,r=!0){const{input:i=Ae,output:n=ae,render:l,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=l.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 l of i)l.cb(...r),l.once&&n.push(()=>i.splice(i.indexOf(l),1));for(const l of n)l()}prompt(){return new Promise(t=>{if(this._abortSignal){if(this._abortSignal.aborted)return this.state="cancel",this.close(),t(ee);this._abortSignal.addEventListener("abort",()=>{this.state="cancel",this.close()},{once:!0})}this.rl=ke.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),K(this.input,!0),this.output.on("resize",this.render),this.render(),this.once("submit",()=>{this.output.write(O.cursor.show),this.output.off("resize",this.render),K(this.input,!1),t(this.value)}),this.once("cancel",()=>{this.output.write(O.cursor.show),this.output.off("resize",this.render),K(this.input,!1),t(ee)})})}_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&&P.aliases.has(r.name)&&this.emit("cursor",P.aliases.get(r.name)),P.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=et(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")}ye([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
- `),K(this.input,!1),this.rl?.close(),this.rl=void 0,this.emit(`${this.state}`,this.value),this.unsubscribe()}restoreCursor(){const t=U(this._prevFrame,process.stdout.columns,{hard:!0,trim:!1}).split(`
12
- `).length-1;this.output.write(O.cursor.move(-999,t*-1))}render(){const t=U(this._render(this)??"",process.stdout.columns,{hard:!0,trim:!1});if(t!==this._prevFrame){if(this.state==="initial")this.output.write(O.cursor.hide);else{const r=Qe(this._prevFrame,t),i=xe(this.output);if(this.restoreCursor(),r){const n=Math.max(0,r.numLinesAfter-i),l=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(O.cursor.move(0,a-l)),this.output.write(O.erase.lines(1));const o=t.split(`
13
- `);this.output.write(o[a]),this._prevFrame=t,this.output.write(O.cursor.move(0,o.length-a-1));return}else if(r.lines.length>1){if(n<l)a=n;else{const s=a-l;s>0&&this.output.write(O.cursor.move(0,s))}this.output.write(O.erase.down());const o=t.split(`
8
+ `),n=Math.max(r.length,i.length),l=[];for(let a=0;a<n;a++)r[a]!==i[a]&&l.push(a);return{lines:l,numLinesBefore:r.length,numLinesAfter:i.length,numLines:n}}globalThis.process.platform.startsWith("win");const se=Symbol("clack:cancel");function V(e){return e===se}function z(e,t){const r=e;r.isTTY&&r.setRawMode(t)}const we=e=>"columns"in e&&typeof e.columns=="number"?e.columns:80,ve=e=>"rows"in e&&typeof e.rows=="number"?e.rows:20;function ie(e,t,r,i=r,n=r,l){const a=we(e??le);return K(t,a-r.length,{hard:!0,trim:!1}).split(`
9
+ `).map((o,s,u)=>{const m=o;return s===0?`${i}${m}`:s===u.length-1?`${n}${m}`:`${r}${m}`}).join(`
10
+ `)}function rt(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 Se{input;output;_abortSignal;rl;opts;_render;_track=!1;_prevFrame="";_subscribers=new Map;_cursor=0;state="initial";error="";value;userInput="";get accessible(){return et(this.opts.accessible)}constructor(t,r=!0){const{input:i=je,output:n=le,render:l,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=l.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 l of i)l.cb(...r),l.once&&n.push(()=>i.splice(i.indexOf(l),1));for(const l of n)l()}prompt(){return new Promise(t=>{if(this._abortSignal){if(this._abortSignal.aborted)return this.state="cancel",this.close(),t(se);this._abortSignal.addEventListener("abort",()=>{this.state="cancel",this.close()},{once:!0})}this.rl=Oe.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),z(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),z(this.input,!1),t(this.value)}),this.once("cancel",()=>{this.output.write(P.cursor.show),this.output.off("resize",this.render),z(this.input,!1),t(se)})})}_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&&N.aliases.has(r.name)&&this.emit("cursor",N.aliases.get(r.name)),N.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=rt(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")}xe([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
+ `),z(this.input,!1),this.rl?.close(),this.rl=void 0,this.emit(`${this.state}`,this.value),this.unsubscribe()}restoreCursor(){const t=K(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=K(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=tt(this._prevFrame,t),i=ve(this.output);if(this.restoreCursor(),r){const n=Math.max(0,r.numLinesAfter-i),l=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-l)),this.output.write(P.erase.lines(1));const o=t.split(`
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<l)a=n;else{const s=a-l;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(O.erase.down())}this.output.write(t),this.state==="initial"&&(this.state="active"),this._prevFrame=t}}}let tt=class extends we{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?G(i,1,this.options):i,this.changeValue(),this.on("cursor",n=>{switch(n){case"left":case"up":this.cursor=G(this.cursor,-1,this.options);break;case"down":case"right":this.cursor=G(this.cursor,1,this.options);break}this.changeValue()})}};class rt extends we{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}${c("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 st(){return k.platform!=="win32"?k.env.TERM!=="linux":!!k.env.CI||!!k.env.WT_SESSION||!!k.env.TERMINUS_SUBLIME||k.env.ConEmuTask==="{cmd::Cmder}"||k.env.TERM_PROGRAM==="Terminus-Sublime"||k.env.TERM_PROGRAM==="vscode"||k.env.TERM==="xterm-256color"||k.env.TERM==="alacritty"||k.env.TERMINAL_EMULATOR==="JetBrains-JediTerm"}const it=st(),A=(e,t)=>it?e:t,ve=A("\u25C6","*"),nt=A("\u25A0","x"),at=A("\u25B2","x"),Se=A("\u25C7","o"),E=A("\u2502","|"),N=A("\u2514","\u2014"),ot=A("\u25CF",">"),Ee=A("\u25CB"," "),lt=A("\u25CF","\u2022"),ct=A("\u25C6","*"),ut=A("\u25B2","!"),ht=A("\u25A0","x"),_e=e=>{switch(e){case"initial":case"active":return c("cyan",ve);case"cancel":return c("red",nt);case"error":return c("yellow",at);case"submit":return c("green",Se);case"validating":return c("dim",ve)}},mt=e=>{switch(e){case"initial":case"active":return c("cyan",E);case"cancel":return c("red",E);case"error":return c("yellow",E);case"submit":return c("green",E)}};function dt(e,t){const r=[`${t?`${c("cyan",E)} `:""}${e.join(" \u2022 ")}`];return t&&r.push(c("cyan",N)),r}const Ie=(e,t,r,i,n,l=!1)=>{let a=t,o=0;if(l)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}},ft=({cursor:e,options:t,style:r,output:i=process.stdout,maxItems:n=Number.POSITIVE_INFINITY,columnPadding:l=0,rowPadding:a=4})=>{const o=$e(i)-l,s=xe(i),u=c("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 b=h<t.length&&m>0,g=h<t.length&&m+h<t.length;const $=Math.min(m+h,t.length),_=[];let F=0;b&&F++,g&&F++;const T=m+(b?1:0),R=$-(g?1:0);for(let v=T;v<R;v++){const f=t[v],y=f?r(f,v===e):"",p=U(y,o,{hard:!0,trim:!1}).split(`
16
- `);_.push(p),F+=p.length}if(F>d){let v=0,f=0,y=F;const p=e-T;let S=d;const B=()=>Ie(_,y,0,p,S),C=()=>Ie(_,y,p+1,_.length,S,!0);b?({lineCount:y,removals:v}=B(),y>S&&(g||(S-=1),{lineCount:y,removals:f}=C())):(g||(S-=1),{lineCount:y,removals:f}=C(),y>S&&(S-=1,{lineCount:y,removals:v}=B())),v>0&&(b=!0,_.splice(0,v)),f>0&&(g=!0,_.splice(_.length-f,f))}const I=[];b&&I.push(u);for(const v of _)for(const f of v)I.push(f);return g&&I.push(u),I};`${c("dim","\u2191/\u2193")}`,`${c("dim","Space:")}`,`${c("dim","Enter:")}`;const L={message:(e=[],{symbol:t=c("gray",E),secondarySymbol:r=c("gray",E),output:i=process.stdout,spacing:n=1,withGuide:l}={})=>{const a=[],o=l??P.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
- `);if(h.length>0){const[m,...b]=h;m.length>0?a.push(`${u}${m}`):a.push(o?t:"");for(const g of b)g.length>0?a.push(`${d}${g}`):a.push(o?r:"")}i.write(`${a.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 st=class extends Se{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?H(i,1,this.options):i,this.changeValue(),this.on("cursor",n=>{switch(n){case"left":case"up":this.cursor=H(this.cursor,-1,this.options);break;case"down":case"right":this.cursor=H(this.cursor,1,this.options);break}this.changeValue()})}};class it extends Se{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}${c("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 nt(){return j.platform!=="win32"?j.env.TERM!=="linux":!!j.env.CI||!!j.env.WT_SESSION||!!j.env.TERMINUS_SUBLIME||j.env.ConEmuTask==="{cmd::Cmder}"||j.env.TERM_PROGRAM==="Terminus-Sublime"||j.env.TERM_PROGRAM==="vscode"||j.env.TERM==="xterm-256color"||j.env.TERM==="alacritty"||j.env.TERMINAL_EMULATOR==="JetBrains-JediTerm"}const at=nt(),A=(e,t)=>at?e:t,Ee=A("\u25C6","*"),ot=A("\u25A0","x"),lt=A("\u25B2","x"),_e=A("\u25C7","o"),I=A("\u2502","|"),D=A("\u2514","\u2014"),ct=A("\u25CF",">"),Ie=A("\u25CB"," "),ut=A("\u25CF","\u2022"),ht=A("\u25C6","*"),mt=A("\u25B2","!"),dt=A("\u25A0","x"),Fe=e=>{switch(e){case"initial":case"active":return c("cyan",Ee);case"cancel":return c("red",ot);case"error":return c("yellow",lt);case"submit":return c("green",_e);case"validating":return c("dim",Ee)}},pt=e=>{switch(e){case"initial":case"active":return c("cyan",I);case"cancel":return c("red",I);case"error":return c("yellow",I);case"submit":return c("green",I)}};function ft(e,t){const r=[`${t?`${c("cyan",I)} `:""}${e.join(" \u2022 ")}`];return t&&r.push(c("cyan",D)),r}const Ce=(e,t,r,i,n,l=!1)=>{let a=t,o=0;if(l)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}},gt=({cursor:e,options:t,style:r,output:i=process.stdout,maxItems:n=Number.POSITIVE_INFINITY,columnPadding:l=0,rowPadding:a=4})=>{const o=we(i)-l,s=ve(i),u=c("dim","..."),m=Math.max(s-a,0),h=Math.max(Math.min(n,m),5);let d=0;e>=h-3&&(d=Math.max(Math.min(e-h+3,t.length-h),0));let y=h<t.length&&d>0,f=h<t.length&&d+h<t.length;const b=Math.min(d+h,t.length),F=[];let T=0;y&&T++,f&&T++;const R=d+(y?1:0),L=b-(f?1:0);for(let v=R;v<L;v++){const S=t[v],x=S?r(S,v===e):"",E=K(x,o,{hard:!0,trim:!1}).split(`
16
+ `);F.push(E),T+=E.length}if(T>m){let v=0,S=0,x=T;const E=e-R;let p=m;const _=()=>Ce(F,x,0,E,p),g=()=>Ce(F,x,E+1,F.length,p,!0);y?({lineCount:x,removals:v}=_(),x>p&&(f||(p-=1),{lineCount:x,removals:S}=g())):(f||(p-=1),{lineCount:x,removals:S}=g(),x>p&&(p-=1,{lineCount:x,removals:v}=_())),v>0&&(y=!0,F.splice(0,v)),S>0&&(f=!0,F.splice(F.length-S,S))}const k=[];y&&k.push(u);for(const v of F)for(const S of v)k.push(S);return f&&k.push(u),k};`${c("dim","\u2191/\u2193")}`,`${c("dim","Space:")}`,`${c("dim","Enter:")}`;const B={message:(e=[],{symbol:t=c("gray",I),secondarySymbol:r=c("gray",I),output:i=process.stdout,spacing:n=1,withGuide:l}={})=>{const a=[],o=l??N.withGuide,s=o?r:"",u=o?`${t} `:"",m=o?`${r} `:"";for(let d=0;d<n;d++)a.push(s);const h=Array.isArray(e)?e:e.split(`
17
+ `);if(h.length>0){const[d,...y]=h;d.length>0?a.push(`${u}${d}`):a.push(o?t:"");for(const f of y)f.length>0?a.push(`${m}${f}`):a.push(o?r:"")}i.write(`${a.join(`
18
18
  `)}
19
- `)},info:(e,t)=>{L.message(e,{...t,symbol:c("blue",lt)})},success:(e,t)=>{L.message(e,{...t,symbol:c("green",ct)})},step:(e,t)=>{L.message(e,{...t,symbol:c("green",Se)})},warn:(e,t)=>{L.message(e,{...t,symbol:c("yellow",ut)})},warning:(e,t)=>{L.warn(e,t)},error:(e,t)=>{L.message(e,{...t,symbol:c("red",ht)})}},pt=(e="",t)=>{const r=process.stdout,i=`${c("gray",N)} `;r.write(`${i}${c("red",e)}
19
+ `)},info:(e,t)=>{B.message(e,{...t,symbol:c("blue",ut)})},success:(e,t)=>{B.message(e,{...t,symbol:c("green",ht)})},step:(e,t)=>{B.message(e,{...t,symbol:c("green",_e)})},warn:(e,t)=>{B.message(e,{...t,symbol:c("yellow",mt)})},warning:(e,t)=>{B.warn(e,t)},error:(e,t)=>{B.message(e,{...t,symbol:c("red",dt)})}},bt=(e="",t)=>{const r=process.stdout,i=`${c("gray",D)} `;r.write(`${i}${c("red",e)}
20
20
 
21
- `)},gt=(e="",t)=>{const r=process.stdout,i=`${c("gray",E)}
22
- ${c("gray",N)} `;r.write(`${i}${e}
21
+ `)},yt=(e="",t)=>{const r=process.stdout,i=`${c("gray",I)}
22
+ ${c("gray",D)} `;r.write(`${i}${e}
23
23
 
24
- `)},bt=[`${c("dim","\u2191/\u2193")} to navigate`,`${c("dim","Enter:")} confirm`],H=(e,t)=>e.includes(`
24
+ `)},$t=[`${c("dim","\u2191/\u2193")} to navigate`,`${c("dim","Enter:")} confirm`],J=(e,t)=>e.includes(`
25
25
  `)?e.split(`
26
26
  `).map(r=>t(r)).join(`
27
- `):t(e),re=e=>{const t=(i,n)=>{if(i===void 0)return"";const l=i.label??String(i.value);switch(n){case"disabled":return`${c("gray",Ee)} ${H(l,a=>c("gray",a))}${i.hint?` ${c("dim",`(${i.hint??"disabled"})`)}`:""}`;case"selected":return`${H(l,a=>c("dim",a))}`;case"active":return`${c("green",ot)} ${l}${i.hint?` ${c("dim",`(${i.hint})`)}`:""}`;case"cancelled":return`${H(l,a=>c(["strikethrough","dim"],a))}`;default:return`${c("dim",Ee)} ${H(l,a=>c("dim",a))}`}},r=e.showInstructions??!0;return new tt({options:e.options,signal:e.signal,input:e.input,output:e.output,initialValue:e.initialValue,render(){const i=e.withGuide??P.withGuide,n=`${_e(this.state)} `,l=`${mt(this.state)} `,a=te(e.output,e.message,l,n),o=`${i?`${c("gray",E)}
27
+ `):t(e),ne=e=>{const t=(i,n)=>{if(i===void 0)return"";const l=i.label??String(i.value);switch(n){case"disabled":return`${c("gray",Ie)} ${J(l,a=>c("gray",a))}${i.hint?` ${c("dim",`(${i.hint??"disabled"})`)}`:""}`;case"selected":return`${J(l,a=>c("dim",a))}`;case"active":return`${c("green",ct)} ${l}${i.hint?` ${c("dim",`(${i.hint})`)}`:""}`;case"cancelled":return`${J(l,a=>c(["strikethrough","dim"],a))}`;default:return`${c("dim",Ie)} ${J(l,a=>c("dim",a))}`}},r=e.showInstructions??!0;return new st({options:e.options,signal:e.signal,input:e.input,output:e.output,initialValue:e.initialValue,render(){const i=e.withGuide??N.withGuide,n=`${Fe(this.state)} `,l=`${pt(this.state)} `,a=ie(e.output,e.message,l,n),o=`${i?`${c("gray",I)}
28
28
  `:""}${a}
29
- `;switch(this.state){case"submit":{const s=i?`${c("gray",E)} `:"",u=te(e.output,t(this.options[this.cursor],"selected"),s);return`${o}${u}`}case"cancel":{const s=i?`${c("gray",E)} `:"",u=te(e.output,t(this.options[this.cursor],"cancelled"),s);return`${o}${u}${i?`
30
- ${c("gray",E)}`:""}`}default:{const s=i?`${c("cyan",E)} `:"",u=o.split(`
31
- `).length,d=r?dt(bt,i):i?[c("cyan",N)]:[],h=d.join(`
32
- `),m=d.length+1;return`${o}${s}${ft({output:e.output,cursor:this.cursor,options:this.options,maxItems:e.maxItems,columnPadding:s.length,rowPadding:u+m,style:(b,g)=>t(b,b.disabled?"disabled":g?"active":"inactive")}).join(`
29
+ `;switch(this.state){case"submit":{const s=i?`${c("gray",I)} `:"",u=ie(e.output,t(this.options[this.cursor],"selected"),s);return`${o}${u}`}case"cancel":{const s=i?`${c("gray",I)} `:"",u=ie(e.output,t(this.options[this.cursor],"cancelled"),s);return`${o}${u}${i?`
30
+ ${c("gray",I)}`:""}`}default:{const s=i?`${c("cyan",I)} `:"",u=o.split(`
31
+ `).length,m=r?ft($t,i):i?[c("cyan",D)]:[],h=m.join(`
32
+ `),d=m.length+1;return`${o}${s}${gt({output:e.output,cursor:this.cursor,options:this.options,maxItems:e.maxItems,columnPadding:s.length,rowPadding:u+d,style:(y,f)=>t(y,y.disabled?"disabled":f?"active":"inactive")}).join(`
33
33
  ${s}`)}
34
34
  ${h}
35
- `}}}}).prompt()};`${c("gray",E)}`;const Fe=e=>new rt({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??P.withGuide,r=`${`${t?`${c("gray",E)}
36
- `:""}${_e(this.state)} `}${e.message}
37
- `,i=e.placeholder&&e.placeholder.length>0?c("inverse",e.placeholder[0])+c("dim",e.placeholder.slice(1)):c(["inverse","hidden"],"_"),n=this.userInput?this.userInputWithCursor:i,l=this.value??"";switch(this.state){case"validating":{const a=t?`${c("cyan",E)} `:"",o=t?c("cyan",N):"",s=c("dim",n),u=c("dim","Validating...");return`${r}${a}${s}
35
+ `}}}}).prompt()};`${c("gray",I)}`;const Te=e=>new it({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??N.withGuide,r=`${`${t?`${c("gray",I)}
36
+ `:""}${Fe(this.state)} `}${e.message}
37
+ `,i=e.placeholder&&e.placeholder.length>0?c("inverse",e.placeholder[0])+c("dim",e.placeholder.slice(1)):c(["inverse","hidden"],"_"),n=this.userInput?this.userInputWithCursor:i,l=this.value??"";switch(this.state){case"validating":{const a=t?`${c("cyan",I)} `:"",o=t?c("cyan",D):"",s=c("dim",n),u=c("dim","Validating...");return`${r}${a}${s}
38
38
  ${o} ${u}
39
- `}case"error":{const a=this.error?` ${c("yellow",this.error)}`:"",o=t?`${c("yellow",E)} `:"",s=t?c("yellow",N):"";return`${r.trim()}
39
+ `}case"error":{const a=this.error?` ${c("yellow",this.error)}`:"",o=t?`${c("yellow",I)} `:"",s=t?c("yellow",D):"";return`${r.trim()}
40
40
  ${o}${n}
41
41
  ${s}${a}
42
- `}case"submit":{const a=l?`${t?" ":""}${c("dim",l)}`:"",o=t?c("gray",E):"";return`${r}${o}${a}`}case"cancel":{const a=l?` ${c(["strikethrough","dim"],l)}`:"",o=t?c("gray",E):"";return`${r}${o}${a}${l.trim()?`
43
- ${o}`:""}`}default:{const a=t?`${c("cyan",E)} `:"",o=t?c("cyan",N):"";return`${r}${a}${n}
42
+ `}case"submit":{const a=l?`${t?" ":""}${c("dim",l)}`:"",o=t?c("gray",I):"";return`${r}${o}${a}`}case"cancel":{const a=l?` ${c(["strikethrough","dim"],l)}`:"",o=t?c("gray",I):"";return`${r}${o}${a}${l.trim()?`
43
+ ${o}`:""}`}default:{const a=t?`${c("cyan",I)} `:"",o=t?c("cyan",D):"";return`${r}${a}${n}
44
44
  ${o}
45
- `}}}}).prompt();function se(e){return e==null?[]:Array.isArray(e)?e:[e]}function yt(e,t,r,i){var n,l=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]=l==null?a:Array.isArray(l)?l.concat(a):[l,a]}function $t(e,t){e=e||[],t=t||{};var r,i,n,l,a,o={_:[]},s=0,u=0,d=0,h=e.length;const m=t.alias!==void 0,b=t.unknown!==void 0,g=t.default!==void 0;if(t.alias=t.alias||{},t.string=se(t.string),t.boolean=se(t.boolean),m)for(r in t.alias)for(i=t.alias[r]=se(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(g){for(r in t.default)if(l=typeof t.default[r],i=t.alias[r]=t.alias[r]||[],t[l]!==void 0)for(t[l].push(r),s=0;s<i.length;s++)t[l].push(i[s])}const $=b?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(l=n.substring(u+3),b&&!~$.indexOf(l))return t.unknown(n);o[l]=!1}else{for(d=u+1;d<n.length&&n.charCodeAt(d)!==61;d++);for(l=n.substring(u,d),a=n.substring(++d)||s+1===h||(""+e[s+1]).charCodeAt(0)===45||e[++s],i=u===2?[l]:l,d=0;d<i.length;d++){if(l=i[d],b&&!~$.indexOf(l))return t.unknown("-".repeat(u)+l);yt(o,l,d+1<i.length||a,t)}}}if(g)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 z={exports:{}},Ce;function xt(){if(Ce)return z.exports;Ce=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+l(h,s,u,m)+s:o+h+s},l=(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 z.exports=a(),z.exports.createColors=a,z.exports}var wt=xt();const vt=Ye(wt),{cyan:St,magenta:Et,yellow:_t}=vt,J=[{name:"engine",display:"Engine",description:"with the PlayCanvas Engine API directly",color:_t},{name:"react",display:"React",description:"with @playcanvas/react",color:St},{name:"web-components",display:"Web Components",description:"with @playcanvas/web-components",color:Et}],It={physics:{engine:["assets"],react:["react"],"web-components":["assets"]}},D=[{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"}],Ft=async({argFormat:e,yes:t,cancel:r})=>{if(e&&J.some(n=>n.name===e))return e;if(e&&t)throw new Error(`Unknown format: ${e}. Choose from: ${J.map(n=>n.name).join(", ")}`);if(t)return J[0].name;const i=await re({message:e?`"${e}" isn't a valid format. Please choose from below: `:"Select a format:",options:J.map(n=>({label:n.color(n.display),value:n.name,hint:n.description}))});return W(i)&&r(),i},Ct=async({argStarter:e,yes:t,cancel:r})=>{if(e&&D.some(n=>n.name===e))return e;if(t){if(!e)return D[0].name;throw new Error(`Unknown starter: ${e}. Choose from: ${D.map(n=>n.name).join(", ")}`)}const i=await re({message:e?`"${e}" isn't a valid starter. Please choose from below: `:"Select a starter:",initialValue:D[0].name,options:D.map(n=>({label:n.display,value:n.name,hint:`${n.category} \xB7 ${n.description}`}))});return W(i)&&r(),i},Te=e=>/^(?:@[a-z\d\-*~][a-z\d\-*._~]*\/)?[a-z\d\-~][a-z\d\-._~]*$/.test(e),ie=e=>e.trim().toLowerCase().replace(/\s+/g,"-").replace(/^[._]/,"").replace(/[^a-z\d\-~]+/g,"-"),Tt=async({targetDir:e,yes:t,cancel:r})=>{let i=x.basename(x.resolve(e));if(!Te(i)){if(t)return ie(i);const n=await Fe({message:"Package name:",defaultValue:ie(i),placeholder:ie(i),validate:l=>{if(!l||!Te(l))return"Invalid package.json name"}});if(W(n))return r();i=n}return i},Y=e=>e.trim().replace(/\/+$/g,""),Rt=e=>{const t=w.readdirSync(e);return t.length===0||t.length===1&&t[0]===".git"},At=e=>{if(w.existsSync(e))for(const t of w.readdirSync(e))t!==".git"&&w.rmSync(x.resolve(e,t),{recursive:!0,force:!0})},kt=async({argTargetDir:e,defaultTargetDir:t,yes:r,cancel:i})=>{let n=e?Y(String(e)):void 0;if(!n&&r)return t;if(!n){const l=await Fe({message:"Project name:",defaultValue:t,placeholder:t,validate:a=>{if(a&&!Y(a))return"Invalid project name"}});W(l)&&i(),n=Y(l)}return n},jt=async({targetDir:e,argOverwrite:t,yes:r,cancel:i})=>{if(w.existsSync(e)&&!Rt(e)){if(r&&!t)throw new Error(`Target directory "${e}" is not empty. Pass --overwrite to remove its contents.`);const n=t?"yes":await re({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(W(n))return i();switch(n){case"yes":At(e);break;case"no":i();return}}},Ot=["scripts","dependencies","devDependencies","peerDependencies","engines"],Re="## Prerequisites",Bt=e=>{const{cwd:t,targetDir:r,format:i,starter:n,packageName:l,skills:a,renameFiles:o}=e,s=x.resolve(t,r);w.mkdirSync(s,{recursive:!0});const u=process.env.npm_config_user_agent?.split("/")[0]??"npm";L.step(`Creating project in ${s}...`);const d=je(import.meta.url),h=x.dirname(d),m=e.templatesDir??[x.resolve(h,"../templates"),x.resolve(h,"../../templates")].find(f=>w.existsSync(x.join(f,i)));if(!m)throw new Error(`Format directory not found for: ${i}`);const b=D.find(f=>f.name===n);if(!b)throw new Error(`Starter not found: ${n}`);const g=x.join(m,"_shared",n),$=x.join(m,i,n);if(!w.existsSync($))throw new Error(`Starter directory not found: ${i}/${n}`);const _=[x.join(m,"_common"),x.join(m,i,"base"),g,...(b.features??[]).flatMap(f=>It[f][i].map(y=>x.join(m,"_features",f,y))),$].filter(f=>w.existsSync(f)),F=x.join(g,"README.md");for(const f of _){const y=x.join(f,"package.json");w.cpSync(f,s,{recursive:!0,filter:p=>p!==y&&p!==F})}if(w.existsSync(F)){const f=x.join(s,"README.md"),y=`${w.readFileSync(F,"utf8").trimEnd()}
45
+ `}}}}).prompt();function ae(e){return e==null?[]:Array.isArray(e)?e:[e]}function xt(e,t,r,i){var n,l=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]=l==null?a:Array.isArray(l)?l.concat(a):[l,a]}function wt(e,t){e=e||[],t=t||{};var r,i,n,l,a,o={_:[]},s=0,u=0,m=0,h=e.length;const d=t.alias!==void 0,y=t.unknown!==void 0,f=t.default!==void 0;if(t.alias=t.alias||{},t.string=ae(t.string),t.boolean=ae(t.boolean),d)for(r in t.alias)for(i=t.alias[r]=ae(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(l=typeof t.default[r],i=t.alias[r]=t.alias[r]||[],t[l]!==void 0)for(t[l].push(r),s=0;s<i.length;s++)t[l].push(i[s])}const b=y?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(l=n.substring(u+3),y&&!~b.indexOf(l))return t.unknown(n);o[l]=!1}else{for(m=u+1;m<n.length&&n.charCodeAt(m)!==61;m++);for(l=n.substring(u,m),a=n.substring(++m)||s+1===h||(""+e[s+1]).charCodeAt(0)===45||e[++s],i=u===2?[l]:l,m=0;m<i.length;m++){if(l=i[m],y&&!~b.indexOf(l))return t.unknown("-".repeat(u)+l);xt(o,l,m+1<i.length||a,t)}}}if(f)for(r in t.default)o[r]===void 0&&(o[r]=t.default[r]);if(d)for(r in o)for(i=t.alias[r]||[];i.length>0;)o[i.shift()]=o[r];return o}var Y={exports:{}},Re;function vt(){if(Re)return Y.exports;Re=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)=>m=>{let h=""+m,d=h.indexOf(s,o.length);return~d?o+l(h,s,u,d)+s:o+h+s},l=(o,s,u,m)=>{let h="",d=0;do h+=o.substring(d,m)+u,d=m+s.length,m=o.indexOf(s,d);while(~m);return h+o.substring(d)},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 Y.exports=a(),Y.exports.createColors=a,Y.exports}var St=vt();const Et=Xe(St),{cyan:_t,magenta:It,yellow:Ft}=Et,q=[{name:"engine",display:"Engine",description:"with the PlayCanvas Engine API directly",color:Ft},{name:"react",display:"React",description:"with @playcanvas/react",color:_t},{name:"web-components",display:"Web Components",description:"with @playcanvas/web-components",color:It}],Ct={physics:{engine:["assets"],react:["react"],"web-components":["assets"]}},W=[{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"}],Tt=async({argFormat:e,yes:t,cancel:r})=>{if(e&&q.some(n=>n.name===e))return e;if(e&&t)throw new Error(`Unknown format: ${e}. Choose from: ${q.map(n=>n.name).join(", ")}`);if(t)return q[0].name;const i=await ne({message:e?`"${e}" isn't a valid format. Please choose from below: `:"Select a format:",options:q.map(n=>({label:n.color(n.display),value:n.name,hint:n.description}))});return V(i)&&r(),i},Rt=async({argStarter:e,yes:t,cancel:r})=>{if(e&&W.some(n=>n.name===e))return e;if(t){if(!e)return W[0].name;throw new Error(`Unknown starter: ${e}. Choose from: ${W.map(n=>n.name).join(", ")}`)}const i=await ne({message:e?`"${e}" isn't a valid starter. Please choose from below: `:"Select a starter:",initialValue:W[0].name,options:W.map(n=>({label:n.display,value:n.name,hint:`${n.category} \xB7 ${n.description}`}))});return V(i)&&r(),i},Ae=e=>/^(?:@[a-z\d\-*~][a-z\d\-*._~]*\/)?[a-z\d\-~][a-z\d\-._~]*$/.test(e),oe=e=>e.trim().toLowerCase().replace(/\s+/g,"-").replace(/^[._]/,"").replace(/[^a-z\d\-~]+/g,"-"),At=async({targetDir:e,yes:t,cancel:r})=>{let i=$.basename($.resolve(e));if(!Ae(i)){if(t)return oe(i);const n=await Te({message:"Package name:",defaultValue:oe(i),placeholder:oe(i),validate:l=>{if(!l||!Ae(l))return"Invalid package.json name"}});if(V(n))return r();i=n}return i},X=e=>e.trim().replace(/\/+$/g,""),kt=e=>{const t=w.readdirSync(e);return t.length===0||t.length===1&&t[0]===".git"},jt=e=>{if(w.existsSync(e))for(const t of w.readdirSync(e))t!==".git"&&w.rmSync($.resolve(e,t),{recursive:!0,force:!0})},Ot=async({argTargetDir:e,defaultTargetDir:t,yes:r,cancel:i})=>{let n=e?X(String(e)):void 0;if(!n&&r)return t;if(!n){const l=await Te({message:"Project name:",defaultValue:t,placeholder:t,validate:a=>{if(a&&!X(a))return"Invalid project name"}});V(l)&&i(),n=X(l)}return n},Lt=async({targetDir:e,argOverwrite:t,yes:r,cancel:i})=>{if(w.existsSync(e)&&!kt(e)){if(r&&!t)throw new Error(`Target directory "${e}" is not empty. Pass --overwrite to remove its contents.`);const n=t?"yes":await ne({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(V(n))return i();switch(n){case"yes":jt(e);break;case"no":i();return}}},Pt=["scripts","dependencies","devDependencies","peerDependencies","engines"],ke={"README.md":"## Prerequisites","AGENTS.md":"## Checking your work"},Bt="## Agent skills",Mt=["npm","pnpm","yarn","bun"],Nt=e=>e==="yarn"?{install:"yarn",run:"yarn"}:{install:`${e} install`,run:`${e} run`},Dt=e=>{const{cwd:t,targetDir:r,format:i,starter:n,packageName:l,skills:a,renameFiles:o}=e,s=$.resolve(t,r);w.mkdirSync(s,{recursive:!0});const u=process.env.npm_config_user_agent?.split("/")[0]??"npm",m=Mt.includes(u)?u:"npm",{install:h,run:d}=Nt(m);B.step(`Creating project in ${s}...`);const y=Le(import.meta.url),f=$.dirname(y),b=e.templatesDir??[$.resolve(f,"../templates"),$.resolve(f,"../../templates")].find(p=>w.existsSync($.join(p,i)));if(!b)throw new Error(`Format directory not found for: ${i}`);const F=W.find(p=>p.name===n);if(!F)throw new Error(`Starter not found: ${n}`);const T=$.join(b,"_shared",n),R=$.join(b,i,n);if(!w.existsSync(R))throw new Error(`Starter directory not found: ${i}/${n}`);const L=[$.join(b,"_common"),$.join(b,i,"base"),T,...(F.features??[]).flatMap(p=>Ct[p][i].map(_=>$.join(b,"_features",p,_))),R].filter(p=>w.existsSync(p)),k=Object.keys(ke).map(p=>$.join(T,p));for(const p of L){const _=$.join(p,"package.json");w.cpSync(p,s,{recursive:!0,filter:g=>g!==_&&!k.includes(g)})}for(const[p,_]of Object.entries(ke)){const g=$.join(s,p);if(!w.existsSync(g))continue;let C=w.readFileSync(g,"utf8");const U=$.join(T,p);if(w.existsSync(U)){const Z=`${w.readFileSync(U,"utf8").trimEnd()}
46
46
 
47
- ${Re}`,p=w.readFileSync(f,"utf8").replace(Re,()=>y);w.writeFileSync(f,p)}for(const[f,y]of Object.entries(o)){const p=x.join(s,f);w.existsSync(p)&&w.renameSync(p,x.join(s,y))}const T=_.map(f=>x.join(f,"package.json")).filter(f=>w.existsSync(f)).map(f=>JSON.parse(w.readFileSync(f,"utf-8"))),R=Object.assign({},...T);for(const f of Ot){const y=Object.assign({},...T.map(S=>S[f])),p=Object.entries(y).sort(([S],[B])=>S>B?1:-1);p.length?R[f]=Object.fromEntries(p):delete R[f]}if(R.name=l,w.writeFileSync(x.join(s,"package.json"),JSON.stringify(R,null,4)+`
48
- `),a){const f=[x.resolve(h,"../skills"),x.resolve(h,"../../skills")].find(p=>w.existsSync(p));if(!f)throw new Error("Skills directory not found");const y=w.readdirSync(f,{withFileTypes:!0}).filter(p=>p.isDirectory()).map(p=>p.name);for(const p of[".claude/skills",".agents/skills"])for(const S of y)w.cpSync(x.join(f,S),x.join(s,p,S),{recursive:!0});L.step("Added PlayCanvas agent skills (opt out with --no-skills)")}let I=`Project created. Now run:
49
- `;const v=x.isAbsolute(r)?s:x.relative(t,s);switch(s!==t&&(I+=`
50
- cd ${v.includes(" ")?`"${v}"`:v}`),u){case"yarn":I+=`
51
- yarn`,I+=`
52
- yarn dev`;break;default:I+=`
53
- ${u} install`,I+=`
54
- ${u} run dev`;break}gt(I)},j=$t(process.argv.slice(2),{alias:{b:"boilerplate",f:"format",h:"help",s:"starter",t:"template",y:"yes"},boolean:["help","overwrite","yes"],string:["format","starter","template","boilerplate"]}),Lt=process.cwd(),Mt=`Usage: npm create playcanvas@latest [OPTION]...
47
+ ${_}`;C=C.replace(_,()=>Z)}a||(C=`${C.split(/^(?=## )/m).filter(Z=>!Z.startsWith(Bt)).join("").trimEnd()}
48
+ `),m!=="npm"&&(C=C.replace(/\bnpm run ([^\s`]+) -- /g,`${d} $1 `).replace(/\bnpm run\b/g,d).replace(/\bnpm install\b/g,h)),w.writeFileSync(g,C)}for(const[p,_]of Object.entries(o)){const g=$.join(s,p);w.existsSync(g)&&w.renameSync(g,$.join(s,_))}const v=L.map(p=>$.join(p,"package.json")).filter(p=>w.existsSync(p)).map(p=>JSON.parse(w.readFileSync(p,"utf-8"))),S=Object.assign({},...v);for(const p of Pt){const _=Object.assign({},...v.map(C=>C[p])),g=Object.entries(_).sort(([C],[U])=>C>U?1:-1);g.length?S[p]=Object.fromEntries(g):delete S[p]}if(S.name=l,w.writeFileSync($.join(s,"package.json"),JSON.stringify(S,null,4)+`
49
+ `),a){const p=[$.resolve(f,"../skills"),$.resolve(f,"../../skills")].find(g=>w.existsSync(g));if(!p)throw new Error("Skills directory not found");const _=w.readdirSync(p,{withFileTypes:!0}).filter(g=>g.isDirectory()).map(g=>g.name);for(const g of[".claude/skills",".agents/skills"])for(const C of _)w.cpSync($.join(p,C),$.join(s,g,C),{recursive:!0});B.step("Added PlayCanvas agent skills (opt out with --no-skills)")}let x=`Project created. Now run:
50
+ `;const E=$.isAbsolute(r)?s:$.relative(t,s);s!==t&&(x+=`
51
+ cd ${E.includes(" ")?`"${E}"`:E}`),x+=`
52
+ ${h}`,x+=`
53
+ ${d} dev`,yt(x)},O=wt(process.argv.slice(2),{alias:{b:"boilerplate",f:"format",h:"help",s:"starter",t:"template",y:"yes"},boolean:["help","overwrite","yes"],string:["format","starter","template","boilerplate"]}),Wt=process.cwd(),Vt=`Usage: npm create playcanvas@latest [OPTION]...
55
54
 
56
55
  Create a new PlayCanvas project with TypeScript.
57
56
  With no arguments, start the CLI in interactive mode.
@@ -66,4 +65,4 @@ Options:
66
65
 
67
66
  Without a terminal, as when an AI agent or CI runs it, the CLI takes the
68
67
  defaults for any option not passed, as --yes does.
69
- `,Pt={_gitignore:".gitignore",_npmrc:".npmrc"},Nt="playcanvas-project",Dt=async()=>{const e=j._[0]?Y(String(j._[0])):void 0,t=j.format??j.template,r=j.starter??j.boilerplate,i=j.overwrite,n=!!(process.stdin.isTTY&&process.stdout.isTTY),l=j.yes||!n;if(j.help){console.log(Mt);return}!n&&!j.yes&&L.info("Not running in a terminal, so using the defaults for any option not passed");const a=()=>{pt("Operation cancelled"),process.exit(0)},o=await kt({argTargetDir:e,defaultTargetDir:Nt,yes:l,cancel:a});await jt({targetDir:o,argOverwrite:i,yes:l,cancel:a});const s=await Tt({targetDir:o,yes:l,cancel:a}),u=await Ft({argFormat:t,yes:l,cancel:a}),d=await Ct({argStarter:r,yes:l,cancel:a}),h=j.skills??!0;Bt({cwd:Lt,targetDir:o,format:u,starter:d,packageName:s,skills:h,renameFiles:Pt})};Dt().catch(e=>{console.error(e instanceof Error?e.message:e),process.exitCode=1});
68
+ `,Ut={_gitignore:".gitignore",_npmrc:".npmrc"},Gt="playcanvas-project",Kt=async()=>{const e=O._[0]?X(String(O._[0])):void 0,t=O.format??O.template,r=O.starter??O.boilerplate,i=O.overwrite,n=!!(process.stdin.isTTY&&process.stdout.isTTY),l=O.yes||!n;if(O.help){console.log(Vt);return}!n&&!O.yes&&B.info("Not running in a terminal, so using the defaults for any option not passed");const a=()=>{bt("Operation cancelled"),process.exit(0)},o=await Ot({argTargetDir:e,defaultTargetDir:Gt,yes:l,cancel:a});await Lt({targetDir:o,argOverwrite:i,yes:l,cancel:a});const s=await At({targetDir:o,yes:l,cancel:a}),u=await Tt({argFormat:t,yes:l,cancel:a}),m=await Rt({argStarter:r,yes:l,cancel:a}),h=O.skills??!0;Dt({cwd:Wt,targetDir:o,format:u,starter:m,packageName:s,skills:h,renameFiles:Ut})};Kt().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.11.1",
3
+ "version": "0.13.0",
4
4
  "description": "Scaffold a PlayCanvas project",
5
5
  "keywords": [
6
6
  "playcanvas",
@@ -44,7 +44,7 @@
44
44
  },
45
45
  "devDependencies": {
46
46
  "@clack/prompts": "^1.8.1",
47
- "@playcanvas/eslint-config": "3.0.0-beta.8",
47
+ "@playcanvas/eslint-config": "3.0.0",
48
48
  "@types/node": "22.20.5",
49
49
  "eslint": "^9.39.5",
50
50
  "mri": "^1.2.0",
@@ -0,0 +1 @@
1
+ @AGENTS.md
@@ -0,0 +1,13 @@
1
+ ## Snapchat Web Lens
2
+
3
+ This project is a Snapchat Web Lens: Snapchat runs it in a sandbox, from an uploaded zip.
4
+
5
+ - Run `npm run package` after a change. It builds into `dist/lens/`, checks the build against the Web Lens upload rules and writes the zip to `dist/`, or reports why it wrote none.
6
+ - A Lens can't reach the network. Bundle every asset into the build, and load nothing from another host, such as a script from a CDN, a web font or a remote model.
7
+ - The zip must stay under 8 MB and `index.html` under 1 MB.
8
+ - Snapchat disables the camera and microphone for Web Lenses. Keep `getUserMedia`, `getDisplayMedia` and `ImageCapture` out of the build entirely.
9
+ - Motion sensors, geolocation, payments, MIDI, USB and WebRTC are off too, so design for pointer input.
10
+ - `localStorage` and IndexedDB start empty every time the Lens opens. To keep data between launches, use the Web Lens API's [storage](https://developers.snap.com/web-lenses/storage), `snapchat.storage`: check `snapchat.isSupported('storage')` after `init()` resolves, write nothing until the first read answers, and chain writes through one promise. Each value can be up to 32 KiB, and the whole store 100 KiB. `src/lens.ts` keeps the best score this way.
11
+ - `npm run start` serves the build under the Lens sandbox's rules, so a request that would fail in Snapchat also fails in the browser console.
12
+ - `src/lens.ts` is the only code that talks to Snapchat. Outside Snapchat, the Web Lens API answers from a stub with canned data, so the game runs in any browser. Call `recordScore(score)` when a run ends.
13
+ - Keep the layout portrait and the HUD inside the safe area, using the `--inset-*` properties from `src/lens.css`.
@@ -18,8 +18,9 @@ This builds the game into `dist/lens/`, checks it against the [Web Lens upload r
18
18
  - the build refers to one of its own files by an absolute path, such as `/assets/game.js`
19
19
  - the build loads anything from another host, such as a script from a CDN or a web font
20
20
  - the build uses something the Lens sandbox blocks, such as an `<iframe>` or a web app manifest
21
+ - the build's code asks for the camera, microphone or screen (`getUserMedia`, `getDisplayMedia` or `ImageCapture`), which Snapchat disables for Web Lenses
21
22
 
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
+ It warns about any other host your scripts mention, since a Lens can't reach the network. It also warns about code that uses something Snapchat [turns off for Web Lenses](https://developers.snap.com/web-lenses/capabilities): motion sensors, geolocation, payments, MIDI, USB and WebRTC, and `localStorage` or IndexedDB, which start empty every time the Lens opens, so keep data between launches with the Web Lens API's [storage](https://developers.snap.com/web-lenses/storage) instead. To check the build itself, `npm run start` serves it under the same rules, so a request for anything outside the zip, or for a device such as the camera, fails in the browser console as it would in Snapchat.
23
24
 
24
25
  ### How it fits together
25
26
 
@@ -19,6 +19,32 @@ const BLOCKED_TAGS = ['base', 'embed', 'iframe', 'object'];
19
19
  // error messages link to
20
20
  const TEXT_HOSTS = ['www.w3.org', 'github.com', 'react.dev', 'api.playcanvas.com'];
21
21
 
22
+ // APIs that open the camera, microphone or screen, which Snapchat disables for Web Lenses
23
+ const CAPTURE_APIS =
24
+ /\b(?:mediaDevices|getUserMedia|webkitGetUserMedia|mozGetUserMedia|getDisplayMedia|ImageCapture)\b/g;
25
+
26
+ // other features Snapchat turns off for Web Lenses: code that relies on them gets nothing, so it's
27
+ // worth a look rather than a failure
28
+ const UNAVAILABLE_APIS = [
29
+ {
30
+ pattern:
31
+ /\b(?:deviceorientation|devicemotion|DeviceOrientationEvent|DeviceMotionEvent|Accelerometer|Gyroscope|Magnetometer|LinearAccelerationSensor|GravitySensor|AbsoluteOrientationSensor|RelativeOrientationSensor)\b/g,
32
+ reason: 'Motion sensors are off in a Web Lens, so design for pointer input rather than tilt'
33
+ },
34
+ {
35
+ pattern: /\b(?:geolocation|PaymentRequest|requestMIDIAccess|navigator\.usb)\b/g,
36
+ reason: 'Geolocation, payments, MIDI and USB are off in a Web Lens'
37
+ },
38
+ {
39
+ pattern: /\bRTC(?:PeerConnection|DataChannel)\b/g,
40
+ reason: "A Web Lens can't make WebRTC connections"
41
+ },
42
+ {
43
+ pattern: /\b(?:localStorage|indexedDB)\b/g,
44
+ reason: "localStorage and IndexedDB start empty every time the Lens opens, so keep data between launches with the Web Lens API's storage (snapchat.storage)"
45
+ }
46
+ ];
47
+
22
48
  const buildDir = path.resolve(process.argv[2] ?? 'dist/lens');
23
49
  const { name = 'web-lens' } = JSON.parse(fs.readFileSync('package.json', 'utf8'));
24
50
  const zipFile = path.join(path.dirname(buildDir), `${name.replace(/^@/, '').replace('/', '-')}.zip`);
@@ -134,6 +160,32 @@ for (const file of files.filter((f) => /\.m?js$/.test(f))) {
134
160
  }
135
161
  }
136
162
 
163
+ // where `pattern` first matches in `source`, as line:column, and every API name it matched
164
+ const findUses = (source, pattern) => {
165
+ const uses = Array.from(source.matchAll(pattern));
166
+ if (!uses.length) return null;
167
+ const before = source.slice(0, uses[0].index);
168
+ return {
169
+ at: `${before.split('\n').length}:${uses[0].index - before.lastIndexOf('\n')}`,
170
+ apis: [...new Set(uses.map(([api]) => api))].join(', ')
171
+ };
172
+ };
173
+
174
+ // features a Lens can't use, in scripts or inline in markup
175
+ for (const file of files.filter((f) => /\.(?:html|m?js)$/.test(f))) {
176
+ const source = read(file);
177
+ const capture = findUses(source, CAPTURE_APIS);
178
+ if (capture) {
179
+ errors.push(
180
+ `${file}:${capture.at}: uses ${capture.apis}. Snapchat disables the camera and microphone for Web Lenses, so remove the capture code`
181
+ );
182
+ }
183
+ for (const { pattern, reason } of UNAVAILABLE_APIS) {
184
+ const use = findUses(source, pattern);
185
+ if (use) warnings.push(`${file}:${use.at}: uses ${use.apis}. ${reason}`);
186
+ }
187
+ }
188
+
137
189
  // a plain zip: deflated entries with forward-slash names and index.html at the root
138
190
  const zipEntries = (entries) => {
139
191
  const local = [];
@@ -0,0 +1,38 @@
1
+ # AGENTS.md
2
+
3
+ A PlayCanvas Engine app in TypeScript, built with Vite. The scene is built in code with the `playcanvas` package's API. `README.md` describes the starter this project began from and how to control it.
4
+
5
+ ## Commands
6
+
7
+ - `npm install` installs dependencies.
8
+ - `npm run dev` starts the dev server, usually at <http://localhost:5173>, with hot reload and the PlayCanvas Inspector.
9
+ - `npm run typecheck` checks types.
10
+ - `npm run lint` runs ESLint, and `npm run lint:fix` fixes what it can.
11
+ - `npm run fmt` checks formatting, and `npm run fmt:fix` applies Prettier.
12
+ - `npm run build` builds the production site into `dist/`, and `npm run start` serves it.
13
+ - `npm run dev` and `npm run start` open the page in the default browser.
14
+
15
+ ## Layout
16
+
17
+ - `index.html` is the page, with the `<canvas id="application-canvas">` the app draws into.
18
+ - `src/main.ts` is the entry point. It creates the graphics device and the app, then the scene.
19
+ - `src/starter.css` styles the page and the starter's panels.
20
+ - `src/inspector.ts` loads the Inspector. `vite.config.ts` adds it in the dev server only, so production builds leave it out.
21
+ - `public/` holds files copied into the build as they are, such as models and textures.
22
+
23
+ ## Conventions
24
+
25
+ - The app is an `AppBase` given only the component systems and resource handlers it uses, which keeps the bundle small. To use another component or asset type, add its system to `componentSystems`, such as `SoundComponentSystem` for `addComponent('sound')`, or its handler to `resourceHandlers`, such as `AudioHandler` for audio assets. Without its system, `addComponent()` returns `null`, and the default build logs no error.
26
+ - Builds use relative URLs (`base: './'` in `vite.config.ts`), so they run from any folder. Load files in `public/` with relative paths such as `./model.glb`, not `/model.glb`.
27
+ - The engine ships ready-made scripts, such as camera controls and character controllers, under `playcanvas/scripts/esm/`. Use one before writing the same behavior yourself.
28
+ - `node_modules/playcanvas/build/playcanvas.d.ts` documents the installed engine's API.
29
+ - TypeScript is strict, and unused locals and parameters are errors.
30
+
31
+ ## Checking your work
32
+
33
+ - Run `npm run typecheck`, `npm run lint` and `npm run build` before you finish. All three must pass.
34
+ - To see the result, run `npm run dev` and open the page in a browser. The console should show `Powered by PlayCanvas` and no errors. Press the backquote key for the Inspector, which shows the hierarchy, assets, materials, textures, frame graph and physics. <kbd>F9</kbd> pauses and <kbd>F10</kbd> steps a frame.
35
+
36
+ ## Agent skills
37
+
38
+ `.claude/skills/` and `.agents/skills/` hold PlayCanvas skills for coding agents. Use them rather than working from memory: `build-app` when restructuring the app, `find-examples` for a feature the project doesn't use yet, `reuse-scripts` before writing a behavior, `inspect-glb` before placing a model, `inspect-runtime` to read state from the running app, and `verify-pixels` to prove a refactor leaves the image unchanged.
@@ -9,13 +9,11 @@ Node.js 22.23.2 or later.
9
9
  ## Getting started
10
10
 
11
11
  ```bash
12
- npm create playcanvas@latest playcanvas-project -- --format engine
13
- cd playcanvas-project
14
12
  npm install
15
13
  npm run dev
16
14
  ```
17
15
 
18
- Open <http://localhost:5173>. Edit the files under `src/` and save to see the scene update.
16
+ The app opens in your browser at <http://localhost:5173>. Edit the files under `src/` and save to see the scene update.
19
17
 
20
18
  ## Inspector
21
19
 
@@ -23,17 +21,23 @@ Open <http://localhost:5173>. Edit the files under `src/` and save to see the sc
23
21
 
24
22
  ## Scripts
25
23
 
26
- | Command | Description |
27
- | ------------------- | --------------------------------- |
28
- | `npm run dev` | Start the Vite development server |
29
- | `npm run build` | Build for production |
30
- | `npm run start` | Preview the production build |
31
- | `npm run lint` | Run ESLint |
32
- | `npm run fmt` | Check formatting |
33
- | `npm run typecheck` | Run TypeScript checks |
24
+ Run these with `npm run <script>`:
25
+
26
+ | Script | Description |
27
+ | ----------- | -------------------------------------------------- |
28
+ | `dev` | Start the Vite development server and open the app |
29
+ | `build` | Build for production |
30
+ | `start` | Preview the production build and open the app |
31
+ | `lint` | Run ESLint |
32
+ | `fmt` | Check formatting |
33
+ | `typecheck` | Run TypeScript checks |
34
34
 
35
35
  Run `npm run build` to generate a deployable static site in `dist/`. Its paths are relative, so it runs from any folder, such as a GitHub Pages project site or an itch.io upload.
36
36
 
37
+ ## Coding agents
38
+
39
+ `AGENTS.md` gives coding agents such as Claude Code, Codex and Cursor this project's commands, layout, conventions and checks. `CLAUDE.md` imports it for Claude Code.
40
+
37
41
  ## Agent skills
38
42
 
39
43
  This project includes [`@playcanvas/skills`](https://github.com/playcanvas/skills) under `.claude/skills/` and `.agents/skills/`, so Claude Code, Codex and Cursor pick up PlayCanvas-specific workflows automatically.
@@ -6,20 +6,20 @@
6
6
  "type": "module",
7
7
  "scripts": {
8
8
  "build": "vite build",
9
- "dev": "vite",
9
+ "dev": "vite --open",
10
10
  "fmt": "prettier --check .",
11
11
  "fmt:fix": "prettier --write .",
12
12
  "lint": "eslint .",
13
13
  "lint:fix": "eslint . --fix",
14
- "start": "vite preview",
14
+ "start": "vite preview --open",
15
15
  "typecheck": "tsc --noEmit"
16
16
  },
17
17
  "dependencies": {
18
18
  "playcanvas": "^2.23.3"
19
19
  },
20
20
  "devDependencies": {
21
- "@playcanvas/eslint-config": "3.0.0-beta.8",
22
- "@playcanvas/inspector": "^0.3.0",
21
+ "@playcanvas/eslint-config": "3.0.0",
22
+ "@playcanvas/inspector": "^0.4.0",
23
23
  "eslint": "^9.39.5",
24
24
  "prettier": "^3.9.10",
25
25
  "typescript": "~6.0.3",
@@ -5,6 +5,11 @@ export default defineConfig({
5
5
  // Relative URLs, so the build runs wherever it's served from: a domain root, a subpath such as
6
6
  // GitHub Pages or itch.io, or a Snapchat Web Lens
7
7
  base: './',
8
+ build: {
9
+ // A PlayCanvas build is 1-2.5 MB minified (300-600 kB gzipped), nearly all of it engine code the
10
+ // first frame needs, so warn only for chunks well past that rather than at Vite's 500 kB default
11
+ chunkSizeWarningLimit: 3000
12
+ },
8
13
  plugins: [
9
14
  {
10
15
  // Dev only: install the PlayCanvas Inspector before the app starts (press ` to toggle)
@@ -11,6 +11,21 @@ const LENS_SANDBOX = [
11
11
  "form-action 'none'"
12
12
  ].join('; ');
13
13
 
14
+ // Approximates its permissions policy, which turns off device features such as the camera and
15
+ // motion sensors
16
+ const LENS_PERMISSIONS = [
17
+ 'camera=()',
18
+ 'microphone=()',
19
+ 'display-capture=()',
20
+ 'accelerometer=()',
21
+ 'gyroscope=()',
22
+ 'magnetometer=()',
23
+ 'geolocation=()',
24
+ 'payment=()',
25
+ 'usb=()',
26
+ 'midi=()'
27
+ ].join(', ');
28
+
14
29
  // https://vite.dev/config/
15
30
  export default defineConfig({
16
31
  // Snapchat serves a Lens from its own origin, so every URL in the build must be relative
@@ -19,13 +34,17 @@ export default defineConfig({
19
34
  // npm run package zips this folder and writes the zip beside it, inside the ignored dist/
20
35
  outDir: 'dist/lens',
21
36
  // source maps only add to the 8 MB upload
22
- sourcemap: false
37
+ sourcemap: false,
38
+ // a PlayCanvas build is 1-2.5 MB minified, nearly all of it engine code the first frame needs,
39
+ // so warn only for chunks well past that rather than at Vite's 500 kB default
40
+ chunkSizeWarningLimit: 3000
23
41
  },
24
42
  preview: {
25
- // npm run start serves the build under the Lens rules, so a request for anything outside
26
- // the zip fails in the browser console as it would in Snapchat
43
+ // npm run start serves the build under the Lens rules, so loading anything from outside the
44
+ // zip, or using a device such as the camera, fails in the browser as it would in Snapchat
27
45
  headers: {
28
46
  'Content-Security-Policy': LENS_SANDBOX,
47
+ 'Permissions-Policy': LENS_PERMISSIONS,
29
48
  'Cross-Origin-Opener-Policy': 'same-origin',
30
49
  'Cross-Origin-Embedder-Policy': 'require-corp'
31
50
  }
@@ -0,0 +1,42 @@
1
+ # AGENTS.md
2
+
3
+ A PlayCanvas app in TypeScript and React, built with Vite. The scene is declared in JSX with `@playcanvas/react`, which drives the PlayCanvas Engine. `README.md` describes the starter this project began from and how to control it.
4
+
5
+ ## Commands
6
+
7
+ - `npm install` installs dependencies.
8
+ - `npm run dev` starts the dev server, usually at <http://localhost:5173>, with hot reload and the PlayCanvas Inspector.
9
+ - `npm run typecheck` checks types.
10
+ - `npm run lint` runs ESLint, and `npm run lint:fix` fixes what it can.
11
+ - `npm run fmt` checks formatting, and `npm run fmt:fix` applies Prettier.
12
+ - `npm run build` builds the production site into `dist/`, and `npm run start` serves it.
13
+ - `npm run dev` and `npm run start` open the page in the default browser.
14
+
15
+ ## Layout
16
+
17
+ - `index.html` is the page, with the `#root` element React renders into.
18
+ - `src/main.tsx` mounts `<App />` inside `<StrictMode>`.
19
+ - `src/App.tsx` holds the scene: an `<Application>` of `<Entity>` elements, with components such as `<Camera>`, `<Light>` and `<Render>` inside them.
20
+ - `src/starter.css` styles the page and the starter's panels.
21
+ - `src/inspector.ts` loads the Inspector. `vite.config.ts` adds it in the dev server only, so production builds leave it out.
22
+ - `public/` holds files copied into the build as they are, such as models and textures.
23
+
24
+ ## Conventions
25
+
26
+ - Import `<Application>` and `<Entity>` from `@playcanvas/react`, components from `@playcanvas/react/components` and hooks such as `useApp`, `useAppEvent` and `useModel` from `@playcanvas/react/hooks`. Engine classes and types come from `playcanvas`.
27
+ - Reach an entity's engine object through a `ref`, typed with `Entity` from `playcanvas`.
28
+ - Do per-frame work in `useAppEvent('update', (dt) => ...)` on engine objects, not in React state, which would re-render every frame.
29
+ - In development, `<StrictMode>` mounts each component twice, so anything an effect adds to the engine, such as an entity, a script or an event listener, needs a cleanup that removes it.
30
+ - Builds use relative URLs (`base: './'` in `vite.config.ts`), so they run from any folder. Load files in `public/` with relative paths such as `./model.glb`, not `/model.glb`.
31
+ - The engine ships ready-made scripts, such as camera controls and character controllers, under `playcanvas/scripts/esm/`. Use one before writing the same behavior yourself.
32
+ - `node_modules/playcanvas/build/playcanvas.d.ts` documents the installed engine's API.
33
+ - TypeScript is strict, and unused locals and parameters are errors.
34
+
35
+ ## Checking your work
36
+
37
+ - Run `npm run typecheck`, `npm run lint` and `npm run build` before you finish. All three must pass.
38
+ - To see the result, run `npm run dev` and open the page in a browser. The console should show `Powered by PlayCanvas` and no errors. Press the backquote key for the Inspector, which shows the hierarchy, assets, materials, textures, frame graph and physics. <kbd>F9</kbd> pauses and <kbd>F10</kbd> steps a frame.
39
+
40
+ ## Agent skills
41
+
42
+ `.claude/skills/` and `.agents/skills/` hold PlayCanvas skills for coding agents. Use them rather than working from memory: `build-app` when restructuring the app, `find-examples` for a feature the project doesn't use yet, `reuse-scripts` before writing a behavior, `inspect-glb` before placing a model, `inspect-runtime` to read state from the running app, and `verify-pixels` to prove a refactor leaves the image unchanged.
@@ -9,13 +9,11 @@ Node.js 22.23.2 or later.
9
9
  ## Getting started
10
10
 
11
11
  ```bash
12
- npm create playcanvas@latest playcanvas-project -- --format react
13
- cd playcanvas-project
14
12
  npm install
15
13
  npm run dev
16
14
  ```
17
15
 
18
- Open <http://localhost:5173>. Edit the files under `src/` and save to see the scene update.
16
+ The app opens in your browser at <http://localhost:5173>. Edit the files under `src/` and save to see the scene update.
19
17
 
20
18
  ## Inspector
21
19
 
@@ -23,17 +21,23 @@ Open <http://localhost:5173>. Edit the files under `src/` and save to see the sc
23
21
 
24
22
  ## Scripts
25
23
 
26
- | Command | Description |
27
- | ------------------- | --------------------------------- |
28
- | `npm run dev` | Start the Vite development server |
29
- | `npm run build` | Build for production |
30
- | `npm run start` | Preview the production build |
31
- | `npm run lint` | Run ESLint |
32
- | `npm run fmt` | Check formatting |
33
- | `npm run typecheck` | Run TypeScript checks |
24
+ Run these with `npm run <script>`:
25
+
26
+ | Script | Description |
27
+ | ----------- | -------------------------------------------------- |
28
+ | `dev` | Start the Vite development server and open the app |
29
+ | `build` | Build for production |
30
+ | `start` | Preview the production build and open the app |
31
+ | `lint` | Run ESLint |
32
+ | `fmt` | Check formatting |
33
+ | `typecheck` | Run TypeScript checks |
34
34
 
35
35
  Run `npm run build` to generate a deployable static site in `dist/`. Its paths are relative, so it runs from any folder, such as a GitHub Pages project site or an itch.io upload.
36
36
 
37
+ ## Coding agents
38
+
39
+ `AGENTS.md` gives coding agents such as Claude Code, Codex and Cursor this project's commands, layout, conventions and checks. `CLAUDE.md` imports it for Claude Code.
40
+
37
41
  ## Agent skills
38
42
 
39
43
  This project includes [`@playcanvas/skills`](https://github.com/playcanvas/skills) under `.claude/skills/` and `.agents/skills/`, so Claude Code, Codex and Cursor pick up PlayCanvas-specific workflows automatically.
@@ -1,15 +1,4 @@
1
1
  import reactConfig from '@playcanvas/eslint-config/react';
2
2
  import typescriptConfig from '@playcanvas/eslint-config/typescript';
3
3
 
4
- export default [
5
- { ignores: ['.claude', '.agents', 'public/ammo'] },
6
- ...typescriptConfig,
7
- ...reactConfig,
8
- {
9
- rules: {
10
- 'react/jsx-uses-react': 'off',
11
- 'react/prop-types': 'off',
12
- 'react/react-in-jsx-scope': 'off'
13
- }
14
- }
15
- ];
4
+ export default [{ ignores: ['.claude', '.agents', 'public/ammo'] }, ...typescriptConfig, ...reactConfig];
@@ -6,12 +6,12 @@
6
6
  "type": "module",
7
7
  "scripts": {
8
8
  "build": "vite build",
9
- "dev": "vite",
9
+ "dev": "vite --open",
10
10
  "fmt": "prettier --check .",
11
11
  "fmt:fix": "prettier --write .",
12
12
  "lint": "eslint .",
13
13
  "lint:fix": "eslint . --fix",
14
- "start": "vite preview",
14
+ "start": "vite preview --open",
15
15
  "typecheck": "tsc --noEmit"
16
16
  },
17
17
  "dependencies": {
@@ -21,8 +21,8 @@
21
21
  "react-dom": "^19.3.1"
22
22
  },
23
23
  "devDependencies": {
24
- "@playcanvas/eslint-config": "3.0.0-beta.8",
25
- "@playcanvas/inspector": "^0.3.0",
24
+ "@playcanvas/eslint-config": "3.0.0",
25
+ "@playcanvas/inspector": "^0.4.0",
26
26
  "@types/react": "^19.3.0",
27
27
  "@types/react-dom": "^19.3.0",
28
28
  "@vitejs/plugin-react": "^6.1.2",
@@ -6,6 +6,11 @@ export default defineConfig({
6
6
  // Relative URLs, so the build runs wherever it's served from: a domain root, a subpath such as
7
7
  // GitHub Pages or itch.io, or a Snapchat Web Lens
8
8
  base: './',
9
+ build: {
10
+ // A PlayCanvas build is 1-2.5 MB minified (300-600 kB gzipped), nearly all of it engine code the
11
+ // first frame needs, so warn only for chunks well past that rather than at Vite's 500 kB default
12
+ chunkSizeWarningLimit: 3000
13
+ },
9
14
  plugins: [
10
15
  react(),
11
16
  {
@@ -12,6 +12,21 @@ const LENS_SANDBOX = [
12
12
  "form-action 'none'"
13
13
  ].join('; ');
14
14
 
15
+ // Approximates its permissions policy, which turns off device features such as the camera and
16
+ // motion sensors
17
+ const LENS_PERMISSIONS = [
18
+ 'camera=()',
19
+ 'microphone=()',
20
+ 'display-capture=()',
21
+ 'accelerometer=()',
22
+ 'gyroscope=()',
23
+ 'magnetometer=()',
24
+ 'geolocation=()',
25
+ 'payment=()',
26
+ 'usb=()',
27
+ 'midi=()'
28
+ ].join(', ');
29
+
15
30
  // https://vite.dev/config/
16
31
  export default defineConfig({
17
32
  // Snapchat serves a Lens from its own origin, so every URL in the build must be relative
@@ -20,13 +35,17 @@ export default defineConfig({
20
35
  // npm run package zips this folder and writes the zip beside it, inside the ignored dist/
21
36
  outDir: 'dist/lens',
22
37
  // source maps only add to the 8 MB upload
23
- sourcemap: false
38
+ sourcemap: false,
39
+ // a PlayCanvas build is 1-2.5 MB minified, nearly all of it engine code the first frame needs,
40
+ // so warn only for chunks well past that rather than at Vite's 500 kB default
41
+ chunkSizeWarningLimit: 3000
24
42
  },
25
43
  preview: {
26
- // npm run start serves the build under the Lens rules, so a request for anything outside
27
- // the zip fails in the browser console as it would in Snapchat
44
+ // npm run start serves the build under the Lens rules, so loading anything from outside the
45
+ // zip, or using a device such as the camera, fails in the browser as it would in Snapchat
28
46
  headers: {
29
47
  'Content-Security-Policy': LENS_SANDBOX,
48
+ 'Permissions-Policy': LENS_PERMISSIONS,
30
49
  'Cross-Origin-Opener-Policy': 'same-origin',
31
50
  'Cross-Origin-Embedder-Policy': 'require-corp'
32
51
  }
@@ -0,0 +1,40 @@
1
+ # AGENTS.md
2
+
3
+ A PlayCanvas app in TypeScript, built with Vite. The scene is declared in HTML with `@playcanvas/web-components`, custom elements that drive the PlayCanvas Engine. `README.md` describes the starter this project began from and how to control it.
4
+
5
+ ## Commands
6
+
7
+ - `npm install` installs dependencies.
8
+ - `npm run dev` starts the dev server, usually at <http://localhost:5173>, with hot reload and the PlayCanvas Inspector.
9
+ - `npm run typecheck` checks types.
10
+ - `npm run lint` runs ESLint, and `npm run lint:fix` fixes what it can.
11
+ - `npm run fmt` checks formatting, and `npm run fmt:fix` applies Prettier.
12
+ - `npm run build` builds the production site into `dist/`, and `npm run start` serves it.
13
+ - `npm run dev` and `npm run start` open the page in the default browser.
14
+
15
+ ## Layout
16
+
17
+ - `index.html` holds the scene: `<pc-app>` wraps a `<pc-scene>` of `<pc-entity>` elements, with components such as `<pc-camera>`, `<pc-light>` and `<pc-render>` inside them.
18
+ - `src/main.ts` does what HTML can't, such as attaching scripts and handling input.
19
+ - `src/starter.css` styles the page and the starter's panels.
20
+ - `src/inspector.ts` loads the Inspector. `vite.config.ts` adds it in the dev server only, so production builds leave it out.
21
+ - `public/` holds files copied into the build as they are, such as models and textures.
22
+
23
+ ## Conventions
24
+
25
+ - Attribute values are strings, such as `position="0 0 3"` and `clear-color="#8099e6"`. `node_modules/@playcanvas/web-components/dist/custom-elements.json` lists every element and its attributes.
26
+ - Attach a script that's imported through the bundler in `src/main.ts`, not with `<pc-script-instance name="...">`, which only finds scripts that a `<pc-asset>` fetched at runtime. Wait for the element with `whenReady()`, then add the script through its `.entity`.
27
+ - `<pc-app>` dispatches ordinary pointer events, such as `pointerup`, on the `<pc-entity>` under the pointer, so listen for them on the element. Only entities with a listener receive them.
28
+ - Builds use relative URLs (`base: './'` in `vite.config.ts`), so they run from any folder. Load files in `public/` with relative paths such as `./model.glb`, not `/model.glb`.
29
+ - The engine ships ready-made scripts, such as camera controls and character controllers, under `playcanvas/scripts/esm/`. Use one before writing the same behavior yourself.
30
+ - `node_modules/playcanvas/build/playcanvas.d.ts` documents the installed engine's API.
31
+ - TypeScript is strict, and unused locals and parameters are errors.
32
+
33
+ ## Checking your work
34
+
35
+ - Run `npm run typecheck`, `npm run lint` and `npm run build` before you finish. All three must pass.
36
+ - To see the result, run `npm run dev` and open the page in a browser. The console should show `Powered by PlayCanvas` and no errors. Press the backquote key for the Inspector, which shows the hierarchy, assets, materials, textures, frame graph and physics. <kbd>F9</kbd> pauses and <kbd>F10</kbd> steps a frame.
37
+
38
+ ## Agent skills
39
+
40
+ `.claude/skills/` and `.agents/skills/` hold PlayCanvas skills for coding agents. Use them rather than working from memory: `build-app` when restructuring the app, `find-examples` for a feature the project doesn't use yet, `reuse-scripts` before writing a behavior, `inspect-glb` before placing a model, `inspect-runtime` to read state from the running app, and `verify-pixels` to prove a refactor leaves the image unchanged.
@@ -10,13 +10,11 @@ Node.js 22.23.2 or later.
10
10
  ## Getting started
11
11
 
12
12
  ```bash
13
- npm create playcanvas@latest playcanvas-project -- --format web-components
14
- cd playcanvas-project
15
13
  npm install
16
14
  npm run dev
17
15
  ```
18
16
 
19
- Open <http://localhost:5173>. Edit `index.html` to change the scene.
17
+ The app opens in your browser at <http://localhost:5173>. Edit `index.html` to change the scene.
20
18
 
21
19
  ## Inspector
22
20
 
@@ -24,14 +22,16 @@ Open <http://localhost:5173>. Edit `index.html` to change the scene.
24
22
 
25
23
  ## Scripts
26
24
 
27
- | Command | Description |
28
- | ------------------- | --------------------------------- |
29
- | `npm run dev` | Start the Vite development server |
30
- | `npm run build` | Build for production |
31
- | `npm run start` | Preview the production build |
32
- | `npm run lint` | Run ESLint |
33
- | `npm run fmt` | Check formatting |
34
- | `npm run typecheck` | Run TypeScript checks |
25
+ Run these with `npm run <script>`:
26
+
27
+ | Script | Description |
28
+ | ----------- | -------------------------------------------------- |
29
+ | `dev` | Start the Vite development server and open the app |
30
+ | `build` | Build for production |
31
+ | `start` | Preview the production build and open the app |
32
+ | `lint` | Run ESLint |
33
+ | `fmt` | Check formatting |
34
+ | `typecheck` | Run TypeScript checks |
35
35
 
36
36
  Run `npm run build` to generate a deployable static site in `dist/`. Its paths are relative, so it runs from any folder, such as a GitHub Pages project site or an itch.io upload.
37
37
 
@@ -62,6 +62,10 @@ JetBrains IDEs pick it up automatically. For VS Code, add to `.vscode/settings.j
62
62
  }
63
63
  ```
64
64
 
65
+ ## Coding agents
66
+
67
+ `AGENTS.md` gives coding agents such as Claude Code, Codex and Cursor this project's commands, layout, conventions and checks. `CLAUDE.md` imports it for Claude Code.
68
+
65
69
  ## Agent skills
66
70
 
67
71
  This project includes [`@playcanvas/skills`](https://github.com/playcanvas/skills) under `.claude/skills/` and `.agents/skills/`, so Claude Code, Codex and Cursor pick up PlayCanvas-specific workflows automatically.
@@ -6,21 +6,21 @@
6
6
  "type": "module",
7
7
  "scripts": {
8
8
  "build": "vite build",
9
- "dev": "vite",
9
+ "dev": "vite --open",
10
10
  "fmt": "prettier --check .",
11
11
  "fmt:fix": "prettier --write .",
12
12
  "lint": "eslint .",
13
13
  "lint:fix": "eslint . --fix",
14
- "start": "vite preview",
14
+ "start": "vite preview --open",
15
15
  "typecheck": "tsc --noEmit"
16
16
  },
17
17
  "dependencies": {
18
- "@playcanvas/web-components": "^0.28.0",
18
+ "@playcanvas/web-components": "^0.28.1",
19
19
  "playcanvas": "^2.23.3"
20
20
  },
21
21
  "devDependencies": {
22
- "@playcanvas/eslint-config": "3.0.0-beta.8",
23
- "@playcanvas/inspector": "^0.3.0",
22
+ "@playcanvas/eslint-config": "3.0.0",
23
+ "@playcanvas/inspector": "^0.4.0",
24
24
  "eslint": "^9.39.5",
25
25
  "prettier": "^3.9.10",
26
26
  "typescript": "~6.0.3",
@@ -5,6 +5,11 @@ export default defineConfig({
5
5
  // Relative URLs, so the build runs wherever it's served from: a domain root, a subpath such as
6
6
  // GitHub Pages or itch.io, or a Snapchat Web Lens
7
7
  base: './',
8
+ build: {
9
+ // A PlayCanvas build is 1-2.5 MB minified (300-600 kB gzipped), nearly all of it engine code the
10
+ // first frame needs, so warn only for chunks well past that rather than at Vite's 500 kB default
11
+ chunkSizeWarningLimit: 3000
12
+ },
8
13
  plugins: [
9
14
  {
10
15
  // Dev only: install the PlayCanvas Inspector before the app starts (press ` to toggle)
@@ -11,6 +11,21 @@ const LENS_SANDBOX = [
11
11
  "form-action 'none'"
12
12
  ].join('; ');
13
13
 
14
+ // Approximates its permissions policy, which turns off device features such as the camera and
15
+ // motion sensors
16
+ const LENS_PERMISSIONS = [
17
+ 'camera=()',
18
+ 'microphone=()',
19
+ 'display-capture=()',
20
+ 'accelerometer=()',
21
+ 'gyroscope=()',
22
+ 'magnetometer=()',
23
+ 'geolocation=()',
24
+ 'payment=()',
25
+ 'usb=()',
26
+ 'midi=()'
27
+ ].join(', ');
28
+
14
29
  // https://vite.dev/config/
15
30
  export default defineConfig({
16
31
  // Snapchat serves a Lens from its own origin, so every URL in the build must be relative
@@ -19,13 +34,17 @@ export default defineConfig({
19
34
  // npm run package zips this folder and writes the zip beside it, inside the ignored dist/
20
35
  outDir: 'dist/lens',
21
36
  // source maps only add to the 8 MB upload
22
- sourcemap: false
37
+ sourcemap: false,
38
+ // a PlayCanvas build is 1-2.5 MB minified, nearly all of it engine code the first frame needs,
39
+ // so warn only for chunks well past that rather than at Vite's 500 kB default
40
+ chunkSizeWarningLimit: 3000
23
41
  },
24
42
  preview: {
25
- // npm run start serves the build under the Lens rules, so a request for anything outside
26
- // the zip fails in the browser console as it would in Snapchat
43
+ // npm run start serves the build under the Lens rules, so loading anything from outside the
44
+ // zip, or using a device such as the camera, fails in the browser as it would in Snapchat
27
45
  headers: {
28
46
  'Content-Security-Policy': LENS_SANDBOX,
47
+ 'Permissions-Policy': LENS_PERMISSIONS,
29
48
  'Cross-Origin-Opener-Policy': 'same-origin',
30
49
  'Cross-Origin-Embedder-Policy': 'require-corp'
31
50
  }