@dreamboard-games/cli 0.1.30-alpha.0 → 0.1.30-alpha.1
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/dist/agent-verifier/agent-workspace-verifier.mjs +7 -7
- package/dist/agent-verifier/{chunk-2NZNKIND.mjs → chunk-4WD3YU2E.mjs} +1 -1
- package/dist/agent-verifier/{chunk-PM3SVG6R.mjs → chunk-7E65UQLY.mjs} +1 -1
- package/dist/agent-verifier/{chunk-DTMJCPS4.mjs → chunk-CJEEA6NJ.mjs} +1 -1
- package/dist/agent-verifier/{chunk-RJBLBYHX.mjs → chunk-JH22JNYD.mjs} +1 -1
- package/dist/agent-verifier/{compile-WNCQQVOF.mjs → compile-5QSPIOUT.mjs} +4 -4
- package/dist/agent-verifier/{materialize-workspace-EWGZIVOY.mjs → materialize-workspace-FKALAE2T.mjs} +2 -2
- package/dist/agent-verifier/{static-scaffold-4YEQME5N.mjs → static-scaffold-AJMZZQWS.mjs} +2 -2
- package/dist/agent-verifier/{sync-LOQAH4RC.mjs → sync-3DUQH32H.mjs} +4 -4
- package/dist/agent-verifier/{test-YOJERVHN.mjs → test-P4U5INTD.mjs} +2 -2
- package/dist/{chunk-TSJVWTJO.js → chunk-C6UAT6EH.js} +7 -2
- package/dist/{chunk-TSJVWTJO.js.map → chunk-C6UAT6EH.js.map} +1 -1
- package/dist/{chunk-3XNJT3RK.js → chunk-RS7UXJZV.js} +24 -9
- package/dist/{chunk-3XNJT3RK.js.map → chunk-RS7UXJZV.js.map} +1 -1
- package/dist/{global-config-UKSWNDTX.js → global-config-AGFBDFYD.js} +2 -2
- package/dist/index.js +522 -175
- package/dist/index.js.map +1 -1
- package/dist/internal.js +2 -2
- package/package.json +1 -1
- /package/dist/{global-config-UKSWNDTX.js.map → global-config-AGFBDFYD.js.map} +0 -0
|
@@ -3,9 +3,9 @@ import {
|
|
|
3
3
|
assertCompilerPortableDependencies,
|
|
4
4
|
consola,
|
|
5
5
|
resolveProjectContext
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-JH22JNYD.mjs";
|
|
7
7
|
import "./chunk-HJFQDSTU.mjs";
|
|
8
|
-
import "./chunk-
|
|
8
|
+
import "./chunk-4WD3YU2E.mjs";
|
|
9
9
|
import "./chunk-2E5P5NWG.mjs";
|
|
10
10
|
import "./chunk-MINCYHXN.mjs";
|
|
11
11
|
import "./chunk-LM3OZLZG.mjs";
|
|
@@ -47,7 +47,7 @@ Usage:
|
|
|
47
47
|
async function materializePreparedWorkspace(args) {
|
|
48
48
|
const inputPath = readRequiredOption(args, "--input");
|
|
49
49
|
const input = JSON.parse(await readFile(inputPath, "utf8"));
|
|
50
|
-
const { materializeWorkspaceProject } = await import("./materialize-workspace-
|
|
50
|
+
const { materializeWorkspaceProject } = await import("./materialize-workspace-FKALAE2T.mjs");
|
|
51
51
|
await materializeWorkspaceProject({
|
|
52
52
|
...input,
|
|
53
53
|
agentManaged: true,
|
|
@@ -80,9 +80,9 @@ async function verifyAgentWorkspace(rawMode, args) {
|
|
|
80
80
|
}
|
|
81
81
|
async function runFullBackendConnectedVerification(parsedFlags) {
|
|
82
82
|
const [{ default: cmdSync }, { default: cmdCompile }, { default: cmdTest }] = await Promise.all([
|
|
83
|
-
import("./sync-
|
|
84
|
-
import("./compile-
|
|
85
|
-
import("./test-
|
|
83
|
+
import("./sync-3DUQH32H.mjs"),
|
|
84
|
+
import("./compile-5QSPIOUT.mjs"),
|
|
85
|
+
import("./test-P4U5INTD.mjs")
|
|
86
86
|
]);
|
|
87
87
|
await runCommandDefinition(cmdSync, { ...parsedFlags, force: true });
|
|
88
88
|
await runCommandDefinition(cmdCompile, parsedFlags);
|
|
@@ -149,7 +149,7 @@ async function runCloudLocalVerification(projectRoot, projectConfig, config) {
|
|
|
149
149
|
{ assertReducerContractPreflight },
|
|
150
150
|
{ getProjectLocalMaintainerRegistry }
|
|
151
151
|
] = await Promise.all([
|
|
152
|
-
import("./static-scaffold-
|
|
152
|
+
import("./static-scaffold-AJMZZQWS.mjs"),
|
|
153
153
|
import("./local-files-MTPLP62S.mjs"),
|
|
154
154
|
import("./workspace-codegen-JDZJRSDV.mjs"),
|
|
155
155
|
import("./workspace-dependencies-HZ6VVS4G.mjs"),
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
} from "./chunk-SHUMAVAP.mjs";
|
|
15
15
|
|
|
16
16
|
// src/build-target.ts
|
|
17
|
-
var injectedBuildChannel = true ? "
|
|
17
|
+
var injectedBuildChannel = true ? "development" : void 0;
|
|
18
18
|
var BUILD_CHANNEL = injectedBuildChannel === "published" ? "published" : "development";
|
|
19
19
|
var IS_PUBLISHED_BUILD = BUILD_CHANNEL === "published";
|
|
20
20
|
var PUBLISHED_ENVIRONMENT = "prod";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
resolveRemoteProject
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-7E65UQLY.mjs";
|
|
5
5
|
import {
|
|
6
6
|
CONFIG_FLAG_ARGS,
|
|
7
7
|
findProjectCompiledResultsForRevision,
|
|
@@ -25,18 +25,18 @@ import {
|
|
|
25
25
|
assertReleaseEnvironmentPortableDependencies,
|
|
26
26
|
consola,
|
|
27
27
|
resolveProjectContext
|
|
28
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-JH22JNYD.mjs";
|
|
29
29
|
import "./chunk-HJFQDSTU.mjs";
|
|
30
30
|
import {
|
|
31
31
|
getLocalDiff
|
|
32
32
|
} from "./chunk-EIQWDQWJ.mjs";
|
|
33
33
|
import {
|
|
34
34
|
assertCliStaticScaffoldComplete
|
|
35
|
-
} from "./chunk-
|
|
35
|
+
} from "./chunk-CJEEA6NJ.mjs";
|
|
36
36
|
import "./chunk-2GBBP27W.mjs";
|
|
37
37
|
import {
|
|
38
38
|
updateProjectState
|
|
39
|
-
} from "./chunk-
|
|
39
|
+
} from "./chunk-4WD3YU2E.mjs";
|
|
40
40
|
import "./chunk-SYPLYRGB.mjs";
|
|
41
41
|
import "./chunk-2QMNAVV4.mjs";
|
|
42
42
|
import "./chunk-6UUJEYDV.mjs";
|
|
@@ -10,11 +10,11 @@ import {
|
|
|
10
10
|
} from "./chunk-EIQWDQWJ.mjs";
|
|
11
11
|
import {
|
|
12
12
|
scaffoldStaticWorkspace
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-CJEEA6NJ.mjs";
|
|
14
14
|
import "./chunk-2GBBP27W.mjs";
|
|
15
15
|
import {
|
|
16
16
|
updateProjectState
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-4WD3YU2E.mjs";
|
|
18
18
|
import "./chunk-SYPLYRGB.mjs";
|
|
19
19
|
import "./chunk-2QMNAVV4.mjs";
|
|
20
20
|
import "./chunk-6UUJEYDV.mjs";
|
|
@@ -5,9 +5,9 @@ import {
|
|
|
5
5
|
resolveSdkDependencyRange,
|
|
6
6
|
resolveStaticAssetRoot,
|
|
7
7
|
scaffoldStaticWorkspace
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-CJEEA6NJ.mjs";
|
|
9
9
|
import "./chunk-2GBBP27W.mjs";
|
|
10
|
-
import "./chunk-
|
|
10
|
+
import "./chunk-4WD3YU2E.mjs";
|
|
11
11
|
import "./chunk-SYPLYRGB.mjs";
|
|
12
12
|
import "./chunk-2QMNAVV4.mjs";
|
|
13
13
|
import "./chunk-6UUJEYDV.mjs";
|
|
@@ -9,7 +9,7 @@ import "./chunk-HBNDKQT5.mjs";
|
|
|
9
9
|
import "./chunk-CEDUHGNH.mjs";
|
|
10
10
|
import {
|
|
11
11
|
resolveRemoteProject
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-7E65UQLY.mjs";
|
|
13
13
|
import {
|
|
14
14
|
CONFIG_FLAG_ARGS,
|
|
15
15
|
createGameRevisionSdk,
|
|
@@ -34,7 +34,7 @@ import {
|
|
|
34
34
|
assertReleaseEnvironmentPortableDependencies,
|
|
35
35
|
consola,
|
|
36
36
|
resolveProjectContext
|
|
37
|
-
} from "./chunk-
|
|
37
|
+
} from "./chunk-JH22JNYD.mjs";
|
|
38
38
|
import "./chunk-HJFQDSTU.mjs";
|
|
39
39
|
import {
|
|
40
40
|
collectLocalFiles,
|
|
@@ -46,13 +46,13 @@ import {
|
|
|
46
46
|
import {
|
|
47
47
|
assertCliStaticScaffoldComplete,
|
|
48
48
|
scaffoldStaticWorkspace
|
|
49
|
-
} from "./chunk-
|
|
49
|
+
} from "./chunk-CJEEA6NJ.mjs";
|
|
50
50
|
import "./chunk-2GBBP27W.mjs";
|
|
51
51
|
import {
|
|
52
52
|
BUILD_CHANNEL,
|
|
53
53
|
IS_PUBLISHED_BUILD,
|
|
54
54
|
updateProjectState
|
|
55
|
-
} from "./chunk-
|
|
55
|
+
} from "./chunk-4WD3YU2E.mjs";
|
|
56
56
|
import {
|
|
57
57
|
computeManifestHash
|
|
58
58
|
} from "./chunk-SYPLYRGB.mjs";
|
|
@@ -30,13 +30,13 @@ import {
|
|
|
30
30
|
configureClient,
|
|
31
31
|
consola,
|
|
32
32
|
resolveProjectContext
|
|
33
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-JH22JNYD.mjs";
|
|
34
34
|
import "./chunk-HJFQDSTU.mjs";
|
|
35
35
|
import "./chunk-EIQWDQWJ.mjs";
|
|
36
36
|
import "./chunk-2GBBP27W.mjs";
|
|
37
37
|
import {
|
|
38
38
|
IS_PUBLISHED_BUILD
|
|
39
|
-
} from "./chunk-
|
|
39
|
+
} from "./chunk-4WD3YU2E.mjs";
|
|
40
40
|
import "./chunk-SYPLYRGB.mjs";
|
|
41
41
|
import "./chunk-2QMNAVV4.mjs";
|
|
42
42
|
import "./chunk-6UUJEYDV.mjs";
|
|
@@ -296,7 +296,7 @@ async function defaultBackendResolver() {
|
|
|
296
296
|
}
|
|
297
297
|
async function readCredentialBackendPreference() {
|
|
298
298
|
try {
|
|
299
|
-
const { loadGlobalConfig: loadGlobalConfig2 } = await import("./global-config-
|
|
299
|
+
const { loadGlobalConfig: loadGlobalConfig2 } = await import("./global-config-AGFBDFYD.js");
|
|
300
300
|
const config = await loadGlobalConfig2();
|
|
301
301
|
return config.credentialBackend === "keychain";
|
|
302
302
|
} catch {
|
|
@@ -338,6 +338,10 @@ async function migrateFromFileBackendIfNeeded(target) {
|
|
|
338
338
|
} catch {
|
|
339
339
|
}
|
|
340
340
|
}
|
|
341
|
+
async function getActiveCredentialBackendName() {
|
|
342
|
+
const backend = await getCredentialBackend();
|
|
343
|
+
return backend.name;
|
|
344
|
+
}
|
|
341
345
|
async function getStoredSession() {
|
|
342
346
|
const backend = await getCredentialBackend();
|
|
343
347
|
return backend.read();
|
|
@@ -418,6 +422,7 @@ export {
|
|
|
418
422
|
readJsonFile,
|
|
419
423
|
writeJsonFile,
|
|
420
424
|
atomicWriteFile,
|
|
425
|
+
getActiveCredentialBackendName,
|
|
421
426
|
getStoredSession,
|
|
422
427
|
setCredentials,
|
|
423
428
|
setAccessOnlySession,
|
|
@@ -427,4 +432,4 @@ export {
|
|
|
427
432
|
loadGlobalConfig,
|
|
428
433
|
saveGlobalConfig
|
|
429
434
|
};
|
|
430
|
-
//# sourceMappingURL=chunk-
|
|
435
|
+
//# sourceMappingURL=chunk-C6UAT6EH.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/config/global-config.ts","../src/constants.ts","../src/utils/fs.ts","../src/utils/atomic-file.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","import type { EnvironmentConfig } from \"./types.js\";\n\nexport const DEFAULT_API_BASE_URL = \"https://api.dreamboard.games\";\nexport const DEFAULT_WEB_BASE_URL = \"https://dreamboard.games\";\n\nexport const PROJECT_DIR_NAME = \".dreamboard\";\n\n// Predefined environment configurations\nexport const ENVIRONMENT_CONFIGS: Record<string, EnvironmentConfig> = {\n local: {\n apiBaseUrl: \"http://localhost:8080\",\n webBaseUrl: \"http://localhost:5173\",\n clerkOAuthIssuer: process.env.DREAMBOARD_LOCAL_CLERK_OAUTH_ISSUER,\n clerkOAuthClientId: process.env.DREAMBOARD_LOCAL_CLERK_OAUTH_CLIENT_ID,\n clerkOAuthScope: process.env.DREAMBOARD_LOCAL_CLERK_OAUTH_SCOPE,\n },\n staging: {\n apiBaseUrl: \"https://api-staging.dreamboard.games\",\n webBaseUrl: \"https://staging.dreamboard.games\",\n clerkOAuthIssuer:\n process.env.DREAMBOARD_STAGING_CLERK_OAUTH_ISSUER ??\n process.env.DREAMBOARD_CLERK_OAUTH_ISSUER,\n clerkOAuthClientId:\n process.env.DREAMBOARD_STAGING_CLERK_OAUTH_CLIENT_ID ??\n process.env.DREAMBOARD_CLERK_OAUTH_CLIENT_ID,\n clerkOAuthScope:\n process.env.DREAMBOARD_STAGING_CLERK_OAUTH_SCOPE ??\n process.env.DREAMBOARD_CLERK_OAUTH_SCOPE,\n },\n prod: {\n apiBaseUrl: \"https://api.dreamboard.games\",\n webBaseUrl: \"https://dreamboard.games\",\n clerkOAuthIssuer:\n process.env.DREAMBOARD_PROD_CLERK_OAUTH_ISSUER ??\n process.env.DREAMBOARD_CLERK_OAUTH_ISSUER,\n clerkOAuthClientId:\n process.env.DREAMBOARD_PROD_CLERK_OAUTH_CLIENT_ID ??\n process.env.DREAMBOARD_CLERK_OAUTH_CLIENT_ID,\n clerkOAuthScope:\n process.env.DREAMBOARD_PROD_CLERK_OAUTH_SCOPE ??\n process.env.DREAMBOARD_CLERK_OAUTH_SCOPE,\n },\n};\nexport const PROJECT_CONFIG_FILE = \"project.json\";\nexport const PROJECT_STATE_FILE = \"state.json\";\nexport const SNAPSHOT_FILE = \"snapshot.json\";\nexport const MANIFEST_FILE = \"manifest.ts\";\nexport const GENERATED_DIR_NAME = \"generated\";\nexport const MATERIALIZED_MANIFEST_FILE = `${PROJECT_DIR_NAME}/${GENERATED_DIR_NAME}/manifest.json`;\nexport const MANIFEST_TYPECHECK_CONFIG_FILE = \"manifest.tsconfig.json\";\nexport const RULE_FILE = \"rule.md\";\nexport const DEFAULT_LOGIN_TIMEOUT_MS = 5 * 60 * 1000;\nexport const DEFAULT_TURN_DELAY_MS = 250;\n\nexport const LOCAL_IGNORE_DIRS = new Set([\n \".dreamboard\",\n \".git\",\n \"node_modules\",\n \"dist\",\n]);\n","import { mkdir, readFile, stat, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\n\nexport async function ensureDir(dirPath: string): Promise<void> {\n await mkdir(dirPath, { recursive: true });\n}\n\nexport async function exists(filePath: string): Promise<boolean> {\n try {\n await stat(filePath);\n return true;\n } catch {\n return false;\n }\n}\n\nexport async function readTextFile(filePath: string): Promise<string> {\n return readFile(filePath, \"utf8\");\n}\n\nexport async function readTextFileIfExists(\n filePath: string,\n): Promise<string | null> {\n try {\n return await readFile(filePath, \"utf8\");\n } catch {\n return null;\n }\n}\n\nexport async function writeTextFile(\n filePath: string,\n content: string,\n): Promise<void> {\n await ensureDir(path.dirname(filePath));\n await writeFile(filePath, content, \"utf8\");\n}\n\nexport async function readJsonFile<T>(filePath: string): Promise<T> {\n const data = await readTextFile(filePath);\n return JSON.parse(data) as T;\n}\n\nexport async function writeJsonFile(\n filePath: string,\n data: unknown,\n): Promise<void> {\n await writeTextFile(filePath, `${JSON.stringify(data, null, 2)}\\n`);\n}\n","/**\n * Primitives for safely mutating local state files owned by the CLI.\n *\n * Two guarantees:\n * - Writes are atomic-ish: we stage the payload in a sibling temp file with\n * the target permissions, fsync the contents, then `rename` over the target.\n * On POSIX `rename` within the same directory is atomic; on Windows it is\n * atomic within the same volume which is always the case for files we write\n * inside `~/.dreamboard`.\n * - We refuse to clobber a file with an empty payload. The original bug that\n * wiped refresh tokens on a failing `sync`/`compile` hinged on `undefined`\n * JSON values being persisted and reloaded as `{}`. Forbidding empty\n * writes here removes that entire failure mode at the primitive level.\n *\n * Additionally, `withFileLock` provides a cross-process advisory lock built on\n * `O_CREAT | O_EXCL` so that parallel CLI invocations (e.g. `dreamboard sync`\n * running while `dreamboard compile` is in flight) serialize around mutations\n * of the same credential state.\n */\n\nimport { constants as fsConstants, promises as fs, type Stats } from \"node:fs\";\nimport path from \"node:path\";\nimport crypto from \"node:crypto\";\n\nexport type AtomicWriteOptions = {\n /** File mode applied to the written file (default: 0o600). */\n mode?: number;\n /** Call `fsync` on the temp file before renaming. Default: true. */\n fsync?: boolean;\n};\n\nexport async function atomicWriteFile(\n targetPath: string,\n contents: string,\n options: AtomicWriteOptions = {},\n): Promise<void> {\n if (contents.length === 0) {\n throw new Error(\n `Refusing to atomicWriteFile an empty payload to ${targetPath}`,\n );\n }\n const mode = options.mode ?? 0o600;\n const shouldFsync = options.fsync ?? true;\n const dir = path.dirname(targetPath);\n await fs.mkdir(dir, { recursive: true });\n\n const suffix = crypto.randomBytes(6).toString(\"hex\");\n const tmpPath = `${targetPath}.tmp-${process.pid}-${suffix}`;\n\n const fh = await fs.open(\n tmpPath,\n fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL,\n mode,\n );\n try {\n await fh.writeFile(contents, \"utf8\");\n try {\n await fh.chmod(mode);\n } catch {\n // Some filesystems (e.g. network volumes, Windows) refuse chmod.\n // Ignoring here is safe: the `open` call above already created the\n // file with the requested mode on systems that honor it.\n }\n if (shouldFsync) {\n try {\n await fh.sync();\n } catch {\n // Best-effort. Not all backends (tmpfs on some platforms) support fsync.\n }\n }\n } finally {\n await fh.close();\n }\n\n try {\n await fs.rename(tmpPath, targetPath);\n } catch (err) {\n await fs.unlink(tmpPath).catch(() => undefined);\n throw err;\n }\n}\n\nexport type FileLockOptions = {\n /** Max number of acquisition attempts before giving up. Default: 100. */\n retries?: number;\n /** Minimum backoff between retries in ms. Default: 20. */\n minDelayMs?: number;\n /** Maximum backoff between retries in ms. Default: 200. */\n maxDelayMs?: number;\n /**\n * A lockfile older than this is considered stale and forcibly removed.\n * Guards against crashed processes leaving a permanent lock. Default: 30s.\n */\n staleMs?: number;\n};\n\nexport async function withFileLock<T>(\n lockPath: string,\n fn: () => Promise<T>,\n options: FileLockOptions = {},\n): Promise<T> {\n const retries = options.retries ?? 100;\n const minDelayMs = options.minDelayMs ?? 20;\n const maxDelayMs = options.maxDelayMs ?? 200;\n const staleMs = options.staleMs ?? 30_000;\n\n await fs.mkdir(path.dirname(lockPath), { recursive: true });\n\n let attempt = 0;\n let acquired = false;\n while (!acquired) {\n try {\n const fh = await fs.open(\n lockPath,\n fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL,\n 0o600,\n );\n await fh.writeFile(`${process.pid}\\n`, \"utf8\");\n await fh.close();\n acquired = true;\n break;\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code !== \"EEXIST\") {\n throw err;\n }\n }\n\n let stat: Stats | null = null;\n try {\n stat = await fs.stat(lockPath);\n } catch {\n continue;\n }\n if (stat !== null) {\n const ageMs = Date.now() - stat.mtimeMs;\n if (ageMs > staleMs) {\n await fs.unlink(lockPath).catch(() => undefined);\n continue;\n }\n }\n\n attempt += 1;\n if (attempt >= retries) {\n throw new Error(\n `Timed out acquiring file lock at ${lockPath} after ${retries} attempts.`,\n );\n }\n const jitter = Math.floor(\n Math.random() * Math.max(1, maxDelayMs - minDelayMs),\n );\n await new Promise((resolve) => setTimeout(resolve, minDelayMs + jitter));\n }\n\n try {\n return await fn();\n } finally {\n await fs.unlink(lockPath).catch(() => undefined);\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","path","stat","path","fs","path","fs","loadGlobalConfig","path","os"]}
|
|
1
|
+
{"version":3,"sources":["../src/config/global-config.ts","../src/constants.ts","../src/utils/fs.ts","../src/utils/atomic-file.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","import type { EnvironmentConfig } from \"./types.js\";\n\nexport const DEFAULT_API_BASE_URL = \"https://api.dreamboard.games\";\nexport const DEFAULT_WEB_BASE_URL = \"https://dreamboard.games\";\n\nexport const PROJECT_DIR_NAME = \".dreamboard\";\n\n// Predefined environment configurations\nexport const ENVIRONMENT_CONFIGS: Record<string, EnvironmentConfig> = {\n local: {\n apiBaseUrl: \"http://localhost:8080\",\n webBaseUrl: \"http://localhost:5173\",\n clerkOAuthIssuer: process.env.DREAMBOARD_LOCAL_CLERK_OAUTH_ISSUER,\n clerkOAuthClientId: process.env.DREAMBOARD_LOCAL_CLERK_OAUTH_CLIENT_ID,\n clerkOAuthScope: process.env.DREAMBOARD_LOCAL_CLERK_OAUTH_SCOPE,\n },\n staging: {\n apiBaseUrl: \"https://api-staging.dreamboard.games\",\n webBaseUrl: \"https://staging.dreamboard.games\",\n clerkOAuthIssuer:\n process.env.DREAMBOARD_STAGING_CLERK_OAUTH_ISSUER ??\n process.env.DREAMBOARD_CLERK_OAUTH_ISSUER,\n clerkOAuthClientId:\n process.env.DREAMBOARD_STAGING_CLERK_OAUTH_CLIENT_ID ??\n process.env.DREAMBOARD_CLERK_OAUTH_CLIENT_ID,\n clerkOAuthScope:\n process.env.DREAMBOARD_STAGING_CLERK_OAUTH_SCOPE ??\n process.env.DREAMBOARD_CLERK_OAUTH_SCOPE,\n },\n prod: {\n apiBaseUrl: \"https://api.dreamboard.games\",\n webBaseUrl: \"https://dreamboard.games\",\n clerkOAuthIssuer:\n process.env.DREAMBOARD_PROD_CLERK_OAUTH_ISSUER ??\n process.env.DREAMBOARD_CLERK_OAUTH_ISSUER,\n clerkOAuthClientId:\n process.env.DREAMBOARD_PROD_CLERK_OAUTH_CLIENT_ID ??\n process.env.DREAMBOARD_CLERK_OAUTH_CLIENT_ID,\n clerkOAuthScope:\n process.env.DREAMBOARD_PROD_CLERK_OAUTH_SCOPE ??\n process.env.DREAMBOARD_CLERK_OAUTH_SCOPE,\n },\n};\nexport const PROJECT_CONFIG_FILE = \"project.json\";\nexport const PROJECT_STATE_FILE = \"state.json\";\nexport const SNAPSHOT_FILE = \"snapshot.json\";\nexport const MANIFEST_FILE = \"manifest.ts\";\nexport const GENERATED_DIR_NAME = \"generated\";\nexport const MATERIALIZED_MANIFEST_FILE = `${PROJECT_DIR_NAME}/${GENERATED_DIR_NAME}/manifest.json`;\nexport const MANIFEST_TYPECHECK_CONFIG_FILE = \"manifest.tsconfig.json\";\nexport const RULE_FILE = \"rule.md\";\nexport const DEFAULT_LOGIN_TIMEOUT_MS = 5 * 60 * 1000;\nexport const DEFAULT_TURN_DELAY_MS = 250;\n\nexport const LOCAL_IGNORE_DIRS = new Set([\n \".dreamboard\",\n \".git\",\n \"node_modules\",\n \"dist\",\n]);\n","import { mkdir, readFile, stat, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\n\nexport async function ensureDir(dirPath: string): Promise<void> {\n await mkdir(dirPath, { recursive: true });\n}\n\nexport async function exists(filePath: string): Promise<boolean> {\n try {\n await stat(filePath);\n return true;\n } catch {\n return false;\n }\n}\n\nexport async function readTextFile(filePath: string): Promise<string> {\n return readFile(filePath, \"utf8\");\n}\n\nexport async function readTextFileIfExists(\n filePath: string,\n): Promise<string | null> {\n try {\n return await readFile(filePath, \"utf8\");\n } catch {\n return null;\n }\n}\n\nexport async function writeTextFile(\n filePath: string,\n content: string,\n): Promise<void> {\n await ensureDir(path.dirname(filePath));\n await writeFile(filePath, content, \"utf8\");\n}\n\nexport async function readJsonFile<T>(filePath: string): Promise<T> {\n const data = await readTextFile(filePath);\n return JSON.parse(data) as T;\n}\n\nexport async function writeJsonFile(\n filePath: string,\n data: unknown,\n): Promise<void> {\n await writeTextFile(filePath, `${JSON.stringify(data, null, 2)}\\n`);\n}\n","/**\n * Primitives for safely mutating local state files owned by the CLI.\n *\n * Two guarantees:\n * - Writes are atomic-ish: we stage the payload in a sibling temp file with\n * the target permissions, fsync the contents, then `rename` over the target.\n * On POSIX `rename` within the same directory is atomic; on Windows it is\n * atomic within the same volume which is always the case for files we write\n * inside `~/.dreamboard`.\n * - We refuse to clobber a file with an empty payload. The original bug that\n * wiped refresh tokens on a failing `sync`/`compile` hinged on `undefined`\n * JSON values being persisted and reloaded as `{}`. Forbidding empty\n * writes here removes that entire failure mode at the primitive level.\n *\n * Additionally, `withFileLock` provides a cross-process advisory lock built on\n * `O_CREAT | O_EXCL` so that parallel CLI invocations (e.g. `dreamboard sync`\n * running while `dreamboard compile` is in flight) serialize around mutations\n * of the same credential state.\n */\n\nimport { constants as fsConstants, promises as fs, type Stats } from \"node:fs\";\nimport path from \"node:path\";\nimport crypto from \"node:crypto\";\n\nexport type AtomicWriteOptions = {\n /** File mode applied to the written file (default: 0o600). */\n mode?: number;\n /** Call `fsync` on the temp file before renaming. Default: true. */\n fsync?: boolean;\n};\n\nexport async function atomicWriteFile(\n targetPath: string,\n contents: string,\n options: AtomicWriteOptions = {},\n): Promise<void> {\n if (contents.length === 0) {\n throw new Error(\n `Refusing to atomicWriteFile an empty payload to ${targetPath}`,\n );\n }\n const mode = options.mode ?? 0o600;\n const shouldFsync = options.fsync ?? true;\n const dir = path.dirname(targetPath);\n await fs.mkdir(dir, { recursive: true });\n\n const suffix = crypto.randomBytes(6).toString(\"hex\");\n const tmpPath = `${targetPath}.tmp-${process.pid}-${suffix}`;\n\n const fh = await fs.open(\n tmpPath,\n fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL,\n mode,\n );\n try {\n await fh.writeFile(contents, \"utf8\");\n try {\n await fh.chmod(mode);\n } catch {\n // Some filesystems (e.g. network volumes, Windows) refuse chmod.\n // Ignoring here is safe: the `open` call above already created the\n // file with the requested mode on systems that honor it.\n }\n if (shouldFsync) {\n try {\n await fh.sync();\n } catch {\n // Best-effort. Not all backends (tmpfs on some platforms) support fsync.\n }\n }\n } finally {\n await fh.close();\n }\n\n try {\n await fs.rename(tmpPath, targetPath);\n } catch (err) {\n await fs.unlink(tmpPath).catch(() => undefined);\n throw err;\n }\n}\n\nexport type FileLockOptions = {\n /** Max number of acquisition attempts before giving up. Default: 100. */\n retries?: number;\n /** Minimum backoff between retries in ms. Default: 20. */\n minDelayMs?: number;\n /** Maximum backoff between retries in ms. Default: 200. */\n maxDelayMs?: number;\n /**\n * A lockfile older than this is considered stale and forcibly removed.\n * Guards against crashed processes leaving a permanent lock. Default: 30s.\n */\n staleMs?: number;\n};\n\nexport async function withFileLock<T>(\n lockPath: string,\n fn: () => Promise<T>,\n options: FileLockOptions = {},\n): Promise<T> {\n const retries = options.retries ?? 100;\n const minDelayMs = options.minDelayMs ?? 20;\n const maxDelayMs = options.maxDelayMs ?? 200;\n const staleMs = options.staleMs ?? 30_000;\n\n await fs.mkdir(path.dirname(lockPath), { recursive: true });\n\n let attempt = 0;\n let acquired = false;\n while (!acquired) {\n try {\n const fh = await fs.open(\n lockPath,\n fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL,\n 0o600,\n );\n await fh.writeFile(`${process.pid}\\n`, \"utf8\");\n await fh.close();\n acquired = true;\n break;\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code !== \"EEXIST\") {\n throw err;\n }\n }\n\n let stat: Stats | null = null;\n try {\n stat = await fs.stat(lockPath);\n } catch {\n continue;\n }\n if (stat !== null) {\n const ageMs = Date.now() - stat.mtimeMs;\n if (ageMs > staleMs) {\n await fs.unlink(lockPath).catch(() => undefined);\n continue;\n }\n }\n\n attempt += 1;\n if (attempt >= retries) {\n throw new Error(\n `Timed out acquiring file lock at ${lockPath} after ${retries} attempts.`,\n );\n }\n const jitter = Math.floor(\n Math.random() * Math.max(1, maxDelayMs - minDelayMs),\n );\n await new Promise((resolve) => setTimeout(resolve, minDelayMs + jitter));\n }\n\n try {\n return await fn();\n } finally {\n await fs.unlink(lockPath).catch(() => undefined);\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","path","stat","path","fs","path","fs","loadGlobalConfig","path","os"]}
|
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
setCredentials,
|
|
23
23
|
writeJsonFile,
|
|
24
24
|
writeTextFile
|
|
25
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-C6UAT6EH.js";
|
|
26
26
|
import {
|
|
27
27
|
StaleContractArtifactError,
|
|
28
28
|
__export as __export2
|
|
@@ -999,7 +999,7 @@ var createClient = (config2 = {}) => {
|
|
|
999
999
|
var client = createClient(createConfig({ baseUrl: "https://dreamboard.games" }));
|
|
1000
1000
|
|
|
1001
1001
|
// src/build-target.ts
|
|
1002
|
-
var injectedBuildChannel = true ? "
|
|
1002
|
+
var injectedBuildChannel = true ? "development" : void 0;
|
|
1003
1003
|
var BUILD_CHANNEL = injectedBuildChannel === "published" ? "published" : "development";
|
|
1004
1004
|
var IS_PUBLISHED_BUILD = BUILD_CHANNEL === "published";
|
|
1005
1005
|
var PUBLISHED_ENVIRONMENT = "prod";
|
|
@@ -16052,6 +16052,9 @@ function parseNewCommandArgs(args) {
|
|
|
16052
16052
|
function parseCloneCommandArgs(args) {
|
|
16053
16053
|
return parseArgs("clone", cloneCommandArgsSchema, args);
|
|
16054
16054
|
}
|
|
16055
|
+
function parseQueryCommandArgs(args) {
|
|
16056
|
+
return parseArgs("query", queryCommandArgsSchema, args);
|
|
16057
|
+
}
|
|
16055
16058
|
function parsePullCommandArgs(args) {
|
|
16056
16059
|
return parseArgs("pull", pullCommandArgsSchema, args);
|
|
16057
16060
|
}
|
|
@@ -16076,6 +16079,9 @@ function parseLoginCommandArgs(args) {
|
|
|
16076
16079
|
function parseConfigCommandArgs(args) {
|
|
16077
16080
|
return parseArgs("config", configCommandArgsSchema, args);
|
|
16078
16081
|
}
|
|
16082
|
+
function parseAuthCommandArgs(args) {
|
|
16083
|
+
return parseArgs("auth", authCommandArgsSchema, args);
|
|
16084
|
+
}
|
|
16079
16085
|
|
|
16080
16086
|
// src/command-args.ts
|
|
16081
16087
|
var CONFIG_FLAG_ARGS = {
|
|
@@ -16171,6 +16177,11 @@ var getProjectSources = (options) => (options.client ?? client).get({
|
|
|
16171
16177
|
url: "/api/projects/{projectId}/sources",
|
|
16172
16178
|
...options
|
|
16173
16179
|
});
|
|
16180
|
+
var queryWorkshopRulebook = (options) => (options.client ?? client).get({
|
|
16181
|
+
security: [{ scheme: "bearer", type: "http" }],
|
|
16182
|
+
url: "/api/workshop/query",
|
|
16183
|
+
...options
|
|
16184
|
+
});
|
|
16174
16185
|
var listCompiledResults = (options) => (options.client ?? client).get({
|
|
16175
16186
|
security: [{ scheme: "bearer", type: "http" }],
|
|
16176
16187
|
url: "/api/games/{gameId}/compiled-results",
|
|
@@ -39701,13 +39712,14 @@ async function runReducerNativeScenarios(options) {
|
|
|
39701
39712
|
}
|
|
39702
39713
|
|
|
39703
39714
|
export {
|
|
39715
|
+
createPkcePair,
|
|
39716
|
+
buildClerkAuthorizationUrl,
|
|
39717
|
+
exchangeClerkOAuthCode,
|
|
39704
39718
|
IS_PUBLISHED_BUILD,
|
|
39719
|
+
PUBLISHED_ENVIRONMENT,
|
|
39705
39720
|
loadProjectConfig,
|
|
39706
39721
|
updateProjectState,
|
|
39707
39722
|
findProjectRoot,
|
|
39708
|
-
createPkcePair,
|
|
39709
|
-
buildClerkAuthorizationUrl,
|
|
39710
|
-
exchangeClerkOAuthCode,
|
|
39711
39723
|
resolveLocalHarnessAccessToken,
|
|
39712
39724
|
resolveConfig,
|
|
39713
39725
|
configureClient,
|
|
@@ -39721,6 +39733,7 @@ export {
|
|
|
39721
39733
|
parsePlayerCountFlags,
|
|
39722
39734
|
parseNewCommandArgs,
|
|
39723
39735
|
parseCloneCommandArgs,
|
|
39736
|
+
parseQueryCommandArgs,
|
|
39724
39737
|
parsePullCommandArgs,
|
|
39725
39738
|
parseSyncCommandArgs,
|
|
39726
39739
|
parseCompileCommandArgs,
|
|
@@ -39729,12 +39742,12 @@ export {
|
|
|
39729
39742
|
parseJoinCommandArgs,
|
|
39730
39743
|
parseLoginCommandArgs,
|
|
39731
39744
|
parseConfigCommandArgs,
|
|
39732
|
-
|
|
39733
|
-
parsePositiveInt,
|
|
39734
|
-
CONFIG_FLAG_ARGS,
|
|
39745
|
+
parseAuthCommandArgs,
|
|
39735
39746
|
getApiVersion,
|
|
39747
|
+
queryWorkshopRulebook,
|
|
39736
39748
|
getSessionSnapshot,
|
|
39737
39749
|
getSessionEventBatch,
|
|
39750
|
+
CONFIG_FLAG_ARGS,
|
|
39738
39751
|
STALE_CONTRACT_ARTIFACT_CODE,
|
|
39739
39752
|
STALE_CONTRACT_ARTIFACT_EXIT_CODE,
|
|
39740
39753
|
toApiProblem,
|
|
@@ -39744,6 +39757,8 @@ export {
|
|
|
39744
39757
|
isStaleContractArtifactError,
|
|
39745
39758
|
getCliErrorExitCode,
|
|
39746
39759
|
formatCliError,
|
|
39760
|
+
normalizeSlug,
|
|
39761
|
+
parsePositiveInt,
|
|
39747
39762
|
findCompiledResultsForAuthoringState,
|
|
39748
39763
|
findProjectCompiledResultsForRevision,
|
|
39749
39764
|
getProjectCompiledResultSdk,
|
|
@@ -39806,4 +39821,4 @@ export {
|
|
|
39806
39821
|
generateReducerNativeArtifacts,
|
|
39807
39822
|
runReducerNativeScenarios
|
|
39808
39823
|
};
|
|
39809
|
-
//# sourceMappingURL=chunk-
|
|
39824
|
+
//# sourceMappingURL=chunk-RS7UXJZV.js.map
|