@goodfellow-cms/broker 0.5.0 → 0.7.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
@@ -1,10 +1,10 @@
1
1
  # @goodfellow-cms/broker
2
2
 
3
- Goodfellow's token broker: a small static page, on an address of its own, where editors sign in. It keeps their GitHub or GitLab sign-in and AI keys in its own storage, shows the site's admin panel in a frame, and makes every call to the git host and AI services for it, so nothing on the site's own address can read them. It asks the editor, outside the admin panel's frame, before publishing the site's code, and never changes its workflows.
3
+ Goodfellow's token broker: a small static page, on an address of its own, where editors sign in. It keeps their GitHub, GitLab or Bitbucket sign-in and AI keys in its own storage, shows the site's admin panel in a frame, and makes every call to the git host and AI services for it, so nothing on the site's own address can read them. It asks the editor, outside the admin panel's frame, before publishing the site's code, and never changes its workflows.
4
4
 
5
5
  The package is the built page, in `site/`, with `site/files.json` listing its files. The quickest way to run one is **Site Settings → Security → Add a token broker** in the site's admin panel, or `npx goodfellow broker add` in the site's folder, which make its repository from this page. By hand, copy `site/` into a repository of its own, add a `broker.json` naming the site (see `site/broker.example.json`), put it online at an address that serves nothing else, and set that address in the site's admin panel, under **Site Settings → Security**.
6
6
 
