@dreamboard-games/cli 0.1.30-alpha.2 → 0.1.30-alpha.4

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.
Files changed (103) hide show
  1. package/dist/agent-verifier/agent-workspace-verifier.mjs +227 -0
  2. package/dist/agent-verifier/agent-workspace-verifier.mjs.map +1 -0
  3. package/dist/agent-verifier/chunk-27EEIZCI.mjs +185 -0
  4. package/dist/agent-verifier/chunk-27EEIZCI.mjs.map +1 -0
  5. package/dist/agent-verifier/chunk-5NYBTZB4.mjs +226 -0
  6. package/dist/agent-verifier/chunk-5NYBTZB4.mjs.map +1 -0
  7. package/dist/agent-verifier/chunk-776W3UGV.mjs +167 -0
  8. package/dist/agent-verifier/chunk-776W3UGV.mjs.map +1 -0
  9. package/dist/agent-verifier/chunk-C3VW3DTA.mjs +2909 -0
  10. package/dist/agent-verifier/chunk-C3VW3DTA.mjs.map +1 -0
  11. package/dist/agent-verifier/chunk-F2DIOJJZ.mjs +302 -0
  12. package/dist/agent-verifier/chunk-F2DIOJJZ.mjs.map +1 -0
  13. package/dist/agent-verifier/chunk-G42BGGG2.mjs +70 -0
  14. package/dist/agent-verifier/chunk-G42BGGG2.mjs.map +1 -0
  15. package/dist/agent-verifier/chunk-H6XDQJ3N.mjs +11 -0
  16. package/dist/agent-verifier/chunk-H76MT5UR.mjs +57 -0
  17. package/dist/agent-verifier/chunk-H76MT5UR.mjs.map +1 -0
  18. package/dist/agent-verifier/chunk-IAYRNVUC.mjs +49 -0
  19. package/dist/agent-verifier/chunk-IAYRNVUC.mjs.map +1 -0
  20. package/dist/agent-verifier/chunk-IDVQXGAO.mjs +222 -0
  21. package/dist/agent-verifier/chunk-IDVQXGAO.mjs.map +1 -0
  22. package/dist/agent-verifier/chunk-JO5AMVZU.mjs +1744 -0
  23. package/dist/agent-verifier/chunk-JO5AMVZU.mjs.map +1 -0
  24. package/dist/agent-verifier/chunk-JZTH3EMV.mjs +14523 -0
  25. package/dist/agent-verifier/chunk-JZTH3EMV.mjs.map +1 -0
  26. package/dist/agent-verifier/chunk-KDBSVLCF.mjs +624 -0
  27. package/dist/agent-verifier/chunk-KDBSVLCF.mjs.map +1 -0
  28. package/dist/agent-verifier/chunk-MW2QIWWA.mjs +729 -0
  29. package/dist/agent-verifier/chunk-MW2QIWWA.mjs.map +1 -0
  30. package/dist/agent-verifier/chunk-NAK77WXW.mjs +767 -0
  31. package/dist/agent-verifier/chunk-NAK77WXW.mjs.map +1 -0
  32. package/dist/agent-verifier/chunk-ON62IGWK.mjs +3137 -0
  33. package/dist/agent-verifier/chunk-ON62IGWK.mjs.map +1 -0
  34. package/dist/agent-verifier/chunk-QBAF7EYR.mjs +214 -0
  35. package/dist/agent-verifier/chunk-QBAF7EYR.mjs.map +1 -0
  36. package/dist/agent-verifier/chunk-QZH6IEZS.mjs +39 -0
  37. package/dist/agent-verifier/chunk-QZH6IEZS.mjs.map +1 -0
  38. package/dist/agent-verifier/chunk-TAEQKBJB.mjs +107 -0
  39. package/dist/agent-verifier/chunk-TAEQKBJB.mjs.map +1 -0
  40. package/dist/agent-verifier/chunk-UIOLGH4A.mjs +150 -0
  41. package/dist/agent-verifier/chunk-UIOLGH4A.mjs.map +1 -0
  42. package/dist/agent-verifier/chunk-XKCJBIRY.mjs +75 -0
  43. package/dist/agent-verifier/chunk-XKCJBIRY.mjs.map +1 -0
  44. package/dist/agent-verifier/chunk-XQXDOBYB.mjs +382 -0
  45. package/dist/agent-verifier/chunk-XQXDOBYB.mjs.map +1 -0
  46. package/dist/agent-verifier/chunk-YDIOW2BO.mjs +45 -0
  47. package/dist/agent-verifier/chunk-YDIOW2BO.mjs.map +1 -0
  48. package/dist/agent-verifier/chunk-YE7UAO3T.mjs +129 -0
  49. package/dist/agent-verifier/chunk-YE7UAO3T.mjs.map +1 -0
  50. package/dist/agent-verifier/chunk-Z6OZWUIZ.mjs +261 -0
  51. package/dist/agent-verifier/chunk-Z6OZWUIZ.mjs.map +1 -0
  52. package/dist/agent-verifier/chunk-ZEELHSY3.mjs +20 -0
  53. package/dist/agent-verifier/chunk-ZEELHSY3.mjs.map +1 -0
  54. package/dist/agent-verifier/compile-576O7TYP.mjs +312 -0
  55. package/dist/agent-verifier/compile-576O7TYP.mjs.map +1 -0
  56. package/dist/agent-verifier/global-config-NYCSCAUI.mjs +18 -0
  57. package/dist/agent-verifier/keychain-backend-A3MRWLPF.mjs +135 -0
  58. package/dist/agent-verifier/keychain-backend-A3MRWLPF.mjs.map +1 -0
  59. package/dist/agent-verifier/local-files-QVJ2H3MH.mjs +45 -0
  60. package/dist/agent-verifier/local-files-QVJ2H3MH.mjs.map +1 -0
  61. package/dist/agent-verifier/local-typecheck-2JWG5IGL.mjs +10 -0
  62. package/dist/agent-verifier/local-typecheck-2JWG5IGL.mjs.map +1 -0
  63. package/dist/agent-verifier/materialize-workspace-OZKOQCSQ.mjs +89 -0
  64. package/dist/agent-verifier/materialize-workspace-OZKOQCSQ.mjs.map +1 -0
  65. package/dist/agent-verifier/project-state-XKUSCFSV.mjs +33 -0
  66. package/dist/agent-verifier/project-state-XKUSCFSV.mjs.map +1 -0
  67. package/dist/agent-verifier/prompt-VKHMCQT6.mjs +756 -0
  68. package/dist/agent-verifier/prompt-VKHMCQT6.mjs.map +1 -0
  69. package/dist/agent-verifier/reducer-bundle-preflight-7NYZF5ZT.mjs +20 -0
  70. package/dist/agent-verifier/reducer-bundle-preflight-7NYZF5ZT.mjs.map +1 -0
  71. package/dist/agent-verifier/reducer-contract-preflight-COD2CO22.mjs +11 -0
  72. package/dist/agent-verifier/reducer-contract-preflight-COD2CO22.mjs.map +1 -0
  73. package/dist/agent-verifier/reducer-native-test-harness-QC7HZUK4.mjs +50 -0
  74. package/dist/agent-verifier/reducer-native-test-harness-QC7HZUK4.mjs.map +1 -0
  75. package/dist/agent-verifier/static-scaffold-JBUE3ROP.mjs +27 -0
  76. package/dist/agent-verifier/static-scaffold-JBUE3ROP.mjs.map +1 -0
  77. package/dist/agent-verifier/sync-C6S3OGCD.mjs +588 -0
  78. package/dist/agent-verifier/sync-C6S3OGCD.mjs.map +1 -0
  79. package/dist/agent-verifier/test-Y5UGQV7J.mjs +353 -0
  80. package/dist/agent-verifier/test-Y5UGQV7J.mjs.map +1 -0
  81. package/dist/agent-verifier/workspace-codegen-WPZHMATU.mjs +10 -0
  82. package/dist/agent-verifier/workspace-codegen-WPZHMATU.mjs.map +1 -0
  83. package/dist/agent-verifier/workspace-dependencies-B6A2ZX55.mjs +15 -0
  84. package/dist/agent-verifier/workspace-dependencies-B6A2ZX55.mjs.map +1 -0
  85. package/dist/chunk-2H7UOFLK.js +11 -0
  86. package/dist/chunk-2H7UOFLK.js.map +1 -0
  87. package/dist/{chunk-N7XPNNUI.js → chunk-3NRROR4P.js} +3 -3
  88. package/dist/{chunk-TAQKH67O.js → chunk-M4SCKH5M.js} +8 -2224
  89. package/dist/chunk-M4SCKH5M.js.map +1 -0
  90. package/dist/{global-config-S4ZIPECE.js → global-config-YBFEGJQG.js} +3 -3
  91. package/dist/global-config-YBFEGJQG.js.map +1 -0
  92. package/dist/index.js +4 -4
  93. package/dist/internal.js +3 -3
  94. package/dist/{keychain-backend-HDF4TZDL.js → keychain-backend-JHTXAKWC.js} +2 -2
  95. package/dist/{prompt-NDV3AE5L.js → prompt-GMZABCJC.js} +2 -2
  96. package/package.json +3 -2
  97. package/dist/chunk-SEGVTWSK.js +0 -44
  98. package/dist/chunk-TAQKH67O.js.map +0 -1
  99. /package/dist/{chunk-SEGVTWSK.js.map → agent-verifier/chunk-H6XDQJ3N.mjs.map} +0 -0
  100. /package/dist/{global-config-S4ZIPECE.js.map → agent-verifier/global-config-NYCSCAUI.mjs.map} +0 -0
  101. /package/dist/{chunk-N7XPNNUI.js.map → chunk-3NRROR4P.js.map} +0 -0
  102. /package/dist/{keychain-backend-HDF4TZDL.js.map → keychain-backend-JHTXAKWC.js.map} +0 -0
  103. /package/dist/{prompt-NDV3AE5L.js.map → prompt-GMZABCJC.js.map} +0 -0
