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

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.
@@ -12,20 +12,62 @@
12
12
  * gate if this committed copy drifts from the source. Edit the CLI file and
13
13
  * regenerate; edits made here are overwritten.
14
14
  */
15
- /** Schema version of `.primitive/config.json` this core understands. */
15
+ /** Schema version of `primitive/config.json` this core understands. */
16
16
  export declare const PRIMITIVE_CONFIG_VERSION = 1;
17
17
  /** Schema version of `.primitive/local.json` this core writes and reads. */
18
18
  export declare const PRIMITIVE_LOCAL_STATE_VERSION = 1;
19
- export declare const PROJECT_CONFIG_DIR = ".primitive";
19
+ /**
20
+ * The two directories, split apart by #3153 so one name never means two
21
+ * things: `primitive/` is the COMMITTED tree (the config and every
22
+ * environment's TOML), `.primitive/` is MACHINE-LOCAL state (credentials, this
23
+ * machine's selection, snapshot backups) and is gitignored whole.
24
+ */
25
+ export declare const PROJECT_TREE_DIR = "primitive";
26
+ export declare const PROJECT_LOCAL_STATE_DIR = ".primitive";
20
27
  export declare const PROJECT_CONFIG_FILENAME = "config.json";
21
28
  export declare const LOCAL_STATE_FILENAME = "local.json";
22
- export declare const PROJECT_CONFIG_DISPLAY_NAME = ".primitive/config.json";
29
+ export declare const PROJECT_CONFIG_DISPLAY_NAME = "primitive/config.json";
23
30
  export declare const LOCAL_STATE_DISPLAY_NAME = ".primitive/local.json";
31
+ /**
32
+ * The one refusal for a project that never migrated off the pre-#3153 layout:
33
+ * a `.primitive/config.json` with no `primitive/config.json` beside it.
34
+ *
35
+ * That shape is refused rather than walked past because the walk's next stop
36
+ * is the parent directory, and in a nested checkout the parent may be a
37
+ * DIFFERENT project — a command run in the unmigrated one would silently act
38
+ * on the other's app. Nothing else is refused (#3392): anything left under
39
+ * `.primitive/` beside a migrated tree is machine-local junk, not signal.
40
+ *
41
+ * One sentence, naming both halves — the file that was found and the file
42
+ * that is expected — and no document. The migration page this once pointed
43
+ * at was never published (#3155, won't-fix), and every reader that shares
44
+ * this refusal — this core, the generated Vite-plugin copy, the Swift
45
+ * pre-build script, the Vue template's deploy script — says the same thing.
46
+ */
47
+ export declare function unmigratedProjectMessage(found: string, expected: string): string;
48
+ /**
49
+ * The one refusal for a command run outside a Primitive project (#3154,
50
+ * intent criterion 5).
51
+ *
52
+ * There is exactly one way to resolve the configuration tree, the credentials
53
+ * and the app: the environment named by `primitive/config.json`. Outside a
54
+ * project there is no such thing, and the CLI says so once, in these words,
55
+ * wherever the question is asked — the resolver core, the CLI's
56
+ * `requireCurrentEnvironment()`, and the credentials store.
57
+ *
58
+ * Two constraints on the wording, both test-asserted:
59
+ *
60
+ * - it names `primitive init`, the one command that creates what is missing;
61
+ * - it never names `primitive env add`, which after this change also fails
62
+ * outside a project — an error may only offer an invocation it does not
63
+ * itself block.
64
+ */
65
+ export declare function missingConfigMessage(where: string): string;
24
66
  /**
25
67
  * Error kinds callers branch on. Every failure is one of these — no reader is
26
68
  * ever left guessing what a broken config meant.
27
69
  */