7
- [Goodfellow](https://github.com/goodfellow-cms/goodfellow-cms) is a website builder for people who aren't developers, with no server or database: pages are files in your own GitHub or GitLab repository, edited in a visual editor built on [Puck](https://puckeditor.com). To start a new site, run `npm create goodfellow@latest my-site`. See the [documentation](https://goodfellow-cms.github.io/goodfellow-cms/) for everything else.
7
+ [Goodfellow](https://github.com/goodfellow-cms/goodfellow-cms) is a website builder for people who aren't developers, with no server or database: pages are files in your own GitHub, GitLab or Bitbucket repository, edited in a visual editor built on [Puck](https://puckeditor.com). To start a new site, run `npm create goodfellow@latest my-site`. See the [documentation](https://goodfellow-cms.github.io/goodfellow-cms/) for everything else.
8
8
 
9
9
  More in the docs: [Token broker](https://goodfellow-cms.github.io/goodfellow-cms/docs/token-broker).
10
10
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goodfellow-cms/broker",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "description": "Goodfellow's token broker: a page of its own that keeps editors' sign-in and AI keys, and shows the site's admin panel in a frame.",
5
5
  "keywords": [
6
6
  "goodfellow",
@@ -26,10 +26,11 @@
26
26
  "site"
27
27
  ],
28
28
  "devDependencies": {
29
- "@goodfellow-cms/ai": "0.5.0",
30
- "@goodfellow-cms/core": "0.5.0",
31
- "@goodfellow-cms/github": "0.5.0",
32
- "@goodfellow-cms/gitlab": "0.5.0",
29
+ "@goodfellow-cms/ai": "0.7.0",
30
+ "@goodfellow-cms/core": "0.7.0",
31
+ "@goodfellow-cms/bitbucket": "0.7.0",
32
+ "@goodfellow-cms/github": "0.7.0",
33
+ "@goodfellow-cms/gitlab": "0.7.0",
33
34
  "happy-dom": "21.0.4",
34
35
  "vite": "8.3.4"
35
36
  },
@@ -0,0 +1,4 @@
1
+ import{c as e}from"./store-BtVaZcVF.js";var t=class extends Error{problem;name=`DomainError`;constructor(e,t){super(t),this.problem=e}},n=`goodfellow-build-problem `;function r(e){let t=e.split(`
2
+ `);for(let e=t.length-1;e>=0;e--){let r=t[e]??``,i=r.indexOf(n);if(i!==-1)try{let e=JSON.parse(r.slice(i+25).trim());if((e.kind===`content`||e.kind===`code`)&&typeof e.message==`string`)return{kind:e.kind,message:e.message,...typeof e.file==`string`&&e.file&&{file:e.file}}}catch{}}}function i(e){let t=e.toLowerCase();if(/deploy-pages|deploy to |pages:deploy/.test(t))return`deploy`;if(/configure-pages|upload-pages-artifact|setup pages|set up pages/.test(t))return`pages`;if(/\b(npm (ci|install)|pnpm install|yarn( install)?|install packages)\b/.test(t))return`install`;if(/\bbuild\b/.test(t))return`build`}var a=`rebuild`;`${a}`;function o(e,t=`# `){let n=[];for(let r of e.split(`
3
+ `)){let e=``;for(let i of r.split(` `))e&&e.length+i.length+1>78-t.length?(n.push(e),e=i):e=e?`${e} ${i}`:i;n.push(e)}return n.map(e=>`${t}${e}`.trimEnd()).join(`
4
+ `)}`${o(`Drafts, from the admin panel's Add to draft and from Claude, are kept on branches named goodfellow-draft-…, and plugins' tasks answer on branches named goodfellow-task-…, which visitors never see, so Vercel doesn't build previews of them.`,` // `)}`,o(`The admin address's header rules, from the code's goodfellow.config.tsx: goodfellow admin and goodfellow broker, run in the code's folder, copy them here, since Vercel reads this file before the code is cloned.`,` // `),`${e}${e}`;export{t as i,i as n,r,a as t};
@@ -0,0 +1,5 @@
1
+ const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./deploy-keys-DAj2oX-N-DRd_HmOG.js","./git-eGyMXUIu.js"])))=>i.map(i=>d[i]);
2
+ import{r as e,t}from"./store-BtVaZcVF.js";import{_ as n,a as r,b as i,c as a,d as o,g as s,h as c,i as l,m as u,n as d,o as f,p,r as m,s as h,t as g,y as _}from"./setup-host-DQm73dc_.js";import{i as v,n as y,t as b}from"./content-repository-setup-HEL-uoaX.js";import{a as x,i as S,n as C,o as w,r as T,s as ee,t as E}from"./git-eGyMXUIu.js";import{t as te}from"./preload-helper-khBccGFq.js";async function D({apiUrl:e,token:t,fetch:n},r,i={}){let a=async a=>n(`${e}${r}`,{method:i.method??`GET`,headers:{accept:i.accept??`application/vnd.github+json`,authorization:`Bearer ${typeof t==`string`?t:await t({refresh:a})}`,"x-github-api-version":`2022-11-28`,...i.body!==void 0&&{"content-type":`application/json`}},body:i.body===void 0?void 0:JSON.stringify(i.body),cache:`no-store`}),o=await a(!1);if(o.status===401&&typeof t!=`string`&&(o=await a(!0)),o.status===401)throw new C(`invalid`,`GitHub didn't accept the sign-in. It may have expired.`);if(!o.ok&&!i.allow?.includes(o.status)){let e=await o.json().catch(()=>({}));throw new E(o.status,`GitHub answered ${o.status}: ${e.message??o.statusText}`)}return o}async function O(e,t,n){return await(await D(e,t,n)).json()}function k(e){return e.split(`/`).map(e=>encodeURIComponent(e)).join(`/`)}var ne=/^[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}$/i,A=`invitation:`;function re(e){return e?.admin?`owner`:e?.maintain||e?.push?`editor`:`viewer`}function ie(e){return e===`admin`?`owner`:e===`write`||e===`maintain`?`editor`:`viewer`}function j(e){return e instanceof E&&(e.status===403||e.status===404)}function ae(e,t,n,r){let i=`/repos/${n}`;function a(){let e=t();if(!e)throw new m(`not-allowed`,`Changing editors on GitHub needs an owner token.`);return e}async function o(e){try{await e(a())}catch(e){throw j(e)?new m(`not-allowed`,`This token can't change the site's editors.`):e}}async function s(e){let t=[];for(let n=1;n<=10;n++){let r=await O(e,`${i}/collaborators?per_page=100&page=${n}`);if(t.push(...r),r.length<100)break}return t}return{invitesByEmail:!1,async list(){let n=t()??e,a=await s(n),o;try{o=await O(n,`${i}/invitations?per_page=100`)}catch(e){if(!j(e))throw e}let c=[...a.map(e=>({id:e.login,login:e.login,avatarUrl:e.avatar_url,role:re(e.permissions),...e.login.toLowerCase()===r.toLowerCase()&&{self:!0}})),...(o??[]).flatMap(e=>e.invitee?[{id:`${A}${e.id}`,login:e.invitee.login,avatarUrl:e.invitee.avatar_url,role:ie(e.permissions),invited:!0}]:[])],l=c.some(e=>e.self&&e.role===`owner`);return{editors:c,manage:o?`yes`:l?`token`:`no`,editorsCanPublish:!0}},async invite(e,t){let n=e.trim().replace(/^@/,``);if(!ne.test(n))throw new m(`invalid`,`That isn't a GitHub username.`);let r=a();try{await D(r,`${i}/collaborators/${encodeURIComponent(n)}`,{method:`PUT`,body:{permission:t===`owner`?`admin`:`push`}})}catch(e){throw e instanceof E&&e.status===404&&(await D(r,`/users/${encodeURIComponent(n)}`,{allow:[404]})).status===404?new m(`not-found`,`There's no GitHub account named ${n}.`):j(e)?new m(`not-allowed`,`This token can't change the site's editors.`):e instanceof E&&e.status===422?new m(`invalid`,`GitHub didn't accept the invitation: ${e.message}`):e}},async setRole(e,t){await o(async n=>{e.id.startsWith(A)?await D(n,`${i}/invitations/${e.id.slice(11)}`,{method:`PATCH`,body:{permissions:t===`owner`?`admin`:`write`}}):await D(n,`${i}/collaborators/${encodeURIComponent(e.id)}`,{method:`PUT`,body:{permission:t===`owner`?`admin`:`push`}})})},async remove(e){await o(t=>D(t,e.id.startsWith(A)?`${i}/invitations/${e.id.slice(11)}`:`${i}/collaborators/${encodeURIComponent(e.id)}`,{method:`DELETE`}))}}}function M(e,t,n,r){let i,a,o,s;return{access:{tokenLink:n,get active(){return i!==void 0},get problem(){return s},...r&&{async startRedirect(e){s=void 0,await r(e)}},async applyToken(n){let r={...e,token:n.trim()};try{if(!(await O(r,`/repos/${t}`)).permissions?.admin)throw new m(`not-allowed`,`This token's account isn't an owner of the site.`);await O(r,`/repos/${t}/invitations?per_page=1`)}catch(e){throw e instanceof m?e:e instanceof C||j(e)?new m(`not-allowed`,`This token can't change the site's editors.`):e}i=r,a=r,s=void 0},forget(){i=void 0,a=void 0,o?.(),o=void 0}},api:()=>i,codeApi:()=>a,useSignIn(t,n,r){i={...e,token:t},a=r?{...e,token:r}:void 0,o=n,s=void 0},failed(e){s=e}}}var oe=[`185.199.108.153`,`185.199.109.153`,`185.199.110.153`,`185.199.111.153`],se=[`2606:50c0:8000::153`,`2606:50c0:8001::153`,`2606:50c0:8002::153`,`2606:50c0:8003::153`],ce=new Set([`approved`,`issued`,`uploaded`]),le=new Set([`errored`,`bad_authz`,`authorization_revoked`]);function N(e,t,{apex:n,www:r}){let i=`${(e.split(`/`)[0]??``).toLowerCase()}.github.io`;return n?[...oe.map(e=>({type:`A`,name:t,value:e,purpose:`site`})),...se.map(e=>({type:`AAAA`,name:t,value:e,purpose:`site`,optional:!0})),...r?[{type:`CNAME`,name:`www.${t}`,value:i,purpose:`www`,optional:!0}]:[]]:[{type:`CNAME`,name:t,value:i,purpose:`site`}]}function ue(e,t){let n=`/repos/${t}/pages`;async function r(){let t=await D(e(),n,{allow:[404]});return t.status===404?void 0:await t.json()}async function i(t){try{await D(e(),n,{method:`PUT`,body:t})}catch(e){throw e instanceof E&&(e.status===403||e.status===404)?new v(`not-allowed`,`This sign-in can't change the site's GitHub Pages settings.`):e instanceof E&&(e.status===400||e.status===422)?/taken|already/i.test(e.message)?new v(`taken`,`Another GitHub Pages site already uses this domain.`):`https_enforced`in t?new v(`not-ready`,`The domain's certificate isn't ready yet.`):new v(`invalid`,`GitHub Pages doesn't accept this domain.`):e}}return{severalDomains:!1,async site(){let e=await r();return e&&{url:e.html_url,domain:e.cname??void 0}},async connect(e,n){return await i({cname:e}),N(t,e,n)},async records(e,n){return N(t,e,n)},async status(t){let r=await O(e(),n),i=r.https_certificate?.state??``;return{verified:!0,certificate:ce.has(i)?`ready`:le.has(i)?`failed`:`pending`,httpsOnly:r.https_enforced===!0,primary:r.cname===t}},async secure(){await i({https_enforced:!0})},async disconnect(){await i({cname:null})}}}var de=`.github/workflows/scheduled.yml`,fe=[`.github/workflows/deploy.yml`,`.github/workflows/rebuild.yml`];function pe(e){return{async timing(){let[t,...n]=await Promise.all([de,...fe].map(e));return t&&/\bdue\b/.test(t)&&/\bschedule:/.test(t)?`hourly`:n.some(e=>e&&/\bschedule:/.test(e))?`nightly`:`none`}}}function me(e){let t=new URL(e);return t.hostname===`api.github.com`?`https://github.com`:`${t.origin}${t.pathname.replace(/\/api\/v3\/?$/,``)}`.replace(/\/+$/,``)}function he(e,t,n,r={}){let i=r.wait??(e=>new Promise(t=>setTimeout(t,e))),s=(n,r)=>D(e,`/repos/${t}/contents/${n}?ref=${encodeURIComponent(r)}`,{allow:[404]}),c=async()=>(await s(`.github/workflows/${p}`,n)).ok;return{supported:c,secretsPlace:async()=>({kind:`github`,url:`${me(e.apiUrl)}/${t}/settings/secrets/actions/new`}),async run(n){let l=u(n);if(l)throw new f(`failed`,l);if(!await c())throw new f(`missing`,`The site's setup files don't run plugins' tasks: it needs plugin-task.yml.`);try{await D(e,`/repos/${t}/dispatches`,{method:`POST`,body:{event_type:a,client_payload:{request:n.id,plugin:n.plugin,task:n.task,input:n.input,key:n.key}}})}catch(e){throw e instanceof E&&(e.status===403||e.status===404)?new f(`not-allowed`,`Running plugins' tasks needs a sign-in that can publish to the site.`):e}let d=`${h}${n.id}`,p=Date.now()+(r.timeoutMs??3e5);for(;;){let n=await s(o,d);if(n.ok){let{content:r=``}=await n.json();return await D(e,`/repos/${t}/git/refs/heads/${encodeURIComponent(d)}`,{method:`DELETE`,allow:[404,422]}).catch(()=>{}),new TextDecoder().decode(S(r.replace(/\s/g,``)))}if(Date.now()>=p)throw new f(`timeout`,`The site's build service didn't answer in time.`);await i(r.pollMs??4e3)}}}}var ge=`deploy.yml`,_e=`update.yml`,ve=`publish-update`;function P(e,t,r,a,o,c=ge){let l=`/repos/${r}/actions/workflows/${c}`,u=`.github/workflows/${c}`;return{async setup(){let e=await o.read(u);return e&&/update --(publish|commit)\b/.test(e)?`ready`:`missing`},async lastRun(){let t=await D(e,`${l}/runs?branch=${encodeURIComponent(a)}&per_page=20`,{allow:[403,404]});if(!t.ok)return;let{workflow_runs:s=[]}=await t.json();for(let t of s.filter(e=>[`schedule`,`workflow_dispatch`].includes(e.event))){let a={date:t.created_at,detailsUrl:t.html_url};if(t.status!==`completed`)return{state:`running`,...a};let{jobs:s=[]}=await O(e,`/repos/${r}/actions/runs/${t.id}/jobs`),c=s.find(e=>e.name===`update`);if(!c||c.conclusion===`skipped`)continue;if(c.conclusion!==`success`)return{state:`failed`,...a};let l=s.find(e=>e.name===ve);if(l&&l.conclusion!==`success`&&l.conclusion!==`skipped`)return{state:`failed`,...a};let u=i(await o.readAt(`package.json`,t.head_sha)??``),d=i(await o.read(`package.json`)??``);if(!u||!d||u===d)return{state:`up-to-date`,...a};let f=n(d,u)<0;return{state:`updated`,...a,result:{state:`updated`,from:u,to:d,...f&&{rollback:f}}}}},nightly:ye(e,t,r,()=>o.read(u)),async rebuild(){await d({})},async start(e){await d({update:e})}};async function d(n){try{await D(t()??e,`${l}/dispatches`,{method:`POST`,body:{ref:a,inputs:n}})}catch(e){throw e instanceof E&&(e.status===403||e.status===401)?new s(`not-allowed`,`Running the site's build needs a token that can run its workflows.`):e instanceof E&&(e.status===404||e.status===422)?new s(`missing`,`The site's GitHub Pages workflow doesn't update Goodfellow.`):e}}}function ye(e,t,n,r){let i=`/repos/${n}/actions/variables`,a=e=>{throw e instanceof E&&(e.status===401||e.status===403)?new s(`not-allowed`,`Changing when the site rebuilds needs GitHub's Variables permission.`):e};return{async supported(){return(await r())?.includes(`GOODFELLOW_NIGHTLY`)??!1},async get(){let n=await D(t()??e,`${i}/${c}`,{allow:[403,404]});if(n.status===404)return`changed`;if(n.ok)return _((await n.json()).value)},async set(n){let r=t()??e;if(n===`changed`){await D(r,`${i}/${c}`,{method:`DELETE`,allow:[404]}).catch(a);return}let o={name:c,value:n};(await D(r,`${i}/GOODFELLOW_NIGHTLY`,{method:`PATCH`,body:o,allow:[404]}).catch(a)).status===404&&await D(r,i,{method:`POST`,body:o}).catch(a)}}}function F(e){return e instanceof C&&e.problem===`no-access`||e instanceof E&&(e.status===403||e.status===404)}function be(e){let{read:t}=e,n=k(e.repo),r,i=e=>(r??=O(e,`/repos/${n}`).then(e=>e.default_branch),r.catch(()=>{r=void 0}),r);async function a(){if(!t)throw new C(`no-access`,`This sign-in has no access to ${e.repo}.`);return e.open(t,await i(t))}async function o(e){let t=await a();return t.readAt(e,await t.revision())}async function s(){if(!t)throw new C(`no-access`,`This sign-in has no access to ${e.repo}.`);let r=await i(t);return P(t,e.manage,n,r,{read:o,readAt:async(e,t)=>(await a()).readAt(e,t)},_e)}return{code:{repo:e.repo,webUrl:`${e.webUrl}/${e.repo}`,read:o,history:async(e,t)=>(await a()).history(e,t),readAt:async(e,t)=>(await a()).readAt(e,t)},async setup(){try{return await(await s()).setup()}catch(e){if(F(e))return`no-access`;throw e}},async lastRun(){try{return await(await s()).lastRun()}catch(e){if(F(e))return;throw e}},async start(e){await(await s()).start(e)},async rebuild(){await(await s()).rebuild?.()}}}var I=/cloudflare/i,L=100,xe=`mutation ($input: CreateCommitOnBranchInput!) {
3
+ createCommitOnBranch(input: $input) { commit { oid } }
4
+ }`;function R(e,t){let n=`${t}/`;return[...e.keys()].filter(e=>e.startsWith(n)).sort()}var z=class n{user;pages;editors;ownerAccess;updates;drafts;scheduledBuilds;tasks;contentOnly;options;api;repo;branch;onSignOut;author;owner;pinned;trees=new Map;treeSizes=new Map;blobs=new Map;constructor(e){this.options=e,this.api=e.api,this.repo=k(e.repo),this.branch=e.branch,this.user=e.user,this.onSignOut=e.onSignOut,this.author=e.author,this.pinned=e.revision;let t=M(this.api,this.repo,e.ownerTokenLink,e.ownerRedirect);this.owner=t,this.ownerAccess=t.access,this.pages=ue(()=>t.api()??this.api,this.repo),this.editors=ae(this.api,t.api,this.repo,this.user.login),this.contentOnly=e.contentOnly===!0,this.contentOnly?e.code&&(this.updates=be({repo:e.code.repo,webUrl:e.code.webUrl,read:e.code.api,manage:t.codeApi,open:(t,r)=>new n({api:t,repo:e.code?.repo??``,branch:r,user:this.user,onSignOut:()=>{},ownerTokenLink:e.ownerTokenLink})})):this.updates=P(this.api,t.api,this.repo,this.branch,this),this.drafts=r(this,this.draftHost()),this.scheduledBuilds=pe(e=>this.read(e)),this.tasks=he(this.api,this.repo,this.branch,e.tasks)}async repositoryVisibility(){let e=await O(this.api,`/repos/${this.repo}`);return e.visibility===`internal`?`internal`:e.private===!1||e.visibility===`public`?`public`:`private`}draftHost(){let e=`/repos/${this.repo}`,t=t=>`${e}/git/refs/heads/${encodeURIComponent(t)}`,r=t=>{let n=decodeURIComponent(this.repo).split(`/`)[0],r=new URLSearchParams({head:`${n}:${t}`,state:`open`});return O(this.api,`${e}/pulls?${r}`)};return{liveBranch:this.branch,branchStore:e=>new n({...this.options,branch:e}),createBranch:async(t,n)=>{if((await D(this.api,`${e}/git/refs`,{method:`POST`,body:{ref:`refs/heads/${t}`,sha:n},allow:[422]})).status===422)throw new l(`exists`,`There's already a draft called ${t}.`)},deleteBranch:async e=>{await D(this.api,t(e),{method:`DELETE`,allow:[404,422]})},mergeBase:async t=>{let n=await D(this.api,`${e}/compare/${encodeURIComponent(this.branch)}...${encodeURIComponent(t)}`,{allow:[404]});if(n.status===404)throw new l(`not-found`,`There's no draft ${t}.`);return(await n.json()).merge_base_commit.sha},listBranches:async t=>(await O(this.api,`${e}/git/matching-refs/heads/${encodeURIComponent(t)}`)).map(e=>e.ref.replace(/^refs\/heads\//,``)),openReview:async({branch:t,title:n,description:i,reviewers:a=[]})=>{let o=await D(this.api,`${e}/pulls`,{method:`POST`,body:{title:n,head:t,base:this.branch,body:i},allow:[403,404,422]}),s;if(o.ok)s=await o.json();else if(o.status!==422)throw new l(`no-reviews`,`This sign-in can't open pull requests on the site's repository.`);else if([s]=await r(t),!s)throw new E(422,`GitHub couldn't open the pull request.`);for(let t of a)await D(this.api,`${e}/pulls/${s.number}/requested_reviewers`,{method:`POST`,body:{reviewers:[t]},allow:[403,404,422]});return{url:s.html_url}},listReviews:async t=>{let n=await O(this.api,`${e}/pulls?${new URLSearchParams({state:`open`,per_page:`100`})}`),r=decodeURIComponent(this.repo).toLowerCase();return n.filter(e=>e.head.ref.startsWith(t)&&(e.head.repo?.full_name??r).toLowerCase()===r).map(e=>({branch:e.head.ref,url:e.html_url,title:e.title}))},closeReview:async t=>{for(let n of await r(t))await D(this.api,`${e}/pulls/${n.number}`,{method:`PATCH`,body:{state:`closed`},allow:[403,404,422]})}}}async revision(){return this.pinned=await this.head(),this.pinned}async head(){return(await O(this.api,`/repos/${this.repo}/git/ref/heads/${encodeURIComponent(this.branch)}`)).object.sha}tree(e){let t=this.trees.get(e);return t||(t=O(this.api,`/repos/${this.repo}/git/trees/${e}?recursive=1`).then(t=>{if(t.truncated)throw new E(413,`The repository has too many files for GitHub to list at once.`);let n=t.tree.filter(e=>e.type===`blob`);return this.treeSizes.set(e,new Map(n.flatMap(e=>e.size===void 0?[]:[[e.path,e.size]]))),new Map(n.map(e=>[e.path,e.sha]))}),t.catch(()=>this.trees.delete(e)),this.trees.set(e,t)),t}async currentTree(){return this.tree(this.pinned??await this.revision())}async read(e){return this.readFrom(await this.currentTree(),e)}readFrom(e,t){let n=e.get(t);if(!n)return Promise.resolve(void 0);let r=this.blobs.get(n);return r||(r=D(this.api,`/repos/${this.repo}/git/blobs/${n}`,{accept:`application/vnd.github.raw+json`}).then(e=>e.text()),r.catch(()=>this.blobs.delete(n)),this.blobs.set(n,r)),r}async readFiles(e){return this.readFilesFrom(await this.currentTree(),e)}async readFilesFrom(e,t){let n=t.flatMap(t=>{let n=e.get(t);return n?[[t,n]]:[]}),r=[...new Set(n.map(([,e])=>e).filter(e=>!this.blobs.has(e)))],[i,a]=decodeURIComponent(this.repo).split(`/`);for(let e=0;e<r.length;e+=L){let t=r.slice(e,e+L),n=t.map((e,t)=>`f${t}: object(oid: "${e}") { ... on Blob { text isBinary } }`),o=await O(this.api,`/graphql`,{method:`POST`,body:{query:`query ($owner: String!, $name: String!) { repository(owner: $owner, name: $name) { ${n.join(` `)} } }`,variables:{owner:i,name:a}}});t.forEach((e,t)=>{let n=o.data?.repository?.[`f${t}`];n&&!n.isBinary&&typeof n.text==`string`&&this.blobs.set(e,Promise.resolve(n.text))})}let o=new Map;for(let[t]of n){let n=await this.readFrom(e,t);n!==void 0&&o.set(t,n)}return o}async readBytes(e){return this.bytesFrom(await this.currentTree(),e)}async bytesFrom(e,t){let n=e.get(t);if(!n)return;let r=await D(this.api,`/repos/${this.repo}/git/blobs/${n}`,{accept:`application/vnd.github.raw+json`});return new Uint8Array(await r.arrayBuffer())}async list(e){return R(await this.currentTree(),e)}filesAt(e){return{read:async t=>this.readFrom(await this.tree(e),t),readFiles:async t=>this.readFilesFrom(await this.tree(e),t),readBytes:async t=>this.bytesFrom(await this.tree(e),t),list:async t=>R(await this.tree(e),t)}}async fileSizes(e){let t=`${e}/`;await this.currentTree();let n=this.treeSizes.get(this.pinned??``)??new Map;return new Map([...n].filter(([e])=>e.startsWith(t)))}useOwnerSignIn(e,t,n){this.owner.useSignIn(e,t,n)}ownerSignInFailed(e){this.owner.failed(e)}async write(t,n){return this.contentOnly&&e(t),this.author?this.writeCommit(t,n,this.author):this.writeGraphQL(t,n)}async writeCommit(e,{message:n,expectedRevision:r},i){let a=await this.tree(r),o=`/repos/${this.repo}`,s=[];try{for(let t of e)if(`delete`in t)a.has(t.path)&&s.push({path:t.path,mode:`100644`,type:`blob`,sha:null});else if(`bytes`in t){let e=await O(this.api,`${o}/git/blobs`,{method:`POST`,body:{content:w(t.bytes),encoding:`base64`}});s.push({path:t.path,mode:`100644`,type:`blob`,sha:e.sha})}else s.push({path:t.path,mode:`100644`,type:`blob`,content:t.content});if(s.length===0)return{revision:r};let c=await O(this.api,`${o}/git/commits/${r}`),l=await O(this.api,`${o}/git/trees`,{method:`POST`,body:{base_tree:c.tree.sha,tree:s}}),u=await O(this.api,`${o}/git/commits`,{method:`POST`,body:{message:n,tree:l.sha,parents:[r],author:{...i,date:new Date().toISOString()}}});if((await D(this.api,`${o}/git/refs/heads/${encodeURIComponent(this.branch)}`,{method:`PATCH`,body:{sha:u.sha,force:!1},allow:[422]})).status===422)throw await this.revision()===r?new E(422,`GitHub couldn't save the changes.`):new t;return this.pinned=u.sha,{revision:u.sha}}catch(e){throw e instanceof E&&(e.status===403||e.status===404)?new C(`cant-publish`,`This sign-in can't publish to the site.`):e}}async writeGraphQL(e,{message:n,expectedRevision:r}){let i=await this.tree(r),a=e.flatMap(e=>`delete`in e?[]:[{path:e.path,contents:`bytes`in e?w(e.bytes):x(e.content)}]),o=e.flatMap(e=>`delete`in e&&i.has(e.path)?[{path:e.path}]:[]);if(a.length===0&&o.length===0)return{revision:r};let s=await O(this.api,`/graphql`,{method:`POST`,body:{query:xe,variables:{input:{branch:{repositoryNameWithOwner:decodeURIComponent(this.repo),branchName:this.branch},message:{headline:n},expectedHeadOid:r,fileChanges:{additions:a,deletions:o}}}}}),c=s.data?.createCommitOnBranch?.commit?.oid;if(c)return this.pinned=c,{revision:c};if(await this.revision()!==r)throw new t;let l=s.errors?.[0];throw l?.type===`FORBIDDEN`?new C(`cant-publish`,`This GitHub account can't publish to the site.`):new E(422,`GitHub couldn't save the changes: ${l?.message??`unknown error`}`)}async history(e,{page:t=1,perPage:n=20}={}){let r=new URLSearchParams({sha:this.pinned??await this.revision(),path:e,per_page:String(n),page:String(t)});return(await O(this.api,`/repos/${this.repo}/commits?${r}`)).map(e=>({revision:e.sha,date:e.commit.author?.date??``,author:e.commit.author?.name||e.author?.login||void 0,message:e.commit.message.split(`
5
+ `)[0]??``}))}async readAt(e,t){return this.readFrom(await this.tree(t),e)}async changedPaths(e,t){let n=await O(this.api,`/repos/${this.repo}/compare/${e}...${t}`);return[...new Set((n.files??[]).flatMap(e=>[e.filename,e.previous_filename??[]].flat()))]}async deployStatus(e){try{let t=(await O(this.api,`/repos/${this.repo}/deployments?sha=${e}&per_page=5`))[0];if(t){let[n]=await O(this.api,`/repos/${this.repo}/deployments/${t.id}/statuses?per_page=1`),r=n?.log_url||n?.target_url||void 0;switch(n?.state){case`success`:case`inactive`:return{state:`live`,detailsUrl:r};case`failure`:case`error`:{let t=await this.workflowRun(e);return{state:`failed`,detailsUrl:r,problem:t?await this.runProblem(t):{step:`deploy`}}}default:return{state:`building`,detailsUrl:r}}}let n=await this.cloudflareBuild(e);if(n)return n;let r=await this.workflowRun(e);if(r?.status===`completed`&&r.conclusion===`cancelled`){let t=await this.head();if(t!==e)return this.deployStatus(t)}if(r?.status===`completed`&&r.conclusion!==`success`&&r.conclusion!==`skipped`)return{state:`failed`,detailsUrl:r.html_url,problem:await this.runProblem(r)};if(r)return{state:`building`,detailsUrl:r.html_url};if((await O(this.api,`/repos/${this.repo}/deployments?per_page=1`)).length>0)return{state:`building`};let i=await this.parentOf(e);return{state:i&&await this.cloudflareBuild(i)?`building`:`unknown`}}catch(e){if(e instanceof E)return{state:`unknown`};throw e}}async cloudflareBuild(e){let t=await D(this.api,`/repos/${this.repo}/commits/${e}/check-runs?per_page=50`,{allow:[403,404]});if(t.ok){let{check_runs:e=[]}=await t.json(),n=e.find(e=>I.test(`${e.app?.slug??``} ${e.name}`));if(n){let e=n.details_url??n.html_url??void 0;return n.status===`completed`?n.conclusion===`success`||n.conclusion===`neutral`||n.conclusion===`skipped`?{state:`live`,detailsUrl:e}:{state:`failed`,detailsUrl:e,problem:{step:`build`,detail:n.output?.title??void 0}}:{state:`building`,detailsUrl:e}}}let n=await D(this.api,`/repos/${this.repo}/commits/${e}/statuses?per_page=50`,{allow:[403,404]});if(!n.ok)return;let r=(await n.json()).find(e=>I.test(e.context));if(!r)return;let i=r.target_url??void 0;return r.state===`success`?{state:`live`,detailsUrl:i}:r.state===`pending`?{state:`building`,detailsUrl:i}:{state:`failed`,detailsUrl:i,problem:{step:`build`,detail:r.description??void 0}}}async parentOf(e){let t=await D(this.api,`/repos/${this.repo}/commits/${e}`,{allow:[403,404,422]});if(t.ok)return(await t.json()).parents?.[0]?.sha}async workflowRun(e){let t=await D(this.api,`/repos/${this.repo}/actions/runs?head_sha=${e}&per_page=20`,{allow:[403,404]});if(!t.ok)return;let{workflow_runs:n=[]}=await t.json(),r=n.filter(e=>!e.path?.endsWith(`/${b}.yml`));return r.find(e=>/deploy|pages/i.test(`${e.path??``} ${e.name??``}`))??r[0]}async runProblem(e){let t=await D(this.api,`/repos/${this.repo}/actions/runs/${e.id}/jobs?per_page=50`,{allow:[403,404]}),n=(t.ok?(await t.json()).jobs??[]:[]).find(e=>e.conclusion===`failure`);if(!n)return{step:e.conclusion===`startup_failure`?`not-started`:`host`,detail:e.conclusion??void 0};let r=n.steps?.find(e=>e.conclusion===`failure`);return r?{step:y(r.name)??(/deploy/i.test(n.name)?`deploy`:`build`),detail:`${n.name}: ${r.name}`}:{step:`not-started`,detail:n.name}}signOut(){this.ownerAccess.forget(),this.onSignOut()}},Se={invalid:`invalid`,"no-access":`no-access`,"cant-publish":`cant-publish`,cancelled:`redirect-failed`,"not-installed":`not-installed`,"no-address":`no-address`,"wrong-address":`wrong-address`,"local-address":`local-address`,"not-owner":`not-owner`};function B(e){let t=Se[String(e.problem)]??`unavailable`,n=typeof e.installUrl==`string`&&/^https:\/\//.test(e.installUrl)?e.installUrl:void 0;return new C(t,`The sign-in service said: ${String(e.problem??`no answer`)}.`,n)}function V(e){let t;try{t=new URL(e).hostname.toLowerCase()}catch{return!0}return t===`localhost`||t.endsWith(`.localhost`)||t===`0.0.0.0`||t===`[::1]`||/^127(\.\d{1,3}){3}$/.test(t)}function H(e){let t=``;for(let n of e)t+=String.fromCharCode(n);return btoa(t).replace(/\+/g,`-`).replace(/\//g,`_`).replace(/=+$/,``)}async function U(){let e=H(crypto.getRandomValues(new Uint8Array(32))),t=await crypto.subtle.digest(`SHA-256`,new TextEncoder().encode(e));return{verifier:e,challenge:H(new Uint8Array(t))}}function W(){if(typeof window<`u`)return{href:()=>window.location.href,assign:e=>window.location.assign(e),replace:e=>window.history.replaceState(window.history.state,``,e)}}var G=`goodfellow_sign_in`,K=`goodfellow_sign_in_error`,q=class{url;fetcher;constructor(e,t){this.url=e.replace(/\/$/,``),this.fetcher=t}async answer(e,t){let n;try{n=await this.fetcher(`${this.url}${e}`,t)}catch(e){throw new C(`unavailable`,`Couldn't reach the sign-in service: ${e.message}`)}let r=await n.json().catch(()=>({}));if(!n.ok)throw B(r);return r}post(e,t){return this.answer(e,{method:`POST`,headers:{"content-type":`text/plain`},body:JSON.stringify(t)})}async postAnswer(e,t){let n;try{n=await this.fetcher(`${this.url}${e}`,{method:`POST`,headers:{"content-type":`text/plain`},body:JSON.stringify(t)})}catch(e){throw new C(`unavailable`,`Couldn't reach the sign-in service: ${e.message}`)}return{ok:n.ok,body:await n.json().catch(()=>({}))}}async health(){if((await this.answer(`/health`)).ok!==!0)throw new C(`unavailable`,`The sign-in service isn't set up.`)}async check(e,t){await this.answer(`/github/check?${new URLSearchParams({repo:e,purpose:t})}`)}startUrl(e){let t=new URLSearchParams({purpose:e.purpose,...e.repo&&{repo:e.repo},return:e.back,challenge:e.challenge,remember:e.remember?`1`:`0`,...e.install&&{install:`1`}});return`${this.url}/github/start?${t}`}session(e,t,n){return this.post(`/github/session`,{grant:e,verifier:t,...n&&{code:n}})}token(e,t){return this.post(`/github/token`,{session:e,...t&&{code:t}})}};function J(e,t,n,r){let i=t,a;async function o({refresh:t=!1}={}){return(t||Date.parse(i.expiresAt)-3e5<Date.now())&&(a??=e.token(i.session,r).finally(()=>{a=void 0}),i=await a,n(i)),i.token}return{current:()=>i,token:o,async codeToken(e){if(await o(e),!i.code)throw new C(`no-access`,`The sign-in service gave no token for the code repository.`);return i.code.token}}}var Y=`main`;function X(e=`https://github.com`){let t=new URLSearchParams({name:`Goodfellow site setup`,description:`Create a Goodfellow site. It can be deleted once the site is created.`,expires_in:`7`,administration:`write`,contents:`write`,workflows:`write`,pages:`write`,secrets:`write`}),n=new URLSearchParams({scopes:`repo,workflow`,description:`Goodfellow site setup`});return{fineGrained:`${e}/settings/personal-access-tokens/new?${t}`,classic:`${e}/settings/tokens/new?${n}`}}function Z(e){let t=new Map;async function n(e,t){if(t.default_branch===Y)return;let n=`/repos/${k(t.full_name)}`,r=await O(e,`${n}/git/ref/heads/${t.default_branch}`);await D(e,`${n}/git/refs`,{method:`POST`,body:{ref:`refs/heads/${Y}`,sha:r.object.sha}}),await D(e,n,{method:`PATCH`,body:{default_branch:Y}}),await D(e,`${n}/git/refs/heads/${t.default_branch}`,{method:`DELETE`})}async function r(e,t){try{return(await O(e,`/repos/${k(t.full_name)}/pages`,{method:`POST`,body:{build_type:`workflow`}})).html_url}catch(e){if(e instanceof E&&[403,404,409,422].includes(e.status))return;throw e}}async function i(e,t,n){try{return await D(e,`/repos/${k(t.full_name)}/pages`,{method:`PUT`,body:{cname:n}}),!0}catch(e){if(e instanceof E&&[400,403,404,409,422].includes(e.status))return!1;throw e}}async function a(t,n,r){let i=`/repos/${k(n.full_name)}`,a=[];for(let e of r.files){if(`delete`in e)continue;if(`content`in e){a.push({path:e.path,mode:`100644`,type:`blob`,content:e.content});continue}let n=await O(t,`${i}/git/blobs`,{method:`POST`,body:{content:w(e.bytes),encoding:`base64`}});a.push({path:e.path,mode:`100644`,type:`blob`,sha:n.sha})}try{let n=await O(t,`${i}/git/trees`,{method:`POST`,body:{tree:a}}),o=await O(t,`${i}/git/commits`,{method:`POST`,body:{message:r.message,tree:n.sha,parents:[],...e.author&&{author:{...e.author,date:new Date().toISOString()}}}});await D(t,`${i}/git/refs/heads/${Y}`,{method:`PATCH`,body:{sha:o.sha,force:!0}})}catch(e){throw e instanceof E&&(e.status===403||e.status===404)?new g(`not-allowed`,`This sign-in can't write the site's files, or its build setup.`):e}}async function o(e,t){return(await D(e,`/repos/${k(t.full_name)}/rulesets`,{method:`POST`,body:{name:`Protect the published history`,target:`branch`,enforcement:`active`,conditions:{ref_name:{include:[`~DEFAULT_BRANCH`],exclude:[]}},rules:[{type:`non_fast_forward`},{type:`deletion`}]},allow:[403,404,422]})).ok}async function s(e){if(!e.private)return!0;let{newDeployKey:n,sealSecret:r}=await te(()=>import(`./deploy-keys-DAj2oX-N-DRd_HmOG.js`).then(e=>e.t),__vite__mapDeps([0,1]),import.meta.url);try{for(let{reader:i,read:a,secret:o}of e.links.reads){if(!o)continue;let s=e[i],c=e[a],l=t.get(s.toLowerCase()),u=t.get(c.toLowerCase());if(!l||!u)throw Error(`${s} and ${c} have to be created first.`);let d=await n(`goodfellow-${o.toLowerCase()}`);await D(u,`/repos/${k(c)}/keys`,{method:`POST`,body:{title:`${s}'s builds`,key:d.publicKey,read_only:!0}});let f=`/repos/${k(s)}/actions/secrets`,p=await O(l,`${f}/public-key`),m=await r(d.privateKey,S(p.key));await D(l,`${f}/${o}`,{method:`PUT`,body:{encrypted_value:w(m),key_id:p.key_id}})}return!0}catch(e){if(e instanceof E&&[403,404,422].includes(e.status))return!1;throw e}}return{user:e.user,owners:()=>e.owners(),isAvailable:(t,n)=>e.isAvailable(t,n),async createSite(c,l){let u=[];l?.(`repository`);let{repo:d,api:f,signInInstallUrl:p}=await e.create(c),m=async e=>(l?.(`connect`),s(e));await n(f,d);let h;return c.pages&&(l?.(`pages`),h=await r(f,d),h?c.domain&&(await i(f,d,c.domain)?h=`https://${c.domain}/`:u.push(`domain-unavailable`)):u.push(`pages-unavailable`)),t.set(d.full_name.toLowerCase(),f),c.link&&!await m(c.link)&&u.push(`connect-unavailable`),l?.(`files`),await a(f,d,c),l?.(`protection`),await o(f,d)||u.push(`protection-unavailable`),p&&u.push(`sign-in-not-installed`),{repo:d.full_name,webUrl:d.html_url,siteUrl:h,buildsUrl:`${d.html_url}/actions`,pagesUrl:c.pages?`${d.html_url}/settings/pages`:void 0,...p&&{signInInstallUrl:p},warnings:u}}}}async function Ce(e){let t=await O(e,`/user`);async function n(t,n){return(await D(e,`/repos/${k(`${t.path}/${n}`)}`,{allow:[404]})).status===404}return{user:{login:t.login,name:t.name??void 0,avatarUrl:t.avatar_url},async owners(){return[{path:t.login,name:t.name||t.login,kind:`personal`},...(await O(e,`/user/orgs?per_page=100`).catch(()=>[])).map(e=>({path:e.login,name:e.login,kind:`organization`}))]},isAvailable:n,async create(t){let r=t.owner.kind===`personal`?`/user/repos`:`/orgs/${encodeURIComponent(t.owner.path)}/repos`;if(!await n(t.owner,t.name))throw new g(`name-taken`,`There's already a repository called ${t.owner.path}/${t.name}.`);try{return{repo:await O(e,r,{method:`POST`,body:{name:t.name,private:t.private,description:t.description,auto_init:!0}}),api:e}}catch(e){throw e instanceof E&&e.status===422?new g(`name-invalid`,`GitHub can't use "${t.name}" as a repository name.`):e instanceof E&&(e.status===403||e.status===404)?new g(`not-allowed`,`This sign-in can't create repositories in ${t.owner.path}.`):e}}}}function we(e,t,n,r){let i=r.session;async function a(t,n){let r=await e.postAnswer(t,{session:i,...n});if(typeof r.body.session==`string`&&(i=r.body.session),!r.ok)throw Te(r.body);return r.body}let{user:o}=r;return{user:{login:o.login,name:o.name,avatarUrl:o.avatarUrl},author:{name:o.name||o.login,email:o.email},async owners(){let e=await a(`/github/setup/owners`,{});return(e.owners??[]).map(({installed:t,...n})=>({...n,...!t&&e.installUrl&&{installUrl:e.installUrl}}))},async isAvailable(e,t){return(await a(`/github/setup/available`,{owner:e.path,name:t})).available===!0},async create(e){if(e.owner.installUrl)throw new g(`not-installed`,`Goodfellow isn't installed on ${e.owner.path}.`,e.owner.installUrl);let r=await a(`/github/setup/repository`,{owner:e.owner.path,name:e.name,private:e.private,description:e.description,...e.more&&{more:!0}});if(!r.repo||!r.token)throw new C(`unavailable`,`The sign-in service didn't answer.`);return{repo:r.repo,api:{apiUrl:t,token:r.token,fetch:n},...r.editInstallUrl&&{signInInstallUrl:r.editInstallUrl}}}}}function Te(e){switch(e.problem){case`name-taken`:return new g(`name-taken`,`There's already a repository with that name.`);case`name-invalid`:return new g(`name-invalid`,`GitHub can't use that as a repository name.`);case`not-allowed`:return new g(`not-allowed`,`This sign-in can't create repositories there.`);case`not-installed`:return new g(`not-installed`,`Goodfellow isn't installed there.`,e.installUrl);default:return B(e)}}function Q(e={}){let t=(e.apiUrl??`https://api.github.com`).replace(/\/$/,``),n=X(e.webUrl),r=e.fetch??((e,t)=>globalThis.fetch(e,t)),i=e.location??W(),a=e.signIn,o=a&&i&&!V(i.href())?new q(a,r):void 0,s=T(`goodfellow:github:setup:pending`,e.storage);return{name:`GitHub`,canRedirect:o!==void 0,tokenLinks:[{url:n.fineGrained,label:`signIn.token.create`},{url:n.classic,label:`signIn.token.createBroad`,hint:`signIn.token.createBroadHint`}],async restore(){if(!i)return null;let e=new URL(i.href()),n=e.searchParams.get(G),a=e.searchParams.get(K);if(!n&&!a)return null;e.searchParams.delete(G),e.searchParams.delete(K),i.replace(e.toString());let c=s.load();if(s.clear(),a||!n||!c||!o)throw B({problem:a??`cancelled`});let l=await o.session(n,c);return Z(we(o,t,r,l))},async signInWithToken(e){let n=e.trim();if(!n)throw new C(`invalid`,`Paste a token to sign in.`);return Z(await Ce({apiUrl:t,token:n,fetch:r}))},async startRedirect({install:e}={}){if(!o||!i)throw new C(`unavailable`,`Signing in with GitHub isn't available here.`);await o.health();let{verifier:t,challenge:n}=await U();s.save(t,!1),i.assign(o.startUrl({purpose:`setup`,back:i.href(),challenge:n,remember:!1,install:e}))}}}function Ee(e){return e instanceof C&&e.problem!==`unavailable`}function $(e,t=`https://github.com`){let[n=``]=e.split(`/`),r=new URLSearchParams({name:`Goodfellow admin`,description:`Edit ${e} in its admin panel`,target_name:n,expires_in:`90`,contents:`write`,deployments:`read`,actions:`read`,statuses:`read`}),i=new URLSearchParams({scopes:`repo`,description:`Goodfellow admin for ${e}`}),a=new URLSearchParams({name:`Goodfellow owner`,description:`Manage the editors, domain and updates of ${e}`,target_name:n,expires_in:`1`,administration:`write`,pages:`write`,actions:`write`,variables:`write`});return{fineGrained:`${t}/settings/personal-access-tokens/new?${r}`,classic:`${t}/settings/tokens/new?${i}`,owner:`${t}/settings/personal-access-tokens/new?${a}`}}function De(e){if(!/^[\w.-]+\/[\w.-]+$/.test(e.repo))throw Error(`github({ repo }) must look like "owner/name" (got "${e.repo}").`);if(e.code!==void 0&&!/^[\w.-]+\/[\w.-]+$/.test(e.code))throw Error(`github({ code }) must look like "owner/name" (got "${e.code}").`);let t=(e.apiUrl??`https://api.github.com`).replace(/\/$/,``),n=(e.webUrl??`https://github.com`).replace(/\/$/,``),r=t=>e.contentOnly&&e.code?{code:{repo:e.code,webUrl:n,...t&&{api:t}}}:{},i=$(e.repo,e.webUrl),a=T(`goodfellow:github:${e.repo}`,e.storage),o=T(`goodfellow:github:pending:${e.repo}`,e.storage),s=d(e.fetch??((e,t)=>globalThis.fetch(e,t)),{host:`GitHub`}),c=e.location??W(),l=e.signIn,u=l&&c&&!V(c.href())?new q(l,s):void 0,f=()=>e.contentOnly?e.code:void 0;function p(e,t){a.save(e.type===`token`?e.token:JSON.stringify(e),t)}function m(){let e=a.load();if(e){try{let t=JSON.parse(e);if(t.type===`worker`||t.type===`token`)return t}catch{}return{type:`token`,token:e}}}async function h(t,n,{install:r}={}){if(!u||!c)throw new C(`unavailable`,`Signing in with GitHub isn't available here.`);r||await u.check(e.repo,t);let{verifier:i,challenge:a}=await U();o.save(JSON.stringify({verifier:i,purpose:t,remember:n}),!1),c.assign(u.startUrl({purpose:t,repo:e.repo,back:c.href(),challenge:a,remember:n,install:r}))}async function g(n,o){let c=J(u,n,e=>p({type:`worker`,session:e.session},o),e.code);p({type:`worker`,session:n.session},o);let l={apiUrl:t,token:c.token,fetch:s},d=await O(l,`/repos/${k(e.repo)}`),{user:f}=n;return new z({api:l,repo:e.repo,branch:e.branch??d.default_branch,...e.contentOnly&&{contentOnly:!0},...r({apiUrl:t,token:c.codeToken,fetch:s}),user:{login:f.login,name:f.name,avatarUrl:f.avatarUrl},author:{name:f.name||f.login,email:f.email},onSignOut:()=>{a.clear(),v(c.current())},ownerTokenLink:{url:i.owner,label:`owner.token.create`},ownerRedirect:e=>h(`owner`,!1,e)})}function _(e){D({apiUrl:t,token:e,fetch:s},`/installation/token`,{method:`DELETE`}).catch(()=>{})}function v(e){_(e.token),e.code&&_(e.code.token)}async function y(e,t,n){let r=u;try{let i=J(r,await r.session(t,n,f()),()=>{},f());e.useOwnerSignIn(i.token,()=>v(i.current()),f()?i.codeToken:void 0)}catch(t){e.ownerSignInFailed(t)}}async function b(){if(!c)return;let e=new URL(c.href()),t=e.searchParams.get(G),n=e.searchParams.get(K);if(!t&&!n)return;e.searchParams.delete(G),e.searchParams.delete(K),c.replace(e.toString());let r=o.load();o.clear();let i=r?JSON.parse(r):void 0;if(!i||!u)throw new C(`redirect-failed`,`Signing in with GitHub didn't finish in this tab.`);if(i.purpose===`owner`){let e=await x();if(!e)throw new C(`redirect-failed`,`Sign in again, then sign in as an owner.`);return n?e.ownerSignInFailed(B({problem:n})):t&&await y(e,t,i.verifier),e}if(n||!t)throw B({problem:n});return g(await u.session(t,i.verifier,f()),i.remember)}async function x(){let e=m();if(!e)return null;try{return e.type===`token`?await S(e.token):u?await g(await u.token(e.session,f()),a.remembered()):(a.clear(),null)}catch(e){throw Ee(e)&&a.clear(),e}}async function S(n){let o={apiUrl:t,token:n,fetch:s},c=await O(o,`/user`),l;try{l=await O(o,`/repos/${k(e.repo)}`)}catch(t){throw t instanceof E&&(t.status===404||t.status===403)?new C(`no-access`,`The GitHub account ${c.login} can't see ${e.repo}.`):t}if(l.permissions?.push===!1)throw new C(`cant-publish`,`The GitHub account ${c.login} can see ${e.repo} but can't publish to it.`);return new z({api:o,repo:e.repo,branch:e.branch??l.default_branch,...e.contentOnly&&{contentOnly:!0},...r(o),user:{login:c.login,name:c.name??void 0,avatarUrl:c.avatar_url},onSignOut:()=>a.clear(),ownerTokenLink:{url:i.owner,label:`owner.token.create`},...u&&{ownerRedirect:e=>h(`owner`,!1,e)}})}function w({token:n}){let r={apiUrl:t,token:typeof n==`string`?n:()=>n(),fetch:s},a,o=()=>(a??=e.branch?Promise.resolve(e.branch):O(r,`/repos/${k(e.repo)}`).then(e=>e.default_branch),a.catch(()=>{a=void 0}),a),c=async(t,n)=>new z({api:r,repo:e.repo,branch:await o(),...e.contentOnly&&{contentOnly:!0},user:{login:n?.name??e.repo,...n&&{name:n.name}},onSignOut:()=>{},ownerTokenLink:{url:i.owner,label:`owner.token.create`},...t&&{revision:t},...n&&{author:n}});return{...e.contentOnly&&{contentOnly:!0},async revision(){return(await c()).revision()},at:c}}return{name:`GitHub`,source:ee(`@goodfellow-cms/github`,`github`,e,[`fetch`,`storage`,`location`]),canRedirect:u!==void 0,setup:()=>Q({...e.apiUrl&&{apiUrl:e.apiUrl},...e.webUrl&&{webUrl:e.webUrl},...e.fetch&&{fetch:e.fetch},...e.storage&&{storage:e.storage},...e.location&&{location:e.location},signIn:!1}),tokenLinks:[{url:i.fineGrained,label:`signIn.token.create`},{url:i.classic,label:`signIn.token.createBroad`,hint:`signIn.token.createBroadHint`}],async restore(){let e=await b();return e===void 0?x():e},async signInWithToken(e,t){let n=e.trim();if(!n)throw new C(`invalid`,`Paste a token to sign in.`);let r=await S(n);return p({type:`token`,token:n},t),r},startRedirect(e,t){return h(`edit`,e,t)},server:w}}export{z as GitHubBackend,De as github,N as githubPagesRecords,Q as githubSetup,X as githubSetupTokenLinks,$ as githubTokenLinks};
@@ -0,0 +1,2 @@
1
+ import{n as e,r as t,t as n}from"./store-BtVaZcVF.js";import{a as r,d as i,g as a,h as o,i as s,l as c,m as l,n as u,o as d,r as f,s as ee,t as p,v as m}from"./setup-host-DQm73dc_.js";import{n as h,r as g,s as _,t as v}from"./git-eGyMXUIu.js";import{t as te}from"./scheduled-builds-BPfDaBYt.js";var ne=`bitbucket-pipelines.yml`,y=`VERCEL_DEPLOY_HOOK`,b={update:`update`,scheduled:`scheduled`,task:c,rebuild:`rebuild`},x={update:`0 17 4 * * ? *`};function re(e){return`0 ${e.getUTCMinutes()} ${e.getUTCHours()} ${e.getUTCDate()} ${e.getUTCMonth()+1} ? ${e.getUTCFullYear()}`}function S(e){let t=/^0 (\d{1,2}) (\d{1,2}) (\d{1,2}) (\d{1,2}) \? (\d{4})$/.exec(e.trim());if(!t)return;let[,n,r,i,a,o]=t.map(Number);return new Date(Date.UTC(o??0,(a??1)-1,i,r,n))}`${y}${y}${y}`;function C(e){return e.type===`api-token`?`Basic ${btoa(`${e.email}:${e.token}`)}`:`Bearer ${e.type===`oauth`?e.access:e.token}`}function w(e){let t=e.trim(),n=t.indexOf(`:`),r=t.slice(0,n);return n>0&&/^[^\s@:]+@[^\s@:]+$/.test(r)?{type:`api-token`,email:r,token:t.slice(n+1)}:{type:`token`,token:t}}async function ie(e){let t=await e.json().catch(()=>({})),n=t.error?.message??t.message;return typeof n==`string`?n:``}async function T(e,t,n={}){let r=t.startsWith(`https://`)||t.startsWith(`http://`)?t:`${e.apiUrl}${t}`,i=await e.fetch(r,{method:n.method??(n.body!==void 0||n.form?`POST`:`GET`),headers:{authorization:await e.authorization(),...n.body!==void 0&&{"content-type":`application/json`}},body:n.form??(n.body===void 0?void 0:JSON.stringify(n.body)),cache:`no-store`});if(i.status===401&&!n.allow?.includes(401))throw new h(`invalid`,`Bitbucket didn't accept the sign-in. It may have expired.`);if(!i.ok&&!n.allow?.includes(i.status)){let e=await ie(i);throw new v(i.status,`Bitbucket answered ${i.status}: ${e||i.statusText}`)}return i}async function E(e,t,n){return await(await T(e,t,n)).json()}async function D(e,t,n=20){let r=[],i=t;for(let t=0;i&&t<n;t++){let t=await E(e,i);r.push(...t.values),i=t.next}return r}function O(e){return`"${e.replace(/\\/g,`\\\\`).replace(/"/g,`\\"`)}"`}async function k(e,t,n,r,i={}){return E(e,`/repositories/${t}/pipelines/`,{body:{target:{type:`pipeline_ref_target`,ref_type:`branch`,ref_name:n,selector:{type:`custom`,pattern:r}},variables:Object.entries(i).map(([e,t])=>({key:e,value:t,secured:!1}))}})}function A(e,t){return D(e,`/repositories/${t}/pipelines_config/schedules?pagelen=100`,5)}function j(e,t){return e.find(e=>e.target?.selector?.pattern===t)}function ae(e,t,n){return`${e}/${t}/pipelines/results/${n.build_number??encodeURIComponent(n.uuid)}`}var M=`https://bitbucket.org`,N=`https://api.bitbucket.org/2.0`;function P(e){throw e instanceof h||e instanceof v&&(e.status===403||e.status===404)?new a(`not-allowed`,`Setting up updates on Bitbucket needs the repository's Admin permission: use an owner token.`):e}function F(e){return e instanceof v&&(e.status===403||e.status===404)}function I(e,t,n,r,i){let{update:s}=b,c=`/repositories/${n}/pipelines_config`,l=()=>t()??e;async function u(){return j(await A(l(),n),s)}async function d(){return D(l(),`${c}/variables?pagelen=100`,5)}return{async setup(){if(!(await i())?.includes(`update --publish`))return`missing`;try{let[e,t]=await Promise.all([E(l(),c),A(l(),n)]);return e.enabled&&j(t,s)?`ready`:`needs-setup`}catch(e){if(F(e))return`needs-setup`;throw e}},async enable(){(await E(l(),c).catch(P)).enabled||await T(l(),c,{method:`PUT`,body:{enabled:!0}}).catch(P),!j(await A(l(),n).catch(P),s)&&await T(l(),`${c}/schedules`,{body:{type:`pipeline_schedule`,enabled:!0,cron_pattern:x.update,target:{type:`pipeline_ref_target`,ref_type:`branch`,ref_name:r,selector:{type:`custom`,pattern:s}}}}).catch(P)},async lastRun(){let t=await E(e,`/repositories/${n}/pipelines/?${new URLSearchParams({"target.selector.pattern":s,sort:`-created_on`,pagelen:`10`})}`);for(let r of t.values){let t={date:r.created_on,detailsUrl:ae(M,n,r)};if(r.state?.name!==`COMPLETED`)return{state:`running`,...t};let i=`/repositories/${n}/pipelines/${encodeURIComponent(r.uuid)}/steps`,[a]=(await E(e,i)).values;if(!a)continue;let o;try{let t=await T(e,`${i}/${encodeURIComponent(a.uuid)}/log`,{allow:[404]});o=t.ok?await t.text():``}catch(e){if(e instanceof h)throw e;return{state:`unknown`,...t}}let s=m(o);if(s)return{state:s.state,result:s,...t}}},async start(e){await k(l(),n,r,s,{GOODFELLOW_UPDATE:e}).catch(P)},nightly:{async supported(){return(await i())?.includes(` nightly `)??!1},async get(){try{let e=await u();return e?e.enabled?(await d()).find(e=>e.key===`GOODFELLOW_NIGHTLY`)?.value===`always`?`always`:`changed`:`off`:void 0}catch(e){if(F(e))return;throw e}},async set(e){let t=await u().catch(P);if(!t)throw new a(`missing`,`The site has no nightly schedule yet: turn on updates first.`);await T(l(),`${c}/schedules/${encodeURIComponent(t.uuid)}`,{method:`PUT`,body:{enabled:e!==`off`}}).catch(P);let n=(await d().catch(P)).find(e=>e.key===o);e===`always`&&n?await T(l(),`${c}/variables/${encodeURIComponent(n.uuid)}`,{method:`PUT`,body:{key:o,value:`always`,secured:!1}}).catch(P):e===`always`?await T(l(),`${c}/variables`,{body:{key:o,value:`always`,secured:!1}}).catch(P):n&&await T(l(),`${c}/variables/${encodeURIComponent(n.uuid)}`,{method:`DELETE`,allow:[404]}).catch(P)}},async rebuild(){await k(l(),n,r,s,{GOODFELLOW_REBUILD:`true`}).catch(P)}}}function L(e){return e instanceof h&&e.problem===`no-access`||e instanceof v&&(e.status===403||e.status===404)}function oe(e,t,n,r){let i,a=()=>(i??=E(e,`/repositories/${n}`).then(e=>e.mainbranch?.name??`main`),i.catch(()=>{i=void 0}),i);async function o(){return r(await a())}async function s(e){let t=await o();return t.readAt(e,await t.revision())}async function c(){return I(e,t,n,await a(),()=>s(ne))}return{code:{repo:n,webUrl:`${M}/${n}`,read:s,history:async(e,t)=>(await o()).history(e,t),readAt:async(e,t)=>(await o()).readAt(e,t)},async setup(){try{return await(await c()).setup()}catch(e){if(L(e))return`no-access`;throw e}},async enable(){await(await c()).enable?.()},async lastRun(){try{return await(await c()).lastRun()}catch(e){if(L(e))return;throw e}},async start(e){await(await c()).start(e)},async rebuild(){await(await c()).rebuild?.()}}}function R(e){return e===`admin`?`owner`:e===`write`?`editor`:`viewer`}function z(e){return e===`owner`?`admin`:`write`}function B(e){return e instanceof v&&(e.status===403||e.status===404)}function se(e,t){let n=t.toLowerCase();return[e.nickname,e.display_name,e.account_id,e.uuid].some(e=>e?.toLowerCase()===n)}function ce(e,t,n,r,i){let a=`/repositories/${n}/permissions-config/users`,o=n.split(`/`)[0]??``;function s(){let n=t()??(i===`admin`?e:void 0);if(!n)throw new f(`not-allowed`,`Changing editors on Bitbucket needs an owner token.`);return n}async function c(e){try{await e(s())}catch(e){throw e instanceof f?e:B(e)?new f(`not-allowed`,`This token can't change the site's editors.`):e}}async function l(e,t){if(t.includes(`@`)){let n=new URLSearchParams({q:`user.email IN (${O(t)})`});return(await E(e,`/workspaces/${o}/members?${n}`).catch(()=>void 0))?.values[0]?.user}return(await D(e,`/workspaces/${o}/members?pagelen=100`,10)).map(e=>e.user).find(e=>se(e,t))}return{invitesByEmail:!1,membersOnly:!0,async list(){let n=t()??e,o;try{o=await D(n,`${a}?pagelen=100`,10)}catch(e){if(!B(e))throw e;return{editors:[{id:r.login,login:r.login,name:r.name,role:R(i??`write`),self:!0}],manage:i===`admin`?`token`:`no`,editorsCanPublish:!0}}return{editors:o.filter(e=>e.permission!==`none`).map(e=>({id:e.user.uuid,login:e.user.nickname,name:e.user.display_name,avatarUrl:e.user.links?.avatar?.href,role:R(e.permission),...e.user.nickname?.toLowerCase()===r.login.toLowerCase()&&{self:!0}})),manage:t()||i===`admin`?`yes`:`no`,editorsCanPublish:!0}},async invite(e,t){let n=e.trim().replace(/^@/,``);if(!n||/\s{2,}|[/\\]/.test(n))throw new f(`invalid`,`That isn't a Bitbucket account name or email address.`);let r=s(),i=await l(r,n);if(!i)throw new f(`not-found`,`Nobody called ${n} is in the Bitbucket workspace ${o}: add them to the workspace on Bitbucket first.`);let c=`${a}/${encodeURIComponent(i.uuid)}`,u=await T(r,c,{allow:[403,404]});if(u.ok&&(await u.json()).permission!==`none`)throw new f(`already`,`${n} already edits the site.`);let d=await T(r,c,{method:`PUT`,body:{permission:z(t)},allow:[400,402,403,404]});if(!d.ok)throw d.status===402?new f(`invalid`,`The Bitbucket workspace's plan has as many users as it allows.`):d.status===400?new f(`invalid`,`Bitbucket didn't accept ${n}.`):new f(`not-allowed`,`This token can't change the site's editors.`)},async setRole(e,t){await c(n=>T(n,`${a}/${encodeURIComponent(e.id)}`,{method:`PUT`,body:{permission:z(t)}}))},async remove(e){await c(t=>T(t,`${a}/${encodeURIComponent(e.id)}`,{method:`DELETE`,allow:[404]}))}}}function V(e,t,n){let r;return{access:{tokenLink:n,get active(){return r!==void 0},async applyToken(n){let i=w(n),a={...e,authorization:async()=>C(i)};try{await E(a,`/repositories/${t}/permissions-config/users?pagelen=1`)}catch(e){throw e instanceof h||B(e)?new f(`not-allowed`,`This token can't change the site's editors.`):e}r=a},forget(){r=void 0}},api:()=>r}}var H=`name: Scheduled change`;function U(e){return e instanceof v&&(e.status===401||e.status===403)}function W(e){throw U(e)?new te(`not-allowed`,`Adding Pipelines schedules on Bitbucket needs permission to change the repository's Pipelines settings.`):e}function G(e){return e.toISOString().slice(0,16)}function le(e,t,n,r,i,a=()=>new Date){let{scheduled:o}=b,s=`/repositories/${n}/pipelines_config/schedules`;return{async timing(){let e=await i().catch(()=>void 0);return e?.includes(H)?`exact`:e?.includes(` nightly `)?`nightly`:`none`},async sync(i){let c=t()??e,l=a(),u=new Map;for(let e of[...i].filter(e=>e>l).sort((e,t)=>e.getTime()-t.getTime())){if(u.size>=50)break;u.set(G(e),e)}let d=new Map;for(let e of await A(c,n).catch(W)){if(e.target?.selector?.pattern!==o)continue;let t=S(e.cron_pattern);t&&d.set(G(t),e)}for(let[e,t]of d)u.has(e)||await T(c,`${s}/${encodeURIComponent(t.uuid)}`,{method:`DELETE`,allow:[403,404]});for(let[e,t]of u)d.has(e)||await E(c,s,{body:{type:`pipeline_schedule`,enabled:!0,cron_pattern:re(t),target:{type:`pipeline_ref_target`,ref_type:`branch`,ref_name:r,selector:{type:`custom`,pattern:o}}}}).catch(W)}}}function ue(e,t,n,r,a={}){let o=a.wait??(e=>new Promise(t=>setTimeout(t,e))),s=async()=>(await r().catch(()=>void 0)??``).includes(`GOODFELLOW_TASK_PLUGIN`);return{supported:s,secretsPlace:async()=>({kind:`bitbucket`,url:`${M}/${t}/admin/pipelines/repository-variables`}),async run(r){let c=l(r);if(c)throw new d(`failed`,c);if(!await s())throw new d(`missing`,`The site's setup files don't run plugins' tasks: bitbucket-pipelines.yml needs the plugin-task pipeline.`);let u;try{u=await k(e,t,n,b.task,{GOODFELLOW_TASK_REQUEST:r.id,GOODFELLOW_TASK_PLUGIN:r.plugin,GOODFELLOW_TASK_NAME:r.task,GOODFELLOW_TASK_INPUT:r.input,GOODFELLOW_TASK_KEY:r.key})}catch(e){throw e instanceof v&&e.status===400?new d(`missing`,`The site's repository doesn't have Bitbucket Pipelines turned on.`):e instanceof v&&(e.status===403||e.status===404)?new d(`not-allowed`,`Running plugins' tasks needs a sign-in that can run the site's pipelines.`):e}let f=`${ee}${r.id}`,p=`/repositories/${t}/pipelines/${encodeURIComponent(u.uuid)}`,m=Date.now()+(a.timeoutMs??3e5),g=!1;for(;;){let n=await T(e,`/repositories/${t}/src/${encodeURIComponent(f)}/${i}`,{allow:[404]});if(n.ok){let r=await n.text();return await T(e,`/repositories/${t}/refs/branches/${encodeURIComponent(f)}`,{method:`DELETE`,allow:[403,404]}).catch(()=>{}),r}if(g)throw new d(`failed`,`The site's build service couldn't run the task.`);if(g=(await E(e,p).catch(e=>{if(e instanceof h)throw e}))?.state?.name===`COMPLETED`,!g){if(Date.now()>=m)throw new d(`timeout`,`The site's build service didn't answer in time.`);await o(a.pollMs??4e3)}}}}}var de=10,K=8;function q(e){return!(e.url??``).startsWith(`${M}/`)}function J(e){return e.split(`/`).map(encodeURIComponent).join(`/`)}function Y(e,t){let n=`${t}/`;return[...e.keys()].filter(e=>e.startsWith(n)).sort()}function X(e){return(e??``).split(`
2
+ `)[0]?.trim()??``}function fe(e){return e.author?.user?.display_name||e.author?.raw?.replace(/\s*<[^>]*>\s*$/,``).trim()||void 0}var Z=class i{user;editors;ownerAccess;updates;drafts;scheduledBuilds;tasks;contentOnly;options;api;repo;branch;onSignOut;owner;pinned;trees=new Map;texts=new Map;constructor(e){this.options=e,this.api=e.api,this.repo=e.repo,this.branch=e.branch,this.user=e.user,this.onSignOut=e.onSignOut,this.pinned=e.revision,this.contentOnly=e.contentOnly===!0,this.owner=V(this.api,this.repo,e.ownerTokenLink??{url:`https://bitbucket.org/${this.repo}/admin/access-tokens`,label:`owner.token.create`}),this.ownerAccess=this.owner.access;let t=async()=>this.readAt(`bitbucket-pipelines.yml`,this.pinned??await this.revision());if(this.editors=ce(this.api,this.owner.api,this.repo,this.user,e.permission),!this.contentOnly)this.updates=I(this.api,this.owner.api,this.repo,this.branch,t);else if(e.code){let t=e.code;this.updates=oe(this.api,this.owner.api,t,e=>new i({api:this.api,repo:t,branch:e,user:this.user,onSignOut:()=>{}}))}this.drafts=r(this,this.draftHost()),this.scheduledBuilds=le(this.api,this.owner.api,this.repo,this.branch,t),this.tasks=ue(this.api,this.repo,this.branch,t,e.tasks)}path(e){return`/repositories/${this.repo}${e}`}async repositoryVisibility(){let{is_private:e}=await E(this.api,this.path(``));return e===!1?`public`:`private`}draftHost(){let e=async e=>{let t=await T(this.api,this.path(`/refs/branches/${encodeURIComponent(e)}`),{allow:[404]});if(t.ok)return(await t.json()).target.hash},t=async e=>{let t=new URLSearchParams({q:`source.branch.name = ${O(e)} AND state = "OPEN"`});return(await E(this.api,this.path(`/pullrequests?${t}`))).values},n=async e=>{let t=this.repo.split(`/`)[0]??``,n=await D(this.api,`/workspaces/${t}/members?pagelen=100`,10).catch(()=>[]);return e.flatMap(e=>{let t=n.find(t=>t.user.nickname?.toLowerCase()===e.toLowerCase());return t?.user.account_id?[{account_id:t.user.account_id}]:[]})};return{liveBranch:this.branch,branchStore:e=>new i({...this.options,branch:e,revision:void 0}),createBranch:async(t,n)=>{let r=await T(this.api,this.path(`/refs/branches`),{body:{name:t,target:{hash:n}},allow:[400,409]});if(!r.ok)throw await e(t)?new s(`exists`,`There's already a draft called ${t}.`):new v(r.status,`Bitbucket couldn't make the branch ${t}.`)},deleteBranch:async e=>{await T(this.api,this.path(`/refs/branches/${encodeURIComponent(e)}`),{method:`DELETE`,allow:[404]})},mergeBase:async t=>{let[n,r]=await Promise.all([e(t),e(this.branch)]);if(!n||!r)throw new s(`not-found`,`There's no draft ${t}.`);return(await E(this.api,this.path(`/merge-base/${n}..${r}`))).hash},listBranches:async e=>{let t=new URLSearchParams({q:`name ~ ${O(e)}`,pagelen:`100`});return(await D(this.api,this.path(`/refs/branches?${t}`),5)).map(e=>e.name).filter(t=>t.startsWith(e))},openReview:async({branch:e,title:r,description:i,reviewers:a=[]})=>{let o=t=>T(this.api,this.path(`/pullrequests`),{body:{title:r,description:i,source:{branch:{name:e}},destination:{branch:{name:this.branch}},close_source_branch:!0,...t.length>0&&{reviewers:t}},allow:[400,403,404]}),c=await n(a.filter(e=>e.toLowerCase()!==this.user.login.toLowerCase())),l=await o(c);if(l.status===400&&c.length>0&&(l=await o([])),l.ok)return{url:(await l.json()).links.html.href};if(l.status!==400)throw new s(`no-reviews`,`This sign-in can't open pull requests on the site's repository.`);let[u]=await t(e);if(!u)throw new s(`no-reviews`,`Bitbucket couldn't open the pull request.`);return{url:u.links.html.href}},listReviews:async e=>{let t=new URLSearchParams({q:`source.branch.name ~ ${O(e)} AND state = "OPEN"`,pagelen:`50`});return(await D(this.api,this.path(`/pullrequests?${t}`),5)).filter(t=>t.source.branch.name.startsWith(e)).map(e=>({branch:e.source.branch.name,url:e.links.html.href,title:e.title}))},closeReview:async e=>{for(let n of await t(e))await T(this.api,this.path(`/pullrequests/${n.id}/decline`),{body:{},allow:[400,403,404]})}}}async revision(){let e=this.pinned;if(this.pinned=await this.head(),e&&e!==this.pinned&&this.hasTextsAt(e)){let t=await this.changedPaths(e,this.pinned).catch(()=>void 0);t&&this.carryOver(e,this.pinned,new Set(t))}return this.pinned}async head(){return(await E(this.api,this.path(`/refs/branches/${encodeURIComponent(this.branch)}`))).target.hash}hasTextsAt(e){for(let t of this.texts.keys())if(t.startsWith(`${e}:`))return!0;return!1}carryOver(e,t,n){for(let[r,i]of[...this.texts]){if(!r.startsWith(`${e}:`))continue;let a=r.slice(e.length+1);!n.has(a)&&!this.texts.has(`${t}:${a}`)&&this.texts.set(`${t}:${a}`,i)}}async listFolder(e,t){let n=new URLSearchParams({max_depth:String(de),pagelen:`100`}),r=await T(this.api,this.path(`/src/${e}/${J(t)}/?${n}`),{allow:[404]});if(r.status===404)return[];let i=[],a=await r.json();for(;;){for(let e of a.values)e.type===`commit_file`&&i.push([e.path,e.size??0]);if(!a.next)return i;a=await E(this.api,a.next)}}tree(t){let n=this.trees.get(t);return n||(n=Promise.all(e.map(e=>this.listFolder(t,e))).then(e=>new Map(e.flat())),n.catch(()=>this.trees.delete(t)),this.trees.set(t,n)),n}async current(){return this.pinned??await this.revision()}async read(e){let t=await this.current();return this.readFrom(t,await this.tree(t),e)}readFrom(e,t,n){if(!t.has(n))return Promise.resolve(void 0);let r=`${e}:${n}`,i=this.texts.get(r);return i||(i=T(this.api,this.path(`/src/${e}/${J(n)}`)).then(e=>e.text()),i.catch(()=>this.texts.delete(r)),this.texts.set(r,i)),i}async readFiles(e){let t=await this.current();return this.readFilesFrom(t,await this.tree(t),e)}async readFilesFrom(e,t,n){let r=new Map,i=n.filter(e=>t.has(e));for(let n=0;n<i.length;n+=K){let a=i.slice(n,n+K),o=await Promise.all(a.map(n=>this.readFrom(e,t,n)));a.forEach((e,t)=>{let n=o[t];n!==void 0&&r.set(e,n)})}return r}async readBytes(e){let t=await this.current();return this.bytesFrom(t,await this.tree(t),e)}async bytesFrom(e,t,n){if(!t.has(n))return;let r=await T(this.api,this.path(`/src/${e}/${J(n)}`));return new Uint8Array(await r.arrayBuffer())}async list(e){return Y(await this.tree(await this.current()),e)}filesAt(e){return{read:async t=>this.readFrom(e,await this.tree(e),t),readFiles:async t=>this.readFilesFrom(e,await this.tree(e),t),readBytes:async t=>this.bytesFrom(e,await this.tree(e),t),list:async t=>Y(await this.tree(e),t)}}async fileSizes(e){let t=await this.tree(await this.current());return new Map(Y(t,e).map(e=>[e,t.get(e)??0]))}async existsAt(t,n,r){return e.some(e=>n.startsWith(`${e}/`))?t.has(n):(await T(this.api,this.path(`/src/${r}/${J(n)}?format=meta`),{allow:[404]})).ok}async write(e,{message:r,expectedRevision:i}){this.contentOnly&&t(e);let a=await this.tree(i),o=new FormData,s=0;for(let t of e){if(`delete`in t){if(!await this.existsAt(a,t.path,i))continue;o.append(`files`,t.path)}else{let e=`bytes`in t?new Uint8Array(t.bytes):t.content;o.append(`/${t.path}`,new Blob([e]),t.path.split(`/`).at(-1)??t.path)}s+=1}if(s===0)return{revision:i};o.append(`message`,r),o.append(`branch`,this.branch),o.append(`parents`,i),this.options.author&&o.append(`author`,`${this.options.author.name} <${this.options.author.email}>`);let c=await T(this.api,this.path(`/src`),{form:o,allow:[403,409]});if(c.status===409)throw new n;if(c.status===403)throw new h(`cant-publish`,`This Bitbucket account can't publish to the site.`);let l=await this.commitAfter(i,r);return this.carryOver(i,l,new Set(e.map(e=>e.path))),this.pinned=l,{revision:l}}async commitAfter(e,t){let n=new URLSearchParams({pagelen:`20`}),r=await E(this.api,this.path(`/commits/${encodeURIComponent(this.branch)}?${n}`)),i=r.values.find(n=>n.parents?.[0]?.hash===e&&X(n.message)===X(t))??r.values.find(t=>t.parents?.[0]?.hash===e);return i?i.hash:this.head()}async history(e,{page:t=1,perPage:n=20}={}){let r=new URLSearchParams({path:e,pagelen:String(n)}),i=this.path(`/commits/${await this.current()}?${r}`),a={values:[]};for(let e=1;i&&e<=t;e++)if(a=await E(this.api,i),i=a.next,e<t&&!i)return[];return a.values.map(e=>({revision:e.hash,date:e.date??``,author:fe(e),message:X(e.message)}))}async readAt(e,t){let n=await T(this.api,this.path(`/src/${t}/${J(e)}`),{allow:[404]});return n.ok?n.text():void 0}async changedPaths(e,t){let n=new URLSearchParams({topic:`false`,pagelen:`500`}),r=(await D(this.api,this.path(`/diffstat/${t}..${e}?${n}`))).flatMap(e=>[e.old?.path,e.new?.path]);return[...new Set(r.filter(e=>!!e))]}async deployStatus(e){try{let t=await this.statuses(e),n=t.filter(q);if(n.length===0){if(t.length>0)return{state:`unknown`};let n=(await E(this.api,this.path(`/commit/${e}`))).parents?.[0]?.hash;return{state:(n?(await this.statuses(n)).filter(q):[]).length>0?`building`:`unknown`}}let r=n.find(e=>e.state===`FAILED`)??n.find(e=>e.state===`STOPPED`);if(r?.state===`STOPPED`){let t=await this.head();if(t!==e)return this.deployStatus(t)}if(r)return{state:`failed`,...r.url&&{detailsUrl:r.url}};let i=n[0]?.url;return{state:n.every(e=>e.state===`SUCCESSFUL`)?`live`:`building`,...i&&{detailsUrl:i}}}catch(e){if(e instanceof v)return{state:`unknown`};throw e}}statuses(e){return D(this.api,this.path(`/commit/${e}/statuses?pagelen=100`),3)}signOut(){this.ownerAccess.forget(),this.onSignOut()}},Q=`main`,pe=/^[a-z0-9][a-z0-9_.-]{0,61}$/;function $(e={}){let t=e.fetch??((e,t)=>globalThis.fetch(e,t));async function n(e){let n={apiUrl:N,authorization:async()=>C(e),fetch:t},r=await E(n,`/user`,{allow:[403]}).catch(e=>{throw e instanceof v?new h(`invalid`,`Creating a repository on Bitbucket needs an API token, not an access token.`):e}),i=r.username??r.nickname??``;if(!i)throw new h(`invalid`,`Creating a repository on Bitbucket needs an API token, not an access token.`);return{user:{login:i,name:r.display_name},owners:async()=>{let e=await D(n,`/user/workspaces?pagelen=100`,5);return e.filter(t=>t.permission!==`member`||e.length===1).map(({workspace:e})=>({path:e.slug,name:e.name??e.slug,kind:e.slug===i?`personal`:`organization`})).sort((e,t)=>e.kind===t.kind?e.name.localeCompare(t.name):e.kind===`personal`?-1:1)},async isAvailable(e,t){return(await T(n,`/repositories/${e.path}/${t.toLowerCase()}`,{allow:[403,404]})).status===404},async createSite(e,t){let r=e.name.toLowerCase();if(!pe.test(r))throw new p(`name-invalid`,`That name can't be used for a repository.`);let i=`${e.owner.path}/${r}`;t?.(`repository`);let a=await T(n,`/repositories/${i}`,{body:{scm:`git`,is_private:e.private,...e.description&&{description:e.description}},allow:[400,403]});if(a.status===400)throw new p(`name-taken`,`There's already a repository called ${i}.`);if(a.status===403)throw new p(`not-allowed`,`This account can't create repositories in ${e.owner.name}.`);t?.(`files`);let o=new FormData;for(let t of e.files){if(`delete`in t)continue;let e=`bytes`in t?new Uint8Array(t.bytes):t.content;o.append(`/${t.path}`,new Blob([e]),t.path.split(`/`).at(-1)??t.path)}if(o.append(`message`,e.message),o.append(`branch`,Q),(await T(n,`/repositories/${i}/src`,{form:o,allow:[403]})).status===403)throw new p(`not-allowed`,`This account can't write to the new repository.`);await T(n,`/repositories/${i}`,{method:`PUT`,body:{mainbranch:{name:Q}},allow:[400,403]});let s=e.pages?[`pages-unavailable`]:[];return{repo:i,webUrl:`${M}/${i}`,buildsUrl:`${M}/${i}/pipelines`,warnings:s}}}}return{name:`Bitbucket`,canRedirect:!1,tokenLinks:[{url:`https://id.atlassian.com/manage-profile/security/api-tokens`,label:`signIn.token.create`,hint:`signIn.token.apiTokenHint`}],async restore(){return null},async signInWithToken(e){let t=e.trim();if(!t)throw new h(`invalid`,`Paste a token to sign in.`);return n(w(t))},async startRedirect(){throw Error(`Creating repositories on Bitbucket needs a token.`)}}}function me(){return{href:()=>window.location.href,assign:e=>window.location.assign(e),replace:e=>window.history.replaceState(window.history.state,``,e)}}function he(){return[...crypto.getRandomValues(new Uint8Array(16))].map(e=>e.toString(16).padStart(2,`0`)).join(``)}function ge(e){let t=u(e.fetch??((e,t)=>globalThis.fetch(e,t)),{host:`Bitbucket`}),n=g(`goodfellow:bitbucket:${e.repo}`,e.storage),r=g(`goodfellow:bitbucket:pending:${e.repo}`,e.storage),i=()=>e.location??me(),[a=``]=e.repo.split(`/`);async function o(r,i){if(r.type===`oauth`&&r.expiresAt<Date.now())throw new h(`invalid`,`The Bitbucket sign-in has expired.`);let o={apiUrl:N,authorization:async()=>C(r),fetch:t},s=await T(o,`/repositories/${e.repo}`,{allow:[403,404]});if(!s.ok)throw new h(`no-access`,`This Bitbucket sign-in can't see ${e.repo}.`);let c=await s.json(),l=await T(o,`/user`,{allow:[401,403]}),u=l.ok?await l.json():void 0,d=u?.username??u?.nickname,f;if(d){let t=new URLSearchParams({q:`repository.full_name = ${O(e.repo)}`});if(f=await E(o,`/user/workspaces/${a}/permissions/repositories?${t}`).then(e=>e.values[0]?.permission,()=>void 0),f===`read`)throw new h(`cant-publish`,`The Bitbucket account ${d} can see ${e.repo} but can't publish to it.`)}return n.save(JSON.stringify(r),i),new Z({api:o,repo:e.repo,branch:e.branch??c.mainbranch?.name??`main`,user:d?{login:d,name:u?.display_name,avatarUrl:u?.links?.avatar?.href}:{login:`access-token`,name:`Access token`},...f&&{permission:f},...e.contentOnly&&{contentOnly:!0,...e.code&&{code:e.code}},onSignOut:()=>n.clear()})}async function s(){let e=new URL(i().href()),t=new URLSearchParams(e.hash.replace(/^#/,``)),n=t.get(`access_token`),a=t.get(`error`)??e.searchParams.get(`error`);if(!n&&!a)return null;let s=r.load();if(!s)return null;r.clear();let c=JSON.parse(s);e.hash=c.hash;for(let t of[`error`,`error_description`])e.searchParams.delete(t);if(i().replace(e.toString()),a||!n||t.get(`state`)!==c.state)throw new h(`redirect-failed`,`Signing in with Bitbucket was cancelled or didn't finish.`);let l=Number(t.get(`expires_in`)??7200);return o({type:`oauth`,access:n,expiresAt:Date.now()+(Number.isFinite(l)?l:7200)*1e3},c.remember)}function c({token:n}){let r={apiUrl:N,authorization:async()=>C(w(typeof n==`string`?n:await n())),fetch:t},i,a=()=>(i??=e.branch?Promise.resolve(e.branch):E(r,`/repositories/${e.repo}`).then(e=>e.mainbranch?.name??`main`),i.catch(()=>{i=void 0}),i),o=async(t,n)=>new Z({api:r,repo:e.repo,branch:await a(),...e.contentOnly&&{contentOnly:!0,...e.code&&{code:e.code}},user:{login:n?.name??e.repo,...n&&{name:n.name}},onSignOut:()=>{},...t&&{revision:t},...n&&{author:n}});return{...e.contentOnly&&{contentOnly:!0,...e.code&&{code:e.code}},async revision(){return(await o()).revision()},at:o}}return{name:`Bitbucket`,source:_(`@goodfellow-cms/bitbucket`,`bitbucket`,e,[`fetch`,`storage`,`location`]),canRedirect:!!e.clientId,redirectAddressesListed:!!e.clientId,setup:()=>$({...e.fetch&&{fetch:e.fetch}}),tokenLinks:[{url:`${M}/${e.repo}/admin/access-tokens`,label:`signIn.token.create`},{url:`https://id.atlassian.com/manage-profile/security/api-tokens`,label:`signIn.token.createBroad`,hint:`signIn.token.apiTokenHint`}],async restore(){let e=await s();if(e)return e;let t=n.load();if(!t)return null;try{return await o(JSON.parse(t),n.remembered())}catch(e){throw e instanceof h&&n.clear(),e}},async signInWithToken(e,t){let n=e.trim();if(!n)throw new h(`invalid`,`Paste a token to sign in.`);return o(w(n),t)},async startRedirect(t){if(!e.clientId)throw Error(`bitbucket({ clientId }) is needed to sign in with Bitbucket.`);let n=he(),a={state:n,remember:t,hash:new URL(i().href()).hash};r.save(JSON.stringify(a),!1);let o=new URLSearchParams({client_id:e.clientId,response_type:`token`,state:n});i().assign(`${M}/site/oauth2/authorize?${o}`)},server:c}}export{N as BITBUCKET_API,M as BITBUCKET_WEB,Z as BitbucketBackend,ge as bitbucket,$ as bitbucketSetup,J as encodePath};
@@ -0,0 +1,6 @@
1
+ import{n as e,r as t,t as n}from"./store-BtVaZcVF.js";import{a as r,d as i,g as a,h as o,i as s,l as c,m as l,n as u,o as d,r as f,t as p,v as m}from"./setup-host-DQm73dc_.js";import{i as h,n as ee,r as g}from"./content-repository-setup-HEL-uoaX.js";import{n as _,o as v,r as y,s as b,t as x}from"./git-eGyMXUIu.js";import{t as S}from"./scheduled-builds-BPfDaBYt.js";async function C(e,t,n={}){let r=async r=>e.fetch(`${e.apiUrl}${t}`,{method:n.method??`GET`,headers:{authorization:`Bearer ${await e.token({refresh:r})}`,...n.body!==void 0&&{"content-type":`application/json`}},body:n.body===void 0?void 0:JSON.stringify(n.body),cache:`no-store`}),i=await r(!1);if(i.status===401&&e.canRefresh&&(i=await r(!0)),i.status===401)throw new _(`invalid`,`GitLab didn't accept the sign-in. It may have expired.`);if(!i.ok&&!n.allow?.includes(i.status)){let e=await i.json().catch(()=>({})),t=typeof e.message==`string`?e.message:JSON.stringify(e.message??e.error??``);throw new x(i.status,`GitLab answered ${i.status}: ${t||i.statusText}`)}return i}async function w(e,t,n){return await(await C(e,t,n)).json()}var T=30,E=40,te=`member:`,D=`invitation:`,ne=/^[^\s@]+@[^\s@]+\.[^\s@]+$/,re=/^[\w][\w.-]*$/;function O(e){return e>=E?`owner`:e>=T?`editor`:`viewer`}function k(e){return e===`owner`?E:T}function A(e){throw e instanceof x&&(e.status===401||e.status===403)?new f(`not-allowed`,`Changing editors on GitLab needs the Maintainer role in the project.`):e}function j(e,t,n,r){let i=`/projects/${encodeURIComponent(t)}`,a=`${i}/protected_branches/${encodeURIComponent(n)}`;async function o(t){let n=[];for(let r=1;r<=10;r++){let i=await w(e,`${t}${t.includes(`?`)?`&`:`?`}per_page=100&page=${r}`);if(n.push(...i),i.length<100)break}return n}return{invitesByEmail:!0,async list(){let[t,n]=await Promise.all([o(`${i}/members/all`),o(`${i}/members`)]),s=new Set(n.map(e=>e.id)),c=t.find(e=>e.username.toLowerCase()===r.toLowerCase()),l=(c?.access_level??0)>=E,u=l?await o(`${i}/invitations`).catch(()=>[]):[],d=await C(e,a,{allow:[404]}),f=d.ok?(await d.json()).push_access_levels??[]:[],p=!d.ok||f.some(e=>e.access_level!==null&&e.access_level<=T);return{editors:[...t.map(e=>({id:`${te}${e.id}`,login:e.username,name:e.name,avatarUrl:e.avatar_url,role:O(e.access_level),...!s.has(e.id)&&{inherited:!0},...e.id===c?.id&&{self:!0}})),...u.map(e=>({id:`${D}${e.invite_email}`,email:e.invite_email,role:O(e.access_level),invited:!0}))],manage:l?`yes`:`no`,editorsCanPublish:p}},async invite(t,n){let r=t.trim().replace(/^@/,``);if(r.includes(`@`)){if(!ne.test(r))throw new f(`invalid`,`That isn't an email address.`);let t=await w(e,`${i}/invitations`,{method:`POST`,body:{email:r,access_level:k(n)}}).catch(A);if(t.status!==`success`){let e=typeof t.message==`string`?t.message:Object.values(t.message??{})[0];throw/already/i.test(e??``)?new f(`already`,`They've already been invited.`):new f(`invalid`,`GitLab didn't accept the invitation: ${e??t.status}`)}return}if(!re.test(r))throw new f(`invalid`,`That isn't a GitLab username or email address.`);let[a]=await w(e,`/users?${new URLSearchParams({username:r})}`);if(!a)throw new f(`not-found`,`There's no GitLab account named ${r}.`);try{await C(e,`${i}/members`,{method:`POST`,body:{user_id:a.id,access_level:k(n)}})}catch(e){if(e instanceof x&&e.status===409)throw new f(`already`,`${r} already edits the site.`);A(e)}},async setRole(t,n){await C(e,t.id.startsWith(D)?`${i}/invitations/${encodeURIComponent(t.id.slice(11))}`:`${i}/members/${t.id.slice(7)}`,{method:`PUT`,body:{access_level:k(n)}}).catch(A)},async remove(t){await C(e,t.id.startsWith(D)?`${i}/invitations/${encodeURIComponent(t.id.slice(11))}`:`${i}/members/${t.id.slice(7)}`,{method:`DELETE`}).catch(A)},async letEditorsPublish(){let t=t=>C(e,`${i}/protected_branches`,{method:`POST`,body:{name:n,push_access_level:t,merge_access_level:t,allow_force_push:!1}});await C(e,a,{method:`DELETE`,allow:[404]}).catch(A);try{await t(T)}catch(e){await t(E).catch(()=>void 0),A(e)}}}}var ie=`35.185.44.232`,ae=`2600:1901:0:7b8a::`;function M(e,{apex:t,www:n}){return t&&n?[e,`www.${e}`]:[e]}function N(e,t,n,r){let i=`${(e.split(`/`)[0]??``).toLowerCase()}.gitlab.io`,a=n.apex?[{type:`A`,name:t,value:ie,purpose:`site`},{type:`AAAA`,name:t,value:ae,purpose:`site`,optional:!0}]:[{type:`CNAME`,name:t,value:i,purpose:`site`}],o=n.apex&&n.www?[{type:`CNAME`,name:`www.${t}`,value:i,purpose:`www`,optional:!0}]:[],s=M(t,n).flatMap(e=>{let n=r[e];if(!n)return[];let i=n.startsWith(`gitlab-pages-verification-code=`)?n:`gitlab-pages-verification-code=${n}`;return[{type:`TXT`,name:`_gitlab-pages-verification-code.${e}`,value:i,purpose:`verification`,optional:e!==t}]});return[...a,...o,...s]}function oe(e,t){let n=`/projects/${encodeURIComponent(t)}/pages`,r=e=>`${n}/domains/${encodeURIComponent(e)}`;function i(e){throw e instanceof x&&(e.status===401||e.status===403)?new h(`not-allowed`,`Connecting a domain on GitLab needs the Maintainer role in the project.`):e}async function a(){let t=await C(e,n,{allow:[404]}).catch(i);return t.ok?await t.json():void 0}async function o(t){let n=await Promise.all(t.map(async t=>{let n=await C(e,r(t),{allow:[404]});return n.ok?[t,(await n.json()).verification_code??``]:[t,``]}));return Object.fromEntries(n)}async function s(t){let n=await w(e,r(t)).catch(i);if(n.verified)return n;let a=await C(e,`${r(t)}/verify`,{method:`PUT`,allow:[400,404,422]});return a.ok?await a.json():n}return{severalDomains:!0,async site(){let t=await a();if(!t)return;let r=(await w(e,`${n}/domains`).catch(()=>[])).map(e=>e.domain),i=(t.primary_domain&&r.includes(t.primary_domain)?t.primary_domain:void 0)??r.sort((e,t)=>e.length-t.length)[0];return{url:t.url,domain:i}},async connect(s,c){let l=await a();l&&l.is_unique_domain_enabled===!1&&await C(e,n,{method:`PATCH`,body:{pages_unique_domain_enabled:!0}}).catch(i);for(let t of M(s,c))try{await C(e,`${n}/domains`,{method:`POST`,body:{domain:t,auto_ssl_enabled:!0}})}catch(n){if(n instanceof x&&n.status===400){if(/taken/i.test(n.message)){if((await C(e,r(t),{allow:[404]})).ok)continue;throw new h(`taken`,`Another GitLab Pages site already uses this domain.`)}throw new h(`invalid`,`GitLab Pages doesn't accept this domain.`)}i(n)}return N(t,s,c,await o(M(s,c)))},async records(e,n){return N(t,e,n,await o(M(e,n)))},async status(t){let n=await s(t),i=await C(e,r(`www.${t}`),{allow:[404]});i.ok&&!(await i.json()).verified&&await s(`www.${t}`);let o=await a();return{verified:n.verified,certificate:n.certificate&&!n.certificate.expired?`ready`:`pending`,httpsOnly:o?.force_https===!0,primary:o?.primary_domain===void 0||o.primary_domain===t}},async secure(t,{primary:a=!0}={}){let o=await w(e,r(t)).catch(i);if(!o.verified||!o.certificate)throw new h(`not-ready`,`The domain's certificate isn't ready yet.`);if(!a){await C(e,n,{method:`PATCH`,body:{pages_https_only:!0}}).catch(i);return}try{await C(e,n,{method:`PATCH`,body:{pages_https_only:!0,pages_primary_domain:t}})}catch(t){t instanceof x&&t.status===400||i(t),await C(e,n,{method:`PATCH`,body:{pages_https_only:!0}}).catch(i)}},async disconnect(t){for(let n of[t,`www.${t}`])await C(e,r(n),{method:`DELETE`,allow:[404]}).catch(i)}}}var se=`Goodfellow: build the site for a scheduled change at `,ce=`GOODFELLOW_SCHEDULED_CHANGE`;function P(e){throw e instanceof x&&(e.status===401||e.status===403)?new S(`not-allowed`,`Adding pipeline schedules on GitLab needs the Developer role, with permission to merge to the main branch.`):e}function le(e){return`${e.getUTCMinutes()} ${e.getUTCHours()} ${e.getUTCDate()} ${e.getUTCMonth()+1} *`}function ue(e){return`${e.toISOString().slice(0,16)}Z`}function de(e,t,n,r,i=()=>new Date){let a=`/projects/${encodeURIComponent(t)}/pipeline_schedules`;return{async timing(){return await r()===void 0?`none`:`exact`},async sync(t){let r=i(),o=new Map;for(let e of[...t].filter(e=>e>r).sort((e,t)=>e.getTime()-t.getTime())){if(o.size>=50)break;o.set(ue(e),e)}let s=await w(e,`${a}?per_page=100`).catch(P),c=new Map;for(let e of s)e.description?.startsWith(`Goodfellow: build the site for a scheduled change at `)&&c.set(e.description.slice(53),e);for(let[t,n]of c)o.has(t)||await C(e,`${a}/${n.id}`,{method:`DELETE`,allow:[403,404]});for(let[t,r]of o){if(c.has(t))continue;let i=await w(e,a,{method:`POST`,body:{description:`${se}${t}`,ref:n,cron:le(r),cron_timezone:`UTC`,active:!0}}).catch(P);await C(e,`${a}/${i.id}/variables`,{method:`POST`,body:{key:ce,value:t,variable_type:`env_var`}}).catch(P)}}}}function fe(e,t,n,r,a={}){let o=`/projects/${encodeURIComponent(t)}`,s=a.wait??(e=>new Promise(t=>setTimeout(t,e))),u=async()=>(await r().catch(()=>void 0)??``).includes(`$GOODFELLOW_TASK_PLUGIN`);return{supported:u,secretsPlace:async()=>({kind:`gitlab`,url:`${e.apiUrl.replace(/\/api\/v4\/?$/,``)}/${t}/-/settings/ci_cd#js-cicd-variables-settings`}),async run(t){let r=l(t);if(r)throw new d(`failed`,r);if(!await u())throw new d(`missing`,`The site's setup files don't run plugins' tasks: .gitlab-ci.yml needs the plugin-task job.`);let f=(e,t)=>({key:e,variable_type:`env_var`,value:t}),p;try{p=await w(e,`${o}/pipeline`,{method:`POST`,body:{ref:n,variables:[f(`GOODFELLOW_TASK_REQUEST`,t.id),f(`GOODFELLOW_TASK_PLUGIN`,t.plugin),f(`GOODFELLOW_TASK_NAME`,t.task),f(`GOODFELLOW_TASK_INPUT`,t.input),f(`GOODFELLOW_TASK_KEY`,t.key)]}})}catch(e){throw e instanceof x&&(e.status===403||e.status===401)?new d(`not-allowed`,`Running plugins' tasks needs a sign-in that can publish to the site.`):e}let m=Date.now()+(a.timeoutMs??3e5);for(;;){let t=(await w(e,`${o}/pipelines/${p.id}/jobs?per_page=100`)).find(e=>e.name===c);if(t?.status===`success`){let n=await C(e,`${o}/jobs/${t.id}/artifacts/${i}`,{allow:[404]});if(n.ok)return await n.text();throw new d(`failed`,`The site's build service ran the task but left no answer.`)}if(t&&[`failed`,`canceled`,`skipped`].includes(t.status))throw new d(`failed`,`The site's build service couldn't run the task.`);if(Date.now()>=m)throw new d(`timeout`,`The site's build service didn't answer in time.`);await s(a.pollMs??4e3)}}}}var F=`.gitlab-ci.yml`,I=`17 4 * * *`;function L(e){throw e instanceof x&&(e.status===401||e.status===403)?new a(`not-allowed`,`Setting up updates on GitLab needs the Maintainer role in the project.`):e}function R(e,t,n,r,i=`pages`){let s=`/projects/${encodeURIComponent(t)}`;async function c(){return w(e,`${s}/pipeline_schedules`)}async function l(){return(await c()).find(e=>(e.ref===n||e.ref===`refs/heads/${n}`)&&!e.description?.startsWith(`Goodfellow: build the site for a scheduled change at `))}return{async setup(){if(!(await r(F))?.includes(`update --publish`))return`missing`;try{let[t,r]=await Promise.all([w(e,s),c()]),i=r.some(e=>(e.ref===n||e.ref===`refs/heads/${n}`)&&(e.active||!e.description?.startsWith(`Goodfellow: build the site for a scheduled change at `)));return t.ci_push_repository_for_job_token_allowed&&i?`ready`:`needs-setup`}catch(e){if(e instanceof x&&e.status===403)return`needs-setup`;throw e}},async enable(){await C(e,s,{method:`PUT`,body:{ci_push_repository_for_job_token_allowed:!0}}).catch(L),await l().catch(L)||await C(e,`${s}/pipeline_schedules`,{method:`POST`,body:{description:`Rebuild the site and install Goodfellow's fixes each night`,ref:n,cron:I,cron_timezone:`UTC`,active:!0}}).catch(L)},async lastRun(){let t=await w(e,`${s}/pipelines?${new URLSearchParams({ref:n,per_page:`20`})}`);for(let n of t){if(n.source!==`schedule`&&n.source!==`api`)continue;let t={date:n.created_at,detailsUrl:n.web_url};if([`created`,`pending`,`running`,`waiting_for_resource`,`preparing`].includes(n.status))return{state:`running`,...t};let r=(await w(e,`${s}/pipelines/${n.id}/jobs?per_page=100`)).find(e=>e.name===i||e.name===`update`);if(!r)continue;let a=await C(e,`${s}/jobs/${r.id}/trace`,{allow:[403,404]}),o=a.ok?m(await a.text()):void 0;if(o)return{state:o.state,result:o,...t}}},async start(t){await C(e,`${s}/pipeline`,{method:`POST`,body:{ref:n,variables:[{key:`GOODFELLOW_UPDATE`,variable_type:`env_var`,value:t}]}}).catch(L)},nightly:{async supported(){return(await r(F))?.includes(`nightly`)??!1},async get(){try{let t=await l();return t?t.active?(await w(e,`${s}/pipeline_schedules/${t.id}`)).variables?.find(e=>e.key===`GOODFELLOW_NIGHTLY`)?.value===`always`?`always`:`changed`:`off`:void 0}catch(e){if(e instanceof x&&e.status===403)return;throw e}},async set(t){let n=await l().catch(L);if(!n)throw new a(`missing`,`The site has no nightly schedule yet: turn on updates first.`);let r=`${s}/pipeline_schedules/${n.id}`;if(await C(e,r,{method:`PUT`,body:{active:t!==`off`}}).catch(L),t===`always`){let t={key:o,value:`always`};(await C(e,`${r}/variables/GOODFELLOW_NIGHTLY`,{method:`PUT`,body:t,allow:[404]}).catch(L)).status===404&&await C(e,`${r}/variables`,{method:`POST`,body:t}).catch(L)}else t===`changed`&&await C(e,`${r}/variables/${o}`,{method:`DELETE`,allow:[404]}).catch(L)}},async rebuild(){await C(e,`${s}/pipeline`,{method:`POST`,body:{ref:n,variables:[{key:`GOODFELLOW_REBUILD`,variable_type:`env_var`,value:`true`}]}}).catch(L)}}}function z(e){return e instanceof x&&(e.status===403||e.status===404)}function B(e){let{api:t,project:n}=e,r,i=()=>(r??=w(t,`/projects/${encodeURIComponent(n)}`).then(e=>e.default_branch),r.catch(()=>{r=void 0}),r),a=async()=>e.open(await i()),o=async e=>{let t=await a();return t.readAt(e,await t.revision())},s=async()=>R(t,n,await i(),o,`update`);return{code:{repo:n,webUrl:`${e.webUrl}/${n}`,read:o,history:async(e,t)=>(await a()).history(e,t),readAt:async(e,t)=>(await a()).readAt(e,t)},async setup(){try{return await(await s()).setup()}catch(e){if(z(e))return`no-access`;throw e}},async enable(){await(await s()).enable?.()},async lastRun(){try{return await(await s()).lastRun()}catch(e){if(z(e))return;throw e}},async start(e){await(await s()).start(e)},async rebuild(){await(await s()).rebuild?.()}}}var V=100,H=50,pe=RegExp(`\x1B\\[[\\d;]*[A-Za-z]`,`g`);async function me(e){let t=new TextEncoder().encode(`blob ${e.length}\0`),n=new Uint8Array(t.length+e.length);return n.set(t),n.set(e,t.length),[...new Uint8Array(await crypto.subtle.digest(`SHA-1`,n))].map(e=>e.toString(16).padStart(2,`0`)).join(``)}function U(e){return e.name!==`rebuild`&&e.name!==`update`&&e.name!==`lockfile`}function W(e,t){let n=`${t}/`;return[...e.keys()].filter(e=>e.startsWith(n)).sort()}var G=class i{user;pages;editors;updates;drafts;scheduledBuilds;tasks;contentOnly;options;api;project;branch;onSignOut;pinned;caches;trees;blobs;constructor(e){if(this.options=e,this.api=e.api,this.project=encodeURIComponent(e.project),this.branch=e.branch,this.user=e.user,this.onSignOut=e.onSignOut,this.pinned=e.revision,this.caches=e.caches??{trees:new Map,blobs:new Map},this.trees=this.caches.trees,this.blobs=this.caches.blobs,this.pages=oe(this.api,e.project),this.editors=j(this.api,e.project,this.branch,this.user.login),this.contentOnly=e.contentOnly===!0,!this.contentOnly)this.updates=R(this.api,e.project,this.branch,async e=>this.readAt(e,this.pinned??await this.revision()));else if(e.code){let t=e.code;this.updates=B({api:this.api,project:t.project,webUrl:t.webUrl,open:e=>new i({api:this.api,project:t.project,branch:e,user:this.user,onSignOut:()=>{}})})}this.drafts=r(this,this.draftHost()),this.scheduledBuilds=de(this.api,e.project,this.branch,async()=>this.readAt(`.gitlab-ci.yml`,this.pinned??await this.revision())),this.tasks=fe(this.api,e.project,this.branch,async()=>this.readAt(`.gitlab-ci.yml`,this.pinned??await this.revision()),e.tasks)}async repositoryVisibility(){let{visibility:e}=await w(this.api,`/projects/${this.project}`);return e===`public`||e===`internal`?e:`private`}draftHost(){let e=`/projects/${this.project}`,t=t=>w(this.api,`${e}/merge_requests?${new URLSearchParams({source_branch:t,state:`opened`})}`),n=async(t,n)=>{let r=[];for(let e of n){let[t]=await w(this.api,`/users?${new URLSearchParams({username:e})}`).catch(()=>[]);t&&r.push(t.id)}for(let n of r.length>1?[r,r.slice(0,1)]:[r])if(n.length===0||(await C(this.api,`${e}/merge_requests/${t}`,{method:`PUT`,body:{reviewer_ids:n},allow:[400,403,404,422]})).ok)return};return{liveBranch:this.branch,branchStore:e=>new i({...this.options,branch:e,caches:this.caches}),createBranch:async(e,t)=>{if((await C(this.api,this.repo(`/branches?${new URLSearchParams({branch:e,ref:t})}`),{method:`POST`,allow:[400]})).status===400)throw new s(`exists`,`There's already a draft called ${e}.`)},deleteBranch:async e=>{await C(this.api,this.repo(`/branches/${encodeURIComponent(e)}`),{method:`DELETE`,allow:[404]})},mergeBase:async e=>{let t=new URLSearchParams;t.append(`refs[]`,this.branch),t.append(`refs[]`,e);let n=await C(this.api,this.repo(`/merge_base?${t}`),{allow:[400,404]});if(!n.ok)throw new s(`not-found`,`There's no draft ${e}.`);return(await n.json()).id},listBranches:async e=>{let t=new URLSearchParams({search:`^${e}`,per_page:`100`});return(await w(this.api,this.repo(`/branches?${t}`))).map(e=>e.name).filter(t=>t.startsWith(e))},openReview:async({branch:r,title:i,description:a,reviewers:o=[]})=>{let c=await C(this.api,`${e}/merge_requests`,{method:`POST`,body:{source_branch:r,target_branch:this.branch,title:i,description:a,remove_source_branch:!0},allow:[403,404,409]}),l;if(c.ok)l=await c.json();else if(c.status!==409)throw new s(`no-reviews`,`This sign-in can't open merge requests on the site's project.`);else if([l]=await t(r),!l)throw new x(409,`GitLab couldn't open the merge request.`);return await n(l.iid,o),{url:l.web_url}},listReviews:async t=>{let n=new URLSearchParams({state:`opened`,per_page:`100`});return(await w(this.api,`${e}/merge_requests?${n}`)).filter(e=>e.source_branch.startsWith(t)).map(e=>({branch:e.source_branch,url:e.web_url,title:e.title}))},closeReview:async n=>{for(let r of await t(n))await C(this.api,`${e}/merge_requests/${r.iid}`,{method:`PUT`,body:{state_event:`close`},allow:[403,404]})}}}repo(e){return`/projects/${this.project}/repository${e}`}async revision(){return this.pinned=await this.head(),this.pinned}async head(){return(await w(this.api,this.repo(`/branches/${encodeURIComponent(this.branch)}`))).commit.id}async listFolder(e,t){let n=[];for(let r=1;;r++){let i=new URLSearchParams({ref:e,path:t,recursive:`true`,per_page:String(V),page:String(r)}),a=await C(this.api,this.repo(`/tree?${i}`),{allow:[404]});if(a.status===404)return n;let o=await a.json();for(let e of o)e.type===`blob`&&n.push([e.path,e.id]);if(o.length<V)return n}}tree(t){let n=this.trees.get(t);return n||(n=Promise.all(e.map(e=>this.listFolder(t,e))).then(e=>new Map(e.flat())),n.catch(()=>this.trees.delete(t)),this.trees.set(t,n)),n}async currentTree(){return this.tree(this.pinned??await this.revision())}async read(e){return this.readFrom(await this.currentTree(),e)}readFrom(e,t){let n=e.get(t);if(!n)return Promise.resolve(void 0);let r=this.blobs.get(n);return r||(r=C(this.api,this.repo(`/blobs/${n}/raw`)).then(e=>e.text()),r.catch(()=>this.blobs.delete(n)),this.blobs.set(n,r)),r}async readFiles(e){let t=this.pinned??await this.revision();return this.readFilesFrom(t,await this.tree(t),e)}async readFilesFrom(e,t,n){let r=n.filter(e=>t.has(e)),i=r.filter(e=>!this.blobs.has(t.get(e)??``));for(let n=0;n<i.length;n+=H){let r=i.slice(n,n+H),a=await w(this.api,`/../graphql`,{method:`POST`,body:{query:`query ($project: ID!, $ref: String!, $paths: [String!]!) {
2
+ project(fullPath: $project) { repository { blobs(ref: $ref, paths: $paths) { nodes { path rawTextBlob } } } }
3
+ }`,variables:{project:decodeURIComponent(this.project),ref:e,paths:r}}});for(let e of a.data?.project?.repository?.blobs?.nodes??[]){let n=t.get(e.path);n&&typeof e.rawTextBlob==`string`&&this.blobs.set(n,Promise.resolve(e.rawTextBlob))}}let a=new Map;for(let e of r){let n=await this.readFrom(t,e);n!==void 0&&a.set(e,n)}return a}async readBytes(e){return this.bytesFrom(await this.currentTree(),e)}async bytesFrom(e,t){let n=e.get(t);if(!n)return;let r=await C(this.api,this.repo(`/blobs/${n}/raw`));return new Uint8Array(await r.arrayBuffer())}async list(e){return W(await this.currentTree(),e)}filesAt(e){return{read:async t=>this.readFrom(await this.tree(e),t),readFiles:async t=>this.readFilesFrom(e,await this.tree(e),t),readBytes:async t=>this.bytesFrom(await this.tree(e),t),list:async t=>W(await this.tree(e),t)}}async fileSizes(e){let t=await this.list(e),n=this.pinned??await this.revision(),r=new Map;for(let e=0;e<t.length;e+=H){let i=await w(this.api,`/../graphql`,{method:`POST`,body:{query:`query ($project: ID!, $ref: String!, $paths: [String!]!) {
4
+ project(fullPath: $project) { repository { blobs(ref: $ref, paths: $paths) { nodes { path size } } } }
5
+ }`,variables:{project:decodeURIComponent(this.project),ref:n,paths:t.slice(e,e+H)}}});for(let e of i.data?.project?.repository?.blobs?.nodes??[]){let t=Number(e.size);Number.isFinite(t)&&r.set(e.path,t)}}return r}async lastCommitFor(e,t){return(await w(this.api,this.repo(`/files/${encodeURIComponent(e)}?ref=${t}`))).last_commit_id}async existsAt(t,n,r){return e.some(e=>n.startsWith(`${e}/`))?t.has(n):this.lastCommitFor(n,r).then(()=>!0,e=>{if(e instanceof x&&e.status===404)return!1;throw e})}async write(e,{message:r,expectedRevision:i}){if(this.contentOnly&&t(e),await this.revision()!==i)throw new n;let a=await this.tree(i),o=(await Promise.all(e.map(async e=>{let t=await this.existsAt(a,e.path,i);if(`delete`in e)return t?{action:`delete`,file_path:e.path,last_commit_id:await this.lastCommitFor(e.path,i)}:void 0;let n=`bytes`in e?{content:v(e.bytes),encoding:`base64`}:{content:e.content};return t?{action:`update`,file_path:e.path,...n,last_commit_id:await this.lastCommitFor(e.path,i)}:{action:`create`,file_path:e.path,...n}}))).filter(e=>e!==void 0);if(o.length===0)return{revision:i};try{let t=await w(this.api,this.repo(`/commits`),{method:`POST`,body:{branch:this.branch,commit_message:r,actions:o,...this.options.author&&{author_name:this.options.author.name,author_email:this.options.author.email}}});return this.pinned=t.id,t.parent_ids?.length===1&&t.parent_ids[0]===i&&await this.carryTree(a,t.id,e).catch(()=>{}),{revision:t.id}}catch(e){throw e instanceof x?e.status===403?new _(`cant-publish`,`This GitLab account can't publish to the site.`):e.status===400&&/already exists|changed since/i.test(e.message)||await this.revision()!==i?new n:e:e}}async carryTree(t,n,r){if(this.trees.has(n)||!globalThis.crypto?.subtle)return;let i=new Map(t);for(let t of r){if(!e.some(e=>t.path.startsWith(`${e}/`)))continue;if(`delete`in t){i.delete(t.path);continue}let n=await me(`bytes`in t?t.bytes:new TextEncoder().encode(t.content));i.set(t.path,n),`content`in t&&!this.blobs.has(n)&&this.blobs.set(n,Promise.resolve(t.content))}this.trees.set(n,Promise.resolve(i))}async history(e,{page:t=1,perPage:n=20}={}){let r=new URLSearchParams({ref_name:this.pinned??await this.revision(),path:e,per_page:String(n),page:String(t)});return(await w(this.api,this.repo(`/commits?${r}`))).map(e=>({revision:e.id,date:e.authored_date??e.committed_date??``,author:e.author_name||void 0,message:e.title}))}async readAt(e,t){let n=await C(this.api,this.repo(`/files/${encodeURIComponent(e)}/raw?${new URLSearchParams({ref:t})}`),{allow:[404]});return n.ok?n.text():void 0}async changedPaths(e,t){let n=await w(this.api,this.repo(`/compare?${new URLSearchParams({from:e,to:t,straight:`true`})}`));return[...new Set((n.diffs??[]).flatMap(e=>[e.old_path,e.new_path]))]}async deployStatus(e){try{let t=await this.statuses(e),n=t.filter(U);if(t.length>0&&n.length===0)return{state:`unknown`};if(n.length===0){if((await w(this.api,`/projects/${this.project}/pipelines?per_page=1`)).length>0)return{state:`building`};let{parent_ids:t=[]}=await w(this.api,this.repo(`/commits/${e}`));return{state:(t[0]?(await this.statuses(t[0])).filter(U):[]).length>0?`building`:`unknown`}}let r=n.find(e=>e.status===`failed`)??n.find(e=>e.status===`canceled`);if(r?.status===`canceled`){let t=await this.head();if(t!==e)return this.deployStatus(t)}if(r){let t=await this.jobProblem(e).catch(()=>void 0);return{state:`failed`,detailsUrl:r.target_url??void 0,...t&&{problem:t}}}return n.every(e=>e.status===`success`||e.status===`skipped`)?{state:`live`,detailsUrl:n[0]?.target_url??void 0}:{state:`building`,detailsUrl:n[0]?.target_url??void 0}}catch(e){if(e instanceof x)return{state:`unknown`};throw e}}statuses(e){return w(this.api,this.repo(`/commits/${e}/statuses`))}async jobProblem(e){let[t]=await w(this.api,`/projects/${this.project}/pipelines?${new URLSearchParams({sha:e,per_page:`1`})}`);if(!t)return;let n=(await w(this.api,`/projects/${this.project}/pipelines/${t.id}/jobs?per_page=100`)).find(e=>e.status===`failed`);if(!n)return;let r=n.failure_reason??``,i=`${n.name}: ${r}`;if(r===`ci_quota_exceeded`)return{step:`not-started`,detail:i};if(/runner_system|stuck_or_timeout|scheduler|api_failure|data_integrity|job_execution_timeout/.test(r))return{step:`host`,detail:i};if(n.name===`pages:deploy`)return{step:`deploy`,detail:i};let a=await C(this.api,`/projects/${this.project}/jobs/${n.id}/trace`,{allow:[403,404]}),o=a.ok?await a.text():``,s=g(o),c=o.split(`
6
+ `).map(e=>e.replace(pe,``).replace(/^.*\r/,``)).filter(e=>e.startsWith(`$ `)).at(-1);return{step:s?`build`:(c?ee(c):void 0)??`build`,detail:i,...s&&{cause:s}}}signOut(){this.onSignOut()}};function K(e){let t=``;for(let n of e)t+=String.fromCharCode(n);return btoa(t).replace(/\+/g,`-`).replace(/\//g,`_`).replace(/=+$/,``)}function q(e=32){return K(crypto.getRandomValues(new Uint8Array(e)))}async function J(e){let t=await crypto.subtle.digest(`SHA-256`,new TextEncoder().encode(e));return K(new Uint8Array(t))}function Y(e,t,n=Date.now()){return{type:`oauth`,access:e.access_token,refresh:e.refresh_token,expiresAt:n+(e.expires_in??7200)*1e3,verifier:t}}function X(e){let t=new URL(e);return`${t.origin}${t.pathname.replace(/\/?$/,`/`)}`}var Z=`main`;function he(){return{href:()=>window.location.href,assign:e=>window.location.assign(e),replace:e=>window.history.replaceState(window.history.state,``,e)}}function Q(e={}){let t=(e.url??`https://gitlab.com`).replace(/\/$/,``),n=`${t}/api/v4`,r=e.fetch??((e,t)=>globalThis.fetch(e,t)),i=y(`goodfellow:gitlab-setup:pending`,e.storage),a=()=>e.location??he();async function o(e){let n=await r(`${t}/oauth/token`,{method:`POST`,headers:{"content-type":`application/x-www-form-urlencoded`},body:new URLSearchParams(e)});if(!n.ok)throw new _(`invalid`,`GitLab didn't accept the sign-in (${n.status}).`);return await n.json()}async function s(t){let i=t,s={apiUrl:n,token:async({refresh:t}={})=>{if(i.type===`token`)return i.token;if(t||i.expiresAt-6e4<Date.now()){let{verifier:t}=i;i=Y(await o({client_id:e.clientId??``,refresh_token:i.refresh,grant_type:`refresh_token`,redirect_uri:X(a().href()),code_verifier:t}),t)}return i.access},canRefresh:i.type===`oauth`,fetch:r},c=await w(s,`/user`);async function l(e,t){return(await C(s,`/projects/${encodeURIComponent(`${e.path}/${t}`)}`,{allow:[404]})).status===404}async function u(e){let t=e=>`/projects/${encodeURIComponent(e)}`;try{let n=new Map;for(let r of[`code`,`content`])n.set(r,(await w(s,t(e[r]))).id);for(let{reader:r,read:i}of e.links.reads)await C(s,`${t(e[i])}/job_token_scope/allowlist`,{method:`POST`,body:{target_project_id:n.get(r)}});e.links.updates&&await C(s,t(e[e.links.updates]),{method:`PUT`,body:{ci_push_repository_for_job_token_allowed:!0}});for(let n of e.links.nightly)await C(s,`${t(e[n])}/pipeline_schedules`,{method:`POST`,body:{description:n===e.links.updates?`Rebuild the site and install Goodfellow's fixes each night`:`Rebuild the site each night`,ref:Z,cron:I,cron_timezone:`UTC`,active:!0}});return!0}catch(e){if(e instanceof x&&[400,403,404].includes(e.status))return!1;throw e}}return{user:{login:c.username,name:c.name,avatarUrl:c.avatar_url},async owners(){let e=(await w(s,`/namespaces?per_page=100`)).map(e=>({path:e.full_path,name:e.kind===`user`?c.name??e.name:e.name,kind:e.kind===`user`?`personal`:`organization`,id:e.id}));return[...e.filter(e=>e.kind===`personal`),...e.filter(e=>e.kind!==`personal`)]},isAvailable:l,async createSite(e,t){if(t?.(`repository`),!await l(e.owner,e.name))throw new p(`name-taken`,`There's already a project called ${e.owner.path}/${e.name}.`);let n;try{n=await w(s,`/projects`,{method:`POST`,body:{name:e.name,path:e.name,namespace_id:e.owner.id,visibility:e.private?`private`:`public`,description:e.description,...e.pages&&{pages_access_level:e.private?`public`:`enabled`}}})}catch(t){throw t instanceof x&&t.status===400?new p(`name-invalid`,`GitLab can't use "${e.name}" as a project name.`):t instanceof x&&(t.status===403||t.status===404)?new p(`not-allowed`,`This sign-in can't create projects in ${e.owner.path}.`):t}let r=[];e.link&&(t?.(`connect`),await u(e.link)||r.push(`connect-unavailable`));let i;if(e.pages&&e.domain){t?.(`pages`);try{await C(s,`/projects/${n.id}/pages/domains`,{method:`POST`,body:{domain:e.domain,auto_ssl_enabled:!0}}),i=`https://${e.domain}/`}catch(e){if(!(e instanceof x)||![400,403,404,409,422].includes(e.status))throw e;r.push(`domain-unavailable`)}}return t?.(`files`),await C(s,`/projects/${n.id}/repository/commits`,{method:`POST`,body:{branch:Z,commit_message:e.message,actions:e.files.flatMap(e=>`delete`in e?[]:`content`in e?[{action:`create`,file_path:e.path,content:e.content}]:[{action:`create`,file_path:e.path,content:v(e.bytes),encoding:`base64`}])}}),{repo:n.path_with_namespace,webUrl:n.web_url,...i&&{siteUrl:i},buildsUrl:`${n.web_url}/-/pipelines`,pagesUrl:e.pages?`${n.web_url}/pages`:void 0,warnings:r}}}}return{name:`GitLab`,canRedirect:!!e.clientId,tokenLinks:[{url:`${t}/-/user_settings/personal_access_tokens?${new URLSearchParams({name:`Goodfellow site setup`,scopes:`api`})}`,label:`signIn.token.create`}],async restore(){let t=new URL(a().href()),n=t.searchParams.get(`code`),r=t.searchParams.get(`error`);if(!n&&!r)return null;let c=i.load();if(!c)return null;i.clear();let l=JSON.parse(c),u=t.searchParams.get(`state`);for(let e of[`code`,`state`,`error`,`error_description`])t.searchParams.delete(e);if(a().replace(t.toString()),r||!n||u!==l.state)throw new _(`redirect-failed`,`Signing in with GitLab was cancelled or didn't finish.`);return s(Y(await o({client_id:e.clientId??``,code:n,grant_type:`authorization_code`,redirect_uri:X(t.toString()),code_verifier:l.verifier}),l.verifier))},async signInWithToken(e){let t=e.trim();if(!t)throw new _(`invalid`,`Paste a token to sign in.`);return s({type:`token`,token:t})},async startRedirect(){if(!e.clientId)throw Error(`gitlabSetup({ clientId }) is needed to sign in with GitLab.`);let n={state:q(16),verifier:q(48)};i.save(JSON.stringify(n),!1);let r=new URLSearchParams({client_id:e.clientId,redirect_uri:X(a().href()),response_type:`code`,state:n.state,scope:`api`,code_challenge:await J(n.verifier),code_challenge_method:`S256`});a().assign(`${t}/oauth/authorize?${r}`)}}}var ge=30;function $(){return{href:()=>window.location.href,assign:e=>window.location.assign(e),replace:e=>window.history.replaceState(window.history.state,``,e)}}function _e(e){let t=(e.url??`https://gitlab.com`).replace(/\/$/,``),n=`${t}/api/v4`,r=u(e.fetch??((e,t)=>globalThis.fetch(e,t)),{host:`GitLab`}),i=y(`goodfellow:gitlab:${t}:${e.project}`,e.storage),a=y(`goodfellow:gitlab:pending:${e.project}`,e.storage),o=()=>e.location??$();async function s(e){let n=await r(`${t}/oauth/token`,{method:`POST`,headers:{"content-type":`application/x-www-form-urlencoded`},body:new URLSearchParams(e)});if(!n.ok)throw new _(`invalid`,`GitLab didn't accept the sign-in (${n.status}).`);return await n.json()}async function c(a,c){let l=a,u={apiUrl:n,token:async({refresh:t}={})=>{if(l.type===`token`)return l.token;if(t||l.expiresAt-6e4<Date.now()){let{verifier:t}=l;l=Y(await s({client_id:e.clientId??``,refresh_token:l.refresh,grant_type:`refresh_token`,redirect_uri:X(o().href()),code_verifier:t}),t),i.save(JSON.stringify(l),c)}return l.access},canRefresh:l.type===`oauth`,fetch:r},d=await w(u,`/user`),f;try{f=await w(u,`/projects/${encodeURIComponent(e.project)}`)}catch(t){throw t instanceof x&&(t.status===404||t.status===403)?new _(`no-access`,`The GitLab account ${d.username} can't see ${e.project}.`):t}if(Math.max(f.permissions?.project_access?.access_level??0,f.permissions?.group_access?.access_level??0)<ge)throw new _(`cant-publish`,`The GitLab account ${d.username} can see ${e.project} but can't publish to it.`);return i.save(JSON.stringify(l),c),new G({api:u,project:e.project,branch:e.branch??f.default_branch,...e.contentOnly&&{contentOnly:!0},...e.contentOnly&&e.code&&{code:{project:e.code,webUrl:t}},user:{login:d.username,name:d.name,avatarUrl:d.avatar_url},onSignOut:()=>i.clear()})}async function l(){let t=new URL(o().href()),n=t.searchParams.get(`code`),r=t.searchParams.get(`state`),i=t.searchParams.get(`error`);if(!n&&!i)return null;let l=a.load();if(!l)return null;a.clear();let u=JSON.parse(l);for(let e of[`code`,`state`,`error`,`error_description`])t.searchParams.delete(e);if(t.hash=u.hash,o().replace(t.toString()),i||!n||r!==u.state)throw new _(`redirect-failed`,`Signing in with GitLab was cancelled or didn't finish.`);return c(Y(await s({client_id:e.clientId??``,code:n,grant_type:`authorization_code`,redirect_uri:X(t.toString()),code_verifier:u.verifier}),u.verifier),u.remember)}function d({token:t}){let i={apiUrl:n,token:async()=>typeof t==`string`?t:t(),canRefresh:!1,fetch:r},a,o=()=>(a??=e.branch?Promise.resolve(e.branch):w(i,`/projects/${encodeURIComponent(e.project)}`).then(e=>e.default_branch),a.catch(()=>{a=void 0}),a),s=async(t,n)=>new G({api:i,project:e.project,branch:await o(),...e.contentOnly&&{contentOnly:!0},user:{login:n?.name??e.project,...n&&{name:n.name}},onSignOut:()=>{},...t&&{revision:t},...n&&{author:n}});return{...e.contentOnly&&{contentOnly:!0},async revision(){return(await s()).revision()},at:s}}return{name:`GitLab`,source:b(`@goodfellow-cms/gitlab`,`gitlab`,e,[`fetch`,`storage`,`location`]),canRedirect:!!e.clientId,setup:()=>Q({...e.url&&{url:e.url},...e.fetch&&{fetch:e.fetch},...e.storage&&{storage:e.storage},...e.location&&{location:e.location}}),redirectAddressesListed:!!e.clientId,tokenLinks:[{url:`${t}/-/user_settings/personal_access_tokens?${new URLSearchParams({name:`Goodfellow admin`,scopes:`api`})}`,label:`signIn.token.create`}],async restore(){let e=await l();if(e)return e;let t=i.load();if(!t)return null;try{return await c(JSON.parse(t),i.remembered())}catch(e){throw e instanceof _&&i.clear(),e}},async signInWithToken(e,t){let n=e.trim();if(!n)throw new _(`invalid`,`Paste a token to sign in.`);return c({type:`token`,token:n},t)},async startRedirect(n){if(!e.clientId)throw Error(`gitlab({ clientId }) is needed to sign in with GitLab.`);let r=q(48),i=q(16),s=new URL(o().href()),c={state:i,verifier:r,remember:n,hash:s.hash};a.save(JSON.stringify(c),!1);let l=new URLSearchParams({client_id:e.clientId,redirect_uri:X(s.toString()),response_type:`code`,state:i,scope:`api`,code_challenge:await J(r),code_challenge_method:`S256`});o().assign(`${t}/oauth/authorize?${l}`)},server:d}}export{G as GitLabBackend,_e as gitlab,N as gitlabPagesRecords,Q as gitlabSetup,X as redirectUri};