@coreframe/preview 0.0.0 → 0.1.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/dist/index.d.ts +55 -0
- package/dist/index.js +1 -0
- package/package.json +36 -10
- package/readme.md +0 -1
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { CoreframeConfig, PreviewTunnelConfig } from "@coreframe/config";
|
|
2
|
+
|
|
3
|
+
//#region src/index.d.ts
|
|
4
|
+
type ResolvedPreviewConfig = {
|
|
5
|
+
appOrigin?: string;
|
|
6
|
+
host: string;
|
|
7
|
+
port: number;
|
|
8
|
+
strictPort: boolean;
|
|
9
|
+
tunnel?: PreviewTunnelConfig;
|
|
10
|
+
};
|
|
11
|
+
type PreviewOptions = {
|
|
12
|
+
cwd?: string;
|
|
13
|
+
env?: NodeJS.ProcessEnv;
|
|
14
|
+
host?: string;
|
|
15
|
+
port?: number;
|
|
16
|
+
};
|
|
17
|
+
type PreviewEntrypoint = (preview: PreviewApi) => Promise<void> | void;
|
|
18
|
+
type PreviewDatabaseApi = {
|
|
19
|
+
readonly dialect: "postgres" | "sqlite";
|
|
20
|
+
readonly env: NodeJS.ProcessEnv;
|
|
21
|
+
migrate(): Promise<void>;
|
|
22
|
+
};
|
|
23
|
+
type PreviewApi = {
|
|
24
|
+
readonly cwd: string;
|
|
25
|
+
readonly database: PreviewDatabaseApi;
|
|
26
|
+
readonly env: NodeJS.ProcessEnv;
|
|
27
|
+
readonly host: string;
|
|
28
|
+
readonly origin: string;
|
|
29
|
+
readonly port: number;
|
|
30
|
+
build(): Promise<void>;
|
|
31
|
+
report(label: string, value?: string): void;
|
|
32
|
+
run(command: string, args?: string[]): Promise<void>;
|
|
33
|
+
serve(): Promise<void>;
|
|
34
|
+
url(pathname?: string): string;
|
|
35
|
+
};
|
|
36
|
+
declare function runPreview({
|
|
37
|
+
cwd,
|
|
38
|
+
env,
|
|
39
|
+
host,
|
|
40
|
+
port
|
|
41
|
+
}: PreviewOptions, entrypoint: PreviewEntrypoint): Promise<void>;
|
|
42
|
+
declare function resolvePreviewConfig(config: CoreframeConfig, overrides?: Pick<PreviewOptions, "host" | "port">): ResolvedPreviewConfig;
|
|
43
|
+
declare function createPreviewEnvironment({
|
|
44
|
+
appOrigin,
|
|
45
|
+
config,
|
|
46
|
+
cwd,
|
|
47
|
+
env
|
|
48
|
+
}: {
|
|
49
|
+
appOrigin?: string;
|
|
50
|
+
config: CoreframeConfig;
|
|
51
|
+
cwd: string;
|
|
52
|
+
env: NodeJS.ProcessEnv;
|
|
53
|
+
}): Promise<NodeJS.ProcessEnv>;
|
|
54
|
+
//#endregion
|
|
55
|
+
export { PreviewApi, PreviewDatabaseApi, PreviewEntrypoint, PreviewOptions, ResolvedPreviewConfig, createPreviewEnvironment, resolvePreviewConfig, runPreview };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{access as e,mkdir as t,readFile as n}from"node:fs/promises";import{createServer as r}from"node:net";import i from"node:path";import a from"node:process";import{parseEnv as o}from"node:util";import{coreframeLocalWorkerVariables as s,readProjectConfig as c}from"@coreframe/config";import{migrateLocalDb as l,startProjectPostgres as u}from"@coreframe/db";import d from"nano-spawn";import{supervise as f}from"procband";const p=[`DATABASE_URL`,`PGDATABASE`,`PGHOST`,`PGPASSWORD`,`PGPORT`,`PGUSER`,`TURSO_AUTH_TOKEN`,`TURSO_DATABASE_URL`],m=[`POSTHOG_`,`VITE_POSTHOG_`,`VITE_GA_`],h=[`CI`,`COLORTERM`,`COMSPEC`,`ComSpec`,`FORCE_COLOR`,`HOME`,`NODE_EXTRA_CA_CERTS`,`NODE_OPTIONS`,`NO_COLOR`,`PATH`,`PATHEXT`,`PNPM_HOME`,`SHELL`,`SSL_CERT_DIR`,`SSL_CERT_FILE`,`SYSTEMROOT`,`SystemRoot`,`TEMP`,`TERM`,`TMP`,`TMPDIR`,`USERPROFILE`],g=a.platform===`win32`?`pnpm.cmd`:`pnpm`,_=a.platform===`win32`?`turso.cmd`:`turso`;async function v({cwd:e=a.cwd(),env:t=a.env,host:n,port:r},i){let o=await c({cwd:e}),s=y(o,{host:n,port:r}),l=await b({appOrigin:s.appOrigin,config:o,cwd:e,env:t}),u=s.tunnel?await O(s.tunnel,t,e):void 0,d=S({config:o,cwd:e,env:l}),f,p,m=new Set,h=!1,g,_=()=>{if(!h){h=!0;for(let e of m)e.nodeChildProcess.then(e=>e.kill(`SIGTERM`)).catch(()=>void 0);f?.kill(`SIGTERM`),p?.kill(`SIGTERM`),d.stop()}},v=[`SIGINT`,`SIGTERM`].map(e=>{let t=()=>{g??=e,_()};return a.on(e,t),{handler:t,signal:e}});try{await d.start(),await i(x({config:s,cwd:e,database:d,env:l,tunnelEnvironment:u,commands:m,onServe(e){f=e.server,p=e.tunnel},isStopping:()=>h,onStop:_}))}finally{let e=[...m];_();try{await d.dispose()}finally{await Promise.allSettled([...e,f?.wait(),p?.wait()].filter(Boolean));for(let{handler:e,signal:t}of v)a.off(t,e);g&&(a.exitCode??=g===`SIGINT`?130:143)}}}function y(e,t={}){let n=e.preview??{},r=t.host??n.host??`127.0.0.1`,i=t.port??n.port??4173,a=n.strictPort??!0,o=n.appOrigin;if(!Number.isInteger(i)||i<1||i>65535)throw Error(`Preview port must be an integer between 1 and 65535.`);if(!r.trim())throw Error(`Preview host must not be empty.`);return o&&E(o),n.tunnel&&T(n.tunnel,o,a),{appOrigin:o,host:r,port:i,strictPort:a,tunnel:n.tunnel}}async function b({appOrigin:e,config:t,cwd:n,env:r}){let i=await C(n),a=Object.keys(t.worker?.environment??{}).filter(e=>!m.some(t=>e.startsWith(t))),o=new Set([...s,...p,...a]),c={},l=new Set([...Object.keys(r),...Object.keys(i)].filter(e=>e.startsWith(`VITE_`)&&!m.some(t=>e.startsWith(t))));for(let e of h)r[e]!==void 0&&(c[e]=r[e]);for(let e of o){let t=r[e]??i[e];t!==void 0&&(c[e]=t)}for(let e of l){let t=r[e]??i[e];t!==void 0&&(c[e]=t)}return e!==void 0&&(o.add(`APP_ORIGIN`),c.APP_ORIGIN=e),c.CLOUDFLARE_ENV=`preview`,c.COREFRAME_ENV=`preview`,c.VITE_COREFRAME_ENV=`preview`,c.COREFRAME_PLATFORM=`web`,c.COREFRAME_PREVIEW_WORKER_VARIABLES=JSON.stringify([...o].filter(e=>!m.some(t=>e.startsWith(t)))),c}function x({config:e,cwd:t,database:n,env:r,tunnelEnvironment:o,commands:s,onServe:c,isStopping:l,onStop:u}){let p=!1,m={cwd:t,database:n,env:r,host:e.host,origin:e.appOrigin??A(e.host,e.port),port:e.port,async build(){await m.run(g,[`run`,`build`,`--mode`,`preview`])},report(e,t){console.log(t===void 0?e:`${e}: ${t}`)},async run(e,n=[]){let i=d(e,n,{cwd:t,env:r,stdio:`inherit`});s.add(i);try{await i}finally{s.delete(i)}},async serve(){if(p)throw Error(`Preview serving can only be started once.`);p=!0;let n=f({name:`preview`,command:g,args:[`exec`,`vite`,`preview`,`--mode`,`preview`,`--outDir`,`dist/client`,`--host`,e.host,`--port`,String(e.port),...e.strictPort?[`--strictPort`]:[]],cwd:t,env:r,prefix:!1,stdin:a.stdin}),s;try{s=e.tunnel?f({name:`cloudflared`,command:a.platform===`win32`?`cloudflared.exe`:`cloudflared`,args:[`tunnel`,`run`,`--url`,`http://${M(j(e.host))}:${e.port}`,...e.tunnel.credentialsFile?[`--credentials-file`,i.resolve(t,e.tunnel.credentialsFile)]:[],...e.tunnel.tokenFile?[`--token-file`,i.resolve(t,e.tunnel.tokenFile)]:[],e.tunnel.name],cwd:t,env:{...r,...o},prefix:!1}):void 0}catch(e){throw n.kill(`SIGTERM`),await n.wait(),e}c({server:n,tunnel:s}),m.report(`Preview`,m.url(`/`));try{await Promise.race([n.expectSuccess(),s?.expectSuccess()].filter(Boolean))}catch(e){if(l())return;throw u(),e}},url(e=`/`){return new URL(e,`${m.origin.replace(/\/$/u,``)}/`).toString()}};return m}function S({config:e,cwd:n,env:r}){let a=e.database?.dialect??`postgres`,o,s,c,d=!1,p=r,m=()=>{d||(d=!0,s?.kill(`SIGTERM`))};return{dialect:a,get env(){return p},async migrate(){await l({config:e,cwd:n,env:p})},async start(){if(a===`sqlite`){let e=await N(),r=i.resolve(n,`.coreframe`),a=i.join(r,`turso.db`);await t(r,{recursive:!0}),delete p.DATABASE_URL,p.TURSO_DATABASE_URL=`http://127.0.0.1:${e}`,p.TURSO_AUTH_TOKEN=`dev`,s=f({name:`turso`,command:_,args:[`dev`,`--port`,String(e),`--db-file`,a],cwd:n,env:{...p},prefix:!1}),await s.waitFor(`listening`);return}o=await u({config:e,cwd:n,env:p,failIfRunning:!0,postgresOutput:`on-error`}),Object.assign(p,o.env)},stop:m,async dispose(){c??=(async()=>{m(),await Promise.allSettled([o?.[Symbol.asyncDispose](),s?.wait()]),o=void 0,s=void 0})(),await c}}}async function C(e){let t={};for(let n of[`.env`,`.env.local`,`.env.preview`,`.env.preview.local`]){let r=await w(i.resolve(e,n));r&&(t={...t,...r})}let n=await w(i.resolve(e,`.dev.vars`));n&&(t={...t,...n});let r=await w(i.resolve(e,`.dev.vars.preview`));return r&&(t={...t,...r}),t}async function w(e){try{return Object.fromEntries(Object.entries(o(await n(e,`utf8`))).filter(e=>e[1]!==void 0))}catch(e){if(e.code===`ENOENT`)return;throw e}}function T(e,t,n){if(!t)throw Error(`preview.appOrigin is required when preview.tunnel is configured.`);if(!n)throw Error(`preview.strictPort must be true when preview.tunnel is configured.`);if(!e.name.trim())throw Error(`preview.tunnel.name must not be empty.`);let r=D(e.hostname);if(new URL(t).hostname!==r)throw Error(`preview.appOrigin hostname must match preview.tunnel.hostname.`);if([e.credentialsFile,e.tokenEnv,e.tokenFile].filter(Boolean).length!==1)throw Error(`preview.tunnel requires exactly one credential source: credentialsFile, tokenEnv, or tokenFile.`)}function E(e){let t;try{t=new URL(e)}catch{throw Error(`preview.appOrigin must be an exact HTTPS browser origin.`)}if(t.protocol!==`https:`||t.origin!==e||t.pathname!==`/`||t.search||t.hash||t.username||t.password)throw Error(`preview.appOrigin must be an exact HTTPS browser origin.`);return t}function D(e){let t;try{t=new URL(`https://${e}`)}catch{throw Error(`preview.tunnel.hostname must be an exact hostname.`)}if(t.hostname!==e||t.origin!==`https://${e}`)throw Error(`preview.tunnel.hostname must be an exact hostname.`);return t.hostname}async function O(e,t,n){if(e.credentialsFile&&await k(i.resolve(n,e.credentialsFile)),e.tokenFile&&await k(i.resolve(n,e.tokenFile)),!e.tokenEnv)return{};let r=t[e.tokenEnv];if(!r)throw Error(`Missing Cloudflare Tunnel token in ${e.tokenEnv}.`);return{TUNNEL_TOKEN:r}}async function k(t){try{await e(t)}catch{throw Error(`Cloudflare Tunnel credential file does not exist: ${t}`)}}function A(e,t){return`http://${M(j(e))}:${t}`}function j(e){return e===`0.0.0.0`||e===`::`?`127.0.0.1`:e}function M(e){return e.includes(`:`)&&!e.startsWith(`[`)?`[${e}]`:e}async function N(){let e=r();await new Promise((t,n)=>{e.once(`error`,n),e.listen(0,`127.0.0.1`,()=>t())});let t=e.address();if(await new Promise((t,n)=>{e.close(e=>e?n(e):t())}),!t||typeof t==`string`)throw Error(`Could not allocate a local preview database port.`);return t.port}export{b as createPreviewEnvironment,y as resolvePreviewConfig,v as runPreview};
|
package/package.json
CHANGED
|
@@ -1,13 +1,39 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coreframe/preview",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "",
|
|
5
|
-
"
|
|
6
|
-
|
|
7
|
-
"
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Production-like local preview lifecycle utilities for Coreframe apps",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/airlock-labs/coreframe.git",
|
|
8
|
+
"directory": "node/preview"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist/"
|
|
12
|
+
],
|
|
13
|
+
"type": "module",
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"default": "./dist/index.js"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"nano-spawn": "^2.1.0",
|
|
22
|
+
"procband": "^0.3.8",
|
|
23
|
+
"@coreframe/config": "^0.1.2",
|
|
24
|
+
"@coreframe/db": "^0.1.2"
|
|
8
25
|
},
|
|
9
|
-
"
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@types/node": "^26.1.0",
|
|
28
|
+
"tsdown": "0.22.3",
|
|
29
|
+
"typescript": "6.0.3",
|
|
30
|
+
"vitest": "^4.1.9"
|
|
31
|
+
},
|
|
32
|
+
"scripts": {
|
|
33
|
+
"build": "tsdown",
|
|
34
|
+
"test": "vitest run --reporter=minimal",
|
|
35
|
+
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
36
|
+
"format": "oxlint --fix && oxfmt",
|
|
37
|
+
"lint": "oxlint"
|
|
38
|
+
}
|
|
39
|
+
}
|
package/readme.md
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
Coming soon...
|