28
- export type PrimitiveEnvErrorKind = "missing-config" | "malformed-config" | "unsupported-version" | "corrupt-local-state" | "unknown-environment" | "no-selection" | "missing-app-id";
70
+ export type PrimitiveEnvErrorKind = "missing-config" | "malformed-config" | "unsupported-version" | "corrupt-local-state" | "unknown-environment" | "no-selection" | "missing-app-id" | "stale-layout";
29
71
  export declare class PrimitiveEnvError extends Error {
30
72
  readonly kind: PrimitiveEnvErrorKind;
31
73
  readonly path?: string;
@@ -33,7 +75,12 @@ export declare class PrimitiveEnvError extends Error {
33
75
  }
34
76
  export interface CoreEnvironmentEntry {
35
77
  apiUrl: string;
36
- appId?: string;
78
+ /**
79
+ * The one app this environment names (#3152). Required: selecting an
80
+ * environment selects exactly one app, so there is no per-machine "current
81
+ * app" for a second reader to disagree with.
82
+ */
83
+ appId: string;
37
84
  appName?: string;
38
85
  /**
39
86
  * The app's web counterpart for this environment, as a normalized origin
@@ -64,7 +111,7 @@ export interface CoreEnvironmentEntry {
64
111
  *
65
112
  * `primitive env add --web-url` and the project-config validation reject those
66
113
  * 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
114
+ * hand-edited `primitive/config.json` to an Xcode build: the Swift template's
68
115
  * pre-build script reads the file directly. So every reader that feeds
69
116
  * `primitive.json` applies the contract, and a value that misses it is read as
70
117
  * "this environment has no web counterpart" — code-only email, no trusted
@@ -75,6 +122,21 @@ export interface CoreEnvironmentEntry {
75
122
  * two agree.
76
123
  */
77
124
  export declare function normalizeWebOrigin(value: unknown): string | undefined;
125
+ /**
126
+ * The one remedy for an environment that names no app (#3152), shared by every
127
+ * reader that can hit it: this core, the CLI's `validateProjectConfig`, the
128
+ * Swift template's pre-build script, and the Vue template's cf-deploy.
129
+ *
130
+ * Two constraints on the wording, both test-asserted:
131
+ *
132
+ * - it never says `primitive env add <name>`, which REFUSES an environment
133
+ * name that already exists — the shape this error is always about;
134
+ * - it names no command the failure itself blocks. The CLI validates the
135
+ * project config before any command parses, so every `primitive` command
136
+ * run inside the broken project fails with this same error. The only
137
+ * invocation it may offer is one it says out loud to run elsewhere.
138
+ */
139
+ export declare function missingAppIdMessage(name: string, configPath?: string): string;
78
140
  export interface CoreProjectConfig {
79
141
  version: number;
80
142
  defaultEnvironment?: string;
@@ -87,7 +149,8 @@ export interface ResolvedPrimitiveEnv {
87
149
  apiUrl: string;
88
150
  /** Derived from apiUrl by scheme swap — never authored separately. */
89
151
  wsUrl: string;
90
- appId?: string;
152
+ /** The app this environment names. Always present — see #3152. */
153
+ appId: string;
91
154
  appName?: string;
92
155
  /** The app's web counterpart for the SELECTED environment, if it has one. */
93
156
  webUrl?: string;
@@ -106,11 +169,6 @@ export interface ResolveOptions {
106
169
  explicitEnvName?: string | null;
107
170
  /** Use this config file instead of discovering one. */
108
171
  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
172
  }
115
173
  /**
116
174
  * Derives the WebSocket URL from the API URL. Every apiUrl/wsUrl pair in
@@ -119,26 +177,39 @@ export interface ResolveOptions {
119
177
  */
120
178
  export declare function deriveWsUrl(apiUrl: string): string;
121
179
  /**
122
- * Finds `.primitive/config.json` by walking up from `cwd`, or returns the
180
+ * Finds `primitive/config.json` by walking up from `cwd`, or returns the
123
181
  * PRIMITIVE_PROJECT_CONFIG override when it is set (and exists).
182
+ *
183
+ * The override is checked for provenance BEFORE the file is read (#3153, F2):
184
+ * it bypasses the walk, so without this an old-layout config could be handed
185
+ * straight to a reader and defeat the cutover. An override that points inside
186
+ * a `.primitive/` directory IS the old layout, named explicitly.
187
+ *
188
+ * This is the one function every reader goes through — `resolvePrimitiveEnv`
189
+ * and the CLI's own `loadProjectConfig`/`findProjectRoot` alike — so the
190
+ * unmigrated-project refusal lives HERE, in the walk, or the commands that
191
+ * never call `resolvePrimitiveEnv` would walk past the old anchor into the
192
+ * parent directory's project.
124
193
  */
125
194
  export declare function findPrimitiveConfigPath(options?: {
126
195
  cwd?: string;
127
196
  env?: Record<string, string | undefined>;
128
197
  }): string | null;
129
198
  /**
130
- * The project root for a config path: the parent of `.primitive/`, or the
199
+ * The project root for a config path: the parent of `primitive/`, or the
131
200
  * containing directory when the config was pointed at directly (a fixture via
132
- * PRIMITIVE_PROJECT_CONFIG, which is not inside a `.primitive/` directory).
201
+ * PRIMITIVE_PROJECT_CONFIG, which is not inside a `primitive/` directory).
133
202
  */
134
203
  export declare function projectRootForConfigPath(configPath: string): string;
135
204
  /**
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.
205
+ * Machine-local state lives under the project root's `.primitive/`, not beside
206
+ * the config the config is committed now, and the two must not share a
207
+ * directory. A fixture-pointed bare config still gets its own `.primitive/`
208
+ * in its own directory, so it never picks up a real project's selection.
138
209
  */
139
210
  export declare function localStatePathForConfigPath(configPath: string): string;
140
211
  /**
141
- * Reads and structurally validates `.primitive/config.json`. Throws rather
212
+ * Reads and structurally validates `primitive/config.json`. Throws rather
142
213
  * than guessing: a config that cannot be understood is always louder than a
143
214
  * silent fallback to the wrong backend.
144
215
  */
@@ -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=`.primitive`,m=`config.json`,h=`local.json`,g=`primitive/config.json`,_=`.primitive/local.json`;function v(e,t){return`Found ${e} but no ${t}: this project still uses the retired layout under ${p}/, which is never read.`}function y(e){return`No ${g} found in ${e} or any parent directory. Primitive commands run inside a Primitive project: it is where the CLI reads the environment, the app that environment names, and the configuration tree from. Run 'primitive init' to create one.`}var b=class extends Error{kind;path;constructor(e,t,n){super(t),this.name=`PrimitiveEnvError`,this.kind=e,this.path=n}};function x(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 S(e,t){return`Environment "${e}" in ${t?`${g} (${t})`:g} has no "appId". Every environment names exactly one app — add "appId": "<app-id>" to that environment in ${g}. Find the app id in the Primitive admin UI, with 'primitive apps list' run from another Primitive 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 C(e){if(typeof e==`string`)return e.trim()||void 0}function w(e){return e.startsWith(`https://`)?`wss://`+e.slice(8):e.startsWith(`http://`)?`ws://`+e.slice(7):e}function T(e={}){let n=(e.env??process.env).PRIMITIVE_PROJECT_CONFIG;if(n){let e=(0,a.resolve)(n);if((0,a.basename)((0,a.dirname)(e))===`.primitive`)throw new b(`stale-layout`,v(e,(0,a.join)((0,a.dirname)((0,a.dirname)(e)),f,m)),e);return(0,t.existsSync)(e)?e:null}let r=(0,a.resolve)(e.cwd??process.cwd());for(;;){let e=(0,a.join)(r,f,m);if((0,t.existsSync)(e))return e;let n=(0,a.join)(r,p,m);if((0,t.existsSync)(n))throw new b(`stale-layout`,v(n,e),n);let i=(0,a.dirname)(r);if(i===r)return null;r=i}}function E(e){let t=(0,a.dirname)(e);return(0,a.basename)(t)===`primitive`?(0,a.dirname)(t):t}function D(e){return(0,a.join)(E(e),p,h)}function O(e){if(!(0,t.existsSync)(e))throw new b(`missing-config`,y((0,a.dirname)(e)),e);let n;try{n=(0,t.readFileSync)(e,`utf-8`)}catch(t){throw new b(`malformed-config`,`Failed to read ${g} (${e}): ${t.message}`,e)}let r;try{r=JSON.parse(n)}catch(t){throw new b(`malformed-config`,`Failed to parse ${g} (${e}): ${t.message}`,e)}if(!r||typeof r!=`object`||Array.isArray(r))throw new b(`malformed-config`,`${g} (${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 b(`malformed-config`,`${g} (${e}) is missing required integer field "version".`,e);if(i.version!==1)throw new b(`unsupported-version`,`${g} (${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 b(`malformed-config`,`${g} (${e}) is missing required "environments" object.`,e);let o={};for(let[t,n]of Object.entries(i.environments)){if(!n||typeof n!=`object`||Array.isArray(n))throw new b(`malformed-config`,`Environment "${t}" must be an object in ${g} (${e}).`,e);let r=n;if(typeof r.apiUrl!=`string`||!r.apiUrl)throw new b(`malformed-config`,`Environment "${t}" must have a non-empty "apiUrl" string in ${g} (${e}).`,e);let i=C(r.appId);if(!i)throw new b(`missing-app-id`,S(t,e),e);o[t]={apiUrl:r.apiUrl.replace(/\/$/,``),appId:i,appName:typeof r.appName==`string`?r.appName:void 0,webUrl:x(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:o}}function k(e){let n=D(e);if(!(0,t.existsSync)(n))return null;let r=e=>{throw new b(`corrupt-local-state`,`${_} (${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 A(e,t){let n=Object.keys(e.environments),r=n.join(`, `)||`(none)`,i=t.configPath?`${g} (${t.configPath})`:g,a=(n,a,o)=>{if(!e.environments[n])throw new b(`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 ${_}`);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 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)}function j(e={}){let t=e.env??process.env,n=e.configPath??T({cwd:e.cwd,env:t});if(!n)throw new b(`missing-config`,y(e.cwd??process.cwd()));let r=E(n),i=O(n),{name:a,source:o}=A(i,{explicitEnvName:e.explicitEnvName||null,envVarName:t.PRIMITIVE_ENV||null,localSelection:k(n),configPath:n}),s=i.environments[a];return{name:a,apiUrl:s.apiUrl,wsUrl:w(s.apiUrl),appId:s.appId,appName:s.appName,webUrl:s.webUrl,description:s.description,source:o,configPath:n,localStatePath:D(n),projectRoot:r}}var M=[`VITE_APP_ID`,`VITE_API_URL`,`VITE_WS_URL`,`VITE_APP_NAME`],N=`VITE_EXPECTED_PRIMITIVE_ENV`,P=new Map;function F(e,t){let n=process.env[e];n!==void 0&&n!==``&&P.get(e)!==n||(process.env[e]=t,P.set(e,t))}function I(e={}){return j({cwd:e.root,env:e.env,explicitEnvName:e.primitiveEnv??null})}function L(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 R(e,t){let n=e?.envDir;return n===!1?null:typeof n==`string`&&n!==``?(0,a.resolve)(t,n):t}function z(e,t){let n=process.env[N];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=W((0,a.join)(t,n))[N];if(e!==void 0)return{value:e,where:n}}return null}function B(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} (${N} 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 ${N} 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 V(e,t,n){let r=z(e,t);if(r&&r.value!==``&&r.value!==n.name)throw Error(B(e,r,n))}function H(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=U(u,l),f={};for(let e of M){let t=process.env[e],n=t!==void 0&&P.get(e)!==t?t:d[e];n!==void 0&&n!==``&&(f[e]=n)}let p;try{p=j({cwd:l,explicitEnvName:e.primitiveEnv??null})}catch(e){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,{};throw e}if(!(f.VITE_APP_ID&&f.VITE_API_URL)){let e=R(s,l);V(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 M){if(f[e]!==void 0){g.push(e);continue}let t=m[e];t!==void 0&&(h[`import.meta.env.${e}`]=JSON.stringify(t),F(e,t))}return F(`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(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}},configResolved(e){if(!o||!t)return;let n=R(e,o.root),r=e?.mode??o.mode;n===o.dir&&r===o.mode||(V(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 U(e,t){let n=[`.env`,`.env.local`,`.env.${e}`,`.env.${e}.local`],r={};for(let e of n)Object.assign(r,W((0,a.join)(t,e)));return r}function W(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=b,exports.primitiveDevTools=d,exports.primitiveEnv=H,exports.resolvePrimitiveEnv=I;
@@ -63,14 +63,21 @@ export const keyboardShortcut = ${JSON.stringify(s)};
63
63
  }
64
64
  };
65
65
  }
66
- var h = ".primitive", g = "config.json", _ = "local.json", v = ".primitive/config.json", y = ".primitive/local.json", b = class extends Error {
66
+ var h = "primitive", g = ".primitive", _ = "config.json", v = "local.json", y = "primitive/config.json", b = ".primitive/local.json";
67
+ function x(e, t) {
68
+ return `Found ${e} but no ${t}: this project still uses the retired layout under ${g}/, which is never read.`;
69
+ }
70
+ function S(e) {
71
+ return `No ${y} found in ${e} or any parent directory. Primitive commands run inside a Primitive project: it is where the CLI reads the environment, the app that environment names, and the configuration tree from. Run 'primitive init' to create one.`;
72
+ }
73
+ var C = class extends Error {
67
74
  kind;
68
75
  path;
69
76
  constructor(e, t, n) {
70
77
  super(t), this.name = "PrimitiveEnvError", this.kind = e, this.path = n;
71
78
  }
72
79
  };
73
- function x(e) {
80
+ function w(e) {
74
81
  if (typeof e != "string") return;
75
82
  let t = e.trim();
76
83
  if (!t) return;
@@ -82,74 +89,85 @@ function x(e) {
82
89
  }
83
90
  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
91
  }
85
- function S(e) {
92
+ function T(e, t) {
93
+ return `Environment "${e}" in ${t ? `${y} (${t})` : y} has no "appId". Every environment names exactly one app — add "appId": "<app-id>" to that environment in ${y}. Find the app id in the Primitive admin UI, with 'primitive apps list' run from another Primitive 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.`;
94
+ }
95
+ function E(e) {
96
+ if (typeof e == "string") return e.trim() || void 0;
97
+ }
98
+ function D(e) {
86
99
  return e.startsWith("https://") ? "wss://" + e.slice(8) : e.startsWith("http://") ? "ws://" + e.slice(7) : e;
87
100
  }
88
- function C(t = {}) {
101
+ function O(t = {}) {
89
102
  let n = (t.env ?? process.env).PRIMITIVE_PROJECT_CONFIG;
90
103
  if (n) {
91
104
  let t = c(n);
105
+ if (a(o(t)) === ".primitive") throw new C("stale-layout", x(t, s(o(o(t)), h, _)), t);
92
106
  return e(t) ? t : null;
93
107
  }
94
108
  let r = c(t.cwd ?? process.cwd());
95
109
  for (;;) {
96
- let t = s(r, h, g);
110
+ let t = s(r, h, _);
97
111
  if (e(t)) return t;
98
- let n = o(r);
99
- if (n === r) return null;
100
- r = n;
112
+ let n = s(r, g, _);
113
+ if (e(n)) throw new C("stale-layout", x(n, t), n);
114
+ let i = o(r);
115
+ if (i === r) return null;
116
+ r = i;
101
117
  }
102
118
  }
103
- function w(e) {
119
+ function k(e) {
104
120
  let t = o(e);
105
- return a(t) === ".primitive" ? o(t) : t;
121
+ return a(t) === "primitive" ? o(t) : t;
106
122
  }
107
- function T(e) {
108
- return s(o(e), _);
123
+ function A(e) {
124
+ return s(k(e), g, v);
109
125
  }
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);
126
+ function j(n) {
127
+ if (!e(n)) throw new C("missing-config", S(o(n)), n);
112
128
  let r;
113
129
  try {
114
130
  r = t(n, "utf-8");
115
131
  } catch (e) {
116
- throw new b("malformed-config", `Failed to read ${v} (${n}): ${e.message}`, n);
132
+ throw new C("malformed-config", `Failed to read ${y} (${n}): ${e.message}`, n);
117
133
  }
118
134
  let i;
119
135
  try {
120
136
  i = JSON.parse(r);
121
137
  } catch (e) {
122
- throw new b("malformed-config", `Failed to parse ${v} (${n}): ${e.message}`, n);
138
+ throw new C("malformed-config", `Failed to parse ${y} (${n}): ${e.message}`, n);
123
139
  }
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);
140
+ if (!i || typeof i != "object" || Array.isArray(i)) throw new C("malformed-config", `${y} (${n}) must contain a JSON object at the top level.`, n);
125
141
  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 = {};
142
+ if (typeof a.version != "number" || !Number.isInteger(a.version)) throw new C("malformed-config", `${y} (${n}) is missing required integer field "version".`, n);
143
+ if (a.version !== 1) throw new C("unsupported-version", `${y} (${n}) has version ${a.version}, but this tool understands version 1.`, n);
144
+ if (!a.environments || typeof a.environments != "object" || Array.isArray(a.environments)) throw new C("malformed-config", `${y} (${n}) is missing required "environments" object.`, n);
145
+ let s = {};
130
146
  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);
147
+ if (!t || typeof t != "object" || Array.isArray(t)) throw new C("malformed-config", `Environment "${e}" must be an object in ${y} (${n}).`, n);
132
148
  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] = {
149
+ if (typeof r.apiUrl != "string" || !r.apiUrl) throw new C("malformed-config", `Environment "${e}" must have a non-empty "apiUrl" string in ${y} (${n}).`, n);
150
+ let i = E(r.appId);
151
+ if (!i) throw new C("missing-app-id", T(e, n), n);
152
+ s[e] = {
135
153
  apiUrl: r.apiUrl.replace(/\/$/, ""),
136
- appId: typeof r.appId == "string" ? r.appId : void 0,
154
+ appId: i,
137
155
  appName: typeof r.appName == "string" ? r.appName : void 0,
138
- webUrl: x(r.webUrl),
156
+ webUrl: w(r.webUrl),
139
157
  description: typeof r.description == "string" ? r.description : void 0
140
158
  };
141
159
  }
142
160
  return {
143
161
  version: a.version,
144
162
  defaultEnvironment: typeof a.defaultEnvironment == "string" && a.defaultEnvironment ? a.defaultEnvironment : void 0,
145
- environments: o
163
+ environments: s
146
164
  };
147
165
  }
148
- function D(n) {
149
- let r = T(n);
166
+ function M(n) {
167
+ let r = A(n);
150
168
  if (!e(r)) return null;
151
169
  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);
170
+ throw new C("corrupt-local-state", `${b} (${r}) is unreadable: ${e}. Delete the file or re-run 'primitive env use <name>'.`, r);
153
171
  }, a;
154
172
  try {
155
173
  a = JSON.parse(t(r, "utf-8"));
@@ -160,9 +178,9 @@ function D(n) {
160
178
  let o = a.selectedEnvironment;
161
179
  return o == null || o === "" ? null : typeof o == "string" ? o : i("\"selectedEnvironment\" must be a string");
162
180
  }
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);
181
+ function N(e, t) {
182
+ let n = Object.keys(e.environments), r = n.join(", ") || "(none)", i = t.configPath ? `${y} (${t.configPath})` : y, a = (n, a, o) => {
183
+ if (!e.environments[n]) throw new C("unknown-environment", `Environment "${n}" is not defined in ${i} (selected ${o}). Available: ${r}`, t.configPath);
166
184
  return {
167
185
  name: n,
168
186
  source: a
@@ -170,62 +188,60 @@ function O(e, t) {
170
188
  };
171
189
  if (t.explicitEnvName) return a(t.explicitEnvName, "explicit", "explicitly");
172
190
  if (t.envVarName) return a(t.envVarName, "env-var", "via PRIMITIVE_ENV");
173
- if (t.localSelection) return a(t.localSelection, "local", `in ${y}`);
191
+ if (t.localSelection) return a(t.localSelection, "local", `in ${b}`);
174
192
  if (e.defaultEnvironment) return a(e.defaultEnvironment, "default", "as \"defaultEnvironment\"");
175
193
  if (n.length === 1) return {
176
194
  name: n[0],
177
195
  source: "sole"
178
196
  };
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);
197
+ throw n.length === 0 ? new C("no-selection", `${i} has no environments defined. Run 'primitive env add <name>' or 'primitive init'.`, t.configPath) : new C("no-selection", `No environment selected. Run 'primitive env use <name>', set "defaultEnvironment" in ${i}, or export PRIMITIVE_ENV. Available: ${r}`, t.configPath);
180
198
  }
181
- function k(e = {}) {
182
- let t = e.env ?? process.env, n = e.configPath ?? C({
199
+ function P(e = {}) {
200
+ let t = e.env ?? process.env, n = e.configPath ?? O({
183
201
  cwd: e.cwd,
184
202
  env: t
185
203
  });
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, {
204
+ if (!n) throw new C("missing-config", S(e.cwd ?? process.cwd()));
205
+ let r = k(n), i = j(n), { name: a, source: o } = N(i, {
188
206
  explicitEnvName: e.explicitEnvName || null,
189
207
  envVarName: t.PRIMITIVE_ENV || null,
190
- localSelection: D(n),
208
+ localSelection: M(n),
191
209
  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);
210
+ }), s = i.environments[a];
194
211
  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,
212
+ name: a,
213
+ apiUrl: s.apiUrl,
214
+ wsUrl: D(s.apiUrl),
215
+ appId: s.appId,
216
+ appName: s.appName,
217
+ webUrl: s.webUrl,
218
+ description: s.description,
219
+ source: o,
203
220
  configPath: n,
204
- localStatePath: T(n),
205
- projectRoot: w(n)
221
+ localStatePath: A(n),
222
+ projectRoot: r
206
223
  };
207
224
  }
208
225
  //#endregion
209
226
  //#region src/dev-tools/vite-plugin/primitive-env.ts
210
- var A = [
227
+ var F = [
211
228
  "VITE_APP_ID",
212
229
  "VITE_API_URL",
213
230
  "VITE_WS_URL",
214
231
  "VITE_APP_NAME"
215
- ], j = "VITE_EXPECTED_PRIMITIVE_ENV", M = /* @__PURE__ */ new Map();
216
- function N(e, t) {
232
+ ], I = "VITE_EXPECTED_PRIMITIVE_ENV", L = /* @__PURE__ */ new Map();
233
+ function R(e, t) {
217
234
  let n = process.env[e];
218
- n !== void 0 && n !== "" && M.get(e) !== n || (process.env[e] = t, M.set(e, t));
235
+ n !== void 0 && n !== "" && L.get(e) !== n || (process.env[e] = t, L.set(e, t));
219
236
  }
220
- function P(e = {}) {
221
- return k({
237
+ function z(e = {}) {
238
+ return P({
222
239
  cwd: e.root,
223
240
  env: e.env,
224
- explicitEnvName: e.primitiveEnv ?? null,
225
- requireAppId: e.requireAppId
241
+ explicitEnvName: e.primitiveEnv ?? null
226
242
  });
227
243
  }
228
- function F(e, t, n) {
244
+ function B(e, t, n) {
229
245
  let r = (e) => n.includes(e) ? " (overridden)" : "", i = [
230
246
  `[primitive-env] Primitive environment: ${e.name} (selected: ${e.source})`,
231
247
  `[primitive-env] apiUrl: ${t.apiUrl}${r("VITE_API_URL")}`,
@@ -236,12 +252,12 @@ function F(e, t, n) {
236
252
  ];
237
253
  return n.length > 0 && i.push(`[primitive-env] overridden by the environment: ${n.join(", ")}`), i.join("\n");
238
254
  }
239
- function I(e, t) {
255
+ function V(e, t) {
240
256
  let n = e?.envDir;
241
257
  return n === !1 ? null : typeof n == "string" && n !== "" ? c(t, n) : t;
242
258
  }
243
- function L(e, t) {
244
- let n = process.env[j];
259
+ function H(e, t) {
260
+ let n = process.env[I];
245
261
  if (n !== void 0 && n !== "") return {
246
262
  value: n,
247
263
  where: "the process environment"
@@ -253,7 +269,7 @@ function L(e, t) {
253
269
  ".env.local",
254
270
  ".env"
255
271
  ]) {
256
- let e = H(s(t, n))[j];
272
+ let e = q(s(t, n))[I];
257
273
  if (e !== void 0) return {
258
274
  value: e,
259
275
  where: n
@@ -261,48 +277,47 @@ function L(e, t) {
261
277
  }
262
278
  return null;
263
279
  }
264
- function R(e, t, n) {
280
+ function U(e, t, n) {
265
281
  return [
266
282
  `[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})`,
283
+ `[primitive-env] expected: ${t.value} (${I} in ${t.where})`,
268
284
  `[primitive-env] resolved: ${n.name} (selected: ${n.source})`,
269
285
  `[primitive-env] config: ${n.configPath}`,
270
286
  "",
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}.`,
287
+ `Run the pair you meant — PRIMITIVE_ENV=${t.value} <command> --mode ${e}, or 'primitive env use ${t.value}' — or change/remove ${I} in ${t.where}.`,
272
288
  "",
273
289
  `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
290
  ].join("\n");
275
291
  }
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));
292
+ function W(e, t, n) {
293
+ let r = H(e, t);
294
+ if (r && r.value !== "" && r.value !== n.name) throw Error(U(e, r, n));
279
295
  }
280
- function B(e = {}) {
296
+ function G(e = {}) {
281
297
  let t = null, n = null, r, i = [], a = !1, o = null;
282
298
  return {
283
299
  name: "primitive-env",
284
300
  enforce: "pre",
285
301
  config(s, c) {
286
302
  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];
303
+ let l = s?.root ?? process.cwd(), u = c?.mode ?? "development", d = K(u, l), f = {};
304
+ for (let e of F) {
305
+ let t = process.env[e], n = t !== void 0 && L.get(e) !== t ? t : d[e];
290
306
  n !== void 0 && n !== "" && (f[e] = n);
291
307
  }
292
308
  let p;
293
309
  try {
294
- p = k({
310
+ p = P({
295
311
  cwd: l,
296
- explicitEnvName: e.primitiveEnv ?? null,
297
- requireAppId: !f.VITE_APP_ID
312
+ explicitEnvName: e.primitiveEnv ?? null
298
313
  });
299
314
  } 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, {};
315
+ if (e instanceof C && 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
316
  throw e;
302
317
  }
303
318
  if (!(f.VITE_APP_ID && f.VITE_API_URL)) {
304
- let e = I(s, l);
305
- z(u, e, p), o = {
319
+ let e = V(s, l);
320
+ W(u, e, p), o = {
306
321
  root: l,
307
322
  mode: u,
308
323
  dir: e
@@ -315,25 +330,25 @@ function B(e = {}) {
315
330
  VITE_WS_URL: p.wsUrl,
316
331
  VITE_APP_NAME: p.appName
317
332
  }, h = { "import.meta.env.VITE_PRIMITIVE_ENV": JSON.stringify(p.name) }, g = [];
318
- for (let e of A) {
333
+ for (let e of F) {
319
334
  if (f[e] !== void 0) {
320
335
  g.push(e);
321
336
  continue;
322
337
  }
323
338
  let t = m[e];
324
- t !== void 0 && (h[`import.meta.env.${e}`] = JSON.stringify(t), N(e, t));
339
+ t !== void 0 && (h[`import.meta.env.${e}`] = JSON.stringify(t), R(e, t));
325
340
  }
326
- return N("VITE_PRIMITIVE_ENV", p.name), i = g, n = {
341
+ return R("VITE_PRIMITIVE_ENV", p.name), i = g, n = {
327
342
  apiUrl: f.VITE_API_URL ?? p.apiUrl,
328
343
  wsUrl: f.VITE_WS_URL ?? p.wsUrl,
329
344
  appId: f.VITE_APP_ID ?? p.appId,
330
345
  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 };
346
+ }, e.logResolved !== !1 && console.log(B(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
347
  },
333
348
  configResolved(e) {
334
349
  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 = {
350
+ let n = V(e, o.root), r = e?.mode ?? o.mode;
351
+ n === o.dir && r === o.mode || (W(r, n, t), o = {
337
352
  root: o.root,
338
353
  mode: r,
339
354
  dir: n
@@ -356,17 +371,17 @@ function B(e = {}) {
356
371
  }
357
372
  };
358
373
  }
359
- function V(e, t) {
374
+ function K(e, t) {
360
375
  let n = [
361
376
  ".env",
362
377
  ".env.local",
363
378
  `.env.${e}`,
364
379
  `.env.${e}.local`
365
380
  ], r = {};
366
- for (let e of n) Object.assign(r, H(s(t, e)));
381
+ for (let e of n) Object.assign(r, q(s(t, e)));
367
382
  return r;
368
383
  }
369
- function H(n) {
384
+ function q(n) {
370
385
  if (!e(n)) return {};
371
386
  let r = {};
372
387
  for (let e of t(n, "utf-8").split(/\r?\n/)) {
@@ -380,4 +395,4 @@ function H(n) {
380
395
  return r;
381
396
  }
382
397
  //#endregion
383
- export { b as PrimitiveEnvError, m as primitiveDevTools, B as primitiveEnv, P as resolvePrimitiveEnv };
398
+ export { C as PrimitiveEnvError, m as primitiveDevTools, G as primitiveEnv, z as resolvePrimitiveEnv };
@@ -2,7 +2,7 @@
2
2
  * `primitiveEnv()` — Primitive environment configuration for a Vite app (#2873).
3
3
  *
4
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
5
+ * `primitive/config.json` (which every `primitive` command reads) and again in
6
6
  * `.env` / `.env.production`. This plugin removes the second copy. It resolves
7
7
  * the selected Primitive environment at config time and `define`s the keys the
8
8
  * app already reads:
@@ -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