@dreamboard-games/cli 0.1.30-alpha.17 → 0.1.30-alpha.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/dist/agent-verifier/agent-workspace-verifier.mjs +12 -12
- package/dist/agent-verifier/{chunk-LKQ557TJ.mjs → chunk-334H4LE4.mjs} +3 -3
- package/dist/agent-verifier/{chunk-DWLTCUUX.mjs → chunk-3Y4FRMTK.mjs} +6 -6
- package/dist/agent-verifier/{chunk-NFL3Z4Z7.mjs → chunk-5D3OJBDT.mjs} +12 -8
- package/dist/agent-verifier/{chunk-NFL3Z4Z7.mjs.map → chunk-5D3OJBDT.mjs.map} +1 -1
- package/dist/agent-verifier/{chunk-AXXUGU7Q.mjs → chunk-6AKXIY37.mjs} +54 -36
- package/dist/agent-verifier/chunk-6AKXIY37.mjs.map +1 -0
- package/dist/agent-verifier/{chunk-H3XNWKJU.mjs → chunk-7LFDFXLS.mjs} +2 -2
- package/dist/agent-verifier/{chunk-CO3BRUD6.mjs → chunk-AWZ4M4NS.mjs} +15 -3
- package/dist/agent-verifier/chunk-AWZ4M4NS.mjs.map +1 -0
- package/dist/agent-verifier/{chunk-S4ZQSUPB.mjs → chunk-H5L4KK4Y.mjs} +9 -12
- package/dist/agent-verifier/chunk-H5L4KK4Y.mjs.map +1 -0
- package/dist/agent-verifier/{chunk-V6AQDR7W.mjs → chunk-HUBV22JQ.mjs} +3 -3
- package/dist/agent-verifier/chunk-HUBV22JQ.mjs.map +1 -0
- package/dist/agent-verifier/{chunk-A67WUYN2.mjs → chunk-LEWM26XR.mjs} +4 -4
- package/dist/agent-verifier/chunk-LEWM26XR.mjs.map +1 -0
- package/dist/agent-verifier/{chunk-WAFBU5U7.mjs → chunk-OJFZVGEL.mjs} +38 -13
- package/dist/agent-verifier/chunk-OJFZVGEL.mjs.map +1 -0
- package/dist/agent-verifier/{chunk-G2ECODRB.mjs → chunk-PLXXH5LY.mjs} +2 -2
- package/dist/agent-verifier/{chunk-DPYC2NDB.mjs → chunk-UMW24KZI.mjs} +2 -2
- package/dist/agent-verifier/{chunk-5GCZZ6NW.mjs → chunk-ZOR5FTIG.mjs} +2 -2
- package/dist/agent-verifier/{compile-SDQ25N7K.mjs → compile-MO2URO5Z.mjs} +12 -12
- package/dist/agent-verifier/{global-config-6UGFPLDA.mjs → global-config-SXR6X3OZ.mjs} +3 -3
- package/dist/agent-verifier/{keychain-backend-BQLW5VEC.mjs → keychain-backend-UF3Z26JM.mjs} +1 -1
- package/dist/agent-verifier/keychain-backend-UF3Z26JM.mjs.map +1 -0
- package/dist/agent-verifier/{local-files-WPHUV6GU.mjs → local-files-DAFIR7SN.mjs} +4 -4
- package/dist/agent-verifier/{materialize-workspace-ENPMB66P.mjs → materialize-workspace-PWNT6HQK.mjs} +6 -6
- package/dist/agent-verifier/{reducer-native-test-harness-GY2CCQWN.mjs → reducer-native-test-harness-X2KQYSCD.mjs} +8 -8
- package/dist/agent-verifier/{static-scaffold-K52P52SV.mjs → static-scaffold-HXUQLJVN.mjs} +4 -4
- package/dist/agent-verifier/{sync-JSATJXVJ.mjs → sync-5YM4CSXL.mjs} +13 -13
- package/dist/agent-verifier/{test-LQAGEQLY.mjs → test-CNNVTFIG.mjs} +11 -11
- package/dist/agent-verifier/{workspace-codegen-4IWICKLB.mjs → workspace-codegen-SPPVHURX.mjs} +3 -3
- package/dist/authoring-compatibility-internal.js +1 -1
- package/dist/{chunk-ITT6Y7MM.js → chunk-R6RB4EKH.js} +23 -9
- package/dist/chunk-R6RB4EKH.js.map +1 -0
- package/dist/{chunk-AVOAT522.js → chunk-VWMKJL4A.js} +49 -38
- package/dist/chunk-VWMKJL4A.js.map +1 -0
- package/dist/{chunk-LWJUF42S.js → chunk-YRSE5DLH.js} +45 -23
- package/dist/{chunk-LWJUF42S.js.map → chunk-YRSE5DLH.js.map} +1 -1
- package/dist/{global-config-NLGAFSRU.js → global-config-UHGWFJIK.js} +2 -2
- package/dist/index.js +403 -12
- package/dist/index.js.map +1 -1
- package/dist/internal.js +3 -3
- package/dist/{keychain-backend-47LZ5IX5.js → keychain-backend-GO34KGTG.js} +1 -1
- package/dist/keychain-backend-GO34KGTG.js.map +1 -0
- package/package.json +1 -1
- package/release/authoring-release-set.json +4 -4
- package/dist/agent-verifier/chunk-A67WUYN2.mjs.map +0 -1
- package/dist/agent-verifier/chunk-AXXUGU7Q.mjs.map +0 -1
- package/dist/agent-verifier/chunk-CO3BRUD6.mjs.map +0 -1
- package/dist/agent-verifier/chunk-S4ZQSUPB.mjs.map +0 -1
- package/dist/agent-verifier/chunk-V6AQDR7W.mjs.map +0 -1
- package/dist/agent-verifier/chunk-WAFBU5U7.mjs.map +0 -1
- package/dist/agent-verifier/keychain-backend-BQLW5VEC.mjs.map +0 -1
- package/dist/chunk-AVOAT522.js.map +0 -1
- package/dist/chunk-ITT6Y7MM.js.map +0 -1
- package/dist/keychain-backend-47LZ5IX5.js.map +0 -1
- /package/dist/agent-verifier/{chunk-LKQ557TJ.mjs.map → chunk-334H4LE4.mjs.map} +0 -0
- /package/dist/agent-verifier/{chunk-DWLTCUUX.mjs.map → chunk-3Y4FRMTK.mjs.map} +0 -0
- /package/dist/agent-verifier/{chunk-H3XNWKJU.mjs.map → chunk-7LFDFXLS.mjs.map} +0 -0
- /package/dist/agent-verifier/{chunk-G2ECODRB.mjs.map → chunk-PLXXH5LY.mjs.map} +0 -0
- /package/dist/agent-verifier/{chunk-DPYC2NDB.mjs.map → chunk-UMW24KZI.mjs.map} +0 -0
- /package/dist/agent-verifier/{chunk-5GCZZ6NW.mjs.map → chunk-ZOR5FTIG.mjs.map} +0 -0
- /package/dist/agent-verifier/{compile-SDQ25N7K.mjs.map → compile-MO2URO5Z.mjs.map} +0 -0
- /package/dist/agent-verifier/{global-config-6UGFPLDA.mjs.map → global-config-SXR6X3OZ.mjs.map} +0 -0
- /package/dist/agent-verifier/{local-files-WPHUV6GU.mjs.map → local-files-DAFIR7SN.mjs.map} +0 -0
- /package/dist/agent-verifier/{materialize-workspace-ENPMB66P.mjs.map → materialize-workspace-PWNT6HQK.mjs.map} +0 -0
- /package/dist/agent-verifier/{reducer-native-test-harness-GY2CCQWN.mjs.map → reducer-native-test-harness-X2KQYSCD.mjs.map} +0 -0
- /package/dist/agent-verifier/{static-scaffold-K52P52SV.mjs.map → static-scaffold-HXUQLJVN.mjs.map} +0 -0
- /package/dist/agent-verifier/{sync-JSATJXVJ.mjs.map → sync-5YM4CSXL.mjs.map} +0 -0
- /package/dist/agent-verifier/{test-LQAGEQLY.mjs.map → test-CNNVTFIG.mjs.map} +0 -0
- /package/dist/agent-verifier/{workspace-codegen-4IWICKLB.mjs.map → workspace-codegen-SPPVHURX.mjs.map} +0 -0
- /package/dist/{global-config-NLGAFSRU.js.map → global-config-UHGWFJIK.js.map} +0 -0
|
@@ -15,20 +15,36 @@ import path2 from "path";
|
|
|
15
15
|
import os from "os";
|
|
16
16
|
import path from "path";
|
|
17
17
|
import { promises as fs } from "fs";
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
var IS_PUBLISHED_BUILD = BUILD_CHANNEL === "published";
|
|
23
|
-
var PUBLISHED_ENVIRONMENT = "prod";
|
|
24
|
-
|
|
25
|
-
// src/config/credential-store.ts
|
|
18
|
+
var credentialDirectoryOverrideForTests = null;
|
|
19
|
+
function getCredentialDirectory() {
|
|
20
|
+
return credentialDirectoryOverrideForTests ?? path.join(os.homedir(), PROJECT_DIR_NAME);
|
|
21
|
+
}
|
|
26
22
|
function getCredentialFilePath() {
|
|
27
|
-
return path.join(
|
|
23
|
+
return path.join(getCredentialDirectory(), "auth.json");
|
|
24
|
+
}
|
|
25
|
+
function getCredentialAuditLogPath() {
|
|
26
|
+
return path.join(getCredentialDirectory(), "auth-events.log");
|
|
28
27
|
}
|
|
29
28
|
function getCredentialLockPath() {
|
|
30
29
|
return `${getCredentialFilePath()}.lock`;
|
|
31
30
|
}
|
|
31
|
+
async function appendCredentialAuditEvent(event) {
|
|
32
|
+
try {
|
|
33
|
+
const logPath = getCredentialAuditLogPath();
|
|
34
|
+
await fs.mkdir(path.dirname(logPath), { recursive: true, mode: 448 });
|
|
35
|
+
await fs.appendFile(
|
|
36
|
+
logPath,
|
|
37
|
+
`${JSON.stringify({
|
|
38
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
39
|
+
pid: process.pid,
|
|
40
|
+
...event
|
|
41
|
+
})}
|
|
42
|
+
`,
|
|
43
|
+
{ mode: 384 }
|
|
44
|
+
);
|
|
45
|
+
} catch {
|
|
46
|
+
}
|
|
47
|
+
}
|
|
32
48
|
async function fileRead() {
|
|
33
49
|
const filePath = getCredentialFilePath();
|
|
34
50
|
let data;
|
|
@@ -94,12 +110,27 @@ async function fileWriteAccessOnly(accessToken) {
|
|
|
94
110
|
}
|
|
95
111
|
await writeFilePayload({ authToken: accessToken });
|
|
96
112
|
}
|
|
97
|
-
async function fileClear() {
|
|
113
|
+
async function fileClear(reason = "credential_store_clear") {
|
|
98
114
|
const filePath = getCredentialFilePath();
|
|
99
115
|
try {
|
|
100
116
|
await fs.unlink(filePath);
|
|
117
|
+
await appendCredentialAuditEvent({
|
|
118
|
+
event: "auth_file_deleted",
|
|
119
|
+
reason,
|
|
120
|
+
authPath: filePath,
|
|
121
|
+
backend: "file"
|
|
122
|
+
});
|
|
101
123
|
} catch (err) {
|
|
102
|
-
if (err.code
|
|
124
|
+
if (err.code === "ENOENT") {
|
|
125
|
+
await appendCredentialAuditEvent({
|
|
126
|
+
event: "auth_file_delete_missing",
|
|
127
|
+
reason,
|
|
128
|
+
authPath: filePath,
|
|
129
|
+
backend: "file"
|
|
130
|
+
});
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
throw err;
|
|
103
134
|
}
|
|
104
135
|
}
|
|
105
136
|
var fileCredentialBackend = {
|
|
@@ -121,19 +152,6 @@ var migrationCompleted = false;
|
|
|
121
152
|
var backendResolver = defaultBackendResolver;
|
|
122
153
|
async function defaultBackendResolver() {
|
|
123
154
|
const override = (process.env.DREAMBOARD_CREDENTIAL_BACKEND ?? "").trim().toLowerCase();
|
|
124
|
-
if (IS_PUBLISHED_BUILD) {
|
|
125
|
-
if (override && override !== "keychain" && override !== "auto") {
|
|
126
|
-
throw new CredentialStoreUnavailableError(
|
|
127
|
-
"published builds require the OS credential store"
|
|
128
|
-
);
|
|
129
|
-
}
|
|
130
|
-
const { tryKeychainBackend: tryKeychainBackend2 } = await import("./keychain-backend-47LZ5IX5.js");
|
|
131
|
-
const keychain2 = await tryKeychainBackend2();
|
|
132
|
-
if (keychain2.available) {
|
|
133
|
-
return keychain2.backend;
|
|
134
|
-
}
|
|
135
|
-
throw new CredentialStoreUnavailableError(keychain2.reason);
|
|
136
|
-
}
|
|
137
155
|
if (override === "file") {
|
|
138
156
|
return fileCredentialBackend;
|
|
139
157
|
}
|
|
@@ -146,7 +164,7 @@ async function defaultBackendResolver() {
|
|
|
146
164
|
if (!useKeychain) {
|
|
147
165
|
return fileCredentialBackend;
|
|
148
166
|
}
|
|
149
|
-
const { tryKeychainBackend } = await import("./keychain-backend-
|
|
167
|
+
const { tryKeychainBackend } = await import("./keychain-backend-GO34KGTG.js");
|
|
150
168
|
const keychain = await tryKeychainBackend();
|
|
151
169
|
if (keychain.available) {
|
|
152
170
|
return keychain.backend;
|
|
@@ -155,7 +173,7 @@ async function defaultBackendResolver() {
|
|
|
155
173
|
}
|
|
156
174
|
async function readCredentialBackendPreference() {
|
|
157
175
|
try {
|
|
158
|
-
const { loadGlobalConfig: loadGlobalConfig2 } = await import("./global-config-
|
|
176
|
+
const { loadGlobalConfig: loadGlobalConfig2 } = await import("./global-config-UHGWFJIK.js");
|
|
159
177
|
const config = await loadGlobalConfig2();
|
|
160
178
|
return config.credentialBackend === "keychain";
|
|
161
179
|
} catch {
|
|
@@ -166,9 +184,7 @@ async function getCredentialBackend() {
|
|
|
166
184
|
if (cachedBackend === null) {
|
|
167
185
|
cachedBackend = await backendResolver();
|
|
168
186
|
if (!migrationCompleted && cachedBackend.name !== "file") {
|
|
169
|
-
await migrateFromFileBackendIfNeeded(cachedBackend
|
|
170
|
-
failClosed: IS_PUBLISHED_BUILD
|
|
171
|
-
});
|
|
187
|
+
await migrateFromFileBackendIfNeeded(cachedBackend);
|
|
172
188
|
}
|
|
173
189
|
migrationCompleted = true;
|
|
174
190
|
}
|
|
@@ -182,7 +198,6 @@ async function migrateFromFileBackendIfNeeded(target, options = {}) {
|
|
|
182
198
|
]);
|
|
183
199
|
if (!onDisk) return;
|
|
184
200
|
if (onTarget) {
|
|
185
|
-
await fileCredentialBackend.clear();
|
|
186
201
|
return;
|
|
187
202
|
}
|
|
188
203
|
if (onDisk.accessToken && onDisk.refreshToken) {
|
|
@@ -208,7 +223,6 @@ async function migrateFromFileBackendIfNeeded(target, options = {}) {
|
|
|
208
223
|
} else {
|
|
209
224
|
return;
|
|
210
225
|
}
|
|
211
|
-
await fileCredentialBackend.clear();
|
|
212
226
|
} catch (error) {
|
|
213
227
|
if (options.failClosed) {
|
|
214
228
|
throw new CredentialStoreUnavailableError(
|
|
@@ -246,10 +260,10 @@ async function setAccessOnlySession(accessToken) {
|
|
|
246
260
|
await backend.writeAccessOnly(accessToken);
|
|
247
261
|
});
|
|
248
262
|
}
|
|
249
|
-
async function clearCredentials() {
|
|
263
|
+
async function clearCredentials(reason = "credential_store_clear") {
|
|
250
264
|
await withFileLock(getCredentialLockPath(), async () => {
|
|
251
265
|
const backend = await getCredentialBackend();
|
|
252
|
-
await backend.clear();
|
|
266
|
+
await backend.clear(reason);
|
|
253
267
|
});
|
|
254
268
|
}
|
|
255
269
|
async function withCredentialLock(fn, options) {
|
|
@@ -262,7 +276,7 @@ async function withCredentialLock(fn, options) {
|
|
|
262
276
|
read: () => backend.read(),
|
|
263
277
|
writeFull: (creds) => backend.writeFull(creds),
|
|
264
278
|
writeAccessOnly: (accessToken) => backend.writeAccessOnly(accessToken),
|
|
265
|
-
clear: () => backend.clear()
|
|
279
|
+
clear: (reason) => backend.clear(reason)
|
|
266
280
|
};
|
|
267
281
|
return fn(ops);
|
|
268
282
|
},
|
|
@@ -306,9 +320,6 @@ async function saveGlobalConfig(config) {
|
|
|
306
320
|
}
|
|
307
321
|
|
|
308
322
|
export {
|
|
309
|
-
BUILD_CHANNEL,
|
|
310
|
-
IS_PUBLISHED_BUILD,
|
|
311
|
-
PUBLISHED_ENVIRONMENT,
|
|
312
323
|
getActiveCredentialBackendName,
|
|
313
324
|
getStoredSession,
|
|
314
325
|
setCredentials,
|
|
@@ -320,4 +331,4 @@ export {
|
|
|
320
331
|
loadGlobalConfig,
|
|
321
332
|
saveGlobalConfig
|
|
322
333
|
};
|
|
323
|
-
//# sourceMappingURL=chunk-
|
|
334
|
+
//# sourceMappingURL=chunk-VWMKJL4A.js.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. All builds default to the file backend. The OS keychain is an explicit\n * opt-in through config or `DREAMBOARD_CREDENTIAL_BACKEND=keychain`.\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/**\n * Fully refreshable session. `accessToken` is the Clerk OAuth bootstrap token\n * retained for refresh/exchange compatibility; ordinary API calls use\n * `dreamboardApiToken`.\n */\nexport type Credentials = {\n readonly accessToken: string;\n readonly refreshToken: string;\n readonly tokenExpiresAt?: string;\n readonly dreamboardApiToken?: string;\n readonly dreamboardApiExpiresAt?: 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 dreamboardApiToken?: string;\n readonly dreamboardApiExpiresAt?: 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(reason?: CredentialClearReason): 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(reason?: CredentialClearReason): Promise<void>;\n};\n\nexport type CredentialClearReason =\n | \"auth_clear_command\"\n | \"logout_command\"\n | \"user_token_manager_logout\"\n | \"credential_store_clear\";\n\ntype DiskShape = Partial<{\n clerkAccessToken: string;\n clerkAccessExpiresAt: string;\n accessToken: string;\n authToken: string;\n refreshToken: string;\n tokenExpiresAt: string;\n dreamboardApiToken: string;\n dreamboardApiExpiresAt: string;\n clerkOAuthIssuer: string;\n clerkOAuthClientId: string;\n clerkOAuthTokenUrl: string;\n environment: string;\n}>;\n\nlet credentialDirectoryOverrideForTests: string | null = null;\n\nfunction getCredentialDirectory(): string {\n return (\n credentialDirectoryOverrideForTests ??\n path.join(os.homedir(), PROJECT_DIR_NAME)\n );\n}\n\nexport function getCredentialFilePath(): string {\n return path.join(getCredentialDirectory(), \"auth.json\");\n}\n\nexport function getCredentialAuditLogPath(): string {\n return path.join(getCredentialDirectory(), \"auth-events.log\");\n}\n\nfunction getCredentialLockPath(): string {\n return `${getCredentialFilePath()}.lock`;\n}\n\nasync function appendCredentialAuditEvent(event: {\n readonly event: \"auth_file_deleted\" | \"auth_file_delete_missing\";\n readonly reason: CredentialClearReason;\n readonly authPath: string;\n readonly backend: CredentialBackendName;\n}): Promise<void> {\n try {\n const logPath = getCredentialAuditLogPath();\n await fs.mkdir(path.dirname(logPath), { recursive: true, mode: 0o700 });\n await fs.appendFile(\n logPath,\n `${JSON.stringify({\n timestamp: new Date().toISOString(),\n pid: process.pid,\n ...event,\n })}\\n`,\n { mode: 0o600 },\n );\n } catch {\n // Credential clearing must not fail because local diagnostic logging failed.\n }\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 =\n parsed.clerkAccessToken ?? 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:\n parsed.clerkAccessExpiresAt || parsed.tokenExpiresAt || undefined,\n dreamboardApiToken: parsed.dreamboardApiToken || undefined,\n dreamboardApiExpiresAt: parsed.dreamboardApiExpiresAt || 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 clerkAccessToken: creds.accessToken,\n refreshToken: creds.refreshToken,\n clerkAccessExpiresAt: creds.tokenExpiresAt,\n dreamboardApiToken: creds.dreamboardApiToken,\n dreamboardApiExpiresAt: creds.dreamboardApiExpiresAt,\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(\n reason: CredentialClearReason = \"credential_store_clear\",\n): Promise<void> {\n const filePath = getCredentialFilePath();\n try {\n await fs.unlink(filePath);\n await appendCredentialAuditEvent({\n event: \"auth_file_deleted\",\n reason,\n authPath: filePath,\n backend: \"file\",\n });\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") {\n await appendCredentialAuditEvent({\n event: \"auth_file_delete_missing\",\n reason,\n authPath: filePath,\n backend: \"file\",\n });\n return;\n }\n 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\nexport class CredentialStoreUnavailableError extends Error {\n readonly code = \"CREDENTIAL_STORE_UNAVAILABLE\";\n\n constructor(reason: string) {\n super(`Credential store unavailable: ${reason}`);\n this.name = \"CredentialStoreUnavailableError\";\n }\n}\n\nlet cachedBackend: CredentialBackend | null = null;\nlet migrationCompleted = false;\nlet backendResolver: BackendResolver = defaultBackendResolver;\n\n/**\n * Resolver precedence for all builds:\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 file-default 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. The file is intentionally left in place; implicit backend\n // migration must not make a working CLI session appear to vanish from\n // the default file-backed view.\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 options: { failClosed?: boolean } = {},\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. Leave the\n // file copy alone so a transient keychain override/probe cannot remove\n // the visible file-backed session.\n return;\n }\n if (onDisk.accessToken && onDisk.refreshToken) {\n const migrated: Credentials = {\n accessToken: onDisk.accessToken,\n refreshToken: onDisk.refreshToken,\n tokenExpiresAt: onDisk.tokenExpiresAt,\n dreamboardApiToken: onDisk.dreamboardApiToken,\n dreamboardApiExpiresAt: onDisk.dreamboardApiExpiresAt,\n clerkOAuthIssuer: onDisk.clerkOAuthIssuer,\n clerkOAuthClientId: onDisk.clerkOAuthClientId,\n clerkOAuthTokenUrl: onDisk.clerkOAuthTokenUrl,\n environment: onDisk.environment,\n };\n await target.writeFull(migrated);\n await verifyMigratedSession(target, migrated);\n } else if (onDisk.accessToken) {\n await target.writeAccessOnly(onDisk.accessToken);\n const migrated = await target.read();\n if (migrated?.accessToken !== onDisk.accessToken) {\n throw new Error(\"Credential migration verification failed.\");\n }\n } else {\n return;\n }\n } catch (error) {\n if (options.failClosed) {\n throw new CredentialStoreUnavailableError(\n error instanceof Error ? error.message : String(error),\n );\n }\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\nasync function verifyMigratedSession(\n target: CredentialBackend,\n expected: Credentials,\n): Promise<void> {\n const migrated = await target.read();\n if (\n migrated?.accessToken !== expected.accessToken ||\n migrated.refreshToken !== expected.refreshToken\n ) {\n throw new Error(\"Credential migration verification failed.\");\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 if (process.env.DREAMBOARD_AGENT_TOKEN?.trim()) {\n return null;\n }\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 {\n accessToken,\n refreshToken,\n tokenExpiresAt: snapshot.tokenExpiresAt,\n dreamboardApiToken: snapshot.dreamboardApiToken,\n dreamboardApiExpiresAt: snapshot.dreamboardApiExpiresAt,\n clerkOAuthIssuer: snapshot.clerkOAuthIssuer,\n clerkOAuthClientId: snapshot.clerkOAuthClientId,\n clerkOAuthTokenUrl: snapshot.clerkOAuthTokenUrl,\n environment: snapshot.environment,\n };\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(\n reason: CredentialClearReason = \"credential_store_clear\",\n): Promise<void> {\n await withFileLock(getCredentialLockPath(), async () => {\n const backend = await getCredentialBackend();\n await backend.clear(reason);\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: (reason) => backend.clear(reason),\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 credentialDirectoryOverrideForTests = null;\n}\n\n/** Test-only override of the credential directory. Not exported through the barrel. */\nexport function _setCredentialDirectoryForTests(directory: string | null): void {\n credentialDirectoryOverrideForTests = directory;\n cachedBackend = null;\n migrationCompleted = false;\n}\n"],"mappings":"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","names":["os","path","loadGlobalConfig","path","os"]}
|
|
@@ -17897,8 +17897,19 @@ function isValidGeneratedPath(relativePath) {
|
|
|
17897
17897
|
}
|
|
17898
17898
|
return !relativePath.split("/").some((segment) => segment.length === 0 || segment === "." || segment === "..");
|
|
17899
17899
|
}
|
|
17900
|
+
var ALLOWED_GENERATED_PREFIXES = [
|
|
17901
|
+
"app/",
|
|
17902
|
+
"shared/",
|
|
17903
|
+
"test/generated/",
|
|
17904
|
+
"ui/"
|
|
17905
|
+
];
|
|
17906
|
+
function isAllowedGeneratedPath(relativePath) {
|
|
17907
|
+
return ALLOWED_GENERATED_PREFIXES.some(
|
|
17908
|
+
(prefix) => relativePath.startsWith(prefix)
|
|
17909
|
+
);
|
|
17910
|
+
}
|
|
17900
17911
|
function assertGeneratedPath(pathValue, label) {
|
|
17901
|
-
if (typeof pathValue !== "string" || !isValidGeneratedPath(pathValue)) {
|
|
17912
|
+
if (typeof pathValue !== "string" || !isValidGeneratedPath(pathValue) || !isAllowedGeneratedPath(pathValue)) {
|
|
17902
17913
|
throw new ProjectAuthoringError(
|
|
17903
17914
|
"GENERATED_PATH_CONTRACT_INVALID",
|
|
17904
17915
|
`${label} must be a normalized relative workspace path.`
|
|
@@ -18000,9 +18011,23 @@ function validateProjectAuthoringAdapter(adapter) {
|
|
|
18000
18011
|
}
|
|
18001
18012
|
seen.add(normalized);
|
|
18002
18013
|
}
|
|
18014
|
+
if (adapter.generatedPathPatterns !== void 0 && !Array.isArray(adapter.generatedPathPatterns)) {
|
|
18015
|
+
throw new ProjectAuthoringError(
|
|
18016
|
+
"GENERATED_PATH_CONTRACT_INVALID",
|
|
18017
|
+
"SDK authoring adapter generatedPathPatterns must be an array."
|
|
18018
|
+
);
|
|
18019
|
+
}
|
|
18020
|
+
for (const [index, pattern] of (adapter.generatedPathPatterns ?? []).entries()) {
|
|
18021
|
+
if (!isRecord(pattern) || typeof pattern.prefix !== "string" || typeof pattern.suffix !== "string" || !isValidGeneratedPath(`${pattern.prefix}placeholder${pattern.suffix}`) || !isAllowedGeneratedPath(`${pattern.prefix}placeholder${pattern.suffix}`)) {
|
|
18022
|
+
throw new ProjectAuthoringError(
|
|
18023
|
+
"GENERATED_PATH_CONTRACT_INVALID",
|
|
18024
|
+
`generatedPathPatterns[${index}] must describe a normalized allowlisted workspace path.`
|
|
18025
|
+
);
|
|
18026
|
+
}
|
|
18027
|
+
}
|
|
18003
18028
|
return adapter;
|
|
18004
18029
|
}
|
|
18005
|
-
function validateGeneratedArtifacts(artifacts) {
|
|
18030
|
+
function validateGeneratedArtifacts(adapter, artifacts) {
|
|
18006
18031
|
const seen = /* @__PURE__ */ new Set();
|
|
18007
18032
|
const validated = artifacts.map(assertGeneratedArtifact);
|
|
18008
18033
|
for (const artifact of validated) {
|
|
@@ -18012,6 +18037,15 @@ function validateGeneratedArtifacts(artifacts) {
|
|
|
18012
18037
|
`Generated artifact path '${artifact.path}' was emitted more than once.`
|
|
18013
18038
|
);
|
|
18014
18039
|
}
|
|
18040
|
+
const declared = adapter.generatedPaths.includes(artifact.path) || (adapter.generatedPathPatterns ?? []).some(
|
|
18041
|
+
(pattern) => artifact.path.startsWith(pattern.prefix) && artifact.path.endsWith(pattern.suffix) && artifact.path.length > pattern.prefix.length + pattern.suffix.length
|
|
18042
|
+
);
|
|
18043
|
+
if (!declared) {
|
|
18044
|
+
throw new ProjectAuthoringError(
|
|
18045
|
+
"GENERATED_PATH_CONTRACT_INVALID",
|
|
18046
|
+
`Generated artifact path '${artifact.path}' is not declared by the SDK authoring adapter.`
|
|
18047
|
+
);
|
|
18048
|
+
}
|
|
18015
18049
|
seen.add(artifact.path);
|
|
18016
18050
|
}
|
|
18017
18051
|
return validated;
|
|
@@ -18040,12 +18074,6 @@ function assertResolvedInsidePackage(options) {
|
|
|
18040
18074
|
);
|
|
18041
18075
|
}
|
|
18042
18076
|
}
|
|
18043
|
-
function isAuthoringMetadataVersionCompatible(options) {
|
|
18044
|
-
if (options.metadataVersion === options.packageVersion) {
|
|
18045
|
-
return true;
|
|
18046
|
-
}
|
|
18047
|
-
return options.packageVersion.startsWith(`${options.metadataVersion}-local.`);
|
|
18048
|
-
}
|
|
18049
18077
|
function isRecord2(value) {
|
|
18050
18078
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
18051
18079
|
}
|
|
@@ -18150,10 +18178,7 @@ async function loadProjectAuthoringAdapter(projectRoot) {
|
|
|
18150
18178
|
const adapter = validateProjectAuthoringAdapter(
|
|
18151
18179
|
moduleRecord.projectAuthoringAdapter ?? moduleRecord.default
|
|
18152
18180
|
);
|
|
18153
|
-
if (
|
|
18154
|
-
metadataVersion: adapter.metadata.sdkVersion,
|
|
18155
|
-
packageVersion: packageJson.version
|
|
18156
|
-
})) {
|
|
18181
|
+
if (adapter.metadata.sdkVersion !== packageJson.version) {
|
|
18157
18182
|
throw new ProjectAuthoringError(
|
|
18158
18183
|
"SDK_METADATA_MISMATCH",
|
|
18159
18184
|
`SDK authoring adapter reports version ${adapter.metadata.sdkVersion}, but package metadata is ${packageJson.version}.`
|
|
@@ -18678,7 +18703,7 @@ function isFrameworkOwnedSetupProfilesSeed(content) {
|
|
|
18678
18703
|
async function applyWorkspaceCodegen(options) {
|
|
18679
18704
|
const { projectRoot, manifest } = options;
|
|
18680
18705
|
const { adapter } = await loadProjectAuthoringAdapter(projectRoot);
|
|
18681
|
-
const artifacts = validateGeneratedArtifacts([
|
|
18706
|
+
const artifacts = validateGeneratedArtifacts(adapter, [
|
|
18682
18707
|
...adapter.generateWorkspaceArtifacts(manifest),
|
|
18683
18708
|
...adapter.generateTestArtifacts({ manifest })
|
|
18684
18709
|
]);
|
|
@@ -18970,11 +18995,11 @@ var AUTHORING_RELEASE_SET = {
|
|
|
18970
18995
|
"packages": {
|
|
18971
18996
|
"cli": {
|
|
18972
18997
|
"name": "@dreamboard-games/cli",
|
|
18973
|
-
"version": "0.1.30-alpha.
|
|
18998
|
+
"version": "0.1.30-alpha.19"
|
|
18974
18999
|
},
|
|
18975
19000
|
"sdk": {
|
|
18976
19001
|
"name": "@dreamboard-games/sdk",
|
|
18977
|
-
"version": "0.4.0-alpha.
|
|
19002
|
+
"version": "0.4.0-alpha.6"
|
|
18978
19003
|
},
|
|
18979
19004
|
"apiClient": {
|
|
18980
19005
|
"name": "@dreamboard-games/api-client",
|
|
@@ -18982,7 +19007,7 @@ var AUTHORING_RELEASE_SET = {
|
|
|
18982
19007
|
},
|
|
18983
19008
|
"devHost": {
|
|
18984
19009
|
"name": "@dreamboard-games/dev-host",
|
|
18985
|
-
"version": "0.1.30-alpha.
|
|
19010
|
+
"version": "0.1.30-alpha.19"
|
|
18986
19011
|
}
|
|
18987
19012
|
},
|
|
18988
19013
|
"protocols": {
|
|
@@ -19000,7 +19025,7 @@ var AUTHORING_RELEASE_SET = {
|
|
|
19000
19025
|
"portable": true
|
|
19001
19026
|
},
|
|
19002
19027
|
"packageManager": "pnpm@10.4.1",
|
|
19003
|
-
"releaseSetId": "sha256:
|
|
19028
|
+
"releaseSetId": "sha256:94f9e944361da712ec7f0b420643d3690e73b93a2c23d3a893b0f4183a93a847"
|
|
19004
19029
|
};
|
|
19005
19030
|
|
|
19006
19031
|
// src/templates/testing-types-content.ts
|
|
@@ -19356,14 +19381,11 @@ export default defineScenario({
|
|
|
19356
19381
|
"Sanity check that the scaffolded workspace boots into its initial phase.",
|
|
19357
19382
|
from: "initial-turn",
|
|
19358
19383
|
when: async () => undefined,
|
|
19359
|
-
then: ({ expect,
|
|
19384
|
+
then: ({ expect, players, state }) => {
|
|
19360
19385
|
const playerIds = players();
|
|
19361
19386
|
expect(playerIds).toHaveLength(playerIds.length);
|
|
19362
19387
|
expect(playerIds.length).toBeGreaterThanOrEqual(1);
|
|
19363
19388
|
expect(state()).toBe("setup");
|
|
19364
|
-
for (const playerId of playerIds) {
|
|
19365
|
-
expect(interactions(playerId)).toEqual([]);
|
|
19366
|
-
}
|
|
19367
19389
|
},
|
|
19368
19390
|
});
|
|
19369
19391
|
`;
|
|
@@ -19570,7 +19592,7 @@ async function writeInitialScenario(projectRoot, mode) {
|
|
|
19570
19592
|
projectRoot,
|
|
19571
19593
|
"test/scenarios/smoke-initial-turn.scenario.ts"
|
|
19572
19594
|
);
|
|
19573
|
-
if (existing === null || existing.trim().length === 0 || existing
|
|
19595
|
+
if (existing === null || existing.trim().length === 0 || existing.startsWith(GENERATED_SCENARIO_PREFIX)) {
|
|
19574
19596
|
await writeWorkspaceTextFile(
|
|
19575
19597
|
projectRoot,
|
|
19576
19598
|
"test/scenarios/smoke-initial-turn.scenario.ts",
|
|
@@ -20422,4 +20444,4 @@ export {
|
|
|
20422
20444
|
findProjectRoot,
|
|
20423
20445
|
materializeWorkspaceProject
|
|
20424
20446
|
};
|
|
20425
|
-
//# sourceMappingURL=chunk-
|
|
20447
|
+
//# sourceMappingURL=chunk-YRSE5DLH.js.map
|