primitive-app 3.1.0-alpha.9 → 3.2.0-alpha.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.
@@ -115,7 +115,7 @@ export interface DocumentInfoWithRoot {
115
115
  export interface PermissionEntry {
116
116
  userId: string;
117
117
  email: string;
118
- name: string;
118
+ name?: string;
119
119
  permission: string;
120
120
  grantedAt: string;
121
121
  }
@@ -0,0 +1,170 @@
1
+ /**
2
+ * GENERATED FILE — DO NOT EDIT BY HAND.
3
+ *
4
+ * Source of truth: `cli/src/lib/env-resolver-core.ts` (the CLI owns the
5
+ * environment-resolution algorithm; see issue #2873).
6
+ *
7
+ * Regenerate with:
8
+ * node cli/scripts/gen-primitive-env-resolver.mjs (runs at CLI prebuild)
9
+ *
10
+ * A freshness guard (`--check` mode, asserted by
11
+ * `cli/tests/unit/env-resolver-copy-drift-guard.test.ts`) fails the CLI unit
12
+ * gate if this committed copy drifts from the source. Edit the CLI file and
13
+ * regenerate; edits made here are overwritten.
14
+ */
15
+ /** Schema version of `.primitive/config.json` this core understands. */
16
+ export declare const PRIMITIVE_CONFIG_VERSION = 1;
17
+ /** Schema version of `.primitive/local.json` this core writes and reads. */
18
+ export declare const PRIMITIVE_LOCAL_STATE_VERSION = 1;
19
+ export declare const PROJECT_CONFIG_DIR = ".primitive";
20
+ export declare const PROJECT_CONFIG_FILENAME = "config.json";
21
+ export declare const LOCAL_STATE_FILENAME = "local.json";
22
+ export declare const PROJECT_CONFIG_DISPLAY_NAME = ".primitive/config.json";
23
+ export declare const LOCAL_STATE_DISPLAY_NAME = ".primitive/local.json";
24
+ /**
25
+ * Error kinds callers branch on. Every failure is one of these — no reader is
26
+ * ever left guessing what a broken config meant.
27
+ */
28
+ export type PrimitiveEnvErrorKind = "missing-config" | "malformed-config" | "unsupported-version" | "corrupt-local-state" | "unknown-environment" | "no-selection" | "missing-app-id";
29
+ export declare class PrimitiveEnvError extends Error {
30
+ readonly kind: PrimitiveEnvErrorKind;
31
+ readonly path?: string;
32
+ constructor(kind: PrimitiveEnvErrorKind, message: string, path?: string);
33
+ }
34
+ export interface CoreEnvironmentEntry {
35
+ apiUrl: string;
36
+ appId?: string;
37
+ appName?: string;
38
+ /**
39
+ * The app's web counterpart for this environment, as a normalized origin
40
+ * (#2982). Read through `normalizeWebOrigin`, so a value this reader carries
41
+ * is always an origin — see that function for why the typed read other
42
+ * fields get is not enough here.
43
+ */
44
+ webUrl?: string;
45
+ description?: string;
46
+ }
47
+ /**
48
+ * The app's web counterpart as a normalized ORIGIN, or undefined when the
49
+ * value cannot be one (#2982).
50
+ *
51
+ * Unlike `appId`/`appName`, a typed read is not enough. This value is not
52
+ * merely carried: the Swift app points its emailed sign-in link at
53
+ * `<webUrl>/oauth/callback` and trusts incoming universal links from the same
54
+ * origin, and BOTH jobs need an origin —
55
+ *
56
+ * - https except loopback, the rule the server applies to every redirect URI
57
+ * (`src/auth/redirect-uri.ts`): an origin that cannot be allow-listed turns
58
+ * every sign-in request into a 400, and a plain-http link would carry the
59
+ * magic token in clear;
60
+ * - no path, query or fragment: the web client serves its callback on its own
61
+ * origin plus `/oauth/callback`, which is also the exact path the
62
+ * `apple-app-site-association` component claims;
63
+ * - no credentials, which have no business in a link that goes out by email.
64
+ *
65
+ * `primitive env add --web-url` and the project-config validation reject those
66
+ * shapes with a field-named error, but neither is on the path from a
67
+ * hand-edited `.primitive/config.json` to an Xcode build: the Swift template's
68
+ * pre-build script reads the file directly. So every reader that feeds
69
+ * `primitive.json` applies the contract, and a value that misses it is read as
70
+ * "this environment has no web counterpart" — code-only email, no trusted
71
+ * origin, nothing silently pointed at the wrong URL.
72
+ *
73
+ * The message-producing twin of this rule lives in `cli/src/lib/web-url.ts`;
74
+ * this file cannot import it (see COPYABILITY above), and a test pins that the
75
+ * two agree.
76
+ */
77
+ export declare function normalizeWebOrigin(value: unknown): string | undefined;
78
+ export interface CoreProjectConfig {
79
+ version: number;
80
+ defaultEnvironment?: string;
81
+ environments: Record<string, CoreEnvironmentEntry>;
82
+ }
83
+ /** Which precedence step picked the environment. Printed in the build banner. */
84
+ export type PrimitiveEnvSource = "explicit" | "env-var" | "local" | "default" | "sole";
85
+ export interface ResolvedPrimitiveEnv {
86
+ name: string;
87
+ apiUrl: string;
88
+ /** Derived from apiUrl by scheme swap — never authored separately. */
89
+ wsUrl: string;
90
+ appId?: string;
91
+ appName?: string;
92
+ /** The app's web counterpart for the SELECTED environment, if it has one. */
93
+ webUrl?: string;
94
+ description?: string;
95
+ source: PrimitiveEnvSource;
96
+ configPath: string;
97
+ localStatePath: string;
98
+ projectRoot: string;
99
+ }
100
+ export interface ResolveOptions {
101
+ /** Directory to start the upward walk from. Defaults to process.cwd(). */
102
+ cwd?: string;
103
+ /** Environment variables to read. Defaults to process.env. */
104
+ env?: Record<string, string | undefined>;
105
+ /** An explicit `--env` / `--primitive-env` value, if the caller has one. */
106
+ explicitEnvName?: string | null;
107
+ /** Use this config file instead of discovering one. */
108
+ configPath?: string;
109
+ /**
110
+ * Identity consumers (the Vite plugin, cf-deploy, the Swift script) cannot
111
+ * work without an app ID; plain CLI resolution can, and always could.
112
+ */
113
+ requireAppId?: boolean;
114
+ }
115
+ /**
116
+ * Derives the WebSocket URL from the API URL. Every apiUrl/wsUrl pair in
117
+ * practice is the same host with the scheme swapped, so it is derived rather
118
+ * than authored — one less thing to get out of sync.
119
+ */
120
+ export declare function deriveWsUrl(apiUrl: string): string;
121
+ /**
122
+ * Finds `.primitive/config.json` by walking up from `cwd`, or returns the
123
+ * PRIMITIVE_PROJECT_CONFIG override when it is set (and exists).
124
+ */
125
+ export declare function findPrimitiveConfigPath(options?: {
126
+ cwd?: string;
127
+ env?: Record<string, string | undefined>;
128
+ }): string | null;
129
+ /**
130
+ * The project root for a config path: the parent of `.primitive/`, or the
131
+ * containing directory when the config was pointed at directly (a fixture via
132
+ * PRIMITIVE_PROJECT_CONFIG, which is not inside a `.primitive/` directory).
133
+ */
134
+ export declare function projectRootForConfigPath(configPath: string): string;
135
+ /**
136
+ * Local state lives beside the config it belongs to, so a fixture-pointed
137
+ * config never picks up a real project's selection and vice versa.
138
+ */
139
+ export declare function localStatePathForConfigPath(configPath: string): string;
140
+ /**
141
+ * Reads and structurally validates `.primitive/config.json`. Throws rather
142
+ * than guessing: a config that cannot be understood is always louder than a
143
+ * silent fallback to the wrong backend.
144
+ */
145
+ export declare function readCoreProjectConfig(configPath: string): CoreProjectConfig;
146
+ /**
147
+ * Reads this machine's selection from `.primitive/local.json`. Returns null
148
+ * when there is no file or no selection in it; throws when the file exists but
149
+ * cannot be read as the shape `primitive env use` writes.
150
+ */
151
+ export declare function readLocalSelection(configPath: string): string | null;
152
+ /**
153
+ * Applies the precedence to an already-loaded config. Exposed separately so
154
+ * the CLI wrapper can run its own field-level validation first and still share
155
+ * one ordering with every other reader.
156
+ */
157
+ export declare function selectEnvironmentName(config: CoreProjectConfig, options: {
158
+ explicitEnvName?: string | null;
159
+ envVarName?: string | null;
160
+ localSelection?: string | null;
161
+ configPath?: string;
162
+ }): {
163
+ name: string;
164
+ source: PrimitiveEnvSource;
165
+ };
166
+ /**
167
+ * The whole algorithm end to end: discover the config, read the selection,
168
+ * apply the precedence, derive the WebSocket URL.
169
+ */
170
+ export declare function resolvePrimitiveEnv(options?: ResolveOptions): ResolvedPrimitiveEnv;
@@ -1,8 +1,8 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../../rolldown-runtime-BNMv73og.cjs");let t=require("node:fs"),n=require("node:module"),r=require("glob"),i=require("path");i=e.i(i,1);async function a(e,t=`src/tests`,n=`**/*.primitive-test.ts`){return(await(0,r.glob)(n,{cwd:i.default.join(e,t),ignore:[`**/node_modules/**`,`**/dist/**`],absolute:!0})).map(t=>({absolutePath:t,relativePath:i.default.relative(e,t)}))}var o=`virtual:primitive-devtools`,s=`\0virtual:primitive-devtools`,c=`virtual:primitive-devtools-init`,l=`\0virtual:primitive-devtools-init`;function u(e={}){let{testsDir:r=`src/tests`,testPattern:i=`**/*.primitive-test.ts`,appName:u=`Primitive App`,enabled:d=!0,keyboardShortcut:f}=e,p,m,h=(0,t.readFileSync)((0,n.createRequire)({}.url).resolve(`vue-sonner/style.css`),`utf-8`);return{name:`primitive-devtools`,configResolved(e){p=e.root,m=e.command===`serve`},resolveId(e){if(e===o)return s;if(e===c)return l},async load(e){if(e===s)return`
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../../rolldown-runtime-BNMv73og.cjs");let t=require("node:fs"),n=require("node:module"),r=require("glob"),i=require("path");i=e.i(i,1);let a=require("node:path");async function o(e,t=`src/tests`,n=`**/*.primitive-test.ts`){return(await(0,r.glob)(n,{cwd:i.default.join(e,t),ignore:[`**/node_modules/**`,`**/dist/**`],absolute:!0})).map(t=>({absolutePath:t,relativePath:i.default.relative(e,t)}))}var s=`virtual:primitive-devtools`,c=`\0virtual:primitive-devtools`,l=`virtual:primitive-devtools-init`,u=`\0virtual:primitive-devtools-init`;function d(e={}){let{testsDir:r=`src/tests`,testPattern:i=`**/*.primitive-test.ts`,appName:a,enabled:d=!0,keyboardShortcut:f}=e,p=()=>(typeof a==`function`?a():a)||`Primitive App`,m,h,g=(0,t.readFileSync)((0,n.createRequire)({}.url).resolve(`vue-sonner/style.css`),`utf-8`);return{name:`primitive-devtools`,configResolved(e){m=e.root,h=e.command===`serve`},resolveId(e){if(e===s)return c;if(e===l)return u},async load(e){if(e===c)return`
2
2
  const groups = [];
3
3
  const loadErrors = [];
4
4
 
5
- ${(await a(p,r,i)).map((e,t)=>` try {
5
+ ${(await o(m,r,i)).map((e,t)=>` try {
6
6
  const mod${t} = await import('${e.absolutePath}');
7
7
  const val${t} = mod${t}.default;
8
8
  if (Array.isArray(val${t})) groups.push(...val${t});
@@ -15,9 +15,9 @@ ${(await a(p,r,i)).map((e,t)=>` try {
15
15
 
16
16
  export const testGroups = groups;
17
17
  export const testLoadErrors = loadErrors;
18
- export const appName = ${JSON.stringify(u)};
18
+ export const appName = ${JSON.stringify(p())};
19
19
  export const keyboardShortcut = ${JSON.stringify(f)};
20
- `;if(e===l)return`
20
+ `;if(e===u)return`
21
21
  import { createApp, h } from 'vue';
22
22
  import { createPinia } from 'pinia';
23
23
  import { DevToolsRoot, jsBaoClientService } from 'primitive-app';
@@ -65,4 +65,6 @@ const observer = new MutationObserver(async (mutations, obs) => {
65
65
  });
66
66
 
67
67
  observer.observe(document.body, { childList: true, subtree: true });
68
- `},transformIndexHtml(e){if(!m||!d)return e;let t=`<style data-primitive-devtools-sonner>${h}</style>`;return e.replace(`</body>`,`${t}\n<script type="module" src="/@id/__x00__virtual:primitive-devtools-init"><\/script>\n</body>`)},hotUpdate({file:e,server:t}){if(e.endsWith(`.primitive-test.ts`)){let e=t.moduleGraph.getModuleById(s);e&&(t.moduleGraph.invalidateModule(e),t.ws.send({type:`full-reload`}))}}}}exports.primitiveDevTools=u;
68
+ `},transformIndexHtml(e){if(!h||!d)return e;let t=`<style data-primitive-devtools-sonner>${g}</style>`;return e.replace(`</body>`,`${t}\n<script type="module" src="/@id/__x00__virtual:primitive-devtools-init"><\/script>\n</body>`)},hotUpdate({file:e,server:t}){if(e.endsWith(`.primitive-test.ts`)){let e=t.moduleGraph.getModuleById(c);e&&(t.moduleGraph.invalidateModule(e),t.ws.send({type:`full-reload`}))}}}}var f=`.primitive`,p=`config.json`,m=`local.json`,h=`.primitive/config.json`,g=`.primitive/local.json`,_=class extends Error{kind;path;constructor(e,t,n){super(t),this.name=`PrimitiveEnvError`,this.kind=e,this.path=n}};function v(e){if(typeof e!=`string`)return;let t=e.trim();if(!t)return;let n;try{n=new URL(t)}catch{return}if(!(n.protocol!==`https:`&&n.protocol!==`http:`)&&!(n.protocol===`http:`&&n.hostname!==`localhost`&&n.hostname!==`127.0.0.1`)&&!(n.username||n.password)&&!(n.pathname!==`/`&&n.pathname!==``)&&!(n.search||n.hash))return n.origin}function y(e){return e.startsWith(`https://`)?`wss://`+e.slice(8):e.startsWith(`http://`)?`ws://`+e.slice(7):e}function b(e={}){let n=(e.env??process.env).PRIMITIVE_PROJECT_CONFIG;if(n){let e=(0,a.resolve)(n);return(0,t.existsSync)(e)?e:null}let r=(0,a.resolve)(e.cwd??process.cwd());for(;;){let e=(0,a.join)(r,f,p);if((0,t.existsSync)(e))return e;let n=(0,a.dirname)(r);if(n===r)return null;r=n}}function x(e){let t=(0,a.dirname)(e);return(0,a.basename)(t)===`.primitive`?(0,a.dirname)(t):t}function S(e){return(0,a.join)((0,a.dirname)(e),m)}function C(e){if(!(0,t.existsSync)(e))throw new _(`missing-config`,`No ${h} found at ${e}. Run 'primitive init' to create one.`,e);let n;try{n=(0,t.readFileSync)(e,`utf-8`)}catch(t){throw new _(`malformed-config`,`Failed to read ${h} (${e}): ${t.message}`,e)}let r;try{r=JSON.parse(n)}catch(t){throw new _(`malformed-config`,`Failed to parse ${h} (${e}): ${t.message}`,e)}if(!r||typeof r!=`object`||Array.isArray(r))throw new _(`malformed-config`,`${h} (${e}) must contain a JSON object at the top level.`,e);let i=r;if(typeof i.version!=`number`||!Number.isInteger(i.version))throw new _(`malformed-config`,`${h} (${e}) is missing required integer field "version".`,e);if(i.version!==1)throw new _(`unsupported-version`,`${h} (${e}) has version ${i.version}, but this tool understands version 1.`,e);if(!i.environments||typeof i.environments!=`object`||Array.isArray(i.environments))throw new _(`malformed-config`,`${h} (${e}) is missing required "environments" object.`,e);let a={};for(let[t,n]of Object.entries(i.environments)){if(!n||typeof n!=`object`||Array.isArray(n))throw new _(`malformed-config`,`Environment "${t}" must be an object in ${h} (${e}).`,e);let r=n;if(typeof r.apiUrl!=`string`||!r.apiUrl)throw new _(`malformed-config`,`Environment "${t}" must have a non-empty "apiUrl" string in ${h} (${e}).`,e);a[t]={apiUrl:r.apiUrl.replace(/\/$/,``),appId:typeof r.appId==`string`?r.appId:void 0,appName:typeof r.appName==`string`?r.appName:void 0,webUrl:v(r.webUrl),description:typeof r.description==`string`?r.description:void 0}}return{version:i.version,defaultEnvironment:typeof i.defaultEnvironment==`string`&&i.defaultEnvironment?i.defaultEnvironment:void 0,environments:a}}function w(e){let n=S(e);if(!(0,t.existsSync)(n))return null;let r=e=>{throw new _(`corrupt-local-state`,`${g} (${n}) is unreadable: ${e}. Delete the file or re-run 'primitive env use <name>'.`,n)},i;try{i=JSON.parse((0,t.readFileSync)(n,`utf-8`))}catch(e){return r(e.message)}if(!i||typeof i!=`object`||Array.isArray(i))return r(`expected a JSON object`);let a=i.selectedEnvironment;return a==null||a===``?null:typeof a==`string`?a:r(`"selectedEnvironment" must be a string`)}function T(e,t){let n=Object.keys(e.environments),r=n.join(`, `)||`(none)`,i=t.configPath?`${h} (${t.configPath})`:h,a=(n,a,o)=>{if(!e.environments[n])throw new _(`unknown-environment`,`Environment "${n}" is not defined in ${i} (selected ${o}). Available: ${r}`,t.configPath);return{name:n,source:a}};if(t.explicitEnvName)return a(t.explicitEnvName,`explicit`,`explicitly`);if(t.envVarName)return a(t.envVarName,`env-var`,`via PRIMITIVE_ENV`);if(t.localSelection)return a(t.localSelection,`local`,`in ${g}`);if(e.defaultEnvironment)return a(e.defaultEnvironment,`default`,`as "defaultEnvironment"`);if(n.length===1)return{name:n[0],source:`sole`};throw n.length===0?new _(`no-selection`,`${i} has no environments defined. Run 'primitive env add <name>' or 'primitive init'.`,t.configPath):new _(`no-selection`,`No environment selected. Run 'primitive env use <name>', set "defaultEnvironment" in ${i}, or export PRIMITIVE_ENV. Available: ${r}`,t.configPath)}function E(e={}){let t=e.env??process.env,n=e.configPath??b({cwd:e.cwd,env:t});if(!n)throw new _(`missing-config`,`No ${h} found in ${e.cwd??process.cwd()} or any parent directory. Run 'primitive init' to create one, or 'primitive env add <name>' to add an environment.`);let r=C(n),{name:i,source:a}=T(r,{explicitEnvName:e.explicitEnvName||null,envVarName:t.PRIMITIVE_ENV||null,localSelection:w(n),configPath:n}),o=r.environments[i];if(e.requireAppId&&!o.appId)throw new _(`missing-app-id`,`Environment "${i}" in ${h} (${n}) has no "appId", which this build needs. Add one with 'primitive env add ${i} --api-url ${o.apiUrl} --app-id <id>' or edit the config.`,n);return{name:i,apiUrl:o.apiUrl,wsUrl:y(o.apiUrl),appId:o.appId,appName:o.appName,webUrl:o.webUrl,description:o.description,source:a,configPath:n,localStatePath:S(n),projectRoot:x(n)}}var D=[`VITE_APP_ID`,`VITE_API_URL`,`VITE_WS_URL`,`VITE_APP_NAME`],O=`VITE_EXPECTED_PRIMITIVE_ENV`,k=new Map;function A(e,t){let n=process.env[e];n!==void 0&&n!==``&&k.get(e)!==n||(process.env[e]=t,k.set(e,t))}function j(e={}){return E({cwd:e.root,env:e.env,explicitEnvName:e.primitiveEnv??null,requireAppId:e.requireAppId})}function M(e,t,n){let r=e=>n.includes(e)?` (overridden)`:``,i=[`[primitive-env] Primitive environment: ${e.name} (selected: ${e.source})`,`[primitive-env] apiUrl: ${t.apiUrl}${r(`VITE_API_URL`)}`,`[primitive-env] wsUrl: ${t.wsUrl}${r(`VITE_WS_URL`)}`,`[primitive-env] appId: ${t.appId??`(unset)`}${r(`VITE_APP_ID`)}`,`[primitive-env] appName: ${t.appName??`(unset)`}${r(`VITE_APP_NAME`)}`,`[primitive-env] config: ${e.configPath}`];return n.length>0&&i.push(`[primitive-env] overridden by the environment: ${n.join(`, `)}`),i.join(`
69
+ `)}function N(e,t){let n=e?.envDir;return n===!1?null:typeof n==`string`&&n!==``?(0,a.resolve)(t,n):t}function P(e,t){let n=process.env[O];if(n!==void 0&&n!==``)return{value:n,where:`the process environment`};if(t===null)return null;for(let n of[`.env.${e}.local`,`.env.${e}`,`.env.local`,`.env`]){let e=z((0,a.join)(t,n))[O];if(e!==void 0)return{value:e,where:n}}return null}function F(e,t,n){return[`[primitive-env] Wrong pair: vite mode "${e}" is declared to run against Primitive environment "${t.value}", but "${n.name}" resolved.`,`[primitive-env] expected: ${t.value} (${O} in ${t.where})`,`[primitive-env] resolved: ${n.name} (selected: ${n.source})`,`[primitive-env] config: ${n.configPath}`,``,`Run the pair you meant — PRIMITIVE_ENV=${t.value} <command> --mode ${e}, or 'primitive env use ${t.value}' — or change/remove ${O} in ${t.where}.`,``,`A mode declares this when its .env.${e} carries values coupled to one backend, so continuing would run this environment's identity with another's configuration.`].join(`
70
+ `)}function I(e,t,n){let r=P(e,t);if(r&&r.value!==``&&r.value!==n.name)throw Error(F(e,r,n))}function L(e={}){let t=null,n=null,r,i=[],a=!1,o=null;return{name:`primitive-env`,enforce:`pre`,config(s,c){a=c?.command===`serve`,o=null;let l=s?.root??process.cwd(),u=c?.mode??`development`,d=R(u,l),f={};for(let e of D){let t=process.env[e],n=t!==void 0&&k.get(e)!==t?t:d[e];n!==void 0&&n!==``&&(f[e]=n)}let p;try{p=E({cwd:l,explicitEnvName:e.primitiveEnv??null,requireAppId:!f.VITE_APP_ID})}catch(e){if(e instanceof _&&e.kind===`missing-config`&&f.VITE_APP_ID&&f.VITE_API_URL)return console.warn(`[primitive-env] No .primitive/config.json found; using VITE_APP_ID / VITE_API_URL from the environment. Run 'primitive init' to make the project the source of truth.`),r=f.VITE_APP_NAME,{};throw e}if(!(f.VITE_APP_ID&&f.VITE_API_URL)){let e=N(s,l);I(u,e,p),o={root:l,mode:u,dir:e}}t=p,r=f.VITE_APP_NAME??p.appName;let m={VITE_APP_ID:p.appId,VITE_API_URL:p.apiUrl,VITE_WS_URL:p.wsUrl,VITE_APP_NAME:p.appName},h={"import.meta.env.VITE_PRIMITIVE_ENV":JSON.stringify(p.name)},g=[];for(let e of D){if(f[e]!==void 0){g.push(e);continue}let t=m[e];t!==void 0&&(h[`import.meta.env.${e}`]=JSON.stringify(t),A(e,t))}return A(`VITE_PRIMITIVE_ENV`,p.name),i=g,n={apiUrl:f.VITE_API_URL??p.apiUrl,wsUrl:f.VITE_WS_URL??p.wsUrl,appId:f.VITE_APP_ID??p.appId,appName:r},e.logResolved!==!1&&console.log(M(p,n,g)),g.length>0&&console.warn(`[primitive-env] Overridden by an explicit value in .env or the shell: ${g.join(`, `)}. Remove it to use .primitive/config.json (deploys reject these outright).`),{define:h}},configResolved(e){if(!o||!t)return;let n=N(e,o.root),r=e?.mode??o.mode;n===o.dir&&r===o.mode||(I(r,n,t),o={root:o.root,mode:r,dir:n})},transformIndexHtml(){if(!a||!t||!n)return[];let e=`%c[primitive] env=${t.name} api=${n.apiUrl} app=${n.appId??`(unset)`} (${t.configPath})`+(i.length>0?` overridden: ${i.join(`, `)}`:``);return[{tag:`script`,injectTo:`head`,children:`console.info(${JSON.stringify(e)}, "color:#888");`}]},appName(){return r},resolved(){return t}}}function R(e,t){let n=[`.env`,`.env.local`,`.env.${e}`,`.env.${e}.local`],r={};for(let e of n)Object.assign(r,z((0,a.join)(t,e)));return r}function z(e){if(!(0,t.existsSync)(e))return{};let n={};for(let r of(0,t.readFileSync)(e,`utf-8`).split(/\r?\n/)){let e=r.trim();if(!e||e.startsWith(`#`))continue;let t=e.match(/^(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/);if(!t)continue;let i=t[2].trim(),a=i[0],o=a===`"`||a===`'`||a==="`"?i.indexOf(a,1):-1;n[t[1]]=o===-1?i.replace(/\s+#.*$/,``).trim():i.slice(1,o)}return n}exports.PrimitiveEnvError=_,exports.primitiveDevTools=d,exports.primitiveEnv=L,exports.resolvePrimitiveEnv=j;
@@ -8,3 +8,5 @@
8
8
  */
9
9
  export { primitiveDevTools } from "./plugin";
10
10
  export type { PrimitiveDevToolsOptions } from "./plugin";
11
+ export { primitiveEnv, resolvePrimitiveEnv, PrimitiveEnvError, } from "./primitive-env";
12
+ export type { PrimitiveEnvOptions, PrimitiveEnvPlugin, ResolvePrimitiveEnvOptions, ResolvedPrimitiveEnv, } from "./primitive-env";
@@ -1,38 +1,39 @@
1
- import { readFileSync as e } from "node:fs";
2
- import { createRequire as t } from "node:module";
3
- import { glob as n } from "glob";
4
- import r from "path";
1
+ import { existsSync as e, readFileSync as t } from "node:fs";
2
+ import { createRequire as n } from "node:module";
3
+ import { glob as r } from "glob";
4
+ import i from "path";
5
+ import { basename as a, dirname as o, join as s, resolve as c } from "node:path";
5
6
  //#region src/dev-tools/vite-plugin/test-discovery.ts
6
- async function i(e, t = "src/tests", i = "**/*.primitive-test.ts") {
7
- return (await n(i, {
8
- cwd: r.join(e, t),
7
+ async function l(e, t = "src/tests", n = "**/*.primitive-test.ts") {
8
+ return (await r(n, {
9
+ cwd: i.join(e, t),
9
10
  ignore: ["**/node_modules/**", "**/dist/**"],
10
11
  absolute: !0
11
12
  })).map((t) => ({
12
13
  absolutePath: t,
13
- relativePath: r.relative(e, t)
14
+ relativePath: i.relative(e, t)
14
15
  }));
15
16
  }
16
17
  //#endregion
17
18
  //#region src/dev-tools/vite-plugin/plugin.ts
18
- var a = "virtual:primitive-devtools", o = "\0virtual:primitive-devtools", s = "virtual:primitive-devtools-init", c = "\0virtual:primitive-devtools-init";
19
- function l(n = {}) {
20
- let { testsDir: r = "src/tests", testPattern: l = "**/*.primitive-test.ts", appName: u = "Primitive App", enabled: d = !0, keyboardShortcut: f } = n, p, m, h = e(t(import.meta.url).resolve("vue-sonner/style.css"), "utf-8");
19
+ var u = "virtual:primitive-devtools", d = "\0virtual:primitive-devtools", f = "virtual:primitive-devtools-init", p = "\0virtual:primitive-devtools-init";
20
+ function m(e = {}) {
21
+ let { testsDir: r = "src/tests", testPattern: i = "**/*.primitive-test.ts", appName: a, enabled: o = !0, keyboardShortcut: s } = e, c = () => (typeof a == "function" ? a() : a) || "Primitive App", m, h, g = t(n(import.meta.url).resolve("vue-sonner/style.css"), "utf-8");
21
22
  return {
22
23
  name: "primitive-devtools",
23
24
  configResolved(e) {
24
- p = e.root, m = e.command === "serve";
25
+ m = e.root, h = e.command === "serve";
25
26
  },
26
27
  resolveId(e) {
27
- if (e === a) return o;
28
- if (e === s) return c;
28
+ if (e === u) return d;
29
+ if (e === f) return p;
29
30
  },
30
31
  async load(e) {
31
- if (e === o) return `
32
+ if (e === d) return `
32
33
  const groups = [];
33
34
  const loadErrors = [];
34
35
 
35
- ${(await i(p, r, l)).map((e, t) => ` try {
36
+ ${(await l(m, r, i)).map((e, t) => ` try {
36
37
  const mod${t} = await import('${e.absolutePath}');
37
38
  const val${t} = mod${t}.default;
38
39
  if (Array.isArray(val${t})) groups.push(...val${t});
@@ -44,23 +45,339 @@ ${(await i(p, r, l)).map((e, t) => ` try {
44
45
 
45
46
  export const testGroups = groups;
46
47
  export const testLoadErrors = loadErrors;
47
- export const appName = ${JSON.stringify(u)};
48
- export const keyboardShortcut = ${JSON.stringify(f)};
48
+ export const appName = ${JSON.stringify(c())};
49
+ export const keyboardShortcut = ${JSON.stringify(s)};
49
50
  `;
50
- if (e === c) return "\nimport { createApp, h } from 'vue';\nimport { createPinia } from 'pinia';\nimport { DevToolsRoot, jsBaoClientService } from 'primitive-app';\nimport { testGroups, testLoadErrors, appName, keyboardShortcut } from 'virtual:primitive-devtools';\n\n// Wait for the host app to register its client on window.__primitiveAppClient\nasync function waitForAppClient() {\n return new Promise((resolve) => {\n const check = () => {\n if (window.__primitiveAppClient) {\n resolve(window.__primitiveAppClient);\n } else {\n setTimeout(check, 100);\n }\n };\n check();\n });\n}\n\n// Wait for main app to mount, then inject dev tools\nconst observer = new MutationObserver(async (mutations, obs) => {\n const appEl = document.getElementById('app');\n if (appEl && appEl.children.length > 0) {\n obs.disconnect();\n\n // Wait for the app's client to be available and link it to the debug suite\n const appClient = await waitForAppClient();\n jsBaoClientService.setExternalClient(appClient);\n\n // Create mount point for dev tools\n const devToolsMount = document.createElement('div');\n devToolsMount.id = 'primitive-devtools-root';\n document.body.appendChild(devToolsMount);\n\n // Create the dev tools app with its own Pinia instance\n // The jsBaoClientService is linked to the app's client above\n const devToolsApp = createApp({\n render() {\n return h(DevToolsRoot, { testGroups, testLoadErrors, appName, keyboardShortcut });\n }\n });\n devToolsApp.use(createPinia());\n devToolsApp.mount('#primitive-devtools-root');\n }\n});\n\nobserver.observe(document.body, { childList: true, subtree: true });\n";
51
+ if (e === p) return "\nimport { createApp, h } from 'vue';\nimport { createPinia } from 'pinia';\nimport { DevToolsRoot, jsBaoClientService } from 'primitive-app';\nimport { testGroups, testLoadErrors, appName, keyboardShortcut } from 'virtual:primitive-devtools';\n\n// Wait for the host app to register its client on window.__primitiveAppClient\nasync function waitForAppClient() {\n return new Promise((resolve) => {\n const check = () => {\n if (window.__primitiveAppClient) {\n resolve(window.__primitiveAppClient);\n } else {\n setTimeout(check, 100);\n }\n };\n check();\n });\n}\n\n// Wait for main app to mount, then inject dev tools\nconst observer = new MutationObserver(async (mutations, obs) => {\n const appEl = document.getElementById('app');\n if (appEl && appEl.children.length > 0) {\n obs.disconnect();\n\n // Wait for the app's client to be available and link it to the debug suite\n const appClient = await waitForAppClient();\n jsBaoClientService.setExternalClient(appClient);\n\n // Create mount point for dev tools\n const devToolsMount = document.createElement('div');\n devToolsMount.id = 'primitive-devtools-root';\n document.body.appendChild(devToolsMount);\n\n // Create the dev tools app with its own Pinia instance\n // The jsBaoClientService is linked to the app's client above\n const devToolsApp = createApp({\n render() {\n return h(DevToolsRoot, { testGroups, testLoadErrors, appName, keyboardShortcut });\n }\n });\n devToolsApp.use(createPinia());\n devToolsApp.mount('#primitive-devtools-root');\n }\n});\n\nobserver.observe(document.body, { childList: true, subtree: true });\n";
51
52
  },
52
53
  transformIndexHtml(e) {
53
- if (!m || !d) return e;
54
- let t = `<style data-primitive-devtools-sonner>${h}</style>`;
54
+ if (!h || !o) return e;
55
+ let t = `<style data-primitive-devtools-sonner>${g}</style>`;
55
56
  return e.replace("</body>", `${t}\n<script type="module" src="/@id/__x00__virtual:primitive-devtools-init"><\/script>\n</body>`);
56
57
  },
57
58
  hotUpdate({ file: e, server: t }) {
58
59
  if (e.endsWith(".primitive-test.ts")) {
59
- let e = t.moduleGraph.getModuleById(o);
60
+ let e = t.moduleGraph.getModuleById(d);
60
61
  e && (t.moduleGraph.invalidateModule(e), t.ws.send({ type: "full-reload" }));
61
62
  }
62
63
  }
63
64
  };
64
65
  }
66
+ var h = ".primitive", g = "config.json", _ = "local.json", v = ".primitive/config.json", y = ".primitive/local.json", b = class extends Error {
67
+ kind;
68
+ path;
69
+ constructor(e, t, n) {
70
+ super(t), this.name = "PrimitiveEnvError", this.kind = e, this.path = n;
71
+ }
72
+ };
73
+ function x(e) {
74
+ if (typeof e != "string") return;
75
+ let t = e.trim();
76
+ if (!t) return;
77
+ let n;
78
+ try {
79
+ n = new URL(t);
80
+ } catch {
81
+ return;
82
+ }
83
+ if (!(n.protocol !== "https:" && n.protocol !== "http:") && !(n.protocol === "http:" && n.hostname !== "localhost" && n.hostname !== "127.0.0.1") && !(n.username || n.password) && !(n.pathname !== "/" && n.pathname !== "") && !(n.search || n.hash)) return n.origin;
84
+ }
85
+ function S(e) {
86
+ return e.startsWith("https://") ? "wss://" + e.slice(8) : e.startsWith("http://") ? "ws://" + e.slice(7) : e;
87
+ }
88
+ function C(t = {}) {
89
+ let n = (t.env ?? process.env).PRIMITIVE_PROJECT_CONFIG;
90
+ if (n) {
91
+ let t = c(n);
92
+ return e(t) ? t : null;
93
+ }
94
+ let r = c(t.cwd ?? process.cwd());
95
+ for (;;) {
96
+ let t = s(r, h, g);
97
+ if (e(t)) return t;
98
+ let n = o(r);
99
+ if (n === r) return null;
100
+ r = n;
101
+ }
102
+ }
103
+ function w(e) {
104
+ let t = o(e);
105
+ return a(t) === ".primitive" ? o(t) : t;
106
+ }
107
+ function T(e) {
108
+ return s(o(e), _);
109
+ }
110
+ function E(n) {
111
+ if (!e(n)) throw new b("missing-config", `No ${v} found at ${n}. Run 'primitive init' to create one.`, n);
112
+ let r;
113
+ try {
114
+ r = t(n, "utf-8");
115
+ } catch (e) {
116
+ throw new b("malformed-config", `Failed to read ${v} (${n}): ${e.message}`, n);
117
+ }
118
+ let i;
119
+ try {
120
+ i = JSON.parse(r);
121
+ } catch (e) {
122
+ throw new b("malformed-config", `Failed to parse ${v} (${n}): ${e.message}`, n);
123
+ }
124
+ if (!i || typeof i != "object" || Array.isArray(i)) throw new b("malformed-config", `${v} (${n}) must contain a JSON object at the top level.`, n);
125
+ let a = i;
126
+ if (typeof a.version != "number" || !Number.isInteger(a.version)) throw new b("malformed-config", `${v} (${n}) is missing required integer field "version".`, n);
127
+ if (a.version !== 1) throw new b("unsupported-version", `${v} (${n}) has version ${a.version}, but this tool understands version 1.`, n);
128
+ if (!a.environments || typeof a.environments != "object" || Array.isArray(a.environments)) throw new b("malformed-config", `${v} (${n}) is missing required "environments" object.`, n);
129
+ let o = {};
130
+ for (let [e, t] of Object.entries(a.environments)) {
131
+ if (!t || typeof t != "object" || Array.isArray(t)) throw new b("malformed-config", `Environment "${e}" must be an object in ${v} (${n}).`, n);
132
+ let r = t;
133
+ if (typeof r.apiUrl != "string" || !r.apiUrl) throw new b("malformed-config", `Environment "${e}" must have a non-empty "apiUrl" string in ${v} (${n}).`, n);
134
+ o[e] = {
135
+ apiUrl: r.apiUrl.replace(/\/$/, ""),
136
+ appId: typeof r.appId == "string" ? r.appId : void 0,
137
+ appName: typeof r.appName == "string" ? r.appName : void 0,
138
+ webUrl: x(r.webUrl),
139
+ description: typeof r.description == "string" ? r.description : void 0
140
+ };
141
+ }
142
+ return {
143
+ version: a.version,
144
+ defaultEnvironment: typeof a.defaultEnvironment == "string" && a.defaultEnvironment ? a.defaultEnvironment : void 0,
145
+ environments: o
146
+ };
147
+ }
148
+ function D(n) {
149
+ let r = T(n);
150
+ if (!e(r)) return null;
151
+ let i = (e) => {
152
+ throw new b("corrupt-local-state", `${y} (${r}) is unreadable: ${e}. Delete the file or re-run 'primitive env use <name>'.`, r);
153
+ }, a;
154
+ try {
155
+ a = JSON.parse(t(r, "utf-8"));
156
+ } catch (e) {
157
+ return i(e.message);
158
+ }
159
+ if (!a || typeof a != "object" || Array.isArray(a)) return i("expected a JSON object");
160
+ let o = a.selectedEnvironment;
161
+ return o == null || o === "" ? null : typeof o == "string" ? o : i("\"selectedEnvironment\" must be a string");
162
+ }
163
+ function O(e, t) {
164
+ let n = Object.keys(e.environments), r = n.join(", ") || "(none)", i = t.configPath ? `${v} (${t.configPath})` : v, a = (n, a, o) => {
165
+ if (!e.environments[n]) throw new b("unknown-environment", `Environment "${n}" is not defined in ${i} (selected ${o}). Available: ${r}`, t.configPath);
166
+ return {
167
+ name: n,
168
+ source: a
169
+ };
170
+ };
171
+ if (t.explicitEnvName) return a(t.explicitEnvName, "explicit", "explicitly");
172
+ if (t.envVarName) return a(t.envVarName, "env-var", "via PRIMITIVE_ENV");
173
+ if (t.localSelection) return a(t.localSelection, "local", `in ${y}`);
174
+ if (e.defaultEnvironment) return a(e.defaultEnvironment, "default", "as \"defaultEnvironment\"");
175
+ if (n.length === 1) return {
176
+ name: n[0],
177
+ source: "sole"
178
+ };
179
+ throw n.length === 0 ? new b("no-selection", `${i} has no environments defined. Run 'primitive env add <name>' or 'primitive init'.`, t.configPath) : new b("no-selection", `No environment selected. Run 'primitive env use <name>', set "defaultEnvironment" in ${i}, or export PRIMITIVE_ENV. Available: ${r}`, t.configPath);
180
+ }
181
+ function k(e = {}) {
182
+ let t = e.env ?? process.env, n = e.configPath ?? C({
183
+ cwd: e.cwd,
184
+ env: t
185
+ });
186
+ if (!n) throw new b("missing-config", `No ${v} found in ${e.cwd ?? process.cwd()} or any parent directory. Run 'primitive init' to create one, or 'primitive env add <name>' to add an environment.`);
187
+ let r = E(n), { name: i, source: a } = O(r, {
188
+ explicitEnvName: e.explicitEnvName || null,
189
+ envVarName: t.PRIMITIVE_ENV || null,
190
+ localSelection: D(n),
191
+ configPath: n
192
+ }), o = r.environments[i];
193
+ if (e.requireAppId && !o.appId) throw new b("missing-app-id", `Environment "${i}" in ${v} (${n}) has no "appId", which this build needs. Add one with 'primitive env add ${i} --api-url ${o.apiUrl} --app-id <id>' or edit the config.`, n);
194
+ return {
195
+ name: i,
196
+ apiUrl: o.apiUrl,
197
+ wsUrl: S(o.apiUrl),
198
+ appId: o.appId,
199
+ appName: o.appName,
200
+ webUrl: o.webUrl,
201
+ description: o.description,
202
+ source: a,
203
+ configPath: n,
204
+ localStatePath: T(n),
205
+ projectRoot: w(n)
206
+ };
207
+ }
208
+ //#endregion
209
+ //#region src/dev-tools/vite-plugin/primitive-env.ts
210
+ var A = [
211
+ "VITE_APP_ID",
212
+ "VITE_API_URL",
213
+ "VITE_WS_URL",
214
+ "VITE_APP_NAME"
215
+ ], j = "VITE_EXPECTED_PRIMITIVE_ENV", M = /* @__PURE__ */ new Map();
216
+ function N(e, t) {
217
+ let n = process.env[e];
218
+ n !== void 0 && n !== "" && M.get(e) !== n || (process.env[e] = t, M.set(e, t));
219
+ }
220
+ function P(e = {}) {
221
+ return k({
222
+ cwd: e.root,
223
+ env: e.env,
224
+ explicitEnvName: e.primitiveEnv ?? null,
225
+ requireAppId: e.requireAppId
226
+ });
227
+ }
228
+ function F(e, t, n) {
229
+ let r = (e) => n.includes(e) ? " (overridden)" : "", i = [
230
+ `[primitive-env] Primitive environment: ${e.name} (selected: ${e.source})`,
231
+ `[primitive-env] apiUrl: ${t.apiUrl}${r("VITE_API_URL")}`,
232
+ `[primitive-env] wsUrl: ${t.wsUrl}${r("VITE_WS_URL")}`,
233
+ `[primitive-env] appId: ${t.appId ?? "(unset)"}${r("VITE_APP_ID")}`,
234
+ `[primitive-env] appName: ${t.appName ?? "(unset)"}${r("VITE_APP_NAME")}`,
235
+ `[primitive-env] config: ${e.configPath}`
236
+ ];
237
+ return n.length > 0 && i.push(`[primitive-env] overridden by the environment: ${n.join(", ")}`), i.join("\n");
238
+ }
239
+ function I(e, t) {
240
+ let n = e?.envDir;
241
+ return n === !1 ? null : typeof n == "string" && n !== "" ? c(t, n) : t;
242
+ }
243
+ function L(e, t) {
244
+ let n = process.env[j];
245
+ if (n !== void 0 && n !== "") return {
246
+ value: n,
247
+ where: "the process environment"
248
+ };
249
+ if (t === null) return null;
250
+ for (let n of [
251
+ `.env.${e}.local`,
252
+ `.env.${e}`,
253
+ ".env.local",
254
+ ".env"
255
+ ]) {
256
+ let e = H(s(t, n))[j];
257
+ if (e !== void 0) return {
258
+ value: e,
259
+ where: n
260
+ };
261
+ }
262
+ return null;
263
+ }
264
+ function R(e, t, n) {
265
+ return [
266
+ `[primitive-env] Wrong pair: vite mode "${e}" is declared to run against Primitive environment "${t.value}", but "${n.name}" resolved.`,
267
+ `[primitive-env] expected: ${t.value} (${j} in ${t.where})`,
268
+ `[primitive-env] resolved: ${n.name} (selected: ${n.source})`,
269
+ `[primitive-env] config: ${n.configPath}`,
270
+ "",
271
+ `Run the pair you meant — PRIMITIVE_ENV=${t.value} <command> --mode ${e}, or 'primitive env use ${t.value}' — or change/remove ${j} in ${t.where}.`,
272
+ "",
273
+ `A mode declares this when its .env.${e} carries values coupled to one backend, so continuing would run this environment's identity with another's configuration.`
274
+ ].join("\n");
275
+ }
276
+ function z(e, t, n) {
277
+ let r = L(e, t);
278
+ if (r && r.value !== "" && r.value !== n.name) throw Error(R(e, r, n));
279
+ }
280
+ function B(e = {}) {
281
+ let t = null, n = null, r, i = [], a = !1, o = null;
282
+ return {
283
+ name: "primitive-env",
284
+ enforce: "pre",
285
+ config(s, c) {
286
+ a = c?.command === "serve", o = null;
287
+ let l = s?.root ?? process.cwd(), u = c?.mode ?? "development", d = V(u, l), f = {};
288
+ for (let e of A) {
289
+ let t = process.env[e], n = t !== void 0 && M.get(e) !== t ? t : d[e];
290
+ n !== void 0 && n !== "" && (f[e] = n);
291
+ }
292
+ let p;
293
+ try {
294
+ p = k({
295
+ cwd: l,
296
+ explicitEnvName: e.primitiveEnv ?? null,
297
+ requireAppId: !f.VITE_APP_ID
298
+ });
299
+ } catch (e) {
300
+ if (e instanceof b && e.kind === "missing-config" && f.VITE_APP_ID && f.VITE_API_URL) return console.warn("[primitive-env] No .primitive/config.json found; using VITE_APP_ID / VITE_API_URL from the environment. Run 'primitive init' to make the project the source of truth."), r = f.VITE_APP_NAME, {};
301
+ throw e;
302
+ }
303
+ if (!(f.VITE_APP_ID && f.VITE_API_URL)) {
304
+ let e = I(s, l);
305
+ z(u, e, p), o = {
306
+ root: l,
307
+ mode: u,
308
+ dir: e
309
+ };
310
+ }
311
+ t = p, r = f.VITE_APP_NAME ?? p.appName;
312
+ let m = {
313
+ VITE_APP_ID: p.appId,
314
+ VITE_API_URL: p.apiUrl,
315
+ VITE_WS_URL: p.wsUrl,
316
+ VITE_APP_NAME: p.appName
317
+ }, h = { "import.meta.env.VITE_PRIMITIVE_ENV": JSON.stringify(p.name) }, g = [];
318
+ for (let e of A) {
319
+ if (f[e] !== void 0) {
320
+ g.push(e);
321
+ continue;
322
+ }
323
+ let t = m[e];
324
+ t !== void 0 && (h[`import.meta.env.${e}`] = JSON.stringify(t), N(e, t));
325
+ }
326
+ return N("VITE_PRIMITIVE_ENV", p.name), i = g, n = {
327
+ apiUrl: f.VITE_API_URL ?? p.apiUrl,
328
+ wsUrl: f.VITE_WS_URL ?? p.wsUrl,
329
+ appId: f.VITE_APP_ID ?? p.appId,
330
+ appName: r
331
+ }, e.logResolved !== !1 && console.log(F(p, n, g)), g.length > 0 && console.warn(`[primitive-env] Overridden by an explicit value in .env or the shell: ${g.join(", ")}. Remove it to use .primitive/config.json (deploys reject these outright).`), { define: h };
332
+ },
333
+ configResolved(e) {
334
+ if (!o || !t) return;
335
+ let n = I(e, o.root), r = e?.mode ?? o.mode;
336
+ n === o.dir && r === o.mode || (z(r, n, t), o = {
337
+ root: o.root,
338
+ mode: r,
339
+ dir: n
340
+ });
341
+ },
342
+ transformIndexHtml() {
343
+ if (!a || !t || !n) return [];
344
+ let e = `%c[primitive] env=${t.name} api=${n.apiUrl} app=${n.appId ?? "(unset)"} (${t.configPath})` + (i.length > 0 ? ` overridden: ${i.join(", ")}` : "");
345
+ return [{
346
+ tag: "script",
347
+ injectTo: "head",
348
+ children: `console.info(${JSON.stringify(e)}, "color:#888");`
349
+ }];
350
+ },
351
+ appName() {
352
+ return r;
353
+ },
354
+ resolved() {
355
+ return t;
356
+ }
357
+ };
358
+ }
359
+ function V(e, t) {
360
+ let n = [
361
+ ".env",
362
+ ".env.local",
363
+ `.env.${e}`,
364
+ `.env.${e}.local`
365
+ ], r = {};
366
+ for (let e of n) Object.assign(r, H(s(t, e)));
367
+ return r;
368
+ }
369
+ function H(n) {
370
+ if (!e(n)) return {};
371
+ let r = {};
372
+ for (let e of t(n, "utf-8").split(/\r?\n/)) {
373
+ let t = e.trim();
374
+ if (!t || t.startsWith("#")) continue;
375
+ let n = t.match(/^(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/);
376
+ if (!n) continue;
377
+ let i = n[2].trim(), a = i[0], o = a === "\"" || a === "'" || a === "`" ? i.indexOf(a, 1) : -1;
378
+ r[n[1]] = o === -1 ? i.replace(/\s+#.*$/, "").trim() : i.slice(1, o);
379
+ }
380
+ return r;
381
+ }
65
382
  //#endregion
66
- export { l as primitiveDevTools };
383
+ export { b as PrimitiveEnvError, m as primitiveDevTools, B as primitiveEnv, P as resolvePrimitiveEnv };
@@ -10,9 +10,15 @@ export interface PrimitiveDevToolsOptions {
10
10
  */
11
11
  testPattern?: string;
12
12
  /**
13
- * App name shown in the dev tools UI
13
+ * App name shown in the dev tools UI.
14
+ *
15
+ * May be a function, evaluated when the dev-tools module is first loaded
16
+ * rather than when the plugin is constructed. That is what lets the label
17
+ * follow `primitiveEnv()`'s resolved app name: the config file must not
18
+ * resolve the environment eagerly (#2873), so the name is not known yet at
19
+ * `vite.config.ts` evaluation time.
14
20
  */
15
- appName?: string;
21
+ appName?: string | (() => string | undefined);
16
22
  /**
17
23
  * Whether to enable the plugin. Defaults to true in development.
18
24
  */
@@ -0,0 +1,97 @@
1
+ /**
2
+ * `primitiveEnv()` — Primitive environment configuration for a Vite app (#2873).
3
+ *
4
+ * A scaffolded app used to type its backend URL and app ID twice: once in
5
+ * `.primitive/config.json` (which every `primitive` command reads) and again in
6
+ * `.env` / `.env.production`. This plugin removes the second copy. It resolves
7
+ * the selected Primitive environment at config time and `define`s the keys the
8
+ * app already reads:
9
+ *
10
+ * VITE_APP_ID VITE_API_URL VITE_WS_URL VITE_APP_NAME VITE_PRIMITIVE_ENV
11
+ *
12
+ * `src/config/envConfig.ts` is untouched — it keeps reading
13
+ * `import.meta.env.*`. The `.env` files shrink to app behavior
14
+ * (`VITE_OAUTH_REDIRECT_URI`, `VITE_ENABLE_AUTH_PROXY`, `VITE_LOG_LEVEL`).
15
+ *
16
+ * ── which environment ─────────────────────────────────────────────────────
17
+ * `--primitive-env` / `PRIMITIVE_ENV` → `primitive env use` (the per-machine
18
+ * selection in `.primitive/local.json`) → the committed `defaultEnvironment` →
19
+ * the sole environment. That order lives in ONE place: the generated copy of
20
+ * the CLI's resolver core sitting beside this file.
21
+ *
22
+ * ── pairing a mode with an environment ────────────────────────────────────
23
+ * The vite mode and the Primitive environment are independent axes. An app
24
+ * whose `.env.<mode>` carries backend-coupled values can pin the combination
25
+ * by declaring `VITE_EXPECTED_PRIMITIVE_ENV` in that file; a run that resolves
26
+ * a different environment stops here, at config time — which covers `vite
27
+ * dev`, `vite build`, the deploy's build, and the headless vitest suite alike.
28
+ * Absent declaration: the axes stay fully independent.
29
+ *
30
+ * ── the escape hatch ──────────────────────────────────────────────────────
31
+ * An explicit `VITE_*` identity key in a `.env` file or the process
32
+ * environment still wins, with a warning naming it. That keeps pure-env CI
33
+ * builds and legacy scaffolds working — which is also why this plugin, not
34
+ * `vite.config.ts`, owns the resolution: an eager top-level `resolvePrimitiveEnv()`
35
+ * in the config file would throw before the escape hatch could apply.
36
+ */
37
+ import type { Plugin } from "vite";
38
+ import { PrimitiveEnvError, type ResolvedPrimitiveEnv } from "./generated-env-resolver-core";
39
+ export { PrimitiveEnvError };
40
+ export type { ResolvedPrimitiveEnv };
41
+ export interface ResolvePrimitiveEnvOptions {
42
+ /** Project root to resolve from. Defaults to `process.cwd()`. */
43
+ root?: string;
44
+ /** Environment variables to read. Defaults to `process.env`. */
45
+ env?: Record<string, string | undefined>;
46
+ /** An explicit environment name (e.g. from `--primitive-env`). */
47
+ primitiveEnv?: string | null;
48
+ /** Fail when the resolved environment has no `appId`. */
49
+ requireAppId?: boolean;
50
+ }
51
+ /**
52
+ * Resolves the Primitive environment for a project. A thin wrapper over the
53
+ * generated resolver core so callers outside this plugin (scripts, tests) get
54
+ * the same answer the plugin does.
55
+ */
56
+ export declare function resolvePrimitiveEnv(options?: ResolvePrimitiveEnvOptions): ResolvedPrimitiveEnv;
57
+ export interface PrimitiveEnvOptions {
58
+ /**
59
+ * Override the environment to build against. Normally left unset — the
60
+ * selection comes from `primitive env use` or `PRIMITIVE_ENV`.
61
+ */
62
+ primitiveEnv?: string;
63
+ /** Print the resolved tuple. Defaults to true. */
64
+ logResolved?: boolean;
65
+ }
66
+ export interface PrimitiveEnvPlugin extends Plugin {
67
+ /**
68
+ * The app name after resolution and any `.env` override — what the dev-tools
69
+ * label should show. Undefined until the `config` hook has run, and when
70
+ * neither the environment nor a `VITE_APP_NAME` supplies one.
71
+ */
72
+ appName(): string | undefined;
73
+ /** The resolved environment, or null when the escape hatch applied. */
74
+ resolved(): ResolvedPrimitiveEnv | null;
75
+ }
76
+ export declare function primitiveEnv(options?: PrimitiveEnvOptions): PrimitiveEnvPlugin;
77
+ /**
78
+ * The `.env` files Vite would load for this mode, in Vite's own precedence
79
+ * order, restricted to the keys this plugin cares about.
80
+ *
81
+ * Deliberately not Vite's `loadEnv`: `vite` is not a runtime dependency of
82
+ * this package, and reaching for it from a consumer's install layout is the
83
+ * kind of resolution that works on one package manager and not the next. The
84
+ * reader below is the same one `cf-deploy` uses for its strict check, so the
85
+ * build and the deploy can never disagree about what a `.env` file says.
86
+ */
87
+ export declare function loadDotEnv(mode: string, root: string): Record<string, string>;
88
+ /**
89
+ * Parses one `.env` file. Missing file → no keys.
90
+ *
91
+ * Quoting follows what Vite's own loader does, because a value read
92
+ * differently here than there is exactly the kind of disagreement this plugin
93
+ * exists to remove: a quoted value ends at its closing quote — a `#` inside it
94
+ * is content, and anything after it on the line is a comment — while an
95
+ * unquoted value ends at the first inline comment.
96
+ */
97
+ export declare function parseDotEnvFile(path: string): Record<string, string>;
package/dist/index.cjs CHANGED
@@ -1,4 +1,4 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./rolldown-runtime-BNMv73og.cjs"),t=require("./lib-CgtC-xno.cjs");let n=require("vue");n=e.i(n,1);let r=require("pinia"),i=require("@vueuse/core"),a=require("js-bao");var o=`data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAMAAAD04JH5AAAAAXNSR0IB2cksfwAAAAlwSFlzAAAWJQAAFiUBSVIk8AAAAutQTFRFAAAAMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzis4yvAAAAPl0Uk5TAAFXmEs8zv/GMQU5sP79nDMEJYXp5H0gABVn1s9gDAZBu/v5pzoCKpHy2+6IJhaN79lUD1/f63MSC1rX7PDQUQk7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`,s=[`title`],c=[`src`],l=`primitive-devtools-anchor`,u=`primitive-devtools-btn-styles`,d=40,f=16,p=3,m=`
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./rolldown-runtime-BNMv73og.cjs"),t=require("./lib-CFnieS8T.cjs");let n=require("vue");n=e.i(n,1);let r=require("pinia"),i=require("@vueuse/core"),a=require("js-bao");var o=`data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAMAAAD04JH5AAAAAXNSR0IB2cksfwAAAAlwSFlzAAAWJQAAFiUBSVIk8AAAAutQTFRFAAAAMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzis4yvAAAAPl0Uk5TAAFXmEs8zv/GMQU5sP79nDMEJYXp5H0gABVn1s9gDAZBu/v5pzoCKpHy2+6IJhaN79lUD1/f63MSC1rX7PDQUQk7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`,s=[`title`],c=[`src`],l=`primitive-devtools-anchor`,u=`primitive-devtools-btn-styles`,d=40,f=16,p=3,m=`
2
2
  .pdt-btn {
3
3
  position: fixed;
4
4
  z-index: 2147483645;
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { a as e, c as t, i as n, n as r, o as i, r as a, s as o, t as s } from "./lib-DjHuEFvC.js";
1
+ import { a as e, c as t, i as n, n as r, o as i, r as a, s as o, t as s } from "./lib-B7ZH2Pdd.js";
2
2
  import * as c from "vue";
3
3
  import { Comment as l, Fragment as u, Teleport as d, camelize as f, cloneVNode as p, computed as m, createBlock as h, createCommentVNode as g, createElementBlock as _, createElementVNode as v, createTextVNode as y, createVNode as b, customRef as x, defineComponent as S, getCurrentInstance as C, getCurrentScope as w, guardReactiveProps as T, h as E, inject as D, isRef as O, isVNode as k, markRaw as A, mergeDefaults as j, mergeProps as M, nextTick as N, normalizeClass as P, normalizeProps as ee, normalizeStyle as F, onBeforeUnmount as te, onMounted as I, onScopeDispose as ne, onUnmounted as re, onUpdated as L, openBlock as R, provide as ie, reactive as z, ref as B, renderList as V, renderSlot as H, resolveDynamicComponent as ae, toDisplayString as U, toHandlerKey as oe, toRaw as se, toRef as ce, toRefs as le, toValue as ue, triggerRef as de, unref as W, useAttrs as fe, vModelSelect as pe, vModelText as me, vShow as he, watch as G, watchEffect as ge, watchPostEffect as _e, withCtx as K, withDirectives as ve, withKeys as ye, withMemo as be, withModifiers as q } from "vue";
4
4
  import { defineStore as xe } from "pinia";
@@ -72,7 +72,7 @@ var l = new class {
72
72
  oauthRedirectUri: e.oauthRedirectUri
73
73
  });
74
74
  let t = [];
75
- (!e.appId || e.appId === "MISSING_APP_ID") && t.push("appId"), (!e.apiUrl || e.apiUrl === "MISSING_API_URL") && t.push("apiUrl"), (!e.wsUrl || e.wsUrl === "MISSING_WS_URL") && t.push("wsUrl"), (!e.oauthRedirectUri || e.oauthRedirectUri === "MISSING_OAUTH_REDIRECT_URI") && t.push("oauthRedirectUri"), t.length > 0 && (s.error("❌ Configuration validation failed!"), s.error("Missing or invalid fields:", t), s.error("This will cause OAuth and API calls to fail."), s.error("Please check your .env file configuration.")), this.config = e;
75
+ (!e.appId || e.appId === "MISSING_APP_ID") && t.push("appId"), (!e.apiUrl || e.apiUrl === "MISSING_API_URL") && t.push("apiUrl"), (!e.wsUrl || e.wsUrl === "MISSING_WS_URL") && t.push("wsUrl"), (!e.oauthRedirectUri || e.oauthRedirectUri === "MISSING_OAUTH_REDIRECT_URI") && t.push("oauthRedirectUri"), t.length > 0 && (s.error("❌ Configuration validation failed!"), s.error("Missing or invalid fields:", t), s.error("This will cause OAuth and API calls to fail."), s.error("Identity (appId/apiUrl/wsUrl) comes from .primitive/config.json via the primitiveEnv() Vite plugin — check that the project has one and that an environment is selected ('primitive env use <name>'). Callback and logging settings come from your .env files.")), this.config = e;
76
76
  }
77
77
  getConfig() {
78
78
  return this.config;
@@ -0,0 +1 @@
1
+ let e=require("js-bao-wss-client");var t={debug:0,info:1,warn:2,error:3,none:4},n=`warn`,r=class e{#e;scope;constructor(e,t){this.#e=e,this.scope=t}get level(){return this.#e.level}set level(e){this.setLevel(e)}debug(...e){this.#t(`debug`,e)}log(...e){this.#t(`info`,e,`log`)}warn(...e){this.#t(`warn`,e)}error(...e){this.#t(`error`,e)}shouldLog(e){return t[e]>=t[this.level]}getLevel(){return this.level}setLevel(e){e in t&&(this.#e.level=e)}forScope(t){let n=t.trim(),r=n?[...this.scope,n]:this.scope;return new e(this.#e,r)}#t(e,t,n){if(!this.shouldLog(e))return;let r=n??(e===`info`?`log`:e),i=this.scope.length?`[${this.scope.join(`:`)}]`:void 0,a=i?[i,...t]:t;console[r](...a)}},i={level:n},a=new r(i,[]).forScope(`PrimitiveApp`);function o(e){i.level=e}var s=a.forScope(`JsBaoClientService`);function c(){if(typeof window>`u`)return!1;let e=window.location.hostname;return e===`localhost`||e===`127.0.0.1`||e===`[::1]`||e.endsWith(`.localhost`)||e.endsWith(`.local`)}var l=new class{client=null;clientInitializationPromise=null;config=null;initialize(e){s.debug(`Initializing with config:`,{appId:e.appId?`${e.appId.substring(0,10)}...`:`MISSING`,apiUrl:e.apiUrl,wsUrl:e.wsUrl,oauthRedirectUri:e.oauthRedirectUri});let t=[];(!e.appId||e.appId===`MISSING_APP_ID`)&&t.push(`appId`),(!e.apiUrl||e.apiUrl===`MISSING_API_URL`)&&t.push(`apiUrl`),(!e.wsUrl||e.wsUrl===`MISSING_WS_URL`)&&t.push(`wsUrl`),(!e.oauthRedirectUri||e.oauthRedirectUri===`MISSING_OAUTH_REDIRECT_URI`)&&t.push(`oauthRedirectUri`),t.length>0&&(s.error(`❌ Configuration validation failed!`),s.error(`Missing or invalid fields:`,t),s.error(`This will cause OAuth and API calls to fail.`),s.error(`Identity (appId/apiUrl/wsUrl) comes from .primitive/config.json via the primitiveEnv() Vite plugin — check that the project has one and that an environment is selected ('primitive env use <name>'). Callback and logging settings come from your .env files.`)),this.config=e}getConfig(){return this.config}async getClientAsync(){if(s.debug(`Getting client async`),this.clientInitializationPromise)return this.clientInitializationPromise;if(!this.config)throw Error(`JsBaoClientService not initialized. Call initialize() first.`);return this.clientInitializationPromise=(async()=>{s.debug(`=== INITIALIZING CLIENT ===`),s.debug(`Configuration:`,this.config);let t=await(0,e.initializeClient)(this.config);if(t.on(`status`,e=>{s.debug(`Client status changed:`,e)}),t.on(`error`,e=>{s.error(`Client error:`,e)}),this.client=t,c()&&(window.__primitiveAppClient=t,this.config?.models&&Array.isArray(this.config.models))){let e={};for(let t of this.config.models){let n=t.schema?.name;if(n){let r=n.charAt(0).toUpperCase()+n.slice(1);e[r]=t}}window.__primitiveAppModels=e,s.debug(`Registered models on window:`,Object.keys(e))}return s.debug(`=== CLIENT INITIALIZATION COMPLETE ===`),t})(),this.clientInitializationPromise}setExternalClient(e){s.debug(`Setting external client from host app`),this.client=e,this.clientInitializationPromise=Promise.resolve(e)}async clearClient(){s.debug(`Clearing client singleton...`),this.client&&(this.client.disconnect().catch(e=>{s.warn(`Background disconnect failed (this is usually harmless):`,e)}),this.client=null,this.clientInitializationPromise=null,c()&&(window.__primitiveAppClient=void 0,window.__primitiveAppModels=void 0))}};function u(e){l.initialize(e)}var d=a.forScope(`TestHarness`),f=`===TEST===`;async function p(e={}){let{networkSync:t=!1}=e,n=await l.getClientAsync(),r=`${f} ${`${Date.now()}-${Math.random().toString(36).substring(2,11)}`}`;if(t){let{metadata:e}=await n.documents.create({title:r}),t=e.documentId,{doc:i}=await n.documents.open(t);if(!i)throw Error(`[TestHarness] Failed to open synced test document`);return n.setDefaultDocumentId(t),d.debug(`Created synced test document`,{docId:t}),m.set(t,{client:n,docId:t,networkSync:!0}),{docId:t}}let{metadata:i}=await n.documents.create({title:r,localOnly:!0}),a=i.documentId,{doc:o}=await n.documents.open(a,{waitForLoad:`local`,enableNetworkSync:!1});if(!o)throw Error(`[TestHarness] Failed to open test document`);return n.setDefaultDocumentId(a),d.debug(`Created test document`,{docId:a}),m.set(a,{client:n,docId:a,networkSync:!1}),{docId:a}}var m=new Map;async function h(e){let t=m.get(e.docId);if(!t){d.warn(`destroyTestDocument called with unknown handle`,{docId:e.docId});return}m.delete(e.docId);try{t.networkSync?await t.client.documents.delete(t.docId,{forceCloseIfOpen:!0}):(await t.client.documents.close(t.docId),await t.client.documents.evict(t.docId,{force:!0}))}catch(e){d.error(`Failed to destroy test document`,{docId:t.docId,err:e})}}async function g(){try{let e=await l.getClientAsync();d.debug(`[deleteAllTestDocuments] Starting cleanup...`),await e.syncMetadata({scope:`all`});let t=(await e.me.ownedDocuments()).filter(e=>e.title?.startsWith(f));if(t.length===0)return 0;d.debug(`[deleteAllTestDocuments] Found test documents to delete`,{count:t.length});let n=0;for(let r of t)try{e.documents.isOpen(r.documentId)&&await e.documents.close(r.documentId),await e.documents.evict(r.documentId,{force:!0}),n++}catch(e){d.error(`[deleteAllTestDocuments] Failed to delete test document`,{documentId:r.documentId,err:e})}return d.debug(`[deleteAllTestDocuments] Cleanup complete`,{evictedCount:n}),n}catch(e){return d.error(`[deleteAllTestDocuments] Top-level error during cleanup`,{err:e}),0}}function _(e){return e.flatMap(e=>e.tests.map(t=>({id:t.id,name:t.name,group:e.name,environment:t.environment??e.environment,run:t.run})))}Object.defineProperty(exports,"a",{enumerable:!0,get:function(){return u}}),Object.defineProperty(exports,"c",{enumerable:!0,get:function(){return o}}),Object.defineProperty(exports,"i",{enumerable:!0,get:function(){return h}}),Object.defineProperty(exports,"n",{enumerable:!0,get:function(){return p}}),Object.defineProperty(exports,"o",{enumerable:!0,get:function(){return l}}),Object.defineProperty(exports,"r",{enumerable:!0,get:function(){return g}}),Object.defineProperty(exports,"s",{enumerable:!0,get:function(){return a}}),Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return _}});
@@ -72,15 +72,20 @@
72
72
  * const docs = await client.me.ownedDocuments();
73
73
  * ```
74
74
  *
75
- * ### Environment Variables
75
+ * ### Where the configuration comes from
76
76
  *
77
- * Set these in your `.env` file:
77
+ * `VITE_APP_ID`, `VITE_API_URL`, `VITE_WS_URL` and `VITE_APP_NAME` are NOT
78
+ * authored by hand. They are filled in at build time by the `primitiveEnv()`
79
+ * plugin (`primitive-app/vite`) from the Primitive environment selected in
80
+ * `.primitive/config.json` — the one place a backend URL and app ID are typed.
81
+ * Choose the environment with `primitive env use <name>`, or `--primitive-env`
82
+ * on a deploy; `VITE_WS_URL` is derived from the API URL by scheme swap.
83
+ *
84
+ * Your `.env` files carry app behavior only:
78
85
  *
79
86
  * ```
80
- * VITE_APP_ID=your-app-id
81
- * VITE_API_URL=https://api.primitive.dev
82
- * VITE_WS_URL=wss://ws.primitive.dev
83
- * VITE_OAUTH_REDIRECT_URI=http://localhost:5173/auth/callback
87
+ * VITE_OAUTH_REDIRECT_URI=/oauth/callback
88
+ * VITE_ENABLE_AUTH_PROXY=false
84
89
  * VITE_LOG_LEVEL=warn
85
90
  * ```
86
91
  */
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../rolldown-runtime-BNMv73og.cjs"),t=require("../lib-CgtC-xno.cjs");let n=require("js-bao-wss-client"),r=require("node:fs"),i=require("node:path");i=e.i(i,1);let a=require("vitest");var o=`000000`;function s(e,t,n){if(e)return e;throw Error(`[primitive-app/testing] Missing ${t}: pass it to registerPrimitiveTests() or set the ${n} environment variable.`)}function c(){let e=i.default.resolve(process.cwd(),`public/sql-wasm.wasm`);if(!(0,r.existsSync)(e)){console.warn(`[primitive-app/testing] ${e} not found — using the client's default database config. If client init fails to load sql-wasm.wasm, pass clientOptions.databaseConfig explicitly.`);return}return{type:`sqljs`,options:{locateFile:()=>e}}}async function l(e){let t=[],n=[];for(let[r,i]of Object.entries(e))try{let e=(await i())?.default;Array.isArray(e)?t.push(...e):e&&typeof e==`object`&&`tests`in e?t.push(e):n.push({file:r,error:Error(`Default export is not a TestGroup (expected { name, tests }).`)})}catch(e){n.push({file:r,error:e})}return{groups:t,failures:n}}async function u(e){let{models:r,testModules:i,otpCode:u=o,testTimeoutMs:d=6e4,clientOptions:f,cleanupTestDocuments:p=!0,failBelowFullScore:m=!0}=e,h=s(e.appId??process.env.VITE_APP_ID,`appId`,`VITE_APP_ID`),g=s(e.apiUrl??process.env.VITE_API_URL,`apiUrl`,`VITE_API_URL`),_=s(e.wsUrl??process.env.VITE_WS_URL,`wsUrl`,`VITE_WS_URL`),v=s(e.email??process.env.PRIMITIVE_TEST_EMAIL,`email`,`PRIMITIVE_TEST_EMAIL`),{groups:y,failures:b}=await l(i);for(let e of b)(0,a.describe)(e.file,()=>{(0,a.it)(`loads without errors`,()=>{throw e.error instanceof Error?e.error:Error(String(e.error))})});if(y.length===0){b.length===0&&console.warn(`[primitive-app/testing] No *.primitive-test.ts groups found — nothing to register.`);return}let x;(0,a.beforeAll)(async()=>{x=await(0,n.initializeClient)({apiUrl:g,wsUrl:_,appId:h,models:r,storageConfig:{type:`auto`},databaseConfig:c(),...f});try{await x.otpVerify(v,u)}catch(e){throw Error(`[primitive-app/testing] OTP sign-in failed for ${v}. The test bypass requires the app's testAccountBaseEmails whitelist to cover this address (and invite-only/domain/waitlist apps reject provisioning like any signup). Underlying error: ${e instanceof Error?e.message:String(e)}`)}t.o.initialize({apiUrl:g,wsUrl:_,appId:h,models:r,oauthRedirectUri:f?.oauthRedirectUri??process.env.VITE_OAUTH_REDIRECT_URI??`http://localhost/oauth/callback-unused-in-node-tests`,...f}),t.o.setExternalClient(x)},12e4),(0,a.afterAll)(async()=>{if(x){if(p)try{await t.r()}catch(e){console.warn(`[primitive-app/testing] Test-document cleanup failed:`,e)}await x.disconnect()}},6e4);for(let e of y)(0,a.describe)(e.name,()=>{for(let t of e.tests){if((t.environment??e.environment)===`browser`){a.it.skip(t.name,()=>{});continue}(0,a.it)(t.name,async()=>{let e=await t.run(e=>console.log(`[${t.id}] ${e}`));e&&console.log(`[${t.id}] ${e}`);let n=e?.match(/^(\d+)\/(\d+)\s*\((\d+(?:\.\d+)?)%\)/);if(m&&n&&Number(n[1])!==Number(n[2]))throw Error(`Scored below full marks: ${e}`)},d)}})}exports.PRIMITIVE_TEST_OTP_CODE=o,exports.registerPrimitiveTests=u;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../rolldown-runtime-BNMv73og.cjs"),t=require("../lib-CFnieS8T.cjs");let n=require("js-bao-wss-client"),r=require("node:fs"),i=require("node:path");i=e.i(i,1);let a=require("vitest");var o=`000000`;function s(e,t,n){if(e)return e;throw Error(`[primitive-app/testing] Missing ${t}: pass it to registerPrimitiveTests() or set the ${n} environment variable.`)}function c(){let e=i.default.resolve(process.cwd(),`public/sql-wasm.wasm`);if(!(0,r.existsSync)(e)){console.warn(`[primitive-app/testing] ${e} not found — using the client's default database config. If client init fails to load sql-wasm.wasm, pass clientOptions.databaseConfig explicitly.`);return}return{type:`sqljs`,options:{locateFile:()=>e}}}async function l(e){let t=[],n=[];for(let[r,i]of Object.entries(e))try{let e=(await i())?.default;Array.isArray(e)?t.push(...e):e&&typeof e==`object`&&`tests`in e?t.push(e):n.push({file:r,error:Error(`Default export is not a TestGroup (expected { name, tests }).`)})}catch(e){n.push({file:r,error:e})}return{groups:t,failures:n}}async function u(e){let{models:r,testModules:i,otpCode:u=o,testTimeoutMs:d=6e4,clientOptions:f,cleanupTestDocuments:p=!0,failBelowFullScore:m=!0}=e,h=s(e.appId??process.env.VITE_APP_ID,`appId`,`VITE_APP_ID`),g=s(e.apiUrl??process.env.VITE_API_URL,`apiUrl`,`VITE_API_URL`),_=s(e.wsUrl??process.env.VITE_WS_URL,`wsUrl`,`VITE_WS_URL`),v=s(e.email??process.env.PRIMITIVE_TEST_EMAIL,`email`,`PRIMITIVE_TEST_EMAIL`),{groups:y,failures:b}=await l(i);for(let e of b)(0,a.describe)(e.file,()=>{(0,a.it)(`loads without errors`,()=>{throw e.error instanceof Error?e.error:Error(String(e.error))})});if(y.length===0){b.length===0&&console.warn(`[primitive-app/testing] No *.primitive-test.ts groups found — nothing to register.`);return}let x;(0,a.beforeAll)(async()=>{x=await(0,n.initializeClient)({apiUrl:g,wsUrl:_,appId:h,models:r,storageConfig:{type:`auto`},databaseConfig:c(),...f});try{await x.otpVerify(v,u)}catch(e){throw Error(`[primitive-app/testing] OTP sign-in failed for ${v}. The test bypass requires the app's testAccountBaseEmails whitelist to cover this address (and invite-only/domain/waitlist apps reject provisioning like any signup). Underlying error: ${e instanceof Error?e.message:String(e)}`)}t.o.initialize({apiUrl:g,wsUrl:_,appId:h,models:r,oauthRedirectUri:f?.oauthRedirectUri??process.env.VITE_OAUTH_REDIRECT_URI??`http://localhost/oauth/callback-unused-in-node-tests`,...f}),t.o.setExternalClient(x)},12e4),(0,a.afterAll)(async()=>{if(x){if(p)try{await t.r()}catch(e){console.warn(`[primitive-app/testing] Test-document cleanup failed:`,e)}await x.disconnect()}},6e4);for(let e of y)(0,a.describe)(e.name,()=>{for(let t of e.tests){if((t.environment??e.environment)===`browser`){a.it.skip(t.name,()=>{});continue}(0,a.it)(t.name,async()=>{let e=await t.run(e=>console.log(`[${t.id}] ${e}`));e&&console.log(`[${t.id}] ${e}`);let n=e?.match(/^(\d+)\/(\d+)\s*\((\d+(?:\.\d+)?)%\)/);if(m&&n&&Number(n[1])!==Number(n[2]))throw Error(`Scored below full marks: ${e}`)},d)}})}exports.PRIMITIVE_TEST_OTP_CODE=o,exports.registerPrimitiveTests=u;
@@ -20,11 +20,13 @@ export interface RegisterPrimitiveTestsOptions {
20
20
  /** WebSocket endpoint URL. Defaults to `process.env.VITE_WS_URL`. */
21
21
  wsUrl?: string;
22
22
  /**
23
- * Sign-in email for the OTP test bypass. Must resolve to the app's
24
- * `testAccountBaseEmails` whitelist (e.g. `you+primitivetest-ci@domain.com`
25
- * when `you@domain.com` is whitelisted). Use a stable suffix per CI project
26
- * so the find-or-create provisioner reuses one test user across runs.
27
- * Defaults to `process.env.PRIMITIVE_TEST_EMAIL`.
23
+ * Sign-in email for the OTP test bypass. Must be a `+primitivetest`
24
+ * derivative of a base on the app's `testAccountBaseEmails` whitelist
25
+ * (e.g. `you+primitivetest-ci@domain.com` when `you@domain.com` is
26
+ * whitelisted) the bare base address itself is never a test account and
27
+ * always fails sign-in. Use a stable suffix per CI project so the
28
+ * find-or-create provisioner reuses one test user across runs. Defaults to
29
+ * `process.env.PRIMITIVE_TEST_EMAIL`.
28
30
  */
29
31
  email?: string;
30
32
  /** OTP code to verify with. Defaults to the "000000" test bypass. */
@@ -1,4 +1,4 @@
1
- import { o as e, r as t } from "../lib-DjHuEFvC.js";
1
+ import { o as e, r as t } from "../lib-B7ZH2Pdd.js";
2
2
  import { initializeClient as n } from "js-bao-wss-client";
3
3
  import { existsSync as r } from "node:fs";
4
4
  import i from "node:path";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "primitive-app",
3
- "version": "3.1.0-alpha.9",
3
+ "version": "3.2.0-alpha.0",
4
4
  "license": "UNLICENSED",
5
5
  "author": "Primitive LLC",
6
6
  "type": "module",
@@ -50,7 +50,7 @@
50
50
  "lint": "eslint . --fix --cache",
51
51
  "format:check": "prettier --check src/",
52
52
  "format": "prettier --write src/",
53
- "codegen": "npx js-bao-codegen && pnpm format"
53
+ "codegen": "npx js-bao-codegen"
54
54
  },
55
55
  "dependencies": {
56
56
  "@internationalized/date": "^3.12.2",
@@ -1 +0,0 @@
1
- let e=require("js-bao-wss-client");var t={debug:0,info:1,warn:2,error:3,none:4},n=`warn`,r=class e{#e;scope;constructor(e,t){this.#e=e,this.scope=t}get level(){return this.#e.level}set level(e){this.setLevel(e)}debug(...e){this.#t(`debug`,e)}log(...e){this.#t(`info`,e,`log`)}warn(...e){this.#t(`warn`,e)}error(...e){this.#t(`error`,e)}shouldLog(e){return t[e]>=t[this.level]}getLevel(){return this.level}setLevel(e){e in t&&(this.#e.level=e)}forScope(t){let n=t.trim(),r=n?[...this.scope,n]:this.scope;return new e(this.#e,r)}#t(e,t,n){if(!this.shouldLog(e))return;let r=n??(e===`info`?`log`:e),i=this.scope.length?`[${this.scope.join(`:`)}]`:void 0,a=i?[i,...t]:t;console[r](...a)}},i={level:n},a=new r(i,[]).forScope(`PrimitiveApp`);function o(e){i.level=e}var s=a.forScope(`JsBaoClientService`);function c(){if(typeof window>`u`)return!1;let e=window.location.hostname;return e===`localhost`||e===`127.0.0.1`||e===`[::1]`||e.endsWith(`.localhost`)||e.endsWith(`.local`)}var l=new class{client=null;clientInitializationPromise=null;config=null;initialize(e){s.debug(`Initializing with config:`,{appId:e.appId?`${e.appId.substring(0,10)}...`:`MISSING`,apiUrl:e.apiUrl,wsUrl:e.wsUrl,oauthRedirectUri:e.oauthRedirectUri});let t=[];(!e.appId||e.appId===`MISSING_APP_ID`)&&t.push(`appId`),(!e.apiUrl||e.apiUrl===`MISSING_API_URL`)&&t.push(`apiUrl`),(!e.wsUrl||e.wsUrl===`MISSING_WS_URL`)&&t.push(`wsUrl`),(!e.oauthRedirectUri||e.oauthRedirectUri===`MISSING_OAUTH_REDIRECT_URI`)&&t.push(`oauthRedirectUri`),t.length>0&&(s.error(`❌ Configuration validation failed!`),s.error(`Missing or invalid fields:`,t),s.error(`This will cause OAuth and API calls to fail.`),s.error(`Please check your .env file configuration.`)),this.config=e}getConfig(){return this.config}async getClientAsync(){if(s.debug(`Getting client async`),this.clientInitializationPromise)return this.clientInitializationPromise;if(!this.config)throw Error(`JsBaoClientService not initialized. Call initialize() first.`);return this.clientInitializationPromise=(async()=>{s.debug(`=== INITIALIZING CLIENT ===`),s.debug(`Configuration:`,this.config);let t=await(0,e.initializeClient)(this.config);if(t.on(`status`,e=>{s.debug(`Client status changed:`,e)}),t.on(`error`,e=>{s.error(`Client error:`,e)}),this.client=t,c()&&(window.__primitiveAppClient=t,this.config?.models&&Array.isArray(this.config.models))){let e={};for(let t of this.config.models){let n=t.schema?.name;if(n){let r=n.charAt(0).toUpperCase()+n.slice(1);e[r]=t}}window.__primitiveAppModels=e,s.debug(`Registered models on window:`,Object.keys(e))}return s.debug(`=== CLIENT INITIALIZATION COMPLETE ===`),t})(),this.clientInitializationPromise}setExternalClient(e){s.debug(`Setting external client from host app`),this.client=e,this.clientInitializationPromise=Promise.resolve(e)}async clearClient(){s.debug(`Clearing client singleton...`),this.client&&(this.client.disconnect().catch(e=>{s.warn(`Background disconnect failed (this is usually harmless):`,e)}),this.client=null,this.clientInitializationPromise=null,c()&&(window.__primitiveAppClient=void 0,window.__primitiveAppModels=void 0))}};function u(e){l.initialize(e)}var d=a.forScope(`TestHarness`),f=`===TEST===`;async function p(e={}){let{networkSync:t=!1}=e,n=await l.getClientAsync(),r=`${f} ${`${Date.now()}-${Math.random().toString(36).substring(2,11)}`}`;if(t){let{metadata:e}=await n.documents.create({title:r}),t=e.documentId,{doc:i}=await n.documents.open(t);if(!i)throw Error(`[TestHarness] Failed to open synced test document`);return n.setDefaultDocumentId(t),d.debug(`Created synced test document`,{docId:t}),m.set(t,{client:n,docId:t,networkSync:!0}),{docId:t}}let{metadata:i}=await n.documents.create({title:r,localOnly:!0}),a=i.documentId,{doc:o}=await n.documents.open(a,{waitForLoad:`local`,enableNetworkSync:!1});if(!o)throw Error(`[TestHarness] Failed to open test document`);return n.setDefaultDocumentId(a),d.debug(`Created test document`,{docId:a}),m.set(a,{client:n,docId:a,networkSync:!1}),{docId:a}}var m=new Map;async function h(e){let t=m.get(e.docId);if(!t){d.warn(`destroyTestDocument called with unknown handle`,{docId:e.docId});return}m.delete(e.docId);try{t.networkSync?await t.client.documents.delete(t.docId,{forceCloseIfOpen:!0}):(await t.client.documents.close(t.docId),await t.client.documents.evict(t.docId,{force:!0}))}catch(e){d.error(`Failed to destroy test document`,{docId:t.docId,err:e})}}async function g(){try{let e=await l.getClientAsync();d.debug(`[deleteAllTestDocuments] Starting cleanup...`),await e.syncMetadata({scope:`all`});let t=(await e.me.ownedDocuments()).filter(e=>e.title?.startsWith(f));if(t.length===0)return 0;d.debug(`[deleteAllTestDocuments] Found test documents to delete`,{count:t.length});let n=0;for(let r of t)try{e.documents.isOpen(r.documentId)&&await e.documents.close(r.documentId),await e.documents.evict(r.documentId,{force:!0}),n++}catch(e){d.error(`[deleteAllTestDocuments] Failed to delete test document`,{documentId:r.documentId,err:e})}return d.debug(`[deleteAllTestDocuments] Cleanup complete`,{evictedCount:n}),n}catch(e){return d.error(`[deleteAllTestDocuments] Top-level error during cleanup`,{err:e}),0}}function _(e){return e.flatMap(e=>e.tests.map(t=>({id:t.id,name:t.name,group:e.name,environment:t.environment??e.environment,run:t.run})))}Object.defineProperty(exports,"a",{enumerable:!0,get:function(){return u}}),Object.defineProperty(exports,"c",{enumerable:!0,get:function(){return o}}),Object.defineProperty(exports,"i",{enumerable:!0,get:function(){return h}}),Object.defineProperty(exports,"n",{enumerable:!0,get:function(){return p}}),Object.defineProperty(exports,"o",{enumerable:!0,get:function(){return l}}),Object.defineProperty(exports,"r",{enumerable:!0,get:function(){return g}}),Object.defineProperty(exports,"s",{enumerable:!0,get:function(){return a}}),Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return _}});