-
Notifications
You must be signed in to change notification settings - Fork 514
Expand file tree
/
Copy pathlocal-emulator.ts
More file actions
149 lines (134 loc) · 5.96 KB
/
local-emulator.ts
File metadata and controls
149 lines (134 loc) · 5.96 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
import { globalPrismaClient } from "@/prisma-client";
import { detectImportPackageFromDir, renderConfigFileContent } from "@stackframe/stack-shared/dist/config-rendering";
import { isValidConfig } from "@stackframe/stack-shared/dist/config/format";
import { LOCAL_EMULATOR_ADMIN_EMAIL, LOCAL_EMULATOR_ADMIN_PASSWORD } from "@stackframe/stack-shared/dist/local-emulator";
import { getEnvVariable } from "@stackframe/stack-shared/dist/utils/env";
import { StatusError } from "@stackframe/stack-shared/dist/utils/errors";
import fs from "fs/promises";
import { createJiti } from "jiti";
import path from "path";
export const LOCAL_EMULATOR_ADMIN_USER_ID = "63abbc96-5329-454a-ba56-e0460173c6c1";
export const LOCAL_EMULATOR_OWNER_TEAM_ID = "5a0c858b-d9e9-49d4-9943-8ce385d86428";
export { LOCAL_EMULATOR_ADMIN_EMAIL, LOCAL_EMULATOR_ADMIN_PASSWORD };
export const LOCAL_EMULATOR_ENV_CONFIG_BLOCKED_MESSAGE =
"Environment configuration overrides cannot be changed in the local emulator. Update this in your production deployment instead.";
export const LOCAL_EMULATOR_ONLY_ENDPOINT_MESSAGE =
"This endpoint is only available in local emulator mode (set NEXT_PUBLIC_STACK_IS_LOCAL_EMULATOR=true).";
export const LOCAL_EMULATOR_HOST_MOUNT_ROOT_ENV = "STACK_LOCAL_EMULATOR_HOST_MOUNT_ROOT";
export const LOCAL_EMULATOR_SHOW_ONBOARDING_VALUE = "show-onboarding" as const;
type LocalEmulatorConfigValue = Record<string, unknown> | typeof LOCAL_EMULATOR_SHOW_ONBOARDING_VALUE;
export function isLocalEmulatorEnabled() {
return getEnvVariable("NEXT_PUBLIC_STACK_IS_LOCAL_EMULATOR", "") === "true";
}
export async function isLocalEmulatorProject(projectId: string) {
if (!isLocalEmulatorEnabled()) {
return false;
}
const project = await globalPrismaClient.localEmulatorProject.findUnique({
where: {
projectId,
},
select: {
projectId: true,
},
});
return project !== null;
}
export async function getLocalEmulatorFilePath(projectId: string): Promise<string | null> {
const result = await globalPrismaClient.localEmulatorProject.findUnique({
where: { projectId },
select: { absoluteFilePath: true },
});
return result?.absoluteFilePath ?? null;
}
export function resolveEmulatorPath(filePath: string): string {
const hostMountRoot = getEnvVariable(LOCAL_EMULATOR_HOST_MOUNT_ROOT_ENV, "");
if (hostMountRoot) {
return path.join(hostMountRoot, filePath);
}
return filePath;
}
async function readConfigContent(filePath: string): Promise<string> {
// Check for base64-encoded config content override from env var
const envContent = getEnvVariable("STACK_LOCAL_EMULATOR_CONFIG_CONTENT", "");
if (envContent) {
return Buffer.from(envContent, "base64").toString("utf-8");
}
const resolvedPath = resolveEmulatorPath(filePath);
try {
return await fs.readFile(resolvedPath, "utf-8");
} catch (e: any) {
if (e?.code === "ENOENT") {
return "";
}
throw e;
}
}
async function readConfigValueFromFile(filePath: string): Promise<LocalEmulatorConfigValue> {
const content = await readConfigContent(filePath);
if (content.trim() === "") {
return {};
}
const evalFilename = /\.[cm]?tsx?$/.test(filePath) ? filePath : `${filePath}.ts`;
const jiti = createJiti(import.meta.url, { cache: false });
let mod: Record<string, unknown>;
try {
mod = jiti.evalModule(content, { filename: evalFilename }) as Record<string, unknown>;
} catch (e) {
const message = e instanceof Error ? e.message : String(e);
throw new StatusError(StatusError.BadRequest, `Error evaluating config in ${filePath}: ${message}`);
}
const config = mod.config;
if (config === LOCAL_EMULATOR_SHOW_ONBOARDING_VALUE) {
return config;
}
if (!isValidConfig(config)) {
throw new StatusError(StatusError.BadRequest, `Invalid config in ${filePath}. The file must export a 'config' object or "show-onboarding".`);
}
return config;
}
export async function isLocalEmulatorOnboardingEnabledInConfig(filePath: string): Promise<boolean> {
const config = await readConfigValueFromFile(filePath);
return config === LOCAL_EMULATOR_SHOW_ONBOARDING_VALUE;
}
export async function readConfigFromFile(filePath: string): Promise<Record<string, unknown>> {
const config = await readConfigValueFromFile(filePath);
if (config === LOCAL_EMULATOR_SHOW_ONBOARDING_VALUE) {
return {};
}
return config;
}
export async function writeConfigToFile(filePath: string, config: Record<string, unknown>): Promise<void> {
const resolvedPath = resolveEmulatorPath(filePath);
const dir = path.dirname(resolvedPath);
const hostMountRoot = getEnvVariable(LOCAL_EMULATOR_HOST_MOUNT_ROOT_ENV, "");
if (hostMountRoot) {
try {
await fs.access(dir);
} catch {
throw new Error(`Local emulator host mount root ${hostMountRoot} is configured but the parent directory for ${filePath} is not available at ${dir}. Ensure the host filesystem is mounted correctly.`);
}
} else {
await fs.mkdir(dir, { recursive: true });
}
const importPackage = detectImportPackageFromDir(dir);
const content = renderConfigFileContent(config, importPackage);
await fs.writeFile(resolvedPath, content, "utf-8");
}
export async function writeShowOnboardingConfigToFile(filePath: string): Promise<void> {
const resolvedPath = resolveEmulatorPath(filePath);
const dir = path.dirname(resolvedPath);
const hostMountRoot = getEnvVariable(LOCAL_EMULATOR_HOST_MOUNT_ROOT_ENV, "");
if (hostMountRoot) {
try {
await fs.access(dir);
} catch {
throw new Error(`Local emulator host mount root ${hostMountRoot} is configured but the parent directory for ${filePath} is not available at ${dir}. Ensure the host filesystem is mounted correctly.`);
}
} else {
await fs.mkdir(dir, { recursive: true });
}
const importPackage = detectImportPackageFromDir(dir) ?? "@stackframe/js";
const content = `import type { StackConfig } from "${importPackage}";\n\nexport const config: StackConfig = "show-onboarding";\n`;
await fs.writeFile(resolvedPath, content, "utf-8");
}