@@ -0,0 +1,226 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ atomicWriteFile,
4
+ withFileLock
5
+ } from "./chunk-TAEQKBJB.mjs";
6
+ import {
7
+ ensureDir,
8
+ readJsonFile
9
+ } from "./chunk-IAYRNVUC.mjs";
10
+ import {
11
+ PROJECT_DIR_NAME
12
+ } from "./chunk-H76MT5UR.mjs";
13
+
14
+ // src/config/global-config.ts
15
+ import os2 from "os";
16
+ import path2 from "path";
17
+
18
+ // src/config/credential-store.ts
19
+ import os from "os";
20
+ import path from "path";
21
+ import { promises as fs } from "fs";
22
+ function getCredentialFilePath() {
23
+ return path.join(os.homedir(), PROJECT_DIR_NAME, "auth.json");
24
+ }
25
+ function getCredentialLockPath() {
26
+ return `${getCredentialFilePath()}.lock`;
27
+ }
28
+ async function fileRead() {
29
+ const filePath = getCredentialFilePath();
30
+ let data;
31
+ try {
32
+ data = await fs.readFile(filePath, "utf8");
33
+ } catch (err) {
34
+ if (err.code === "ENOENT") return null;
35
+ throw err;
36
+ }
37
+ if (data.trim().length === 0) {
38
+ return null;
39
+ }
40
+ let parsed;
41
+ try {
42
+ parsed = JSON.parse(data);
43
+ } catch {
44
+ return null;
45
+ }
46
+ const accessToken = parsed.accessToken ?? parsed.authToken;
47
+ const refreshToken = parsed.refreshToken;
48
+ if (!accessToken && !refreshToken) return null;
49
+ return {
50
+ accessToken: accessToken || void 0,
51
+ refreshToken: refreshToken || void 0,
52
+ tokenExpiresAt: parsed.tokenExpiresAt || void 0,
53
+ clerkOAuthIssuer: parsed.clerkOAuthIssuer || void 0,
54
+ clerkOAuthClientId: parsed.clerkOAuthClientId || void 0,
55
+ clerkOAuthTokenUrl: parsed.clerkOAuthTokenUrl || void 0,
56
+ environment: parsed.environment || void 0
57
+ };
58
+ }
59
+ async function writeFilePayload(payload) {
60
+ await atomicWriteFile(
61
+ getCredentialFilePath(),
62
+ `${JSON.stringify(payload, null, 2)}
63
+ `,
64
+ { mode: 384 }
65
+ );
66
+ }
67
+ async function fileWriteFull(creds) {
68
+ if (!creds.accessToken || !creds.refreshToken) {
69
+ throw new Error(
70
+ "Refusing to persist credentials with an empty accessToken or refreshToken."
71
+ );
72
+ }
73
+ await writeFilePayload({
74
+ authToken: creds.accessToken,
75
+ refreshToken: creds.refreshToken,
76
+ tokenExpiresAt: creds.tokenExpiresAt,
77
+ clerkOAuthIssuer: creds.clerkOAuthIssuer,
78
+ clerkOAuthClientId: creds.clerkOAuthClientId,
79
+ clerkOAuthTokenUrl: creds.clerkOAuthTokenUrl,
80
+ environment: creds.environment
81
+ });
82
+ }
83
+ async function fileWriteAccessOnly(accessToken) {
84
+ if (!accessToken) {
85
+ throw new Error("Refusing to persist an empty access token.");
86
+ }
87
+ await writeFilePayload({ authToken: accessToken });
88
+ }
89
+ async function fileClear() {
90
+ const filePath = getCredentialFilePath();
91
+ try {
92
+ await fs.unlink(filePath);
93
+ } catch (err) {
94
+ if (err.code !== "ENOENT") throw err;
95
+ }
96
+ }
97
+ var fileCredentialBackend = {
98
+ name: "file",
99
+ read: fileRead,
100
+ writeFull: fileWriteFull,
101
+ writeAccessOnly: fileWriteAccessOnly,
102
+ clear: fileClear
103
+ };
104
+ var cachedBackend = null;
105
+ var migrationCompleted = false;
106
+ var backendResolver = defaultBackendResolver;
107
+ async function defaultBackendResolver() {
108
+ const override = (process.env.DREAMBOARD_CREDENTIAL_BACKEND ?? "").trim().toLowerCase();
109
+ if (override === "file") {
110
+ return fileCredentialBackend;
111
+ }
112
+ if (override && override !== "keychain" && override !== "auto") {
113
+ throw new Error(
114
+ `Unknown DREAMBOARD_CREDENTIAL_BACKEND value "${override}" (expected "file", "keychain", or "auto").`
115
+ );
116
+ }
117
+ const useKeychain = override === "keychain" || await readCredentialBackendPreference();
118
+ if (!useKeychain) {
119
+ return fileCredentialBackend;
120
+ }
121
+ const { tryKeychainBackend } = await import("./keychain-backend-A3MRWLPF.mjs");
122
+ const keychain = await tryKeychainBackend();
123
+ if (keychain.available) {
124
+ return keychain.backend;
125
+ }
126
+ return fileCredentialBackend;
127
+ }
128
+ async function readCredentialBackendPreference() {
129
+ try {
130
+ const { loadGlobalConfig: loadGlobalConfig2 } = await import("./global-config-NYCSCAUI.mjs");
131
+ const config = await loadGlobalConfig2();
132
+ return config.credentialBackend === "keychain";
133
+ } catch {
134
+ return false;
135
+ }
136
+ }
137
+ async function getCredentialBackend() {
138
+ if (cachedBackend === null) {
139
+ cachedBackend = await backendResolver();
140
+ if (!migrationCompleted && cachedBackend.name !== "file") {
141
+ await migrateFromFileBackendIfNeeded(cachedBackend);
142
+ }
143
+ migrationCompleted = true;
144
+ }
145
+ return cachedBackend;
146
+ }
147
+ async function migrateFromFileBackendIfNeeded(target) {
148
+ try {
149
+ const [onDisk, onTarget] = await Promise.all([
150
+ fileCredentialBackend.read(),
151
+ target.read()
152
+ ]);
153
+ if (!onDisk) return;
154
+ if (onTarget) {
155
+ await fileCredentialBackend.clear();
156
+ return;
157
+ }
158
+ if (onDisk.accessToken && onDisk.refreshToken) {
159
+ await target.writeFull({
160
+ accessToken: onDisk.accessToken,
161
+ refreshToken: onDisk.refreshToken
162
+ });
163
+ } else if (onDisk.accessToken) {
164
+ await target.writeAccessOnly(onDisk.accessToken);
165
+ } else {
166
+ return;
167
+ }
168
+ await fileCredentialBackend.clear();
169
+ } catch {
170
+ }
171
+ }
172
+ async function getStoredSession() {
173
+ const backend = await getCredentialBackend();
174
+ return backend.read();
175
+ }
176
+ async function setCredentials(creds) {
177
+ await withFileLock(getCredentialLockPath(), async () => {
178
+ const backend = await getCredentialBackend();
179
+ await backend.writeFull(creds);
180
+ });
181
+ }
182
+
183
+ // src/config/global-config.ts
184
+ function normalizeCredentialBackend(value) {
185
+ if (value === "file" || value === "keychain") return value;
186
+ return void 0;
187
+ }
188
+ function getGlobalConfigPath() {
189
+ return path2.join(os2.homedir(), PROJECT_DIR_NAME, "config.json");
190
+ }
191
+ function getGlobalAuthPath() {
192
+ return getCredentialFilePath();
193
+ }
194
+ async function loadGlobalConfig() {
195
+ const config = await readJsonFile(getGlobalConfigPath()).catch(
196
+ () => ({})
197
+ );
198
+ return {
199
+ environment: config.environment,
200
+ credentialBackend: normalizeCredentialBackend(config.credentialBackend)
201
+ };
202
+ }
203
+ async function saveGlobalConfig(config) {
204
+ const configDir = path2.join(os2.homedir(), PROJECT_DIR_NAME);
205
+ await ensureDir(configDir);
206
+ const normalized = {
207
+ environment: config.environment,
208
+ credentialBackend: normalizeCredentialBackend(config.credentialBackend)
209
+ };
210
+ await atomicWriteFile(
211
+ getGlobalConfigPath(),
212
+ `${JSON.stringify(normalized, null, 2)}
213
+ `,
214
+ { mode: 384 }
215
+ );
216
+ }
217
+
218
+ export {
219
+ getStoredSession,
220
+ setCredentials,
221
+ getGlobalConfigPath,
222
+ getGlobalAuthPath,
223
+ loadGlobalConfig,
224
+ saveGlobalConfig
225
+ };
226
+ //# sourceMappingURL=chunk-5NYBTZB4.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/config/global-config.ts","../../src/config/credential-store.ts"],"sourcesContent":["import os from \"node:os\";\nimport path from \"node:path\";\nimport type { CredentialBackendPreference, GlobalConfig } from \"../types.js\";\nimport { PROJECT_DIR_NAME } from \"../constants.js\";\nimport { ensureDir, readJsonFile } from \"../utils/fs.js\";\nimport { atomicWriteFile } from \"../utils/atomic-file.js\";\nimport { getCredentialFilePath } from \"./credential-store.js\";\n\nfunction normalizeCredentialBackend(\n value: unknown,\n): CredentialBackendPreference | undefined {\n if (value === \"file\" || value === \"keychain\") return value;\n // Tolerate unknown / malformed values rather than refusing to load the\n // whole config - an unrecognised backend name should degrade to \"use\n // the default\" instead of locking the user out of their CLI.\n return undefined;\n}\n\nexport function getGlobalConfigPath(): string {\n return path.join(os.homedir(), PROJECT_DIR_NAME, \"config.json\");\n}\n\n/**\n * Path to the on-disk credential file used by the file backend of\n * `CredentialStore`. Re-exported here to avoid circular / ad-hoc imports\n * in UI surface (`auth status`, `config show`, etc).\n */\nexport function getGlobalAuthPath(): string {\n return getCredentialFilePath();\n}\n\n/**\n * Load non-credential CLI configuration.\n *\n * Note: this function used to also load `authToken` / `refreshToken`\n * from `auth.json` and flatten them onto `GlobalConfig`. That shape\n * enabled the refresh-token-wipe bug: `saveGlobalConfig({ ...config })`\n * without explicit auth fields erased the stored refresh token.\n *\n * Credentials are now owned exclusively by `CredentialStore`. Callers\n * that need them must import `getCredentials()` directly.\n */\nexport async function loadGlobalConfig(): Promise<GlobalConfig> {\n const config = await readJsonFile<GlobalConfig>(getGlobalConfigPath()).catch(\n () => ({}) as GlobalConfig,\n );\n return {\n environment: config.environment,\n credentialBackend: normalizeCredentialBackend(config.credentialBackend),\n };\n}\n\n/**\n * Persist non-credential CLI configuration.\n *\n * This function cannot write credentials, by construction: the\n * `GlobalConfig` type has no credential fields. Credentials must be\n * persisted through `setCredentials` / `clearCredentials` from\n * `credential-store.ts`.\n */\nexport async function saveGlobalConfig(config: GlobalConfig): Promise<void> {\n const configDir = path.join(os.homedir(), PROJECT_DIR_NAME);\n await ensureDir(configDir);\n const normalized: GlobalConfig = {\n environment: config.environment,\n credentialBackend: normalizeCredentialBackend(config.credentialBackend),\n };\n await atomicWriteFile(\n getGlobalConfigPath(),\n `${JSON.stringify(normalized, null, 2)}\\n`,\n { mode: 0o600 },\n );\n}\n","/**\n * Single writer for the long-lived Dreamboard session credentials.\n *\n * Design invariants (enforced at the type level and tested in\n * `credential-store.test.ts`):\n *\n * 1. This module is the ONLY place in the CLI that writes credentials to\n * disk or the OS keychain. `global-config.ts` used to own both the\n * config and the credentials via `saveGlobalConfig`, which made it\n * trivial to wipe a refresh token by accident. The `GlobalConfig` type\n * no longer carries credentials, so attempting to persist one through\n * the config path is a type error.\n *\n * 2. The mutating surface is intentionally narrow:\n * - `setCredentials(c)` for refreshable sessions (both tokens present)\n * - `setAccessOnlySession(accessToken)` for the `auth set` / `config set\n * --token` power-user path, which has no refresh token by\n * construction\n * - `clearCredentials()` wipes the file entirely\n * There is no \"partial update\" API. `Credentials` requires both\n * `accessToken` and `refreshToken`, so it is impossible to persist a\n * half-populated refreshable session.\n *\n * 3. Writes go through `atomicWriteFile` + `withFileLock`, so a crash or\n * interrupt during `dreamboard sync`/`compile` cannot leave `auth.json`\n * truncated, and parallel CLI invocations cannot clobber each other's\n * rotated refresh tokens.\n *\n * 4. The on-disk JSON shape for the file backend is kept backward\n * compatible: we continue to read/write `authToken` + `refreshToken`\n * so existing users are not forced to log in again after this change.\n * A newer `accessToken` key is also accepted for read to ease any\n * future format bump.\n *\n * 5. The file backend is the default. The OS keychain is opt-in via\n * `credentialBackend: \"keychain\"` in `~/.dreamboard/config.json`\n * because on macOS the first keychain write triggers a login-password\n * prompt, and re-prompts whenever the executing Node binary's code\n * signature changes (e.g. after an `nvm`/`volta` upgrade). Users who\n * want encrypted-at-rest storage can opt in explicitly; everyone else\n * gets a zero-prompt experience.\n */\n\nimport os from \"node:os\";\nimport path from \"node:path\";\nimport { promises as fs } from \"node:fs\";\nimport { PROJECT_DIR_NAME } from \"../constants.js\";\nimport {\n atomicWriteFile,\n withFileLock,\n type FileLockOptions,\n} from \"../utils/atomic-file.js\";\n\n/** Fully refreshable session: both tokens required. */\nexport type Credentials = {\n readonly accessToken: string;\n readonly refreshToken: string;\n readonly tokenExpiresAt?: string;\n readonly clerkOAuthIssuer?: string;\n readonly clerkOAuthClientId?: string;\n readonly clerkOAuthTokenUrl?: string;\n readonly environment?: string;\n};\n\n/**\n * Raw on-disk snapshot. Either or both fields may be present. The refresh\n * coordinator only acts on snapshots that have both tokens populated.\n */\nexport type StoredSessionSnapshot = {\n readonly accessToken?: string;\n readonly refreshToken?: string;\n readonly tokenExpiresAt?: string;\n readonly clerkOAuthIssuer?: string;\n readonly clerkOAuthClientId?: string;\n readonly clerkOAuthTokenUrl?: string;\n readonly environment?: string;\n};\n\nexport type CredentialBackendName = \"file\" | \"keychain\";\n\nexport type CredentialBackend = {\n readonly name: CredentialBackendName;\n read(): Promise<StoredSessionSnapshot | null>;\n writeFull(creds: Credentials): Promise<void>;\n writeAccessOnly(accessToken: string): Promise<void>;\n clear(): Promise<void>;\n};\n\nexport type CredentialLockOps = {\n readonly backendName: CredentialBackendName;\n read(): Promise<StoredSessionSnapshot | null>;\n writeFull(creds: Credentials): Promise<void>;\n writeAccessOnly(accessToken: string): Promise<void>;\n clear(): Promise<void>;\n};\n\ntype DiskShape = Partial<{\n accessToken: string;\n authToken: string;\n refreshToken: string;\n tokenExpiresAt: string;\n clerkOAuthIssuer: string;\n clerkOAuthClientId: string;\n clerkOAuthTokenUrl: string;\n environment: string;\n}>;\n\nexport function getCredentialFilePath(): string {\n return path.join(os.homedir(), PROJECT_DIR_NAME, \"auth.json\");\n}\n\nfunction getCredentialLockPath(): string {\n return `${getCredentialFilePath()}.lock`;\n}\n\nasync function fileRead(): Promise<StoredSessionSnapshot | null> {\n const filePath = getCredentialFilePath();\n let data: string;\n try {\n data = await fs.readFile(filePath, \"utf8\");\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return null;\n throw err;\n }\n if (data.trim().length === 0) {\n return null;\n }\n let parsed: DiskShape;\n try {\n parsed = JSON.parse(data) as DiskShape;\n } catch {\n return null;\n }\n const accessToken = parsed.accessToken ?? parsed.authToken;\n const refreshToken = parsed.refreshToken;\n if (!accessToken && !refreshToken) return null;\n return {\n accessToken: accessToken || undefined,\n refreshToken: refreshToken || undefined,\n tokenExpiresAt: parsed.tokenExpiresAt || undefined,\n clerkOAuthIssuer: parsed.clerkOAuthIssuer || undefined,\n clerkOAuthClientId: parsed.clerkOAuthClientId || undefined,\n clerkOAuthTokenUrl: parsed.clerkOAuthTokenUrl || undefined,\n environment: parsed.environment || undefined,\n };\n}\n\nasync function writeFilePayload(payload: DiskShape): Promise<void> {\n await atomicWriteFile(\n getCredentialFilePath(),\n `${JSON.stringify(payload, null, 2)}\\n`,\n { mode: 0o600 },\n );\n}\n\nasync function fileWriteFull(creds: Credentials): Promise<void> {\n if (!creds.accessToken || !creds.refreshToken) {\n throw new Error(\n \"Refusing to persist credentials with an empty accessToken or refreshToken.\",\n );\n }\n await writeFilePayload({\n authToken: creds.accessToken,\n refreshToken: creds.refreshToken,\n tokenExpiresAt: creds.tokenExpiresAt,\n clerkOAuthIssuer: creds.clerkOAuthIssuer,\n clerkOAuthClientId: creds.clerkOAuthClientId,\n clerkOAuthTokenUrl: creds.clerkOAuthTokenUrl,\n environment: creds.environment,\n });\n}\n\nasync function fileWriteAccessOnly(accessToken: string): Promise<void> {\n if (!accessToken) {\n throw new Error(\"Refusing to persist an empty access token.\");\n }\n await writeFilePayload({ authToken: accessToken });\n}\n\nasync function fileClear(): Promise<void> {\n const filePath = getCredentialFilePath();\n try {\n await fs.unlink(filePath);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") throw err;\n }\n}\n\nexport const fileCredentialBackend: CredentialBackend = {\n name: \"file\",\n read: fileRead,\n writeFull: fileWriteFull,\n writeAccessOnly: fileWriteAccessOnly,\n clear: fileClear,\n};\n\nexport type BackendResolver = () =>\n | CredentialBackend\n | Promise<CredentialBackend>;\n\nlet cachedBackend: CredentialBackend | null = null;\nlet migrationCompleted = false;\nlet backendResolver: BackendResolver = defaultBackendResolver;\n\n/**\n * Default resolver precedence:\n *\n * 1. `DREAMBOARD_CREDENTIAL_BACKEND` env var (debugging / CI override).\n * - \"file\" -> force file\n * - \"keychain\" -> force keychain (falls back to file if the native\n * module or the OS keyring is unavailable)\n * - \"auto\" -> same as unset (use config)\n * - unknown -> throw so typos fail loud\n * 2. `credentialBackend` in `~/.dreamboard/config.json`.\n * - \"keychain\" -> opt in to the OS keychain (with file fallback)\n * - \"file\" / unset / malformed -> file\n * 3. Default: file backend.\n *\n * Keychain is opt-in because on macOS the OS login-keychain prompts for\n * the user's password the first time a new binary tries to write to an\n * item, and re-prompts whenever the Node binary signature changes. We\n * would rather ship a zero-prompt default and let users who care about\n * encrypted-at-rest storage enable it.\n *\n * The resolver is async because the keychain probe requires a dynamic\n * `@napi-rs/keyring` import.\n */\nasync function defaultBackendResolver(): Promise<CredentialBackend> {\n const override = (process.env.DREAMBOARD_CREDENTIAL_BACKEND ?? \"\")\n .trim()\n .toLowerCase();\n if (override === \"file\") {\n return fileCredentialBackend;\n }\n if (override && override !== \"keychain\" && override !== \"auto\") {\n // Fail loud on typos rather than silently falling back: this env\n // var exists specifically for users who are debugging auth issues\n // and need to know their override took effect.\n throw new Error(\n `Unknown DREAMBOARD_CREDENTIAL_BACKEND value \"${override}\" (expected \"file\", \"keychain\", or \"auto\").`,\n );\n }\n\n const useKeychain =\n override === \"keychain\" || (await readCredentialBackendPreference());\n if (!useKeychain) {\n return fileCredentialBackend;\n }\n\n const { tryKeychainBackend } = await import(\"./keychain-backend.js\");\n const keychain = await tryKeychainBackend();\n if (keychain.available) {\n return keychain.backend;\n }\n // The user explicitly asked for keychain but the platform can't\n // provide one (no libsecret on Linux, missing native module, etc).\n // Silently degrade to the file backend so the CLI stays usable; the\n // active backend is still visible through `dreamboard auth status`.\n return fileCredentialBackend;\n}\n\nasync function readCredentialBackendPreference(): Promise<boolean> {\n try {\n // Dynamic import to avoid a top-level cycle with `global-config.ts`\n // (which imports `getCredentialFilePath` from this module). Using\n // the async path keeps the cycle purely lazy.\n const { loadGlobalConfig } = await import(\"./global-config.js\");\n const config = await loadGlobalConfig();\n return config.credentialBackend === \"keychain\";\n } catch {\n // If the config file is unreadable or the dynamic import fails\n // (e.g. during early bootstrap), fall back to the file-backed\n // default rather than crashing credential lookups.\n return false;\n }\n}\n\n/**\n * Override which backend is used. Tests use this to inject in-memory\n * backends; production code uses the default keychain-first resolver.\n */\nexport function setCredentialBackendResolver(resolver: BackendResolver): void {\n backendResolver = resolver;\n cachedBackend = null;\n migrationCompleted = false;\n}\n\nexport async function getCredentialBackend(): Promise<CredentialBackend> {\n if (cachedBackend === null) {\n cachedBackend = await backendResolver();\n // One-time migration: if we resolved to a non-file backend and\n // `auth.json` still has credentials from the old layout, copy them\n // over and remove the file. We only do this when the new backend is\n // empty, so repeated migrations cannot stomp a newer keychain\n // session with a stale file session.\n if (!migrationCompleted && cachedBackend.name !== \"file\") {\n await migrateFromFileBackendIfNeeded(cachedBackend);\n }\n migrationCompleted = true;\n }\n return cachedBackend;\n}\n\nasync function migrateFromFileBackendIfNeeded(\n target: CredentialBackend,\n): Promise<void> {\n try {\n const [onDisk, onTarget] = await Promise.all([\n fileCredentialBackend.read(),\n target.read(),\n ]);\n if (!onDisk) return;\n if (onTarget) {\n // Target already has a session - the user has already migrated.\n // Remove the file so it cannot get re-used accidentally.\n await fileCredentialBackend.clear();\n return;\n }\n if (onDisk.accessToken && onDisk.refreshToken) {\n await target.writeFull({\n accessToken: onDisk.accessToken,\n refreshToken: onDisk.refreshToken,\n });\n } else if (onDisk.accessToken) {\n await target.writeAccessOnly(onDisk.accessToken);\n } else {\n return;\n }\n await fileCredentialBackend.clear();\n } catch {\n // Migration is best-effort. A failure here should not block CLI\n // operation; on next run the file backend is still consulted\n // directly because the keychain backend's `read` returns null and\n // callers fall through to \"missing session\" → login prompt.\n }\n}\n\nexport async function getActiveCredentialBackendName(): Promise<CredentialBackendName> {\n const backend = await getCredentialBackend();\n return backend.name;\n}\n\n/** Loose read: returns whatever is on disk, including access-only sessions. */\nexport async function getStoredSession(): Promise<StoredSessionSnapshot | null> {\n const backend = await getCredentialBackend();\n return backend.read();\n}\n\n/** Strict read: returns a refreshable pair, or null if either token is missing. */\nexport async function getCredentials(): Promise<Credentials | null> {\n const snapshot = await getStoredSession();\n if (!snapshot) return null;\n const { accessToken, refreshToken } = snapshot;\n if (!accessToken || !refreshToken) return null;\n return { accessToken, refreshToken };\n}\n\nexport async function setCredentials(creds: Credentials): Promise<void> {\n await withFileLock(getCredentialLockPath(), async () => {\n const backend = await getCredentialBackend();\n await backend.writeFull(creds);\n });\n}\n\nexport async function setAccessOnlySession(accessToken: string): Promise<void> {\n await withFileLock(getCredentialLockPath(), async () => {\n const backend = await getCredentialBackend();\n await backend.writeAccessOnly(accessToken);\n });\n}\n\nexport async function clearCredentials(): Promise<void> {\n await withFileLock(getCredentialLockPath(), async () => {\n const backend = await getCredentialBackend();\n await backend.clear();\n });\n}\n\n/**\n * Run `fn` while holding the cross-process credential lock. `fn` receives\n * an ops handle that reads/writes the active backend without re-acquiring\n * the lock (avoiding deadlock).\n *\n * This is the only correct way to perform a read-modify-write on stored\n * credentials (e.g. CLI refresh rotation) in the presence of\n * concurrent CLI invocations.\n */\nexport async function withCredentialLock<T>(\n fn: (ops: CredentialLockOps) => Promise<T>,\n options?: FileLockOptions,\n): Promise<T> {\n return withFileLock(\n getCredentialLockPath(),\n async () => {\n const backend = await getCredentialBackend();\n const ops: CredentialLockOps = {\n backendName: backend.name,\n read: () => backend.read(),\n writeFull: (creds) => backend.writeFull(creds),\n writeAccessOnly: (accessToken) => backend.writeAccessOnly(accessToken),\n clear: () => backend.clear(),\n };\n return fn(ops);\n },\n options,\n );\n}\n\n/** Test-only reset of module state. Not exported through the barrel. */\nexport function _resetCredentialStoreForTests(): void {\n cachedBackend = null;\n migrationCompleted = false;\n backendResolver = defaultBackendResolver;\n}\n"],"mappings":"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","names":["os","path","loadGlobalConfig","path","os"]}
@@ -0,0 +1,167 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ atomicWriteFile
4
+ } from "./chunk-TAEQKBJB.mjs";
5
+ import {
6
+ ensureDir,
7
+ exists,
8
+ readJsonFile
9
+ } from "./chunk-IAYRNVUC.mjs";
10
+ import {
11
+ PROJECT_CONFIG_FILE,
12
+ PROJECT_DIR_NAME,
13
+ PROJECT_STATE_FILE
14
+ } from "./chunk-H76MT5UR.mjs";
15
+
16
+ // src/build-target.ts
17
+ var injectedBuildChannel = true ? "development" : void 0;
18
+ var BUILD_CHANNEL = injectedBuildChannel === "published" ? "published" : "development";
19
+ var IS_PUBLISHED_BUILD = BUILD_CHANNEL === "published";
20
+ var PUBLISHED_ENVIRONMENT = "prod";
21
+
22
+ // src/config/project-config.ts
23
+ import path from "path";
24
+ var LEGACY_DEFAULT_DEPLOYMENT_ID = "legacy";
25
+ var LEGACY_DEFAULT_OWNER_SCOPE_ID = "default";
26
+ var LEGACY_DEFAULT_BINDING_KEY = `${LEGACY_DEFAULT_DEPLOYMENT_ID}:${LEGACY_DEFAULT_OWNER_SCOPE_ID}`;
27
+ function normalizeProjectManifest(config) {
28
+ return {
29
+ schemaVersion: 2,
30
+ projectId: config.projectId,
31
+ slug: config.slug
32
+ };
33
+ }
34
+ function normalizeProjectBinding(config) {
35
+ return {
36
+ deploymentId: config.deploymentId ?? LEGACY_DEFAULT_DEPLOYMENT_ID,
37
+ ownerScopeId: config.ownerScopeId ?? LEGACY_DEFAULT_OWNER_SCOPE_ID,
38
+ gameId: config.gameId,
39
+ remoteHeadDigest: config.remoteHeadDigest,
40
+ jobId: config.jobId,
41
+ agentManaged: config.agentManaged,
42
+ workspacePrepared: config.workspacePrepared,
43
+ allowCreateGame: config.allowCreateGame,
44
+ environment: config.environment,
45
+ authoring: config.authoring,
46
+ compile: config.compile,
47
+ localMaintainerRegistry: config.localMaintainerRegistry,
48
+ apiBaseUrl: config.apiBaseUrl,
49
+ webBaseUrl: config.webBaseUrl,
50
+ packageManifest: config.packageManifest,
51
+ environmentManifest: config.environmentManifest
52
+ };
53
+ }
54
+ function normalizeProjectState(config, existing) {
55
+ const binding = normalizeProjectBinding(config);
56
+ const bindingKey = config.bindingKey || `${binding.deploymentId}:${binding.ownerScopeId}`;
57
+ return {
58
+ schemaVersion: 1,
59
+ bindings: {
60
+ ...existing?.bindings ?? {},
61
+ [bindingKey]: binding
62
+ }
63
+ };
64
+ }
65
+ function mergeManifestAndBinding(manifest, binding, bindingKey) {
66
+ return {
67
+ ...manifest,
68
+ ...binding ?? {
69
+ deploymentId: LEGACY_DEFAULT_DEPLOYMENT_ID,
70
+ ownerScopeId: LEGACY_DEFAULT_OWNER_SCOPE_ID
71
+ },
72
+ gameId: binding?.gameId ?? manifest.projectId,
73
+ bindingKey: bindingKey ?? `${binding?.deploymentId ?? LEGACY_DEFAULT_DEPLOYMENT_ID}:${binding?.ownerScopeId ?? LEGACY_DEFAULT_OWNER_SCOPE_ID}`
74
+ };
75
+ }
76
+ function isProjectManifestV2(value) {
77
+ const candidate = value;
78
+ return candidate?.schemaVersion === 2 && typeof candidate.projectId === "string" && typeof candidate.slug === "string";
79
+ }
80
+ function normalizeLegacyProjectConfig(config) {
81
+ return {
82
+ schemaVersion: 2,
83
+ projectId: config.projectId ?? config.gameId,
84
+ slug: config.slug,
85
+ bindingKey: LEGACY_DEFAULT_BINDING_KEY,
86
+ deploymentId: LEGACY_DEFAULT_DEPLOYMENT_ID,
87
+ ownerScopeId: LEGACY_DEFAULT_OWNER_SCOPE_ID,
88
+ gameId: config.gameId,
89
+ jobId: config.jobId,
90
+ agentManaged: config.agentManaged,
91
+ workspacePrepared: config.workspacePrepared,
92
+ allowCreateGame: config.allowCreateGame,
93
+ environment: config.environment,
94
+ authoring: config.authoring,
95
+ compile: config.compile,
96
+ localMaintainerRegistry: config.localMaintainerRegistry,
97
+ apiBaseUrl: config.apiBaseUrl,
98
+ webBaseUrl: config.webBaseUrl,
99
+ packageManifest: config.packageManifest,
100
+ environmentManifest: config.environmentManifest
101
+ };
102
+ }
103
+ async function loadProjectEnvironmentState(rootDir) {
104
+ const filePath = path.join(rootDir, PROJECT_DIR_NAME, PROJECT_STATE_FILE);
105
+ if (!await exists(filePath)) {
106
+ return { schemaVersion: 1, bindings: {} };
107
+ }
108
+ const state = await readJsonFile(filePath);
109
+ return state.schemaVersion === 1 && state.bindings ? state : { schemaVersion: 1, bindings: {} };
110
+ }
111
+ async function loadProjectConfig(rootDir) {
112
+ const filePath = path.join(rootDir, PROJECT_DIR_NAME, PROJECT_CONFIG_FILE);
113
+ const rawConfig = await readJsonFile(
114
+ filePath
115
+ );
116
+ if (!isProjectManifestV2(rawConfig)) {
117
+ const migrated = normalizeLegacyProjectConfig(
118
+ rawConfig
119
+ );
120
+ await updateProjectState(rootDir, migrated);
121
+ return migrated;
122
+ }
123
+ const state = await loadProjectEnvironmentState(rootDir);
124
+ const entries = Object.entries(state.bindings);
125
+ const [bindingKey, binding] = entries.find(([key]) => key !== LEGACY_DEFAULT_BINDING_KEY) ?? entries.find(([key]) => key === LEGACY_DEFAULT_BINDING_KEY) ?? [];
126
+ return mergeManifestAndBinding(rawConfig, binding, bindingKey);
127
+ }
128
+ async function updateProjectState(rootDir, config) {
129
+ const dir = path.join(rootDir, PROJECT_DIR_NAME);
130
+ await ensureDir(dir);
131
+ const existingState = await loadProjectEnvironmentState(rootDir);
132
+ await atomicWriteFile(
133
+ path.join(dir, PROJECT_CONFIG_FILE),
134
+ `${JSON.stringify(normalizeProjectManifest(config), null, 2)}
135
+ `,
136
+ { mode: 420 }
137
+ );
138
+ await atomicWriteFile(
139
+ path.join(dir, PROJECT_STATE_FILE),
140
+ `${JSON.stringify(normalizeProjectState(config, existingState), null, 2)}
141
+ `,
142
+ { mode: 384 }
143
+ );
144
+ }
145
+ async function findProjectRoot(startDir) {
146
+ let current = path.resolve(startDir);
147
+ for (let i = 0; i < 25; i++) {
148
+ const candidate = path.join(current, PROJECT_DIR_NAME, PROJECT_CONFIG_FILE);
149
+ if (await exists(candidate)) {
150
+ return current;
151
+ }
152
+ const parent = path.dirname(current);
153
+ if (parent === current) break;
154
+ current = parent;
155
+ }
156
+ return null;
157
+ }
158
+
159
+ export {
160
+ BUILD_CHANNEL,
161
+ IS_PUBLISHED_BUILD,
162
+ PUBLISHED_ENVIRONMENT,
163
+ loadProjectConfig,
164
+ updateProjectState,
165
+ findProjectRoot
166
+ };
167
+ //# sourceMappingURL=chunk-776W3UGV.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/build-target.ts","../../src/config/project-config.ts"],"sourcesContent":["declare const __DREAMBOARD_BUILD_CHANNEL__: string | undefined;\n\nconst injectedBuildChannel =\n typeof __DREAMBOARD_BUILD_CHANNEL__ === \"string\"\n ? __DREAMBOARD_BUILD_CHANNEL__\n : undefined;\n\nexport const BUILD_CHANNEL =\n injectedBuildChannel === \"published\" ? \"published\" : \"development\";\n\nexport const IS_PUBLISHED_BUILD = BUILD_CHANNEL === \"published\";\nexport const PUBLISHED_ENVIRONMENT = \"prod\" as const;\n","import path from \"node:path\";\nimport type {\n LegacyProjectConfigV1,\n ProjectConfig,\n ProjectEnvironmentBindingV1,\n ProjectEnvironmentStateV1,\n ProjectManifestV2,\n} from \"../types.js\";\nimport {\n PROJECT_DIR_NAME,\n PROJECT_CONFIG_FILE,\n PROJECT_STATE_FILE,\n} from \"../constants.js\";\nimport { ensureDir, exists, readJsonFile } from \"../utils/fs.js\";\nimport { atomicWriteFile } from \"../utils/atomic-file.js\";\n\nconst LEGACY_DEFAULT_DEPLOYMENT_ID = \"legacy\";\nconst LEGACY_DEFAULT_OWNER_SCOPE_ID = \"default\";\nconst LEGACY_DEFAULT_BINDING_KEY = `${LEGACY_DEFAULT_DEPLOYMENT_ID}:${LEGACY_DEFAULT_OWNER_SCOPE_ID}`;\n\nfunction normalizeProjectManifest(config: ProjectConfig): ProjectManifestV2 {\n return {\n schemaVersion: 2,\n projectId: config.projectId,\n slug: config.slug,\n };\n}\n\nfunction normalizeProjectBinding(\n config: ProjectConfig,\n): ProjectEnvironmentBindingV1 {\n return {\n deploymentId: config.deploymentId ?? LEGACY_DEFAULT_DEPLOYMENT_ID,\n ownerScopeId: config.ownerScopeId ?? LEGACY_DEFAULT_OWNER_SCOPE_ID,\n gameId: config.gameId,\n remoteHeadDigest: config.remoteHeadDigest,\n jobId: config.jobId,\n agentManaged: config.agentManaged,\n workspacePrepared: config.workspacePrepared,\n allowCreateGame: config.allowCreateGame,\n environment: config.environment,\n authoring: config.authoring,\n compile: config.compile,\n localMaintainerRegistry: config.localMaintainerRegistry,\n apiBaseUrl: config.apiBaseUrl,\n webBaseUrl: config.webBaseUrl,\n packageManifest: config.packageManifest,\n environmentManifest: config.environmentManifest,\n };\n}\n\nfunction normalizeProjectState(\n config: ProjectConfig,\n existing?: ProjectEnvironmentStateV1,\n): ProjectEnvironmentStateV1 {\n const binding = normalizeProjectBinding(config);\n const bindingKey =\n config.bindingKey || `${binding.deploymentId}:${binding.ownerScopeId}`;\n return {\n schemaVersion: 1,\n bindings: {\n ...(existing?.bindings ?? {}),\n [bindingKey]: binding,\n },\n };\n}\n\nfunction mergeManifestAndBinding(\n manifest: ProjectManifestV2,\n binding: ProjectEnvironmentBindingV1 | undefined,\n bindingKey: string | undefined,\n): ProjectConfig {\n return {\n ...manifest,\n ...(binding ?? {\n deploymentId: LEGACY_DEFAULT_DEPLOYMENT_ID,\n ownerScopeId: LEGACY_DEFAULT_OWNER_SCOPE_ID,\n }),\n gameId: binding?.gameId ?? manifest.projectId,\n bindingKey:\n bindingKey ??\n `${binding?.deploymentId ?? LEGACY_DEFAULT_DEPLOYMENT_ID}:${\n binding?.ownerScopeId ?? LEGACY_DEFAULT_OWNER_SCOPE_ID\n }`,\n };\n}\n\nfunction isProjectManifestV2(value: unknown): value is ProjectManifestV2 {\n const candidate = value as Partial<ProjectManifestV2>;\n return (\n candidate?.schemaVersion === 2 &&\n typeof candidate.projectId === \"string\" &&\n typeof candidate.slug === \"string\"\n );\n}\n\nfunction normalizeLegacyProjectConfig(\n config: LegacyProjectConfigV1 & { projectId?: string },\n): ProjectConfig {\n return {\n schemaVersion: 2,\n projectId: config.projectId ?? config.gameId,\n slug: config.slug,\n bindingKey: LEGACY_DEFAULT_BINDING_KEY,\n deploymentId: LEGACY_DEFAULT_DEPLOYMENT_ID,\n ownerScopeId: LEGACY_DEFAULT_OWNER_SCOPE_ID,\n gameId: config.gameId,\n jobId: config.jobId,\n agentManaged: config.agentManaged,\n workspacePrepared: config.workspacePrepared,\n allowCreateGame: config.allowCreateGame,\n environment: config.environment,\n authoring: config.authoring,\n compile: config.compile,\n localMaintainerRegistry: config.localMaintainerRegistry,\n apiBaseUrl: config.apiBaseUrl,\n webBaseUrl: config.webBaseUrl,\n packageManifest: config.packageManifest,\n environmentManifest: config.environmentManifest,\n };\n}\n\nasync function loadProjectEnvironmentState(\n rootDir: string,\n): Promise<ProjectEnvironmentStateV1> {\n const filePath = path.join(rootDir, PROJECT_DIR_NAME, PROJECT_STATE_FILE);\n if (!(await exists(filePath))) {\n return { schemaVersion: 1, bindings: {} };\n }\n const state = await readJsonFile<ProjectEnvironmentStateV1>(filePath);\n return state.schemaVersion === 1 && state.bindings\n ? state\n : { schemaVersion: 1, bindings: {} };\n}\n\nexport async function loadProjectConfig(\n rootDir: string,\n): Promise<ProjectConfig> {\n const filePath = path.join(rootDir, PROJECT_DIR_NAME, PROJECT_CONFIG_FILE);\n const rawConfig = await readJsonFile<ProjectManifestV2 | LegacyProjectConfigV1>(\n filePath,\n );\n if (!isProjectManifestV2(rawConfig)) {\n const migrated = normalizeLegacyProjectConfig(\n rawConfig as LegacyProjectConfigV1,\n );\n await updateProjectState(rootDir, migrated);\n return migrated;\n }\n\n const state = await loadProjectEnvironmentState(rootDir);\n const entries = Object.entries(state.bindings);\n const [bindingKey, binding] =\n entries.find(([key]) => key !== LEGACY_DEFAULT_BINDING_KEY) ??\n entries.find(([key]) => key === LEGACY_DEFAULT_BINDING_KEY) ??\n [];\n return mergeManifestAndBinding(rawConfig, binding, bindingKey);\n}\n\nexport async function updateProjectState(\n rootDir: string,\n config: ProjectConfig,\n): Promise<void> {\n const dir = path.join(rootDir, PROJECT_DIR_NAME);\n await ensureDir(dir);\n const existingState = await loadProjectEnvironmentState(rootDir);\n await atomicWriteFile(\n path.join(dir, PROJECT_CONFIG_FILE),\n `${JSON.stringify(normalizeProjectManifest(config), null, 2)}\\n`,\n { mode: 0o644 },\n );\n await atomicWriteFile(\n path.join(dir, PROJECT_STATE_FILE),\n `${JSON.stringify(normalizeProjectState(config, existingState), null, 2)}\\n`,\n { mode: 0o600 },\n );\n}\n\nexport async function findProjectRoot(\n startDir: string,\n): Promise<string | null> {\n let current = path.resolve(startDir);\n for (let i = 0; i < 25; i++) {\n const candidate = path.join(current, PROJECT_DIR_NAME, PROJECT_CONFIG_FILE);\n if (await exists(candidate)) {\n return current;\n }\n const parent = path.dirname(current);\n if (parent === current) break;\n current = parent;\n }\n return null;\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAEA,IAAM,uBACJ,OACI,gBACA;AAEC,IAAM,gBACX,yBAAyB,cAAc,cAAc;AAEhD,IAAM,qBAAqB,kBAAkB;AAC7C,IAAM,wBAAwB;;;ACXrC,OAAO,UAAU;AAgBjB,IAAM,+BAA+B;AACrC,IAAM,gCAAgC;AACtC,IAAM,6BAA6B,GAAG,4BAA4B,IAAI,6BAA6B;AAEnG,SAAS,yBAAyB,QAA0C;AAC1E,SAAO;AAAA,IACL,eAAe;AAAA,IACf,WAAW,OAAO;AAAA,IAClB,MAAM,OAAO;AAAA,EACf;AACF;AAEA,SAAS,wBACP,QAC6B;AAC7B,SAAO;AAAA,IACL,cAAc,OAAO,gBAAgB;AAAA,IACrC,cAAc,OAAO,gBAAgB;AAAA,IACrC,QAAQ,OAAO;AAAA,IACf,kBAAkB,OAAO;AAAA,IACzB,OAAO,OAAO;AAAA,IACd,cAAc,OAAO;AAAA,IACrB,mBAAmB,OAAO;AAAA,IAC1B,iBAAiB,OAAO;AAAA,IACxB,aAAa,OAAO;AAAA,IACpB,WAAW,OAAO;AAAA,IAClB,SAAS,OAAO;AAAA,IAChB,yBAAyB,OAAO;AAAA,IAChC,YAAY,OAAO;AAAA,IACnB,YAAY,OAAO;AAAA,IACnB,iBAAiB,OAAO;AAAA,IACxB,qBAAqB,OAAO;AAAA,EAC9B;AACF;AAEA,SAAS,sBACP,QACA,UAC2B;AAC3B,QAAM,UAAU,wBAAwB,MAAM;AAC9C,QAAM,aACJ,OAAO,cAAc,GAAG,QAAQ,YAAY,IAAI,QAAQ,YAAY;AACtE,SAAO;AAAA,IACL,eAAe;AAAA,IACf,UAAU;AAAA,MACR,GAAI,UAAU,YAAY,CAAC;AAAA,MAC3B,CAAC,UAAU,GAAG;AAAA,IAChB;AAAA,EACF;AACF;AAEA,SAAS,wBACP,UACA,SACA,YACe;AACf,SAAO;AAAA,IACL,GAAG;AAAA,IACH,GAAI,WAAW;AAAA,MACb,cAAc;AAAA,MACd,cAAc;AAAA,IAChB;AAAA,IACA,QAAQ,SAAS,UAAU,SAAS;AAAA,IACpC,YACE,cACA,GAAG,SAAS,gBAAgB,4BAA4B,IACtD,SAAS,gBAAgB,6BAC3B;AAAA,EACJ;AACF;AAEA,SAAS,oBAAoB,OAA4C;AACvE,QAAM,YAAY;AAClB,SACE,WAAW,kBAAkB,KAC7B,OAAO,UAAU,cAAc,YAC/B,OAAO,UAAU,SAAS;AAE9B;AAEA,SAAS,6BACP,QACe;AACf,SAAO;AAAA,IACL,eAAe;AAAA,IACf,WAAW,OAAO,aAAa,OAAO;AAAA,IACtC,MAAM,OAAO;AAAA,IACb,YAAY;AAAA,IACZ,cAAc;AAAA,IACd,cAAc;AAAA,IACd,QAAQ,OAAO;AAAA,IACf,OAAO,OAAO;AAAA,IACd,cAAc,OAAO;AAAA,IACrB,mBAAmB,OAAO;AAAA,IAC1B,iBAAiB,OAAO;AAAA,IACxB,aAAa,OAAO;AAAA,IACpB,WAAW,OAAO;AAAA,IAClB,SAAS,OAAO;AAAA,IAChB,yBAAyB,OAAO;AAAA,IAChC,YAAY,OAAO;AAAA,IACnB,YAAY,OAAO;AAAA,IACnB,iBAAiB,OAAO;AAAA,IACxB,qBAAqB,OAAO;AAAA,EAC9B;AACF;AAEA,eAAe,4BACb,SACoC;AACpC,QAAM,WAAW,KAAK,KAAK,SAAS,kBAAkB,kBAAkB;AACxE,MAAI,CAAE,MAAM,OAAO,QAAQ,GAAI;AAC7B,WAAO,EAAE,eAAe,GAAG,UAAU,CAAC,EAAE;AAAA,EAC1C;AACA,QAAM,QAAQ,MAAM,aAAwC,QAAQ;AACpE,SAAO,MAAM,kBAAkB,KAAK,MAAM,WACtC,QACA,EAAE,eAAe,GAAG,UAAU,CAAC,EAAE;AACvC;AAEA,eAAsB,kBACpB,SACwB;AACxB,QAAM,WAAW,KAAK,KAAK,SAAS,kBAAkB,mBAAmB;AACzE,QAAM,YAAY,MAAM;AAAA,IACtB;AAAA,EACF;AACA,MAAI,CAAC,oBAAoB,SAAS,GAAG;AACnC,UAAM,WAAW;AAAA,MACf;AAAA,IACF;AACA,UAAM,mBAAmB,SAAS,QAAQ;AAC1C,WAAO;AAAA,EACT;AAEA,QAAM,QAAQ,MAAM,4BAA4B,OAAO;AACvD,QAAM,UAAU,OAAO,QAAQ,MAAM,QAAQ;AAC7C,QAAM,CAAC,YAAY,OAAO,IACxB,QAAQ,KAAK,CAAC,CAAC,GAAG,MAAM,QAAQ,0BAA0B,KAC1D,QAAQ,KAAK,CAAC,CAAC,GAAG,MAAM,QAAQ,0BAA0B,KAC1D,CAAC;AACH,SAAO,wBAAwB,WAAW,SAAS,UAAU;AAC/D;AAEA,eAAsB,mBACpB,SACA,QACe;AACf,QAAM,MAAM,KAAK,KAAK,SAAS,gBAAgB;AAC/C,QAAM,UAAU,GAAG;AACnB,QAAM,gBAAgB,MAAM,4BAA4B,OAAO;AAC/D,QAAM;AAAA,IACJ,KAAK,KAAK,KAAK,mBAAmB;AAAA,IAClC,GAAG,KAAK,UAAU,yBAAyB,MAAM,GAAG,MAAM,CAAC,CAAC;AAAA;AAAA,IAC5D,EAAE,MAAM,IAAM;AAAA,EAChB;AACA,QAAM;AAAA,IACJ,KAAK,KAAK,KAAK,kBAAkB;AAAA,IACjC,GAAG,KAAK,UAAU,sBAAsB,QAAQ,aAAa,GAAG,MAAM,CAAC,CAAC;AAAA;AAAA,IACxE,EAAE,MAAM,IAAM;AAAA,EAChB;AACF;AAEA,eAAsB,gBACpB,UACwB;AACxB,MAAI,UAAU,KAAK,QAAQ,QAAQ;AACnC,WAAS,IAAI,GAAG,IAAI,IAAI,KAAK;AAC3B,UAAM,YAAY,KAAK,KAAK,SAAS,kBAAkB,mBAAmB;AAC1E,QAAI,MAAM,OAAO,SAAS,GAAG;AAC3B,aAAO;AAAA,IACT;AACA,UAAM,SAAS,KAAK,QAAQ,OAAO;AACnC,QAAI,WAAW,QAAS;AACxB,cAAU;AAAA,EACZ;AACA,SAAO;AACT;","names":[]}