primitive-app 3.2.0-alpha.0 → 3.2.0-alpha.2

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.
@@ -33,7 +33,12 @@ export declare class PrimitiveEnvError extends Error {
33
33
  }
34
34
  export interface CoreEnvironmentEntry {
35
35
  apiUrl: string;
36
- appId?: string;
36
+ /**
37
+ * The one app this environment names (#3152). Required: selecting an
38
+ * environment selects exactly one app, so there is no per-machine "current
39
+ * app" for a second reader to disagree with.
40
+ */
41
+ appId: string;
37
42
  appName?: string;
38
43
  /**
39
44
  * The app's web counterpart for this environment, as a normalized origin
@@ -75,6 +80,21 @@ export interface CoreEnvironmentEntry {
75
80
  * two agree.
76
81
  */
77
82
  export declare function normalizeWebOrigin(value: unknown): string | undefined;
83
+ /**
84
+ * The one remedy for an environment that names no app (#3152), shared by every
85
+ * reader that can hit it: this core, the CLI's `validateProjectConfig`, the
86
+ * Swift template's pre-build script, and the Vue template's cf-deploy.
87
+ *
88
+ * Two constraints on the wording, both test-asserted:
89
+ *
90
+ * - it never says `primitive env add <name>`, which REFUSES an environment
91
+ * name that already exists — the shape this error is always about;
92
+ * - it names no command the failure itself blocks. The CLI validates the
93
+ * project config before any command parses, so every `primitive` command
94
+ * run inside the broken project fails with this same error. The only
95
+ * invocation it may offer is one it says out loud to run elsewhere.
96
+ */
97
+ export declare function missingAppIdMessage(name: string, configPath?: string): string;
78
98
  export interface CoreProjectConfig {
79
99
  version: number;
80
100
  defaultEnvironment?: string;
@@ -87,7 +107,8 @@ export interface ResolvedPrimitiveEnv {
87
107
  apiUrl: string;
88
108
  /** Derived from apiUrl by scheme swap — never authored separately. */
89
109
  wsUrl: string;
90
- appId?: string;
110
+ /** The app this environment names. Always present — see #3152. */
111
+ appId: string;
91
112
  appName?: string;
92
113
  /** The app's web counterpart for the SELECTED environment, if it has one. */
93
114
  webUrl?: string;
@@ -106,11 +127,6 @@ export interface ResolveOptions {
106
127
  explicitEnvName?: string | null;
107
128
  /** Use this config file instead of discovering one. */
108
129
  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
130
  }
115
131
  /**
116
132
  * Derives the WebSocket URL from the API URL. Every apiUrl/wsUrl pair in
@@ -65,6 +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(!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;
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,t){return`Environment "${e}" in ${t?`${h} (${t})`:h} has no "appId". Every environment names exactly one app — add "appId": "<app-id>" to that environment in ${h}. Find the app id in the Primitive admin UI, with 'primitive apps list' run from a directory outside this project, or as the residual "currentAppId" in .primitive/credentials.json if this environment was ever pointed at an app by the retired per-machine app selection.`}function b(e){if(typeof e==`string`)return e.trim()||void 0}function x(e){return e.startsWith(`https://`)?`wss://`+e.slice(8):e.startsWith(`http://`)?`ws://`+e.slice(7):e}function S(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 C(e){let t=(0,a.dirname)(e);return(0,a.basename)(t)===`.primitive`?(0,a.dirname)(t):t}function w(e){return(0,a.join)((0,a.dirname)(e),m)}function T(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);let i=b(r.appId);if(!i)throw new _(`missing-app-id`,y(t,e),e);a[t]={apiUrl:r.apiUrl.replace(/\/$/,``),appId:i,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 E(e){let n=w(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 D(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 O(e={}){let t=e.env??process.env,n=e.configPath??S({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=T(n),{name:i,source:a}=D(r,{explicitEnvName:e.explicitEnvName||null,envVarName:t.PRIMITIVE_ENV||null,localSelection:E(n),configPath:n}),o=r.environments[i];return{name:i,apiUrl:o.apiUrl,wsUrl:x(o.apiUrl),appId:o.appId,appName:o.appName,webUrl:o.webUrl,description:o.description,source:a,configPath:n,localStatePath:w(n),projectRoot:C(n)}}var k=[`VITE_APP_ID`,`VITE_API_URL`,`VITE_WS_URL`,`VITE_APP_NAME`],A=`VITE_EXPECTED_PRIMITIVE_ENV`,j=new Map;function M(e,t){let n=process.env[e];n!==void 0&&n!==``&&j.get(e)!==n||(process.env[e]=t,j.set(e,t))}function N(e={}){return O({cwd:e.root,env:e.env,explicitEnvName:e.primitiveEnv??null})}function P(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 F(e,t){let n=e?.envDir;return n===!1?null:typeof n==`string`&&n!==``?(0,a.resolve)(t,n):t}function I(e,t){let n=process.env[A];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=V((0,a.join)(t,n))[A];if(e!==void 0)return{value:e,where:n}}return null}function L(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} (${A} 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 ${A} 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 R(e,t,n){let r=I(e,t);if(r&&r.value!==``&&r.value!==n.name)throw Error(L(e,r,n))}function z(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=B(u,l),f={};for(let e of k){let t=process.env[e],n=t!==void 0&&j.get(e)!==t?t:d[e];n!==void 0&&n!==``&&(f[e]=n)}let p;try{p=O({cwd:l,explicitEnvName:e.primitiveEnv??null})}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=F(s,l);R(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 k){if(f[e]!==void 0){g.push(e);continue}let t=m[e];t!==void 0&&(h[`import.meta.env.${e}`]=JSON.stringify(t),M(e,t))}return M(`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(P(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=F(e,o.root),r=e?.mode??o.mode;n===o.dir&&r===o.mode||(R(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 B(e,t){let n=[`.env`,`.env.local`,`.env.${e}`,`.env.${e}.local`],r={};for(let e of n)Object.assign(r,V((0,a.join)(t,e)));return r}function V(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=z,exports.resolvePrimitiveEnv=N;
@@ -82,10 +82,16 @@ function x(e) {
82
82
  }
83
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
84
  }
85
- function S(e) {
85
+ function S(e, t) {
86
+ return `Environment "${e}" in ${t ? `${v} (${t})` : v} has no "appId". Every environment names exactly one app — add "appId": "<app-id>" to that environment in ${v}. Find the app id in the Primitive admin UI, with 'primitive apps list' run from a directory outside this project, or as the residual "currentAppId" in .primitive/credentials.json if this environment was ever pointed at an app by the retired per-machine app selection.`;
87
+ }
88
+ function C(e) {
89
+ if (typeof e == "string") return e.trim() || void 0;
90
+ }
91
+ function w(e) {
86
92
  return e.startsWith("https://") ? "wss://" + e.slice(8) : e.startsWith("http://") ? "ws://" + e.slice(7) : e;
87
93
  }
88
- function C(t = {}) {
94
+ function T(t = {}) {
89
95
  let n = (t.env ?? process.env).PRIMITIVE_PROJECT_CONFIG;
90
96
  if (n) {
91
97
  let t = c(n);
@@ -100,14 +106,14 @@ function C(t = {}) {
100
106
  r = n;
101
107
  }
102
108
  }
103
- function w(e) {
109
+ function E(e) {
104
110
  let t = o(e);
105
111
  return a(t) === ".primitive" ? o(t) : t;
106
112
  }
107
- function T(e) {
113
+ function D(e) {
108
114
  return s(o(e), _);
109
115
  }
110
- function E(n) {
116
+ function O(n) {
111
117
  if (!e(n)) throw new b("missing-config", `No ${v} found at ${n}. Run 'primitive init' to create one.`, n);
112
118
  let r;
113
119
  try {
@@ -131,9 +137,11 @@ function E(n) {
131
137
  if (!t || typeof t != "object" || Array.isArray(t)) throw new b("malformed-config", `Environment "${e}" must be an object in ${v} (${n}).`, n);
132
138
  let r = t;
133
139
  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);
140
+ let i = C(r.appId);
141
+ if (!i) throw new b("missing-app-id", S(e, n), n);
134
142
  o[e] = {
135
143
  apiUrl: r.apiUrl.replace(/\/$/, ""),
136
- appId: typeof r.appId == "string" ? r.appId : void 0,
144
+ appId: i,
137
145
  appName: typeof r.appName == "string" ? r.appName : void 0,
138
146
  webUrl: x(r.webUrl),
139
147
  description: typeof r.description == "string" ? r.description : void 0
@@ -145,8 +153,8 @@ function E(n) {
145
153
  environments: o
146
154
  };
147
155
  }
148
- function D(n) {
149
- let r = T(n);
156
+ function k(n) {
157
+ let r = D(n);
150
158
  if (!e(r)) return null;
151
159
  let i = (e) => {
152
160
  throw new b("corrupt-local-state", `${y} (${r}) is unreadable: ${e}. Delete the file or re-run 'primitive env use <name>'.`, r);
@@ -160,7 +168,7 @@ function D(n) {
160
168
  let o = a.selectedEnvironment;
161
169
  return o == null || o === "" ? null : typeof o == "string" ? o : i("\"selectedEnvironment\" must be a string");
162
170
  }
163
- function O(e, t) {
171
+ function A(e, t) {
164
172
  let n = Object.keys(e.environments), r = n.join(", ") || "(none)", i = t.configPath ? `${v} (${t.configPath})` : v, a = (n, a, o) => {
165
173
  if (!e.environments[n]) throw new b("unknown-environment", `Environment "${n}" is not defined in ${i} (selected ${o}). Available: ${r}`, t.configPath);
166
174
  return {
@@ -178,54 +186,52 @@ function O(e, t) {
178
186
  };
179
187
  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
188
  }
181
- function k(e = {}) {
182
- let t = e.env ?? process.env, n = e.configPath ?? C({
189
+ function j(e = {}) {
190
+ let t = e.env ?? process.env, n = e.configPath ?? T({
183
191
  cwd: e.cwd,
184
192
  env: t
185
193
  });
186
194
  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, {
195
+ let r = O(n), { name: i, source: a } = A(r, {
188
196
  explicitEnvName: e.explicitEnvName || null,
189
197
  envVarName: t.PRIMITIVE_ENV || null,
190
- localSelection: D(n),
198
+ localSelection: k(n),
191
199
  configPath: n
192
200
  }), 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
201
  return {
195
202
  name: i,
196
203
  apiUrl: o.apiUrl,
197
- wsUrl: S(o.apiUrl),
204
+ wsUrl: w(o.apiUrl),
198
205
  appId: o.appId,
199
206
  appName: o.appName,
200
207
  webUrl: o.webUrl,
201
208
  description: o.description,
202
209
  source: a,
203
210
  configPath: n,
204
- localStatePath: T(n),
205
- projectRoot: w(n)
211
+ localStatePath: D(n),
212
+ projectRoot: E(n)
206
213
  };
207
214
  }
208
215
  //#endregion
209
216
  //#region src/dev-tools/vite-plugin/primitive-env.ts
210
- var A = [
217
+ var M = [
211
218
  "VITE_APP_ID",
212
219
  "VITE_API_URL",
213
220
  "VITE_WS_URL",
214
221
  "VITE_APP_NAME"
215
- ], j = "VITE_EXPECTED_PRIMITIVE_ENV", M = /* @__PURE__ */ new Map();
216
- function N(e, t) {
222
+ ], N = "VITE_EXPECTED_PRIMITIVE_ENV", P = /* @__PURE__ */ new Map();
223
+ function F(e, t) {
217
224
  let n = process.env[e];
218
- n !== void 0 && n !== "" && M.get(e) !== n || (process.env[e] = t, M.set(e, t));
225
+ n !== void 0 && n !== "" && P.get(e) !== n || (process.env[e] = t, P.set(e, t));
219
226
  }
220
- function P(e = {}) {
221
- return k({
227
+ function I(e = {}) {
228
+ return j({
222
229
  cwd: e.root,
223
230
  env: e.env,
224
- explicitEnvName: e.primitiveEnv ?? null,
225
- requireAppId: e.requireAppId
231
+ explicitEnvName: e.primitiveEnv ?? null
226
232
  });
227
233
  }
228
- function F(e, t, n) {
234
+ function L(e, t, n) {
229
235
  let r = (e) => n.includes(e) ? " (overridden)" : "", i = [
230
236
  `[primitive-env] Primitive environment: ${e.name} (selected: ${e.source})`,
231
237
  `[primitive-env] apiUrl: ${t.apiUrl}${r("VITE_API_URL")}`,
@@ -236,12 +242,12 @@ function F(e, t, n) {
236
242
  ];
237
243
  return n.length > 0 && i.push(`[primitive-env] overridden by the environment: ${n.join(", ")}`), i.join("\n");
238
244
  }
239
- function I(e, t) {
245
+ function R(e, t) {
240
246
  let n = e?.envDir;
241
247
  return n === !1 ? null : typeof n == "string" && n !== "" ? c(t, n) : t;
242
248
  }
243
- function L(e, t) {
244
- let n = process.env[j];
249
+ function z(e, t) {
250
+ let n = process.env[N];
245
251
  if (n !== void 0 && n !== "") return {
246
252
  value: n,
247
253
  where: "the process environment"
@@ -253,7 +259,7 @@ function L(e, t) {
253
259
  ".env.local",
254
260
  ".env"
255
261
  ]) {
256
- let e = H(s(t, n))[j];
262
+ let e = W(s(t, n))[N];
257
263
  if (e !== void 0) return {
258
264
  value: e,
259
265
  where: n
@@ -261,48 +267,47 @@ function L(e, t) {
261
267
  }
262
268
  return null;
263
269
  }
264
- function R(e, t, n) {
270
+ function B(e, t, n) {
265
271
  return [
266
272
  `[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})`,
273
+ `[primitive-env] expected: ${t.value} (${N} in ${t.where})`,
268
274
  `[primitive-env] resolved: ${n.name} (selected: ${n.source})`,
269
275
  `[primitive-env] config: ${n.configPath}`,
270
276
  "",
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}.`,
277
+ `Run the pair you meant — PRIMITIVE_ENV=${t.value} <command> --mode ${e}, or 'primitive env use ${t.value}' — or change/remove ${N} in ${t.where}.`,
272
278
  "",
273
279
  `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
280
  ].join("\n");
275
281
  }
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));
282
+ function V(e, t, n) {
283
+ let r = z(e, t);
284
+ if (r && r.value !== "" && r.value !== n.name) throw Error(B(e, r, n));
279
285
  }
280
- function B(e = {}) {
286
+ function H(e = {}) {
281
287
  let t = null, n = null, r, i = [], a = !1, o = null;
282
288
  return {
283
289
  name: "primitive-env",
284
290
  enforce: "pre",
285
291
  config(s, c) {
286
292
  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];
293
+ let l = s?.root ?? process.cwd(), u = c?.mode ?? "development", d = U(u, l), f = {};
294
+ for (let e of M) {
295
+ let t = process.env[e], n = t !== void 0 && P.get(e) !== t ? t : d[e];
290
296
  n !== void 0 && n !== "" && (f[e] = n);
291
297
  }
292
298
  let p;
293
299
  try {
294
- p = k({
300
+ p = j({
295
301
  cwd: l,
296
- explicitEnvName: e.primitiveEnv ?? null,
297
- requireAppId: !f.VITE_APP_ID
302
+ explicitEnvName: e.primitiveEnv ?? null
298
303
  });
299
304
  } catch (e) {
300
305
  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
306
  throw e;
302
307
  }
303
308
  if (!(f.VITE_APP_ID && f.VITE_API_URL)) {
304
- let e = I(s, l);
305
- z(u, e, p), o = {
309
+ let e = R(s, l);
310
+ V(u, e, p), o = {
306
311
  root: l,
307
312
  mode: u,
308
313
  dir: e
@@ -315,25 +320,25 @@ function B(e = {}) {
315
320
  VITE_WS_URL: p.wsUrl,
316
321
  VITE_APP_NAME: p.appName
317
322
  }, h = { "import.meta.env.VITE_PRIMITIVE_ENV": JSON.stringify(p.name) }, g = [];
318
- for (let e of A) {
323
+ for (let e of M) {
319
324
  if (f[e] !== void 0) {
320
325
  g.push(e);
321
326
  continue;
322
327
  }
323
328
  let t = m[e];
324
- t !== void 0 && (h[`import.meta.env.${e}`] = JSON.stringify(t), N(e, t));
329
+ t !== void 0 && (h[`import.meta.env.${e}`] = JSON.stringify(t), F(e, t));
325
330
  }
326
- return N("VITE_PRIMITIVE_ENV", p.name), i = g, n = {
331
+ return F("VITE_PRIMITIVE_ENV", p.name), i = g, n = {
327
332
  apiUrl: f.VITE_API_URL ?? p.apiUrl,
328
333
  wsUrl: f.VITE_WS_URL ?? p.wsUrl,
329
334
  appId: f.VITE_APP_ID ?? p.appId,
330
335
  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 };
336
+ }, e.logResolved !== !1 && console.log(L(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
337
  },
333
338
  configResolved(e) {
334
339
  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 = {
340
+ let n = R(e, o.root), r = e?.mode ?? o.mode;
341
+ n === o.dir && r === o.mode || (V(r, n, t), o = {
337
342
  root: o.root,
338
343
  mode: r,
339
344
  dir: n
@@ -356,17 +361,17 @@ function B(e = {}) {
356
361
  }
357
362
  };
358
363
  }
359
- function V(e, t) {
364
+ function U(e, t) {
360
365
  let n = [
361
366
  ".env",
362
367
  ".env.local",
363
368
  `.env.${e}`,
364
369
  `.env.${e}.local`
365
370
  ], r = {};
366
- for (let e of n) Object.assign(r, H(s(t, e)));
371
+ for (let e of n) Object.assign(r, W(s(t, e)));
367
372
  return r;
368
373
  }
369
- function H(n) {
374
+ function W(n) {
370
375
  if (!e(n)) return {};
371
376
  let r = {};
372
377
  for (let e of t(n, "utf-8").split(/\r?\n/)) {
@@ -380,4 +385,4 @@ function H(n) {
380
385
  return r;
381
386
  }
382
387
  //#endregion
383
- export { b as PrimitiveEnvError, m as primitiveDevTools, B as primitiveEnv, P as resolvePrimitiveEnv };
388
+ export { b as PrimitiveEnvError, m as primitiveDevTools, H as primitiveEnv, I as resolvePrimitiveEnv };
@@ -45,8 +45,6 @@ export interface ResolvePrimitiveEnvOptions {
45
45
  env?: Record<string, string | undefined>;
46
46
  /** An explicit environment name (e.g. from `--primitive-env`). */
47
47
  primitiveEnv?: string | null;
48
- /** Fail when the resolved environment has no `appId`. */
49
- requireAppId?: boolean;
50
48
  }
51
49
  /**
52
50
  * Resolves the Primitive environment for a project. A thin wrapper over the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "primitive-app",
3
- "version": "3.2.0-alpha.0",
3
+ "version": "3.2.0-alpha.2",
4
4
  "license": "UNLICENSED",
5
5
  "author": "Primitive LLC",
6
6
  "type": "module",