@picked/vite-plugin-picked 0.0.1-dev.1778561735098 → 0.0.1-dev.1780446300503
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/index.js +54 -9
- package/dist/esm/index.js +340 -113
- package/dist/types/gitignore.d.ts +16 -0
- package/dist/types/gitignore.d.ts.map +1 -0
- package/dist/types/index.d.ts +8 -4
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/prefetch.d.ts +72 -0
- package/dist/types/prefetch.d.ts.map +1 -0
- package/dist/types/retry.d.ts +56 -0
- package/dist/types/retry.d.ts.map +1 -0
- package/dist/types/util.d.ts +18 -0
- package/dist/types/util.d.ts.map +1 -0
- package/dist/types/virtual-module/build.d.ts +33 -0
- package/dist/types/virtual-module/build.d.ts.map +1 -0
- package/dist/types/virtual-module/dev.d.ts +26 -0
- package/dist/types/virtual-module/dev.d.ts.map +1 -0
- package/package.json +2 -2
package/dist/cjs/index.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require(`@picked/client/plugins/dynamic-experiences`);function
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require(`node:fs`),t=require(`@picked/client/plugins/dynamic-experiences`),n=require(`node:fs/promises`),r=require(`node:path`);function i(e,t){return t||=e.slice(0),Object.freeze(Object.defineProperties(e,{raw:{value:Object.freeze(t)}}))}var a;function o(e){let t=JSON.stringify(e),n=e.length>0?e.map(e=>`
|
|
2
2
|
<div class="feature-row">
|
|
3
3
|
<label>
|
|
4
|
-
<input type="checkbox" data-feature-id="${
|
|
5
|
-
<span class="feature-name">${
|
|
6
|
-
<span class="feature-id">${
|
|
4
|
+
<input type="checkbox" data-feature-id="${c(e.id)}" checked>
|
|
5
|
+
<span class="feature-name">${s(e.name)}</span>
|
|
6
|
+
<span class="feature-id">${s(e.id)}</span>
|
|
7
7
|
</label>
|
|
8
|
-
<select data-value-for="${
|
|
8
|
+
<select data-value-for="${c(e.id)}">
|
|
9
9
|
<option value="true" selected>true</option>
|
|
10
10
|
<option value="false">false</option>
|
|
11
11
|
</select>
|
|
12
|
-
</div>`).join(``):`<p class="text no-features">No features configured for this project.</p>`;return String.raw(
|
|
12
|
+
</div>`).join(``):`<p class="text no-features">No features configured for this project.</p>`;return String.raw(a||=i([`<!DOCTYPE html>
|
|
13
13
|
<html lang="en">
|
|
14
14
|
<head>
|
|
15
15
|
<meta charset="UTF-8">
|
|
@@ -855,7 +855,52 @@ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require
|
|
|
855
855
|
<\/script>
|
|
856
856
|
</main>
|
|
857
857
|
</body>
|
|
858
|
-
</html>`]),
|
|
858
|
+
</html>`]),n,t)}function s(e){return e.replaceAll(`&`,`&`).replaceAll(`<`,`<`).replaceAll(`>`,`>`).replaceAll(`"`,`"`)}function c(e){return e.replaceAll(`&`,`&`).replaceAll(`"`,`"`).replaceAll(`<`,`<`).replaceAll(`>`,`>`)}var l=`picked`,u=`\x1B[0m`;function d(){let e=process.env.NO_COLOR;return e===`1`||e===`true`}function f(e){return t=>d()?t:`${e}${t}${u}`}var p=[`debug`,`info`,`warn`,`error`,`silent`],m={debug:{color:f(`\x1B[2m`),log:console.debug,enabled:!1},info:{color:f(`\x1B[36m`),log:console.log,enabled:!0},warn:{color:f(`\x1B[33m`),log:console.warn,enabled:!0},error:{color:f(`\x1B[31m`),log:console.error,enabled:!0},silent:{log:()=>{},enabled:!1}};function h(e,t){return`${new Date().toLocaleTimeString()} [${t?`${l}:${t}`:l}] ${e}`}function g(e,t,n,r){if(!e.enabled)return;let i=h(t,r);e.log(e.color?e.color(i):i),n!==void 0&&e.log(n)}function _(e){let t=m[e],n=new Set;return Object.assign((e,n,r)=>{g(t,e,n,r)},{once(e,r,i){!t.enabled||n.has(e)||(n.add(e),g(t,e,r,i))}})}function ee(e){let t=p.indexOf(e);if(t===-1){g(m.error,`invalid log level: ${e}`);return}for(let[e,n]of p.entries())m[n].enabled=e>=t}var v=f(`\x1B[34m`),y=f(`\x1B[36m`);function b(){let e=[` ${v(`_`)} ${y(`_`)} _ _ _`,`${v(`(_)`)} ${y(`(_)`)} _ __(_)__| |_____ __| |`,` ${v(`_`)} ${v(`_`)} | '_ \\ / _| / / -_) _\` |`,`${v(`(_)`)} ${v(`(_)`)} | .__/_\\__|_\\_\\___\\__,_|`,` |_|
|
|
859
859
|
`];console.log(e.join(`
|
|
860
|
-
`))}var
|
|
861
|
-
`)
|
|
860
|
+
`))}var x={banner:b,debug:_(`debug`),info:_(`info`),warn:_(`warn`),error:_(`error`),setLevel:ee};function te(e){return(t,n,r)=>{if(t.method!==`GET`){r();return}let i=(t.url??``).split(`?`)[0];if(i!==`/`&&i!==`/index.html`){r();return}let a=e();n.setHeader(`Content-Type`,`text/html`),n.statusCode=200,n.end(a)}}var S=`.picked/`;async function C(t,i){if(i)return;let a=(0,r.join)(t,`.gitignore`);if(!(0,e.existsSync)(a)){await(0,n.writeFile)(a,`${S}
|
|
861
|
+
`);return}let o=await(0,n.readFile)(a,`utf8`);w(o)||await(0,n.appendFile)(a,`${o.length>0&&!o.endsWith(`
|
|
862
|
+
`)?`
|
|
863
|
+
`:``}${S}
|
|
864
|
+
`)}function w(e){return e.split(/\r?\n/).map(e=>e.trim()).some(e=>e===S||e===`.picked`)}var T=6,E=5e3;function D(e){let t;try{t=new URL(e).hostname}catch{return!1}return t===`localhost`||t===`127.0.0.1`||t===`::1`||t===`[::1]`?!0:t===`api.picked.app`?!1:t.endsWith(`.picked.app`)}function ne(e){return new Promise(t=>{setTimeout(t,e)})}async function O(e,t){let n=null,r=null;try{n=await e()}catch(e){r=e}if(n!==null)return n;for(let i=1;i<=T;i++){let a=E*2**(i-1);t(i,T,a),await ne(a),r=null;try{n=await e()}catch(e){r=e}if(n!==null)return n}if(r!==null)throw r;return null}function k(e){return Array.isArray(e)?e:[]}var re=1;function A(e){let t=(0,r.join)(e,`.picked`);return{pickedDir:t,manifestPath:(0,r.join)(t,`manifest.json`),projectConfigPath:(0,r.join)(t,`project-config.json`),experiencesDir:(0,r.join)(t,`experiences`)}}async function j(t){if(!(0,e.existsSync)(t))return null;try{let e=await(0,n.readFile)(t,`utf8`),r=JSON.parse(e);return M(r)?r:null}catch{return null}}function M(e){return!(typeof e!=`object`||!e||!(`version`in e)||typeof e.version!=`number`||!(`projectVersionId`in e)||typeof e.projectVersionId!=`string`||!(`workspaceId`in e)||typeof e.workspaceId!=`string`||!(`experiences`in e)||!Array.isArray(e.experiences))}function N(e){let t=new Set;for(let n of k(e.steps))n.layoutContentId&&t.add(n.id);if(t.size===0)return[];let n=[];for(let r of k(e.experiences))k(r.nodes).some(e=>e.type===`step`&&t.has(e.stepId))&&n.push(r.id);return n}async function P(e){let{apiUrl:t,projectId:i,projectRoot:a,headers:o,reuseWhenUnchanged:s=!0}=e,c=A(a),l=await I(t,i,o),u=N(l);if(s){let e=await j(c.manifestPath);if(e&&e.projectVersionId===l.versionId&&e.workspaceId===l.workspaceId)return{config:l,manifest:e,experienceContentPaths:F(c,u)}}await(0,n.mkdir)(c.experiencesDir,{recursive:!0}),await(0,n.writeFile)(c.projectConfigPath,JSON.stringify(l,null,2));let d=new Date().toISOString(),f=[];for(let e of u){let a=await L(t,i,e,o);await(0,n.writeFile)((0,r.join)(c.experiencesDir,`${e}.json`),JSON.stringify(a,null,2)),f.push({id:e,fetchedAt:d})}let p={version:re,fetchedAt:d,projectVersionId:l.versionId,workspaceId:l.workspaceId,projectId:l.id,experiences:f};return await(0,n.writeFile)(c.manifestPath,JSON.stringify(p,null,2)),{config:l,manifest:p,experienceContentPaths:F(c,u)}}function F(e,t){let n={};for(let i of t)n[i]=(0,r.join)(e.experiencesDir,`${i}.json`);return n}async function I(e,n,r){let i=()=>(0,t.fetchProject)(e,n,{headers:r}),a;try{a=D(e)?await O(i,(e,t,n)=>{x.info(`project config not ready, retrying in ${(n/1e3).toString()}s (${e.toString()}/${t.toString()})`)}):await i()}catch(t){throw Error(`[picked] Build-time fetch of project config failed for "${n}" at ${e}: ${R(t)}`)}if(!a)throw Error(`[picked] Build-time fetch of project config returned no usable response for "${n}" at ${e}. Confirm the API is reachable and the project ID is correct.`);return a}async function L(e,n,r,i){let a;try{a=await(0,t.fetchExperienceContent)(e,n,r,{headers:i})}catch(t){throw Error(`[picked] Build-time fetch of content for experience "${r}" failed at ${e}: ${R(t)}`)}if(!a)throw Error(`[picked] Build-time fetch of content for experience "${r}" returned no usable response. Confirm the experience id exists and the API is reachable.`);return a}function R(e){if(e instanceof Error)return e.message;if(typeof e==`string`)return e;try{return JSON.stringify(e)}catch{return`unknown error`}}function z(e){let{projectConfigPath:t,experienceContentPaths:n,manifestMetadata:r}=e,i=e=>JSON.stringify(e),a=Object.entries(n).map(([e,t])=>` ${JSON.stringify(e)}: () => import(${i(t)}).then((m) => m.default ?? m),`).join(`
|
|
865
|
+
`);return`import projectConfigJson from ${i(t)}
|
|
866
|
+
|
|
867
|
+
const experienceImporters = {
|
|
868
|
+
${a}
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
export const projectConfig = projectConfigJson
|
|
872
|
+
|
|
873
|
+
export async function experienceContent(experience, _options) {
|
|
874
|
+
const id = typeof experience === 'string' ? experience : experience?.id
|
|
875
|
+
if (!id) return null
|
|
876
|
+
const importer = experienceImporters[id]
|
|
877
|
+
if (!importer) return null
|
|
878
|
+
try {
|
|
879
|
+
return await importer()
|
|
880
|
+
} catch {
|
|
881
|
+
return null
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
export const manifestMetadata = ${JSON.stringify(r)}
|
|
886
|
+
`}function B(e){let{apiUrl:t,projectId:n}=e;return`import { fetchProject, fetchExperienceContent } from '@picked/client/plugins/dynamic-experiences'
|
|
887
|
+
|
|
888
|
+
const apiUrl = ${JSON.stringify(t)}
|
|
889
|
+
const projectId = ${JSON.stringify(n)}
|
|
890
|
+
|
|
891
|
+
export const projectConfig = await fetchProject(apiUrl, projectId)
|
|
892
|
+
|
|
893
|
+
export async function experienceContent(experience, options) {
|
|
894
|
+
const id = typeof experience === 'string' ? experience : experience?.id
|
|
895
|
+
if (!id) return null
|
|
896
|
+
return await fetchExperienceContent(apiUrl, projectId, id, options)
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
export const manifestMetadata = {
|
|
900
|
+
mode: 'dev',
|
|
901
|
+
fetchedAt: null,
|
|
902
|
+
projectVersionId: projectConfig?.versionId ?? null,
|
|
903
|
+
workspaceId: projectConfig?.workspaceId ?? null,
|
|
904
|
+
}
|
|
905
|
+
`}var V=5;function H(e,t,n){if(t.length===0)return;if(t.length>V){console.log(`\n ${e}: ${t.length.toString()} loaded\n`);return}let r=n.map(e=>Math.max(e.header.length,...t.map(t=>e.value(t).length))),i=(e,t)=>e.padEnd(r[t]??e.length),a=` ${n.map((e,t)=>i(e.header,t)).join(` `)}`,o=` ${r.map(e=>`─`.repeat(e)).join(` `)}`,s=t.map(e=>` ${n.map((t,n)=>i(t.value(e),n)).join(` `)}`);console.log([``,` ${e}:`,``,a,o,...s,``].join(`
|
|
906
|
+
`))}function U(e,t){t.length!==0&&console.log(`\n ${e}: ${t.length.toString()} loaded\n`)}var W=[{header:`Name`,value:e=>e.name},{header:`ID`,value:e=>e.id},{header:`Status`,value:e=>e.enabled?`\x1B[32m enabled\x1B[0m`:`\x1B[2mdisabled\x1B[0m`},{header:`Weight`,value:e=>`${e.weight.toString()}%`.padStart(6)},{header:`Ver`,value:e=>`v${e.version.toString()}`}],G=[{header:`Name`,value:e=>e.name},{header:`Key`,value:e=>e.key},{header:`Component`,value:e=>e.componentName},{header:`Nodes`,value:e=>e.nodes.length.toString()},{header:`Targets`,value:e=>e.targets.length.toString()}],K=[{header:`Name`,value:e=>e.name},{header:`Key`,value:e=>e.key},{header:`Fields`,value:e=>(e.fieldIds?.length??0).toString()}],q=[{header:`Name`,value:e=>e.name},{header:`Key`,value:e=>e.key},{header:`Type`,value:e=>e.type},{header:`Required`,value:e=>e.required?`yes`:`no`}],J=[{header:`Name`,value:e=>e.name},{header:`Key`,value:e=>e.key}];function Y(){return`0.0.1-dev.1780446300503`}var X=`https://api.picked.app`,Z=`virtual:picked/data`,Q=`\0${Z}`;function ie(n){let r=n.apiUrl??X,i=[],a=process.cwd(),s=`serve`,c,l=!1;return{name:`picked`,config:async(o,u)=>{s=u.command,a=process.cwd(),x.banner(),r!==X&&x.warn(`using custom API URL: ${r}`);let d=(0,t.createClientHeaders)(`vite-plugin`,Y());if(u.command===`build`){if(l)return;l=!0,x.info(`prefetching project config + content for "${n.projectId}"`);let t=A(a),o=(0,e.existsSync)(t.pickedDir),s=performance.now(),u=await P({apiUrl:r,projectId:n.projectId,projectRoot:a,headers:d}),f=(performance.now()-s).toFixed(0);await C(a,o);let p=u.config;i=k(p.features),$(p,f),c=z({projectConfigPath:t.projectConfigPath,experienceContentPaths:u.experienceContentPaths,manifestMetadata:{mode:`build`,fetchedAt:u.manifest.fetchedAt,projectVersionId:u.manifest.projectVersionId,workspaceId:u.manifest.workspaceId}})}else{x.info(`fetching project config for "${n.projectId}" (dev)`);let e=performance.now(),a=()=>(0,t.fetchProject)(r,n.projectId,{headers:d}),o=D(r)?await O(a,(e,t,n)=>{x.info(`local API not ready, retrying in ${(n/1e3).toString()}s (${e.toString()}/${t.toString()})`)}):await a(),s=(performance.now()-e).toFixed(0);o?(i=k(o.features),$(o,s)):x.warn(`failed to fetch project config for "${n.projectId}" (${s}ms)`)}},resolveId(e){if(e===Z)return Q},load(e){if(e===Q)return s===`build`?c:B({apiUrl:r,projectId:n.projectId})},configureServer(e){if(n.disableLauncher)return;e.middlewares.use(te(()=>o(i)));let t=e.printUrls.bind(e);e.printUrls=()=>{let n=e.resolvedUrls?.local[0];if(n){let e=n.replace(/:(?<port>\d+)\//,(e,t)=>`:\x1b[1m${t}\x1b[22m/`);console.log(` \x1b[32m➜\x1b[0m \x1b[1mPicked\x1b[22m: \x1b[36m${e}\x1b[0m`)}t()}},generateBundle(){n.disableLauncher||this.emitFile({type:`asset`,fileName:`index.html`,source:o(i)})}}}function $(e,t){let n=k(e.features),r=k(e.experiences),i=k(e.steps),a=k(e.fields),o=k(e.conditions),s=k(e.actions),c=JSON.stringify(e).length,l=[`${n.length.toString()} features`,`${r.length.toString()} experiences`,`${i.length.toString()} steps`,`${a.length.toString()} fields`,`${o.length.toString()} conditions`,`${s.length.toString()} actions`].join(`, `);x.info(`fetched config in ${t}ms (${l})`),H(`Features`,n,W),H(`Experiences`,r,G),H(`Steps`,i,K),H(`Fields`,a,q),H(`Conditions`,o,J),U(`Actions`,s),x.debug(`config size: ${c.toString()} bytes`)}exports.picked=ie;
|
package/dist/esm/index.js
CHANGED
|
@@ -1,25 +1,28 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { existsSync as e } from "node:fs";
|
|
2
|
+
import { createClientHeaders as t, fetchExperienceContent as n, fetchProject as r } from "@picked/client/plugins/dynamic-experiences";
|
|
3
|
+
import { appendFile as i, mkdir as a, readFile as o, writeFile as s } from "node:fs/promises";
|
|
4
|
+
import { join as c } from "node:path";
|
|
2
5
|
//#region \0@oxc-project+runtime@0.124.0/helpers/taggedTemplateLiteral.js
|
|
3
|
-
function
|
|
6
|
+
function l(e, t) {
|
|
4
7
|
return t ||= e.slice(0), Object.freeze(Object.defineProperties(e, { raw: { value: Object.freeze(t) } }));
|
|
5
8
|
}
|
|
6
9
|
//#endregion
|
|
7
10
|
//#region src/launcher.ts
|
|
8
|
-
var
|
|
9
|
-
function
|
|
10
|
-
let t = JSON.stringify(e),
|
|
11
|
+
var u;
|
|
12
|
+
function d(e) {
|
|
13
|
+
let t = JSON.stringify(e), n = e.length > 0 ? e.map((e) => `
|
|
11
14
|
<div class="feature-row">
|
|
12
15
|
<label>
|
|
13
|
-
<input type="checkbox" data-feature-id="${
|
|
14
|
-
<span class="feature-name">${
|
|
15
|
-
<span class="feature-id">${
|
|
16
|
+
<input type="checkbox" data-feature-id="${p(e.id)}" checked>
|
|
17
|
+
<span class="feature-name">${f(e.name)}</span>
|
|
18
|
+
<span class="feature-id">${f(e.id)}</span>
|
|
16
19
|
</label>
|
|
17
|
-
<select data-value-for="${
|
|
20
|
+
<select data-value-for="${p(e.id)}">
|
|
18
21
|
<option value="true" selected>true</option>
|
|
19
22
|
<option value="false">false</option>
|
|
20
23
|
</select>
|
|
21
24
|
</div>`).join("") : "<p class=\"text no-features\">No features configured for this project.</p>";
|
|
22
|
-
return String.raw(
|
|
25
|
+
return String.raw(u ||= l([
|
|
23
26
|
"<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <meta name=\"robots\" content=\"noindex, nofollow\">\n <title>Picked</title>\n <style>\n main {\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n display: grid;\n align-items: center;\n justify-content: center;\n font-family: monospace;\n color: #4A4A4A\n }\n\n .wrap {\n padding-left: 1.5rem;\n padding-right: 1.5rem;\n max-width: 480px;\n width: 100%;\n }\n\n .title {\n font-size: 1.125rem;\n font-weight: 700;\n margin-bottom: 0;\n }\n\n .text {\n margin-top: .5rem;\n font-size: .875rem\n }\n\n .text a {\n color: #4A4A4A;\n text-decoration: none;\n }\n\n .text a:hover {\n cursor: pointer;\n text-decoration: underline\n }\n\n .preview-form {\n margin-top: 2rem;\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n }\n\n .preview-input {\n padding: 0.5rem;\n border: 1px solid #4A4A4A;\n border-radius: 0;\n font-family: monospace;\n }\n\n .preview-button {\n padding: 0.5rem;\n background: #4A4A4A;\n color: white;\n border: none;\n border-radius: 0;\n cursor: pointer;\n font-family: monospace;\n }\n\n .preview-button:hover {\n opacity: 0.9;\n }\n\n .buttons {\n display: grid;\n gap: 0.5em;\n grid-template-columns: 1fr 1fr;\n }\n\n #copy-status {\n font-size: 1em;\n text-align: right;\n margin-top: 0.5em;\n }\n\n .features-section {\n margin-top: 1.5rem;\n }\n\n .features-label {\n font-weight: 700;\n font-size: .875rem;\n margin-bottom: 0.5rem;\n }\n\n .feature-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 0.5rem;\n padding: 0.25rem 0;\n }\n\n .feature-row label {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n cursor: pointer;\n min-width: 0;\n flex: 1;\n }\n\n .feature-name {\n font-size: .875rem;\n }\n\n .feature-id {\n font-size: .75rem;\n color: #888;\n }\n\n .feature-row select {\n font-family: monospace;\n font-size: .75rem;\n padding: 0.125rem 0.25rem;\n border: 1px solid #4A4A4A;\n border-radius: 0;\n background: white;\n flex-shrink: 0;\n }\n\n .no-features {\n color: #888;\n font-style: italic;\n }\n\n .url-history {\n margin-top: 0.5rem;\n font-size: .75rem;\n }\n\n .url-history-item {\n display: flex;\n align-items: center;\n gap: 0.25rem;\n padding: 0.125rem 0;\n }\n\n .url-history-item button {\n background: none;\n border: none;\n cursor: pointer;\n font-family: monospace;\n font-size: .75rem;\n color: #888;\n padding: 0;\n }\n\n .url-history-item button.url-link {\n color: #4A4A4A;\n text-decoration: none;\n text-align: left;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n flex: 1;\n min-width: 0;\n }\n\n .url-history-item button.url-link:hover {\n text-decoration: underline;\n }\n\n .url-history-item button.url-delete:hover {\n color: #b71c1c;\n }\n\n @media (min-width: 640px) {\n .title {\n font-size: 1.25rem\n }\n\n .text {\n font-size: 1rem\n }\n }\n </style>\n</head>\n<body>\n\n<main>\n <div class=\"wrap\">\n <p class=\"title\">Picked</p>\n <p class=\"text\">\n Learn more at\n <a href=\"https://picked.app\">\n https://picked.app\n </a>\n </p>\n <div class=\"preview-form\">\n <input type=\"text\" id=\"url-input\" pattern=\"^(https?://)?.+\" placeholder=\"Enter preview URL\" required\n class=\"preview-input\">\n <div id=\"url-history\" class=\"url-history\"></div>\n <div class=\"features-section\">\n <p class=\"features-label\">Features</p>\n ",
|
|
24
27
|
"\n </div>\n <div class=\"buttons\">\n <button type=\"button\" class=\"preview-button\" id=\"copy-btn\">Copy</button>\n <button type=\"button\" class=\"preview-button\" id=\"preview-btn\">Preview</button>\n </div>\n <p class=\"text\" id=\"copy-status\" aria-live=\"polite\" style=\"visibility:hidden;\">\n \n </p>\n </div>\n </div>\n\n <script>\n var FEATURES = ",
|
|
25
28
|
";\n var STORAGE_KEY_URL = 'picked-launcher-url';\n var STORAGE_KEY_HISTORY = 'picked-launcher-history';\n var STORAGE_KEY_SELECTIONS = 'picked-launcher-selections';\n\n function getUrlInput() {\n return document.getElementById('url-input');\n }\n\n // URL history\n function getHistory() {\n try {\n var raw = localStorage.getItem(STORAGE_KEY_HISTORY);\n if (!raw) return [];\n var parsed = JSON.parse(raw);\n return Array.isArray(parsed) ? parsed : [];\n } catch (e) {\n return [];\n }\n }\n\n function saveHistory(history) {\n localStorage.setItem(STORAGE_KEY_HISTORY, JSON.stringify(history));\n }\n\n function addToHistory(url) {\n var history = getHistory().filter(function(h) { return h !== url; });\n history.unshift(url);\n if (history.length > 10) history = history.slice(0, 10);\n saveHistory(history);\n renderHistory();\n }\n\n function removeFromHistory(url) {\n var history = getHistory().filter(function(h) { return h !== url; });\n saveHistory(history);\n renderHistory();\n }\n\n function renderHistory() {\n var container = document.getElementById('url-history');\n if (!container) return;\n var history = getHistory();\n if (history.length === 0) {\n container.innerHTML = '';\n return;\n }\n container.innerHTML = history.map(function(url) {\n return '<div class=\"url-history-item\">' +\n '<button type=\"button\" class=\"url-link\" data-history-url=\"' + escapeAttr(url) + '\">' + escapeHtml(url) + '</button>' +\n '<button type=\"button\" class=\"url-delete\" data-history-delete=\"' + escapeAttr(url) + '\">x</button>' +\n '</div>';\n }).join('');\n }\n\n // Feature selection persistence\n function getSelections() {\n try {\n var raw = localStorage.getItem(STORAGE_KEY_SELECTIONS);\n if (!raw) return {};\n var parsed = JSON.parse(raw);\n return typeof parsed === 'object' && parsed !== null ? parsed : {};\n } catch (e) {\n return {};\n }\n }\n\n function saveSelections() {\n var selections = {};\n FEATURES.forEach(function(f) {\n var checkbox = document.querySelector('input[data-feature-id=\"' + f.id + '\"]');\n var select = document.querySelector('select[data-value-for=\"' + f.id + '\"]');\n if (checkbox && select) {\n selections[f.id] = { checked: checkbox.checked, value: select.value };\n }\n });\n localStorage.setItem(STORAGE_KEY_SELECTIONS, JSON.stringify(selections));\n }\n\n function restoreSelections() {\n var selections = getSelections();\n FEATURES.forEach(function(f) {\n var saved = selections[f.id];\n if (!saved) return;\n var checkbox = document.querySelector('input[data-feature-id=\"' + f.id + '\"]');\n var select = document.querySelector('select[data-value-for=\"' + f.id + '\"]');\n if (checkbox) checkbox.checked = saved.checked;\n if (select) select.value = saved.value;\n });\n }\n\n function escapeHtml(str) {\n var div = document.createElement('div');\n div.textContent = str;\n return div.innerHTML;\n }\n\n function escapeAttr(str) {\n return str.replace(/&/g, '&').replace(/\"/g, '"').replace(/</g, '<').replace(/>/g, '>');\n }\n\n function buildTargetUrl(inputValue) {\n var value = inputValue;\n if (!value.startsWith('http://') && !value.startsWith('https://')) {\n value = 'https://' + value;\n }\n var hostname = window.location.hostname;\n var subdomain = hostname.split('.').length > 2 ? hostname.split('.')[0] : '';\n var target = value;\n var sep = target.includes('?') ? '&' : '?';\n if (subdomain) {\n target = target + sep + 'cvly_env=' + encodeURIComponent(subdomain);\n sep = '&';\n }\n FEATURES.forEach(function(f) {\n var checkbox = document.querySelector('input[data-feature-id=\"' + f.id + '\"]');\n var select = document.querySelector('select[data-value-for=\"' + f.id + '\"]');\n if (checkbox && checkbox.checked && select) {\n target = target + sep + 'picked-feature=' + encodeURIComponent(f.id + ':' + select.value);\n sep = '&';\n }\n });\n return target;\n }\n\n function showCopyStatus(message, success) {\n var el = document.getElementById('copy-status');\n if (!el) return;\n el.textContent = message;\n el.style.visibility = 'visible';\n el.style.color = success ? '#2e7d32' : '#b71c1c';\n setTimeout(function() {\n el.style.visibility = 'hidden';\n }, 2000);\n }\n\n // Init\n window.addEventListener('load', function() {\n var input = getUrlInput();\n var savedUrl = localStorage.getItem(STORAGE_KEY_URL);\n if (savedUrl && input) {\n input.value = savedUrl;\n }\n renderHistory();\n restoreSelections();\n });\n\n // Delegate click events\n document.addEventListener('click', function(e) {\n var target = e.target;\n\n // History URL click\n var historyUrl = target.getAttribute('data-history-url');\n if (historyUrl) {\n var input = getUrlInput();\n if (input) input.value = historyUrl;\n return;\n }\n\n // History delete click\n var deleteUrl = target.getAttribute('data-history-delete');\n if (deleteUrl) {\n removeFromHistory(deleteUrl);\n return;\n }\n });\n\n // Feature change persistence\n document.addEventListener('change', function(e) {\n var target = e.target;\n if (target.getAttribute('data-feature-id') || target.getAttribute('data-value-for')) {\n saveSelections();\n }\n });\n\n // Copy button\n document.getElementById('copy-btn').addEventListener('click', function() {\n var input = getUrlInput();\n if (!input || !input.value) return;\n localStorage.setItem(STORAGE_KEY_URL, input.value);\n addToHistory(input.value);\n saveSelections();\n var target = buildTargetUrl(input.value);\n navigator.clipboard.writeText(target).then(function() {\n showCopyStatus('Preview URL copied to clipboard', true);\n });\n });\n\n // Preview button\n document.getElementById('preview-btn').addEventListener('click', function() {\n var input = getUrlInput();\n if (!input || !input.value) return;\n localStorage.setItem(STORAGE_KEY_URL, input.value);\n addToHistory(input.value);\n saveSelections();\n var target = buildTargetUrl(input.value);\n window.location.href = target;\n });\n <\/script>\n</main>\n</body>\n</html>"
|
|
@@ -27,48 +30,48 @@ function i(e) {
|
|
|
27
30
|
"<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <meta name=\"robots\" content=\"noindex, nofollow\">\n <title>Picked</title>\n <style>\n main {\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n display: grid;\n align-items: center;\n justify-content: center;\n font-family: monospace;\n color: #4A4A4A\n }\n\n .wrap {\n padding-left: 1.5rem;\n padding-right: 1.5rem;\n max-width: 480px;\n width: 100%;\n }\n\n .title {\n font-size: 1.125rem;\n font-weight: 700;\n margin-bottom: 0;\n }\n\n .text {\n margin-top: .5rem;\n font-size: .875rem\n }\n\n .text a {\n color: #4A4A4A;\n text-decoration: none;\n }\n\n .text a:hover {\n cursor: pointer;\n text-decoration: underline\n }\n\n .preview-form {\n margin-top: 2rem;\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n }\n\n .preview-input {\n padding: 0.5rem;\n border: 1px solid #4A4A4A;\n border-radius: 0;\n font-family: monospace;\n }\n\n .preview-button {\n padding: 0.5rem;\n background: #4A4A4A;\n color: white;\n border: none;\n border-radius: 0;\n cursor: pointer;\n font-family: monospace;\n }\n\n .preview-button:hover {\n opacity: 0.9;\n }\n\n .buttons {\n display: grid;\n gap: 0.5em;\n grid-template-columns: 1fr 1fr;\n }\n\n #copy-status {\n font-size: 1em;\n text-align: right;\n margin-top: 0.5em;\n }\n\n .features-section {\n margin-top: 1.5rem;\n }\n\n .features-label {\n font-weight: 700;\n font-size: .875rem;\n margin-bottom: 0.5rem;\n }\n\n .feature-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 0.5rem;\n padding: 0.25rem 0;\n }\n\n .feature-row label {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n cursor: pointer;\n min-width: 0;\n flex: 1;\n }\n\n .feature-name {\n font-size: .875rem;\n }\n\n .feature-id {\n font-size: .75rem;\n color: #888;\n }\n\n .feature-row select {\n font-family: monospace;\n font-size: .75rem;\n padding: 0.125rem 0.25rem;\n border: 1px solid #4A4A4A;\n border-radius: 0;\n background: white;\n flex-shrink: 0;\n }\n\n .no-features {\n color: #888;\n font-style: italic;\n }\n\n .url-history {\n margin-top: 0.5rem;\n font-size: .75rem;\n }\n\n .url-history-item {\n display: flex;\n align-items: center;\n gap: 0.25rem;\n padding: 0.125rem 0;\n }\n\n .url-history-item button {\n background: none;\n border: none;\n cursor: pointer;\n font-family: monospace;\n font-size: .75rem;\n color: #888;\n padding: 0;\n }\n\n .url-history-item button.url-link {\n color: #4A4A4A;\n text-decoration: none;\n text-align: left;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n flex: 1;\n min-width: 0;\n }\n\n .url-history-item button.url-link:hover {\n text-decoration: underline;\n }\n\n .url-history-item button.url-delete:hover {\n color: #b71c1c;\n }\n\n @media (min-width: 640px) {\n .title {\n font-size: 1.25rem\n }\n\n .text {\n font-size: 1rem\n }\n }\n </style>\n</head>\n<body>\n\n<main>\n <div class=\"wrap\">\n <p class=\"title\">Picked</p>\n <p class=\"text\">\n Learn more at\n <a href=\"https://picked.app\">\n https://picked.app\n </a>\n </p>\n <div class=\"preview-form\">\n <input type=\"text\" id=\"url-input\" pattern=\"^(https?:\\/\\/)?.+\" placeholder=\"Enter preview URL\" required\n class=\"preview-input\">\n <div id=\"url-history\" class=\"url-history\"></div>\n <div class=\"features-section\">\n <p class=\"features-label\">Features</p>\n ",
|
|
28
31
|
"\n </div>\n <div class=\"buttons\">\n <button type=\"button\" class=\"preview-button\" id=\"copy-btn\">Copy</button>\n <button type=\"button\" class=\"preview-button\" id=\"preview-btn\">Preview</button>\n </div>\n <p class=\"text\" id=\"copy-status\" aria-live=\"polite\" style=\"visibility:hidden;\">\n \n </p>\n </div>\n </div>\n\n <script>\n var FEATURES = ",
|
|
29
32
|
";\n var STORAGE_KEY_URL = 'picked-launcher-url';\n var STORAGE_KEY_HISTORY = 'picked-launcher-history';\n var STORAGE_KEY_SELECTIONS = 'picked-launcher-selections';\n\n function getUrlInput() {\n return document.getElementById('url-input');\n }\n\n // URL history\n function getHistory() {\n try {\n var raw = localStorage.getItem(STORAGE_KEY_HISTORY);\n if (!raw) return [];\n var parsed = JSON.parse(raw);\n return Array.isArray(parsed) ? parsed : [];\n } catch (e) {\n return [];\n }\n }\n\n function saveHistory(history) {\n localStorage.setItem(STORAGE_KEY_HISTORY, JSON.stringify(history));\n }\n\n function addToHistory(url) {\n var history = getHistory().filter(function(h) { return h !== url; });\n history.unshift(url);\n if (history.length > 10) history = history.slice(0, 10);\n saveHistory(history);\n renderHistory();\n }\n\n function removeFromHistory(url) {\n var history = getHistory().filter(function(h) { return h !== url; });\n saveHistory(history);\n renderHistory();\n }\n\n function renderHistory() {\n var container = document.getElementById('url-history');\n if (!container) return;\n var history = getHistory();\n if (history.length === 0) {\n container.innerHTML = '';\n return;\n }\n container.innerHTML = history.map(function(url) {\n return '<div class=\"url-history-item\">' +\n '<button type=\"button\" class=\"url-link\" data-history-url=\"' + escapeAttr(url) + '\">' + escapeHtml(url) + '</button>' +\n '<button type=\"button\" class=\"url-delete\" data-history-delete=\"' + escapeAttr(url) + '\">x</button>' +\n '</div>';\n }).join('');\n }\n\n // Feature selection persistence\n function getSelections() {\n try {\n var raw = localStorage.getItem(STORAGE_KEY_SELECTIONS);\n if (!raw) return {};\n var parsed = JSON.parse(raw);\n return typeof parsed === 'object' && parsed !== null ? parsed : {};\n } catch (e) {\n return {};\n }\n }\n\n function saveSelections() {\n var selections = {};\n FEATURES.forEach(function(f) {\n var checkbox = document.querySelector('input[data-feature-id=\"' + f.id + '\"]');\n var select = document.querySelector('select[data-value-for=\"' + f.id + '\"]');\n if (checkbox && select) {\n selections[f.id] = { checked: checkbox.checked, value: select.value };\n }\n });\n localStorage.setItem(STORAGE_KEY_SELECTIONS, JSON.stringify(selections));\n }\n\n function restoreSelections() {\n var selections = getSelections();\n FEATURES.forEach(function(f) {\n var saved = selections[f.id];\n if (!saved) return;\n var checkbox = document.querySelector('input[data-feature-id=\"' + f.id + '\"]');\n var select = document.querySelector('select[data-value-for=\"' + f.id + '\"]');\n if (checkbox) checkbox.checked = saved.checked;\n if (select) select.value = saved.value;\n });\n }\n\n function escapeHtml(str) {\n var div = document.createElement('div');\n div.textContent = str;\n return div.innerHTML;\n }\n\n function escapeAttr(str) {\n return str.replace(/&/g, '&').replace(/\"/g, '"').replace(/</g, '<').replace(/>/g, '>');\n }\n\n function buildTargetUrl(inputValue) {\n var value = inputValue;\n if (!value.startsWith('http://') && !value.startsWith('https://')) {\n value = 'https://' + value;\n }\n var hostname = window.location.hostname;\n var subdomain = hostname.split('.').length > 2 ? hostname.split('.')[0] : '';\n var target = value;\n var sep = target.includes('?') ? '&' : '?';\n if (subdomain) {\n target = target + sep + 'cvly_env=' + encodeURIComponent(subdomain);\n sep = '&';\n }\n FEATURES.forEach(function(f) {\n var checkbox = document.querySelector('input[data-feature-id=\"' + f.id + '\"]');\n var select = document.querySelector('select[data-value-for=\"' + f.id + '\"]');\n if (checkbox && checkbox.checked && select) {\n target = target + sep + 'picked-feature=' + encodeURIComponent(f.id + ':' + select.value);\n sep = '&';\n }\n });\n return target;\n }\n\n function showCopyStatus(message, success) {\n var el = document.getElementById('copy-status');\n if (!el) return;\n el.textContent = message;\n el.style.visibility = 'visible';\n el.style.color = success ? '#2e7d32' : '#b71c1c';\n setTimeout(function() {\n el.style.visibility = 'hidden';\n }, 2000);\n }\n\n // Init\n window.addEventListener('load', function() {\n var input = getUrlInput();\n var savedUrl = localStorage.getItem(STORAGE_KEY_URL);\n if (savedUrl && input) {\n input.value = savedUrl;\n }\n renderHistory();\n restoreSelections();\n });\n\n // Delegate click events\n document.addEventListener('click', function(e) {\n var target = e.target;\n\n // History URL click\n var historyUrl = target.getAttribute('data-history-url');\n if (historyUrl) {\n var input = getUrlInput();\n if (input) input.value = historyUrl;\n return;\n }\n\n // History delete click\n var deleteUrl = target.getAttribute('data-history-delete');\n if (deleteUrl) {\n removeFromHistory(deleteUrl);\n return;\n }\n });\n\n // Feature change persistence\n document.addEventListener('change', function(e) {\n var target = e.target;\n if (target.getAttribute('data-feature-id') || target.getAttribute('data-value-for')) {\n saveSelections();\n }\n });\n\n // Copy button\n document.getElementById('copy-btn').addEventListener('click', function() {\n var input = getUrlInput();\n if (!input || !input.value) return;\n localStorage.setItem(STORAGE_KEY_URL, input.value);\n addToHistory(input.value);\n saveSelections();\n var target = buildTargetUrl(input.value);\n navigator.clipboard.writeText(target).then(function() {\n showCopyStatus('Preview URL copied to clipboard', true);\n });\n });\n\n // Preview button\n document.getElementById('preview-btn').addEventListener('click', function() {\n var input = getUrlInput();\n if (!input || !input.value) return;\n localStorage.setItem(STORAGE_KEY_URL, input.value);\n addToHistory(input.value);\n saveSelections();\n var target = buildTargetUrl(input.value);\n window.location.href = target;\n });\n <\/script>\n</main>\n</body>\n</html>"
|
|
30
|
-
]),
|
|
33
|
+
]), n, t);
|
|
31
34
|
}
|
|
32
|
-
function
|
|
35
|
+
function f(e) {
|
|
33
36
|
return e.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """);
|
|
34
37
|
}
|
|
35
|
-
function
|
|
38
|
+
function p(e) {
|
|
36
39
|
return e.replaceAll("&", "&").replaceAll("\"", """).replaceAll("<", "<").replaceAll(">", ">");
|
|
37
40
|
}
|
|
38
41
|
//#endregion
|
|
39
42
|
//#region src/log.ts
|
|
40
|
-
var
|
|
41
|
-
function
|
|
43
|
+
var m = "picked", ee = "\x1B[0m";
|
|
44
|
+
function te() {
|
|
42
45
|
let e = process.env.NO_COLOR;
|
|
43
46
|
return e === "1" || e === "true";
|
|
44
47
|
}
|
|
45
|
-
function
|
|
46
|
-
return (t) =>
|
|
48
|
+
function h(e) {
|
|
49
|
+
return (t) => te() ? t : `${e}${t}${ee}`;
|
|
47
50
|
}
|
|
48
|
-
var
|
|
51
|
+
var g = [
|
|
49
52
|
"debug",
|
|
50
53
|
"info",
|
|
51
54
|
"warn",
|
|
52
55
|
"error",
|
|
53
56
|
"silent"
|
|
54
|
-
],
|
|
57
|
+
], _ = {
|
|
55
58
|
debug: {
|
|
56
|
-
color:
|
|
59
|
+
color: h("\x1B[2m"),
|
|
57
60
|
log: console.debug,
|
|
58
61
|
enabled: !1
|
|
59
62
|
},
|
|
60
63
|
info: {
|
|
61
|
-
color:
|
|
64
|
+
color: h("\x1B[36m"),
|
|
62
65
|
log: console.log,
|
|
63
66
|
enabled: !0
|
|
64
67
|
},
|
|
65
68
|
warn: {
|
|
66
|
-
color:
|
|
69
|
+
color: h("\x1B[33m"),
|
|
67
70
|
log: console.warn,
|
|
68
71
|
enabled: !0
|
|
69
72
|
},
|
|
70
73
|
error: {
|
|
71
|
-
color:
|
|
74
|
+
color: h("\x1B[31m"),
|
|
72
75
|
log: console.error,
|
|
73
76
|
enabled: !0
|
|
74
77
|
},
|
|
@@ -77,52 +80,52 @@ var d = [
|
|
|
77
80
|
enabled: !1
|
|
78
81
|
}
|
|
79
82
|
};
|
|
80
|
-
function
|
|
81
|
-
return `${(/* @__PURE__ */ new Date()).toLocaleTimeString()} [${t ? `${
|
|
83
|
+
function v(e, t) {
|
|
84
|
+
return `${(/* @__PURE__ */ new Date()).toLocaleTimeString()} [${t ? `${m}:${t}` : m}] ${e}`;
|
|
82
85
|
}
|
|
83
|
-
function
|
|
86
|
+
function y(e, t, n, r) {
|
|
84
87
|
if (!e.enabled) return;
|
|
85
|
-
let i =
|
|
88
|
+
let i = v(t, r);
|
|
86
89
|
e.log(e.color ? e.color(i) : i), n !== void 0 && e.log(n);
|
|
87
90
|
}
|
|
88
|
-
function
|
|
89
|
-
let t =
|
|
91
|
+
function b(e) {
|
|
92
|
+
let t = _[e], n = /* @__PURE__ */ new Set();
|
|
90
93
|
return Object.assign((e, n, r) => {
|
|
91
|
-
|
|
94
|
+
y(t, e, n, r);
|
|
92
95
|
}, { once(e, r, i) {
|
|
93
|
-
!t.enabled || n.has(e) || (n.add(e),
|
|
96
|
+
!t.enabled || n.has(e) || (n.add(e), y(t, e, r, i));
|
|
94
97
|
} });
|
|
95
98
|
}
|
|
96
|
-
function
|
|
97
|
-
let t =
|
|
99
|
+
function x(e) {
|
|
100
|
+
let t = g.indexOf(e);
|
|
98
101
|
if (t === -1) {
|
|
99
|
-
|
|
102
|
+
y(_.error, `invalid log level: ${e}`);
|
|
100
103
|
return;
|
|
101
104
|
}
|
|
102
|
-
for (let [e, n] of
|
|
105
|
+
for (let [e, n] of g.entries()) _[n].enabled = e >= t;
|
|
103
106
|
}
|
|
104
|
-
var
|
|
105
|
-
function
|
|
107
|
+
var S = h("\x1B[34m"), C = h("\x1B[36m");
|
|
108
|
+
function w() {
|
|
106
109
|
let e = [
|
|
107
|
-
` ${
|
|
108
|
-
`${
|
|
109
|
-
` ${
|
|
110
|
-
`${
|
|
110
|
+
` ${S("_")} ${C("_")} _ _ _`,
|
|
111
|
+
`${S("(_)")} ${C("(_)")} _ __(_)__| |_____ __| |`,
|
|
112
|
+
` ${S("_")} ${S("_")} | '_ \\ / _| / / -_) _\` |`,
|
|
113
|
+
`${S("(_)")} ${S("(_)")} | .__/_\\__|_\\_\\___\\__,_|`,
|
|
111
114
|
" |_|\n "
|
|
112
115
|
];
|
|
113
116
|
console.log(e.join("\n"));
|
|
114
117
|
}
|
|
115
|
-
var
|
|
116
|
-
banner:
|
|
117
|
-
debug:
|
|
118
|
-
info:
|
|
119
|
-
warn:
|
|
120
|
-
error:
|
|
121
|
-
setLevel:
|
|
118
|
+
var T = {
|
|
119
|
+
banner: w,
|
|
120
|
+
debug: b("debug"),
|
|
121
|
+
info: b("info"),
|
|
122
|
+
warn: b("warn"),
|
|
123
|
+
error: b("error"),
|
|
124
|
+
setLevel: x
|
|
122
125
|
};
|
|
123
126
|
//#endregion
|
|
124
127
|
//#region src/middleware.ts
|
|
125
|
-
function
|
|
128
|
+
function E(e) {
|
|
126
129
|
return (t, n, r) => {
|
|
127
130
|
if (t.method !== "GET") {
|
|
128
131
|
r();
|
|
@@ -138,14 +141,225 @@ function x(e) {
|
|
|
138
141
|
};
|
|
139
142
|
}
|
|
140
143
|
//#endregion
|
|
141
|
-
//#region src/
|
|
142
|
-
var
|
|
143
|
-
function
|
|
144
|
+
//#region src/gitignore.ts
|
|
145
|
+
var D = ".picked/";
|
|
146
|
+
async function ne(t, n) {
|
|
147
|
+
if (n) return;
|
|
148
|
+
let r = c(t, ".gitignore");
|
|
149
|
+
if (!e(r)) {
|
|
150
|
+
await s(r, `${D}
|
|
151
|
+
`);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
let a = await o(r, "utf8");
|
|
155
|
+
O(a) || await i(r, `${a.length > 0 && !a.endsWith("\n") ? "\n" : ""}${D}
|
|
156
|
+
`);
|
|
157
|
+
}
|
|
158
|
+
function O(e) {
|
|
159
|
+
return e.split(/\r?\n/).map((e) => e.trim()).some((e) => e === D || e === ".picked");
|
|
160
|
+
}
|
|
161
|
+
//#endregion
|
|
162
|
+
//#region src/retry.ts
|
|
163
|
+
var k = 6, A = 5e3;
|
|
164
|
+
function j(e) {
|
|
165
|
+
let t;
|
|
166
|
+
try {
|
|
167
|
+
t = new URL(e).hostname;
|
|
168
|
+
} catch {
|
|
169
|
+
return !1;
|
|
170
|
+
}
|
|
171
|
+
return t === "localhost" || t === "127.0.0.1" || t === "::1" || t === "[::1]" ? !0 : t === "api.picked.app" ? !1 : t.endsWith(".picked.app");
|
|
172
|
+
}
|
|
173
|
+
function re(e) {
|
|
174
|
+
return new Promise((t) => {
|
|
175
|
+
setTimeout(t, e);
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
async function M(e, t) {
|
|
179
|
+
let n = null, r = null;
|
|
180
|
+
try {
|
|
181
|
+
n = await e();
|
|
182
|
+
} catch (e) {
|
|
183
|
+
r = e;
|
|
184
|
+
}
|
|
185
|
+
if (n !== null) return n;
|
|
186
|
+
for (let i = 1; i <= k; i++) {
|
|
187
|
+
let a = A * 2 ** (i - 1);
|
|
188
|
+
t(i, k, a), await re(a), r = null;
|
|
189
|
+
try {
|
|
190
|
+
n = await e();
|
|
191
|
+
} catch (e) {
|
|
192
|
+
r = e;
|
|
193
|
+
}
|
|
194
|
+
if (n !== null) return n;
|
|
195
|
+
}
|
|
196
|
+
if (r !== null) throw r;
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
//#endregion
|
|
200
|
+
//#region src/util.ts
|
|
201
|
+
function N(e) {
|
|
144
202
|
return Array.isArray(e) ? e : [];
|
|
145
203
|
}
|
|
146
|
-
|
|
204
|
+
//#endregion
|
|
205
|
+
//#region src/prefetch.ts
|
|
206
|
+
var P = 1;
|
|
207
|
+
function F(e) {
|
|
208
|
+
let t = c(e, ".picked");
|
|
209
|
+
return {
|
|
210
|
+
pickedDir: t,
|
|
211
|
+
manifestPath: c(t, "manifest.json"),
|
|
212
|
+
projectConfigPath: c(t, "project-config.json"),
|
|
213
|
+
experiencesDir: c(t, "experiences")
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
async function I(t) {
|
|
217
|
+
if (!e(t)) return null;
|
|
218
|
+
try {
|
|
219
|
+
let e = await o(t, "utf8"), n = JSON.parse(e);
|
|
220
|
+
return L(n) ? n : null;
|
|
221
|
+
} catch {
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
function L(e) {
|
|
226
|
+
return !(typeof e != "object" || !e || !("version" in e) || typeof e.version != "number" || !("projectVersionId" in e) || typeof e.projectVersionId != "string" || !("workspaceId" in e) || typeof e.workspaceId != "string" || !("experiences" in e) || !Array.isArray(e.experiences));
|
|
227
|
+
}
|
|
228
|
+
function R(e) {
|
|
229
|
+
let t = /* @__PURE__ */ new Set();
|
|
230
|
+
for (let n of N(e.steps)) n.layoutContentId && t.add(n.id);
|
|
231
|
+
if (t.size === 0) return [];
|
|
232
|
+
let n = [];
|
|
233
|
+
for (let r of N(e.experiences)) N(r.nodes).some((e) => e.type === "step" && t.has(e.stepId)) && n.push(r.id);
|
|
234
|
+
return n;
|
|
235
|
+
}
|
|
236
|
+
async function z(e) {
|
|
237
|
+
let { apiUrl: t, projectId: n, projectRoot: r, headers: i, reuseWhenUnchanged: o = !0 } = e, l = F(r), u = await V(t, n, i), d = R(u);
|
|
238
|
+
if (o) {
|
|
239
|
+
let e = await I(l.manifestPath);
|
|
240
|
+
if (e && e.projectVersionId === u.versionId && e.workspaceId === u.workspaceId) return {
|
|
241
|
+
config: u,
|
|
242
|
+
manifest: e,
|
|
243
|
+
experienceContentPaths: B(l, d)
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
await a(l.experiencesDir, { recursive: !0 }), await s(l.projectConfigPath, JSON.stringify(u, null, 2));
|
|
247
|
+
let f = (/* @__PURE__ */ new Date()).toISOString(), p = [];
|
|
248
|
+
for (let e of d) {
|
|
249
|
+
let r = await H(t, n, e, i);
|
|
250
|
+
await s(c(l.experiencesDir, `${e}.json`), JSON.stringify(r, null, 2)), p.push({
|
|
251
|
+
id: e,
|
|
252
|
+
fetchedAt: f
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
let m = {
|
|
256
|
+
version: P,
|
|
257
|
+
fetchedAt: f,
|
|
258
|
+
projectVersionId: u.versionId,
|
|
259
|
+
workspaceId: u.workspaceId,
|
|
260
|
+
projectId: u.id,
|
|
261
|
+
experiences: p
|
|
262
|
+
};
|
|
263
|
+
return await s(l.manifestPath, JSON.stringify(m, null, 2)), {
|
|
264
|
+
config: u,
|
|
265
|
+
manifest: m,
|
|
266
|
+
experienceContentPaths: B(l, d)
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
function B(e, t) {
|
|
270
|
+
let n = {};
|
|
271
|
+
for (let r of t) n[r] = c(e.experiencesDir, `${r}.json`);
|
|
272
|
+
return n;
|
|
273
|
+
}
|
|
274
|
+
async function V(e, t, n) {
|
|
275
|
+
let i = () => r(e, t, { headers: n }), a;
|
|
276
|
+
try {
|
|
277
|
+
a = j(e) ? await M(i, (e, t, n) => {
|
|
278
|
+
T.info(`project config not ready, retrying in ${(n / 1e3).toString()}s (${e.toString()}/${t.toString()})`);
|
|
279
|
+
}) : await i();
|
|
280
|
+
} catch (n) {
|
|
281
|
+
throw Error(`[picked] Build-time fetch of project config failed for "${t}" at ${e}: ${U(n)}`);
|
|
282
|
+
}
|
|
283
|
+
if (!a) throw Error(`[picked] Build-time fetch of project config returned no usable response for "${t}" at ${e}. Confirm the API is reachable and the project ID is correct.`);
|
|
284
|
+
return a;
|
|
285
|
+
}
|
|
286
|
+
async function H(e, t, r, i) {
|
|
287
|
+
let a;
|
|
288
|
+
try {
|
|
289
|
+
a = await n(e, t, r, { headers: i });
|
|
290
|
+
} catch (t) {
|
|
291
|
+
throw Error(`[picked] Build-time fetch of content for experience "${r}" failed at ${e}: ${U(t)}`);
|
|
292
|
+
}
|
|
293
|
+
if (!a) throw Error(`[picked] Build-time fetch of content for experience "${r}" returned no usable response. Confirm the experience id exists and the API is reachable.`);
|
|
294
|
+
return a;
|
|
295
|
+
}
|
|
296
|
+
function U(e) {
|
|
297
|
+
if (e instanceof Error) return e.message;
|
|
298
|
+
if (typeof e == "string") return e;
|
|
299
|
+
try {
|
|
300
|
+
return JSON.stringify(e);
|
|
301
|
+
} catch {
|
|
302
|
+
return "unknown error";
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
//#endregion
|
|
306
|
+
//#region src/virtual-module/build.ts
|
|
307
|
+
function W(e) {
|
|
308
|
+
let { projectConfigPath: t, experienceContentPaths: n, manifestMetadata: r } = e, i = (e) => JSON.stringify(e), a = Object.entries(n).map(([e, t]) => ` ${JSON.stringify(e)}: () => import(${i(t)}).then((m) => m.default ?? m),`).join("\n");
|
|
309
|
+
return `import projectConfigJson from ${i(t)}
|
|
310
|
+
|
|
311
|
+
const experienceImporters = {
|
|
312
|
+
${a}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
export const projectConfig = projectConfigJson
|
|
316
|
+
|
|
317
|
+
export async function experienceContent(experience, _options) {
|
|
318
|
+
const id = typeof experience === 'string' ? experience : experience?.id
|
|
319
|
+
if (!id) return null
|
|
320
|
+
const importer = experienceImporters[id]
|
|
321
|
+
if (!importer) return null
|
|
322
|
+
try {
|
|
323
|
+
return await importer()
|
|
324
|
+
} catch {
|
|
325
|
+
return null
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
export const manifestMetadata = ${JSON.stringify(r)}
|
|
330
|
+
`;
|
|
331
|
+
}
|
|
332
|
+
//#endregion
|
|
333
|
+
//#region src/virtual-module/dev.ts
|
|
334
|
+
function G(e) {
|
|
335
|
+
let { apiUrl: t, projectId: n } = e;
|
|
336
|
+
return `import { fetchProject, fetchExperienceContent } from '@picked/client/plugins/dynamic-experiences'
|
|
337
|
+
|
|
338
|
+
const apiUrl = ${JSON.stringify(t)}
|
|
339
|
+
const projectId = ${JSON.stringify(n)}
|
|
340
|
+
|
|
341
|
+
export const projectConfig = await fetchProject(apiUrl, projectId)
|
|
342
|
+
|
|
343
|
+
export async function experienceContent(experience, options) {
|
|
344
|
+
const id = typeof experience === 'string' ? experience : experience?.id
|
|
345
|
+
if (!id) return null
|
|
346
|
+
return await fetchExperienceContent(apiUrl, projectId, id, options)
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
export const manifestMetadata = {
|
|
350
|
+
mode: 'dev',
|
|
351
|
+
fetchedAt: null,
|
|
352
|
+
projectVersionId: projectConfig?.versionId ?? null,
|
|
353
|
+
workspaceId: projectConfig?.workspaceId ?? null,
|
|
354
|
+
}
|
|
355
|
+
`;
|
|
356
|
+
}
|
|
357
|
+
//#endregion
|
|
358
|
+
//#region src/index.ts
|
|
359
|
+
var K = 5;
|
|
360
|
+
function q(e, t, n) {
|
|
147
361
|
if (t.length === 0) return;
|
|
148
|
-
if (t.length >
|
|
362
|
+
if (t.length > K) {
|
|
149
363
|
console.log(`\n ${e}: ${t.length.toString()} loaded\n`);
|
|
150
364
|
return;
|
|
151
365
|
}
|
|
@@ -160,10 +374,10 @@ function w(e, t, n) {
|
|
|
160
374
|
""
|
|
161
375
|
].join("\n"));
|
|
162
376
|
}
|
|
163
|
-
function
|
|
377
|
+
function J(e, t) {
|
|
164
378
|
t.length !== 0 && console.log(`\n ${e}: ${t.length.toString()} loaded\n`);
|
|
165
379
|
}
|
|
166
|
-
var
|
|
380
|
+
var Y = [
|
|
167
381
|
{
|
|
168
382
|
header: "Name",
|
|
169
383
|
value: (e) => e.name
|
|
@@ -184,7 +398,7 @@ var E = [
|
|
|
184
398
|
header: "Ver",
|
|
185
399
|
value: (e) => `v${e.version.toString()}`
|
|
186
400
|
}
|
|
187
|
-
],
|
|
401
|
+
], ie = [
|
|
188
402
|
{
|
|
189
403
|
header: "Name",
|
|
190
404
|
value: (e) => e.name
|
|
@@ -205,7 +419,7 @@ var E = [
|
|
|
205
419
|
header: "Targets",
|
|
206
420
|
value: (e) => e.targets.length.toString()
|
|
207
421
|
}
|
|
208
|
-
],
|
|
422
|
+
], ae = [
|
|
209
423
|
{
|
|
210
424
|
header: "Name",
|
|
211
425
|
value: (e) => e.name
|
|
@@ -218,7 +432,7 @@ var E = [
|
|
|
218
432
|
header: "Fields",
|
|
219
433
|
value: (e) => (e.fieldIds?.length ?? 0).toString()
|
|
220
434
|
}
|
|
221
|
-
],
|
|
435
|
+
], oe = [
|
|
222
436
|
{
|
|
223
437
|
header: "Name",
|
|
224
438
|
value: (e) => e.name
|
|
@@ -235,81 +449,94 @@ var E = [
|
|
|
235
449
|
header: "Required",
|
|
236
450
|
value: (e) => e.required ? "yes" : "no"
|
|
237
451
|
}
|
|
238
|
-
],
|
|
452
|
+
], se = [{
|
|
239
453
|
header: "Name",
|
|
240
454
|
value: (e) => e.name
|
|
241
455
|
}, {
|
|
242
456
|
header: "Key",
|
|
243
457
|
value: (e) => e.key
|
|
244
458
|
}];
|
|
245
|
-
function
|
|
246
|
-
|
|
247
|
-
let t = new URL(e).hostname;
|
|
248
|
-
return t === "localhost" || t === "127.0.0.1" || t === "::1";
|
|
249
|
-
} catch {
|
|
250
|
-
return !1;
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
function M(e) {
|
|
254
|
-
return new Promise((t) => {
|
|
255
|
-
setTimeout(t, e);
|
|
256
|
-
});
|
|
257
|
-
}
|
|
258
|
-
async function N(e, n, r) {
|
|
259
|
-
let i = await t(e, n, { headers: r });
|
|
260
|
-
if (i || !j(e)) return i;
|
|
261
|
-
for (let i = 1; i <= L; i++) {
|
|
262
|
-
b.info(`local API not ready, retrying in ${(R / 1e3).toString()}s (${i.toString()}/${L.toString()})`), await M(R);
|
|
263
|
-
let a = await t(e, n, { headers: r });
|
|
264
|
-
if (a) return a;
|
|
265
|
-
}
|
|
266
|
-
return null;
|
|
459
|
+
function ce() {
|
|
460
|
+
return "0.0.1-dev.1780446300503";
|
|
267
461
|
}
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
var F = "https://api.picked.app", I = "__PICKED_PROJECT_CONFIG__", L = 5, R = 2e3;
|
|
272
|
-
function z(t) {
|
|
273
|
-
let n = t.apiUrl ?? F, r = [], a, o = !1;
|
|
462
|
+
var X = "https://api.picked.app", Z = "virtual:picked/data", Q = `\0${Z}`;
|
|
463
|
+
function le(n) {
|
|
464
|
+
let i = n.apiUrl ?? X, a = [], o = process.cwd(), s = "serve", c, l = !1;
|
|
274
465
|
return {
|
|
275
466
|
name: "picked",
|
|
276
|
-
config: async () => {
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
467
|
+
config: async (u, d) => {
|
|
468
|
+
s = d.command, o = process.cwd(), T.banner(), i !== X && T.warn(`using custom API URL: ${i}`);
|
|
469
|
+
let f = t("vite-plugin", ce());
|
|
470
|
+
if (d.command === "build") {
|
|
471
|
+
if (l) return;
|
|
472
|
+
l = !0, T.info(`prefetching project config + content for "${n.projectId}"`);
|
|
473
|
+
let t = F(o), r = e(t.pickedDir), s = performance.now(), u = await z({
|
|
474
|
+
apiUrl: i,
|
|
475
|
+
projectId: n.projectId,
|
|
476
|
+
projectRoot: o,
|
|
477
|
+
headers: f
|
|
478
|
+
}), d = (performance.now() - s).toFixed(0);
|
|
479
|
+
await ne(o, r);
|
|
480
|
+
let p = u.config;
|
|
481
|
+
a = N(p.features), $(p, d), c = W({
|
|
482
|
+
projectConfigPath: t.projectConfigPath,
|
|
483
|
+
experienceContentPaths: u.experienceContentPaths,
|
|
484
|
+
manifestMetadata: {
|
|
485
|
+
mode: "build",
|
|
486
|
+
fetchedAt: u.manifest.fetchedAt,
|
|
487
|
+
projectVersionId: u.manifest.projectVersionId,
|
|
488
|
+
workspaceId: u.manifest.workspaceId
|
|
489
|
+
}
|
|
490
|
+
});
|
|
491
|
+
} else {
|
|
492
|
+
T.info(`fetching project config for "${n.projectId}" (dev)`);
|
|
493
|
+
let e = performance.now(), t = () => r(i, n.projectId, { headers: f }), o = j(i) ? await M(t, (e, t, n) => {
|
|
494
|
+
T.info(`local API not ready, retrying in ${(n / 1e3).toString()}s (${e.toString()}/${t.toString()})`);
|
|
495
|
+
}) : await t(), s = (performance.now() - e).toFixed(0);
|
|
496
|
+
o ? (a = N(o.features), $(o, s)) : T.warn(`failed to fetch project config for "${n.projectId}" (${s}ms)`);
|
|
497
|
+
}
|
|
498
|
+
},
|
|
499
|
+
resolveId(e) {
|
|
500
|
+
if (e === Z) return Q;
|
|
501
|
+
},
|
|
502
|
+
load(e) {
|
|
503
|
+
if (e === Q) return s === "build" ? c : G({
|
|
504
|
+
apiUrl: i,
|
|
505
|
+
projectId: n.projectId
|
|
506
|
+
});
|
|
291
507
|
},
|
|
292
508
|
configureServer(e) {
|
|
293
|
-
if (
|
|
294
|
-
e.middlewares.use(
|
|
295
|
-
let
|
|
509
|
+
if (n.disableLauncher) return;
|
|
510
|
+
e.middlewares.use(E(() => d(a)));
|
|
511
|
+
let t = e.printUrls.bind(e);
|
|
296
512
|
e.printUrls = () => {
|
|
297
|
-
let
|
|
298
|
-
if (
|
|
299
|
-
let e =
|
|
513
|
+
let n = e.resolvedUrls?.local[0];
|
|
514
|
+
if (n) {
|
|
515
|
+
let e = n.replace(/:(?<port>\d+)\//, (e, t) => `:\x1b[1m${t}\x1b[22m/`);
|
|
300
516
|
console.log(` \x1b[32m➜\x1b[0m \x1b[1mPicked\x1b[22m: \x1b[36m${e}\x1b[0m`);
|
|
301
517
|
}
|
|
302
|
-
|
|
518
|
+
t();
|
|
303
519
|
};
|
|
304
520
|
},
|
|
305
521
|
generateBundle() {
|
|
306
|
-
|
|
522
|
+
n.disableLauncher || this.emitFile({
|
|
307
523
|
type: "asset",
|
|
308
524
|
fileName: "index.html",
|
|
309
|
-
source:
|
|
525
|
+
source: d(a)
|
|
310
526
|
});
|
|
311
527
|
}
|
|
312
528
|
};
|
|
313
529
|
}
|
|
530
|
+
function $(e, t) {
|
|
531
|
+
let n = N(e.features), r = N(e.experiences), i = N(e.steps), a = N(e.fields), o = N(e.conditions), s = N(e.actions), c = JSON.stringify(e).length, l = [
|
|
532
|
+
`${n.length.toString()} features`,
|
|
533
|
+
`${r.length.toString()} experiences`,
|
|
534
|
+
`${i.length.toString()} steps`,
|
|
535
|
+
`${a.length.toString()} fields`,
|
|
536
|
+
`${o.length.toString()} conditions`,
|
|
537
|
+
`${s.length.toString()} actions`
|
|
538
|
+
].join(", ");
|
|
539
|
+
T.info(`fetched config in ${t}ms (${l})`), q("Features", n, Y), q("Experiences", r, ie), q("Steps", i, ae), q("Fields", a, oe), q("Conditions", o, se), J("Actions", s), T.debug(`config size: ${c.toString()} bytes`);
|
|
540
|
+
}
|
|
314
541
|
//#endregion
|
|
315
|
-
export {
|
|
542
|
+
export { le as picked };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* If `<root>/.picked/` did not exist before this build, append `.picked/` to the
|
|
3
|
+
* project's `.gitignore` (creating the file if absent and not already containing the
|
|
4
|
+
* line). On subsequent runs, leaves the gitignore alone.
|
|
5
|
+
*
|
|
6
|
+
* @param {string} projectRoot - Absolute path to the Vite project root.
|
|
7
|
+
* @param {boolean} pickedDirAlreadyExists - Whether `<root>/.picked/` existed before the prefetch
|
|
8
|
+
* ran. Pass the result of `existsSync(pickedDir)` evaluated BEFORE any writes.
|
|
9
|
+
* @returns {Promise<void>} Resolves once the gitignore has been updated (or skipped).
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* await appendGitignoreOnFirstRun(process.cwd(), existsSync(pickedDir))
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
export declare function appendGitignoreOnFirstRun(projectRoot: string, pickedDirAlreadyExists: boolean): Promise<void>;
|
|
16
|
+
//# sourceMappingURL=gitignore.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gitignore.d.ts","sourceRoot":"","sources":["../../src/gitignore.ts"],"names":[],"mappings":"AAYA;;;;;;;;;;;;;GAaG;AACH,wBAAsB,yBAAyB,CAC7C,WAAW,EAAE,MAAM,EACnB,sBAAsB,EAAE,OAAO,GAC9B,OAAO,CAAC,IAAI,CAAC,CAqBf"}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -8,11 +8,15 @@ export interface PickedPluginOptions {
|
|
|
8
8
|
disableLauncher?: boolean;
|
|
9
9
|
}
|
|
10
10
|
/**
|
|
11
|
-
* Vite plugin that fetches Picked project config at build time
|
|
12
|
-
* and
|
|
11
|
+
* Vite plugin that fetches Picked project config + per-experience CMS content at build time
|
|
12
|
+
* and exposes both via the `virtual:picked/data` virtual module.
|
|
13
13
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
14
|
+
* Build mode (`vite build`) prefetches every CMS-using experience, writes JSON artefacts
|
|
15
|
+
* to `<root>/.picked/`, and synthesises a virtual module that statically imports the
|
|
16
|
+
* project config (so it's bundled) and lazily imports each experience's content on demand.
|
|
17
|
+
*
|
|
18
|
+
* Dev mode (`vite serve`) writes nothing to disk; the synthesised virtual module performs
|
|
19
|
+
* runtime API fetches via top-level `await` for project config and per-call for content.
|
|
16
20
|
*
|
|
17
21
|
* @param {PickedPluginOptions} options - Plugin configuration.
|
|
18
22
|
* @returns {Plugin} A Vite plugin instance.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AAsIlC,MAAM,WAAW,mBAAmB;IAClC,6BAA6B;IAC7B,SAAS,EAAE,MAAM,CAAA;IACjB,2DAA2D;IAC3D,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,qFAAqF;IACrF,eAAe,CAAC,EAAE,OAAO,CAAA;CAC1B;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,MAAM,CAAC,OAAO,EAAE,mBAAmB,GAAG,MAAM,CA2I3D"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { ProjectConfig } from '@picked/client/plugins/dynamic-experiences';
|
|
2
|
+
interface ManifestExperienceEntry {
|
|
3
|
+
id: string;
|
|
4
|
+
fetchedAt: string;
|
|
5
|
+
}
|
|
6
|
+
interface PickedManifest {
|
|
7
|
+
version: number;
|
|
8
|
+
fetchedAt: string;
|
|
9
|
+
projectVersionId: string;
|
|
10
|
+
workspaceId: string;
|
|
11
|
+
projectId: string;
|
|
12
|
+
experiences: ManifestExperienceEntry[];
|
|
13
|
+
}
|
|
14
|
+
interface PrefetchPaths {
|
|
15
|
+
/** Absolute path to `<root>/.picked/`. */
|
|
16
|
+
pickedDir: string;
|
|
17
|
+
/** Absolute path to `<root>/.picked/manifest.json`. */
|
|
18
|
+
manifestPath: string;
|
|
19
|
+
/** Absolute path to `<root>/.picked/project-config.json`. */
|
|
20
|
+
projectConfigPath: string;
|
|
21
|
+
/** Absolute path to `<root>/.picked/experiences/`. */
|
|
22
|
+
experiencesDir: string;
|
|
23
|
+
}
|
|
24
|
+
interface PrefetchResult {
|
|
25
|
+
config: ProjectConfig;
|
|
26
|
+
manifest: PickedManifest;
|
|
27
|
+
/** Map of experience id → absolute path to its `<id>.json`. */
|
|
28
|
+
experienceContentPaths: Record<string, string>;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Resolves the artefact paths under `<root>/.picked/`.
|
|
32
|
+
*
|
|
33
|
+
* @param {string} projectRoot - Vite project root (typically `process.cwd()`).
|
|
34
|
+
* @returns {PrefetchPaths} Absolute paths under `.picked/`.
|
|
35
|
+
* @example
|
|
36
|
+
* ```typescript
|
|
37
|
+
* const paths = resolvePrefetchPaths(process.cwd())
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export declare function resolvePrefetchPaths(projectRoot: string): PrefetchPaths;
|
|
41
|
+
interface RunPrefetchOptions {
|
|
42
|
+
apiUrl: string;
|
|
43
|
+
projectId: string;
|
|
44
|
+
projectRoot: string;
|
|
45
|
+
/** HTTP headers to forward with every fetch (e.g. `Picked-Client`, `Picked-Version`). */
|
|
46
|
+
headers: Record<string, string>;
|
|
47
|
+
/** When the existing manifest matches the API's versionId+workspaceId, skip per-experience fetches. */
|
|
48
|
+
reuseWhenUnchanged?: boolean;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Runs the full build-mode prefetch: fetches project config, walks CMS-using experiences,
|
|
52
|
+
* fetches each one's content, writes all artefacts under `<root>/.picked/`, and replaces
|
|
53
|
+
* `manifest.json` in full so stale entries cannot accumulate.
|
|
54
|
+
*
|
|
55
|
+
* Hard-fails on any API failure (non-2xx response or network error). Customers shipping
|
|
56
|
+
* with the Vite plugin must ship from a known-good snapshot or not at all.
|
|
57
|
+
*
|
|
58
|
+
* When `reuseWhenUnchanged` is true and the existing manifest's `versionId` + `workspaceId`
|
|
59
|
+
* match the freshly-fetched project config, per-experience fetches are skipped and the
|
|
60
|
+
* existing JSON files are left in place. The manifest is left unchanged.
|
|
61
|
+
*
|
|
62
|
+
* @param {RunPrefetchOptions} options - Prefetch configuration.
|
|
63
|
+
* @returns {Promise<PrefetchResult>} The fetched config, manifest, and content-path map.
|
|
64
|
+
* @throws {Error} When any API request fails or returns malformed data.
|
|
65
|
+
* @example
|
|
66
|
+
* ```typescript
|
|
67
|
+
* const result = await runPrefetch({ apiUrl, projectId, projectRoot: cwd, headers })
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
export declare function runPrefetch(options: RunPrefetchOptions): Promise<PrefetchResult>;
|
|
71
|
+
export {};
|
|
72
|
+
//# sourceMappingURL=prefetch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prefetch.d.ts","sourceRoot":"","sources":["../../src/prefetch.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAEV,aAAa,EACd,MAAM,4CAA4C,CAAA;AAQnD,UAAU,uBAAuB;IAC/B,EAAE,EAAE,MAAM,CAAA;IACV,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,UAAU,cAAc;IACtB,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;IACjB,gBAAgB,EAAE,MAAM,CAAA;IACxB,WAAW,EAAE,MAAM,CAAA;IACnB,SAAS,EAAE,MAAM,CAAA;IACjB,WAAW,EAAE,uBAAuB,EAAE,CAAA;CACvC;AAED,UAAU,aAAa;IACrB,0CAA0C;IAC1C,SAAS,EAAE,MAAM,CAAA;IACjB,uDAAuD;IACvD,YAAY,EAAE,MAAM,CAAA;IACpB,6DAA6D;IAC7D,iBAAiB,EAAE,MAAM,CAAA;IACzB,sDAAsD;IACtD,cAAc,EAAE,MAAM,CAAA;CACvB;AAED,UAAU,cAAc;IACtB,MAAM,EAAE,aAAa,CAAA;IACrB,QAAQ,EAAE,cAAc,CAAA;IACxB,+DAA+D;IAC/D,sBAAsB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAC/C;AAED;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,aAAa,CAQvE;AAsFD,UAAU,kBAAkB;IAC1B,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,MAAM,CAAA;IACjB,WAAW,EAAE,MAAM,CAAA;IACnB,yFAAyF;IACzF,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC/B,uGAAuG;IACvG,kBAAkB,CAAC,EAAE,OAAO,CAAA;CAC7B;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,WAAW,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,cAAc,CAAC,CAiDtF"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file URL-driven retry helper for the build- and dev-mode project-config fetch.
|
|
3
|
+
*
|
|
4
|
+
* Retries are auto-enabled when the configured `apiUrl` points at a host where
|
|
5
|
+
* deploys can race ahead of the API: `localhost`/`127.0.0.1`/`::1` for local
|
|
6
|
+
* development, or any `*.picked.app` host EXCEPT the canonical production host
|
|
7
|
+
* `api.picked.app`. Production and external customer hosts continue to fail fast.
|
|
8
|
+
*
|
|
9
|
+
* The retry schedule is hardcoded — there is no public option. The behavior is
|
|
10
|
+
* an internal deploy-ordering workaround for the Picked monorepo and is not
|
|
11
|
+
* intended for tuning by external SDK consumers.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Whether build- and dev-mode project-config fetches against `apiUrl` should be
|
|
15
|
+
* retried with exponential backoff. True for hosts where the API may briefly
|
|
16
|
+
* be unreachable while a deploy is propagating; false for the canonical
|
|
17
|
+
* production host and every external host.
|
|
18
|
+
*
|
|
19
|
+
* @param {string} apiUrl - The configured API base URL.
|
|
20
|
+
* @returns {boolean} `true` when retries should be attempted, `false` otherwise.
|
|
21
|
+
* @example
|
|
22
|
+
* shouldRetryUrl('http://localhost:54321') // true
|
|
23
|
+
* shouldRetryUrl('https://picked-api-git-foo.picked.app') // true
|
|
24
|
+
* shouldRetryUrl('https://api.picked.app') // false
|
|
25
|
+
*/
|
|
26
|
+
export declare function shouldRetryUrl(apiUrl: string): boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Run `fetchFn` once. If it returns `null` OR throws, retry up to 6 times with
|
|
29
|
+
* exponential backoff (5s → 10s → 20s → 40s → 80s → 160s, ~315 s worst-case wall time).
|
|
30
|
+
*
|
|
31
|
+
* Both thrown errors AND `null` returns are eligible for retry — the SDK's
|
|
32
|
+
* `fetchProject` and `fetchExperienceContent` swallow network errors and return
|
|
33
|
+
* `null`, so retrying only on throws would do nothing useful for the typical
|
|
34
|
+
* "API not yet up" failure mode. If every attempt fails, the most recent thrown
|
|
35
|
+
* error is rethrown; if every attempt simply returned `null`, `null` is returned
|
|
36
|
+
* so the caller can produce its own diagnostic.
|
|
37
|
+
*
|
|
38
|
+
* `onRetry` is invoked once per retry, before the corresponding backoff sleep,
|
|
39
|
+
* with the 1-indexed attempt number, the total retry budget, and the upcoming
|
|
40
|
+
* delay in ms. The callback is the only place log lines are produced — callers
|
|
41
|
+
* own the phrasing and any contextual prefix.
|
|
42
|
+
*
|
|
43
|
+
* @template T - Type of the value resolved by the fetch operation.
|
|
44
|
+
* @param {() => Promise<T | null>} fetchFn - The async fetch operation to run.
|
|
45
|
+
* @param {(attempt: number, totalAttempts: number, nextDelayMs: number) => void} onRetry -
|
|
46
|
+
* Hook invoked before each backoff sleep.
|
|
47
|
+
* @returns {Promise<T | null>} The first non-null result, or `null` after all retries are exhausted.
|
|
48
|
+
* @throws {Error} The last thrown error when every attempt threw.
|
|
49
|
+
* @example
|
|
50
|
+
* const config = await withRetry(
|
|
51
|
+
* () => fetchProject(apiUrl, projectId, { headers }),
|
|
52
|
+
* (attempt, total, ms) => log.info(`retrying in ${ms}ms (${attempt}/${total})`),
|
|
53
|
+
* )
|
|
54
|
+
*/
|
|
55
|
+
export declare function withRetry<T>(fetchFn: () => Promise<T | null>, onRetry: (attempt: number, totalAttempts: number, nextDelayMs: number) => void): Promise<T | null>;
|
|
56
|
+
//# sourceMappingURL=retry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retry.d.ts","sourceRoot":"","sources":["../../src/retry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAKH;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAsBtD;AAgBD;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAsB,SAAS,CAAC,CAAC,EAC/B,OAAO,EAAE,MAAM,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,EAChC,OAAO,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,KAAK,IAAI,GAC7E,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CA+BnB"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Shared utilities for the Picked Vite plugin. Currently exposes a single
|
|
3
|
+
* defensive helper for handling API entity arrays that may be omitted at runtime
|
|
4
|
+
* despite being typed as required.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Safely coerce a value to an array, returning an empty array for nullish inputs.
|
|
8
|
+
* Needed because the API response may omit entity arrays at runtime even though
|
|
9
|
+
* the TypeScript type marks them as required.
|
|
10
|
+
*
|
|
11
|
+
* @template T - The element type of the array.
|
|
12
|
+
* @param {ReadonlyArray<T> | null | undefined} value - The value to coerce.
|
|
13
|
+
* @returns {ReadonlyArray<T>} The original array, or an empty array if the input is nullish.
|
|
14
|
+
* @example
|
|
15
|
+
* toArray(undefined) // []
|
|
16
|
+
*/
|
|
17
|
+
export declare function toArray<T>(value: ReadonlyArray<T> | null | undefined): ReadonlyArray<T>;
|
|
18
|
+
//# sourceMappingURL=util.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../../src/util.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;;;;;;;;GAUG;AACH,wBAAgB,OAAO,CAAC,CAAC,EAAE,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,GAAG,IAAI,GAAG,SAAS,GAAG,aAAa,CAAC,CAAC,CAAC,CAEvF"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Build-mode synthesis for `virtual:picked/data`.
|
|
3
|
+
*
|
|
4
|
+
* Generates a JavaScript module that statically imports the prefetched project config
|
|
5
|
+
* (so it's bundled into the customer's main chunk) and lazily imports each experience's
|
|
6
|
+
* content JSON on demand.
|
|
7
|
+
*/
|
|
8
|
+
interface BuildVirtualModuleOptions {
|
|
9
|
+
/** Absolute path to `<root>/.picked/project-config.json`. */
|
|
10
|
+
projectConfigPath: string;
|
|
11
|
+
/** Map of experience id → absolute path to its content JSON file. */
|
|
12
|
+
experienceContentPaths: Record<string, string>;
|
|
13
|
+
/** Manifest metadata that the synthesised module re-exports as `manifestMetadata`. */
|
|
14
|
+
manifestMetadata: {
|
|
15
|
+
mode: 'build';
|
|
16
|
+
fetchedAt: string | null;
|
|
17
|
+
projectVersionId: string | null;
|
|
18
|
+
workspaceId: string | null;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Synthesises the source code for the build-mode `virtual:picked/data` module.
|
|
23
|
+
*
|
|
24
|
+
* @param {BuildVirtualModuleOptions} options - Build-mode synthesis inputs.
|
|
25
|
+
* @returns {string} JavaScript source as a string for Vite's `load` hook.
|
|
26
|
+
* @example
|
|
27
|
+
* ```typescript
|
|
28
|
+
* const src = buildVirtualModuleSource({ projectConfigPath, experienceContentPaths, manifestMetadata })
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export declare function buildVirtualModuleSource(options: BuildVirtualModuleOptions): string;
|
|
32
|
+
export {};
|
|
33
|
+
//# sourceMappingURL=build.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../../src/virtual-module/build.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,UAAU,yBAAyB;IACjC,6DAA6D;IAC7D,iBAAiB,EAAE,MAAM,CAAA;IACzB,qEAAqE;IACrE,sBAAsB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC9C,sFAAsF;IACtF,gBAAgB,EAAE;QAChB,IAAI,EAAE,OAAO,CAAA;QACb,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;QACxB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAA;QAC/B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;KAC3B,CAAA;CACF;AAED;;;;;;;;;GASG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,yBAAyB,GAAG,MAAM,CAiCnF"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Dev-mode synthesis for `virtual:picked/data`.
|
|
3
|
+
*
|
|
4
|
+
* Generates a JavaScript module that fetches the project config at module-evaluation time
|
|
5
|
+
* via top-level `await`, and resolves per-experience content with per-call API requests.
|
|
6
|
+
* No filesystem writes; matches the customer's runtime API behaviour.
|
|
7
|
+
*/
|
|
8
|
+
interface DevVirtualModuleOptions {
|
|
9
|
+
/** Absolute API base URL (e.g. `https://api.picked.app`). */
|
|
10
|
+
apiUrl: string;
|
|
11
|
+
/** Picked project ID to fetch. */
|
|
12
|
+
projectId: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Synthesises the source code for the dev-mode `virtual:picked/data` module.
|
|
16
|
+
*
|
|
17
|
+
* @param {DevVirtualModuleOptions} options - Dev-mode synthesis inputs.
|
|
18
|
+
* @returns {string} JavaScript source as a string for Vite's `load` hook.
|
|
19
|
+
* @example
|
|
20
|
+
* ```typescript
|
|
21
|
+
* const src = devVirtualModuleSource({ apiUrl: 'https://api.picked.app', projectId: 'p_1' })
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export declare function devVirtualModuleSource(options: DevVirtualModuleOptions): string;
|
|
25
|
+
export {};
|
|
26
|
+
//# sourceMappingURL=dev.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../../src/virtual-module/dev.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,UAAU,uBAAuB;IAC/B,6DAA6D;IAC7D,MAAM,EAAE,MAAM,CAAA;IACd,kCAAkC;IAClC,SAAS,EAAE,MAAM,CAAA;CAClB;AAED;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,uBAAuB,GAAG,MAAM,CAuB/E"}
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@picked/vite-plugin-picked",
|
|
3
3
|
"description": "Vite plugin for build-time config injection",
|
|
4
|
-
"version": "0.0.1-dev.
|
|
4
|
+
"version": "0.0.1-dev.1780446300503",
|
|
5
5
|
"dependencies": {
|
|
6
|
-
"@picked/client": "0.0.1-dev.
|
|
6
|
+
"@picked/client": "0.0.1-dev.1780446300503"
|
|
7
7
|
},
|
|
8
8
|
"devDependencies": {
|
|
9
9
|
"@repo/eslint-config": "workspace:*",
|