visionos-monorepo 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. package/.claude/worktrees/competent-burnell-8d1330/README.md +138 -0
  2. package/.claude/worktrees/competent-burnell-8d1330/cli/package.json +35 -0
  3. package/.claude/worktrees/competent-burnell-8d1330/cli/scripts/copy-web-assets.mjs +12 -0
  4. package/.claude/worktrees/competent-burnell-8d1330/cli/src/commands/logout.ts +12 -0
  5. package/.claude/worktrees/competent-burnell-8d1330/cli/src/commands/open.ts +19 -0
  6. package/.claude/worktrees/competent-burnell-8d1330/cli/src/commands/start.ts +97 -0
  7. package/.claude/worktrees/competent-burnell-8d1330/cli/src/commands/status.ts +23 -0
  8. package/.claude/worktrees/competent-burnell-8d1330/cli/src/commands/userinfo.ts +47 -0
  9. package/.claude/worktrees/competent-burnell-8d1330/cli/src/index.ts +23 -0
  10. package/.claude/worktrees/competent-burnell-8d1330/cli/src/lib/auth.ts +84 -0
  11. package/.claude/worktrees/competent-burnell-8d1330/cli/src/lib/browser.ts +37 -0
  12. package/.claude/worktrees/competent-burnell-8d1330/cli/src/lib/localState.ts +80 -0
  13. package/.claude/worktrees/competent-burnell-8d1330/cli/src/lib/runtime.ts +203 -0
  14. package/.claude/worktrees/competent-burnell-8d1330/cli/src/runtime/index.ts +36 -0
  15. package/.claude/worktrees/competent-burnell-8d1330/cli/src/types/inquirer.d.ts +9 -0
  16. package/.claude/worktrees/competent-burnell-8d1330/cli/tsconfig.json +19 -0
  17. package/.claude/worktrees/competent-burnell-8d1330/client/index.html +15 -0
  18. package/.claude/worktrees/competent-burnell-8d1330/client/package.json +27 -0
  19. package/.claude/worktrees/competent-burnell-8d1330/client/postcss.config.cjs +7 -0
  20. package/.claude/worktrees/competent-burnell-8d1330/client/src/App.tsx +57 -0
  21. package/.claude/worktrees/competent-burnell-8d1330/client/src/components/CliAuthPage.tsx +385 -0
  22. package/.claude/worktrees/competent-burnell-8d1330/client/src/components/ManifestoPage.tsx +946 -0
  23. package/.claude/worktrees/competent-burnell-8d1330/client/src/components/TrackCard.tsx +19 -0
  24. package/.claude/worktrees/competent-burnell-8d1330/client/src/lib/api.ts +58 -0
  25. package/.claude/worktrees/competent-burnell-8d1330/client/src/main.tsx +11 -0
  26. package/.claude/worktrees/competent-burnell-8d1330/client/src/styles/index.css +33 -0
  27. package/.claude/worktrees/competent-burnell-8d1330/client/src/styles/manifesto.css +1398 -0
  28. package/.claude/worktrees/competent-burnell-8d1330/client/tailwind.config.ts +36 -0
  29. package/.claude/worktrees/competent-burnell-8d1330/client/tsconfig.json +25 -0
  30. package/.claude/worktrees/competent-burnell-8d1330/client/vite.config.ts +20 -0
  31. package/.claude/worktrees/competent-burnell-8d1330/package-lock.json +5278 -0
  32. package/.claude/worktrees/competent-burnell-8d1330/package.json +24 -0
  33. package/.claude/worktrees/competent-burnell-8d1330/server/package.json +25 -0
  34. package/.claude/worktrees/competent-burnell-8d1330/server/src/app.ts +71 -0
  35. package/.claude/worktrees/competent-burnell-8d1330/server/src/config/env.ts +14 -0
  36. package/.claude/worktrees/competent-burnell-8d1330/server/src/features/auth/sessionStore.ts +74 -0
  37. package/.claude/worktrees/competent-burnell-8d1330/server/src/index.ts +8 -0
  38. package/.claude/worktrees/competent-burnell-8d1330/server/src/routes/auth.ts +112 -0
  39. package/.claude/worktrees/competent-burnell-8d1330/server/src/routes/health.ts +14 -0
  40. package/.claude/worktrees/competent-burnell-8d1330/server/tsconfig.json +19 -0
  41. package/.claude/worktrees/competent-burnell-8d1330/shared/package.json +24 -0
  42. package/.claude/worktrees/competent-burnell-8d1330/shared/src/index.ts +91 -0
  43. package/.claude/worktrees/competent-burnell-8d1330/shared/tsconfig.json +16 -0
  44. package/.claude/worktrees/competent-burnell-8d1330/tsconfig.base.json +12 -0
  45. package/.claude/worktrees/competent-burnell-8d1330/visionos-manifesto/index.html +392 -0
  46. package/.claude/worktrees/competent-burnell-8d1330/visionos-manifesto/script.js +146 -0
  47. package/.claude/worktrees/competent-burnell-8d1330/visionos-manifesto/styles.css +1082 -0
  48. package/.claude/worktrees/vigilant-napier-0de76f/README.md +138 -0
  49. package/.claude/worktrees/vigilant-napier-0de76f/cli/package.json +35 -0
  50. package/.claude/worktrees/vigilant-napier-0de76f/cli/scripts/copy-web-assets.mjs +12 -0
  51. package/.claude/worktrees/vigilant-napier-0de76f/cli/src/commands/logout.ts +12 -0
  52. package/.claude/worktrees/vigilant-napier-0de76f/cli/src/commands/open.ts +19 -0
  53. package/.claude/worktrees/vigilant-napier-0de76f/cli/src/commands/start.ts +97 -0
  54. package/.claude/worktrees/vigilant-napier-0de76f/cli/src/commands/status.ts +23 -0
  55. package/.claude/worktrees/vigilant-napier-0de76f/cli/src/commands/userinfo.ts +47 -0
  56. package/.claude/worktrees/vigilant-napier-0de76f/cli/src/index.ts +23 -0
  57. package/.claude/worktrees/vigilant-napier-0de76f/cli/src/lib/auth.ts +84 -0
  58. package/.claude/worktrees/vigilant-napier-0de76f/cli/src/lib/browser.ts +37 -0
  59. package/.claude/worktrees/vigilant-napier-0de76f/cli/src/lib/localState.ts +80 -0
  60. package/.claude/worktrees/vigilant-napier-0de76f/cli/src/lib/runtime.ts +203 -0
  61. package/.claude/worktrees/vigilant-napier-0de76f/cli/src/runtime/index.ts +36 -0
  62. package/.claude/worktrees/vigilant-napier-0de76f/cli/src/types/inquirer.d.ts +9 -0
  63. package/.claude/worktrees/vigilant-napier-0de76f/cli/tsconfig.json +19 -0
  64. package/.claude/worktrees/vigilant-napier-0de76f/client/index.html +15 -0
  65. package/.claude/worktrees/vigilant-napier-0de76f/client/package.json +27 -0
  66. package/.claude/worktrees/vigilant-napier-0de76f/client/postcss.config.cjs +7 -0
  67. package/.claude/worktrees/vigilant-napier-0de76f/client/src/App.tsx +57 -0
  68. package/.claude/worktrees/vigilant-napier-0de76f/client/src/components/CliAuthPage.tsx +385 -0
  69. package/.claude/worktrees/vigilant-napier-0de76f/client/src/components/ManifestoPage.tsx +946 -0
  70. package/.claude/worktrees/vigilant-napier-0de76f/client/src/components/TrackCard.tsx +19 -0
  71. package/.claude/worktrees/vigilant-napier-0de76f/client/src/lib/api.ts +58 -0
  72. package/.claude/worktrees/vigilant-napier-0de76f/client/src/main.tsx +11 -0
  73. package/.claude/worktrees/vigilant-napier-0de76f/client/src/styles/index.css +33 -0
  74. package/.claude/worktrees/vigilant-napier-0de76f/client/src/styles/manifesto.css +1398 -0
  75. package/.claude/worktrees/vigilant-napier-0de76f/client/tailwind.config.ts +36 -0
  76. package/.claude/worktrees/vigilant-napier-0de76f/client/tsconfig.json +25 -0
  77. package/.claude/worktrees/vigilant-napier-0de76f/client/vite.config.ts +20 -0
  78. package/.claude/worktrees/vigilant-napier-0de76f/package-lock.json +5278 -0
  79. package/.claude/worktrees/vigilant-napier-0de76f/package.json +24 -0
  80. package/.claude/worktrees/vigilant-napier-0de76f/server/package.json +25 -0
  81. package/.claude/worktrees/vigilant-napier-0de76f/server/src/app.ts +71 -0
  82. package/.claude/worktrees/vigilant-napier-0de76f/server/src/config/env.ts +14 -0
  83. package/.claude/worktrees/vigilant-napier-0de76f/server/src/features/auth/sessionStore.ts +74 -0
  84. package/.claude/worktrees/vigilant-napier-0de76f/server/src/index.ts +8 -0
  85. package/.claude/worktrees/vigilant-napier-0de76f/server/src/routes/auth.ts +112 -0
  86. package/.claude/worktrees/vigilant-napier-0de76f/server/src/routes/health.ts +14 -0
  87. package/.claude/worktrees/vigilant-napier-0de76f/server/tsconfig.json +19 -0
  88. package/.claude/worktrees/vigilant-napier-0de76f/shared/package.json +24 -0
  89. package/.claude/worktrees/vigilant-napier-0de76f/shared/src/index.ts +91 -0
  90. package/.claude/worktrees/vigilant-napier-0de76f/shared/tsconfig.json +16 -0
  91. package/.claude/worktrees/vigilant-napier-0de76f/tsconfig.base.json +12 -0
  92. package/.claude/worktrees/vigilant-napier-0de76f/visionos-manifesto/index.html +392 -0
  93. package/.claude/worktrees/vigilant-napier-0de76f/visionos-manifesto/script.js +146 -0
  94. package/.claude/worktrees/vigilant-napier-0de76f/visionos-manifesto/styles.css +1082 -0
  95. package/.github/workflows/publish.yml +30 -0
  96. package/README.md +175 -0
  97. package/cli/README.md +165 -0
  98. package/cli/package.json +36 -0
  99. package/cli/scripts/copy-web-assets.mjs +12 -0
  100. package/cli/src/commands/lessons.ts +68 -0
  101. package/cli/src/commands/login.ts +46 -0
  102. package/cli/src/commands/logout.ts +12 -0
  103. package/cli/src/commands/open.ts +29 -0
  104. package/cli/src/commands/start.ts +146 -0
  105. package/cli/src/commands/status.ts +28 -0
  106. package/cli/src/commands/userinfo.ts +59 -0
  107. package/cli/src/index.ts +109 -0
  108. package/cli/src/lib/auth.ts +84 -0
  109. package/cli/src/lib/browser.ts +37 -0
  110. package/cli/src/lib/content.ts +57 -0
  111. package/cli/src/lib/lessonPrinter.ts +38 -0
  112. package/cli/src/lib/lessonRunner.ts +381 -0
  113. package/cli/src/lib/localState.ts +114 -0
  114. package/cli/src/lib/loginFlow.ts +74 -0
  115. package/cli/src/lib/progress.ts +94 -0
  116. package/cli/src/lib/runtime.ts +220 -0
  117. package/cli/src/lib/validator.ts +401 -0
  118. package/cli/src/runtime/index.ts +108 -0
  119. package/cli/src/types/inquirer.d.ts +9 -0
  120. package/cli/tsconfig.json +19 -0
  121. package/client/index.html +15 -0
  122. package/client/package.json +27 -0
  123. package/client/postcss.config.cjs +7 -0
  124. package/client/src/App.tsx +102 -0
  125. package/client/src/components/AccountPage.tsx +79 -0
  126. package/client/src/components/AuthPanel.tsx +312 -0
  127. package/client/src/components/CliAuthPage.tsx +367 -0
  128. package/client/src/components/CreatorPortal.tsx +885 -0
  129. package/client/src/components/ErrorBoundary.tsx +92 -0
  130. package/client/src/components/ManifestoPage.tsx +1126 -0
  131. package/client/src/components/TrackCard.tsx +19 -0
  132. package/client/src/lib/api.ts +215 -0
  133. package/client/src/main.tsx +14 -0
  134. package/client/src/styles/index.css +33 -0
  135. package/client/src/styles/manifesto.css +1828 -0
  136. package/client/tailwind.config.ts +36 -0
  137. package/client/tsconfig.json +25 -0
  138. package/client/vercel.json +8 -0
  139. package/client/vite.config.ts +33 -0
  140. package/package.json +27 -0
  141. package/server/package.json +26 -0
  142. package/server/src/app.ts +132 -0
  143. package/server/src/config/env.ts +135 -0
  144. package/server/src/features/accounts/accountStore.ts +359 -0
  145. package/server/src/features/accounts/contentStore.ts +264 -0
  146. package/server/src/features/accounts/password.ts +26 -0
  147. package/server/src/features/auth/sessionStore.ts +79 -0
  148. package/server/src/index.ts +8 -0
  149. package/server/src/routes/auth.ts +328 -0
  150. package/server/src/routes/content.ts +174 -0
  151. package/server/src/routes/health.ts +14 -0
  152. package/server/src/routes/progress.ts +105 -0
  153. package/server/tsconfig.json +19 -0
  154. package/shared/package.json +24 -0
  155. package/shared/src/index.ts +455 -0
  156. package/shared/tsconfig.json +16 -0
  157. package/tsconfig.base.json +12 -0
  158. package/visionos-manifesto/index.html +392 -0
  159. package/visionos-manifesto/script.js +146 -0
  160. package/visionos-manifesto/styles.css +1082 -0
@@ -0,0 +1,36 @@
1
+ import type { Config } from "tailwindcss";
2
+
3
+ const config: Config = {
4
+ content: ["./index.html", "./src/**/*.{ts,tsx}"],
5
+ theme: {
6
+ extend: {
7
+ colors: {
8
+ ink: "#0A0A0A",
9
+ "ink-soft": "#3A3A3A",
10
+ "ink-muted": "#6B6B6B",
11
+ beige: "#E0DFD5",
12
+ "beige-light": "#E8E7DF",
13
+ "beige-dark": "#D4D3CB",
14
+ tide: "#0e7490",
15
+ coral: "#E05A3A",
16
+ sand: "#FAFAF8",
17
+ mist: "#E0DFD5"
18
+ },
19
+ fontFamily: {
20
+ sans: ["Inter", "-apple-system", "BlinkMacSystemFont", "sans-serif"],
21
+ mono: ["JetBrains Mono", "SF Mono", "Fira Code", "monospace"]
22
+ },
23
+ boxShadow: {
24
+ panel: "0 20px 60px rgba(8, 17, 31, 0.12)"
25
+ },
26
+ borderRadius: {
27
+ lg: "16px",
28
+ md: "8px",
29
+ sm: "4px"
30
+ }
31
+ }
32
+ },
33
+ plugins: []
34
+ };
35
+
36
+ export default config;
@@ -0,0 +1,25 @@
1
+ {
2
+ "extends": "../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "target": "ES2020",
5
+ "lib": [
6
+ "ES2020",
7
+ "DOM",
8
+ "DOM.Iterable"
9
+ ],
10
+ "module": "ESNext",
11
+ "moduleResolution": "Bundler",
12
+ "jsx": "react-jsx",
13
+ "types": [
14
+ "vite/client",
15
+ "node"
16
+ ],
17
+ "noEmit": true
18
+ },
19
+ "include": [
20
+ "src",
21
+ "vite.config.ts",
22
+ "tailwind.config.ts",
23
+ "../shared/src/**/*.ts"
24
+ ]
25
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "rewrites": [
3
+ {
4
+ "source": "/(.*)",
5
+ "destination": "/index.html"
6
+ }
7
+ ]
8
+ }
@@ -0,0 +1,33 @@
1
+ import path from "node:path";
2
+ import { fileURLToPath } from "node:url";
3
+ import { defineConfig, loadEnv } from "vite";
4
+ import react from "@vitejs/plugin-react";
5
+
6
+ const rootDir = path.dirname(fileURLToPath(import.meta.url));
7
+
8
+ export default defineConfig(({ mode }) => {
9
+ const env = loadEnv(mode, rootDir, "");
10
+ const apiProxyTarget = env.VITE_API_PROXY_TARGET ?? "https://visionos.onrender.com";
11
+
12
+ return {
13
+ plugins: [react()],
14
+ resolve: {
15
+ alias: {
16
+ "@": path.resolve(rootDir, "./src")
17
+ }
18
+ },
19
+ server: {
20
+ fs: {
21
+ allow: [path.resolve(rootDir, "..")]
22
+ },
23
+ proxy: {
24
+ "/api": {
25
+ target: apiProxyTarget,
26
+ changeOrigin: true,
27
+ secure: true,
28
+ rewrite: (requestPath) => requestPath.replace(/^\/api/, "")
29
+ }
30
+ }
31
+ }
32
+ };
33
+ });
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "visionos-monorepo",
3
+ "version": "0.1.0",
4
+ "private": false,
5
+ "packageManager": "npm@10",
6
+ "workspaces": [
7
+ "cli",
8
+ "server",
9
+ "client",
10
+ "shared"
11
+ ],
12
+ "scripts": {
13
+ "build": "npm run build -w shared && npm run build -w server && npm run build -w client && npm run build -w cli",
14
+ "pack:cli": "npm run build && npm pack -w cli",
15
+ "dev": "concurrently -k -n server,client -c blue,magenta \"npm run dev:server\" \"npm run dev:client\"",
16
+ "dev:cli": "npm run dev -w cli",
17
+ "dev:server": "npm run dev -w server",
18
+ "dev:client": "npm run dev -w client",
19
+ "lint": "npm run lint --workspaces --if-present"
20
+ },
21
+ "devDependencies": {
22
+ "concurrently": "^9.2.1"
23
+ },
24
+ "dependencies": {
25
+ "visionos-cli": "^0.1.7"
26
+ }
27
+ }
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "@visionos/server",
3
+ "version": "0.1.0",
4
+ "private": false,
5
+ "type": "module",
6
+ "scripts": {
7
+ "build": "tsup src/index.ts --format esm --clean --out-dir dist",
8
+ "dev": "tsx watch src/index.ts",
9
+ "start": "node dist/index.js",
10
+ "lint": "tsc --noEmit -p tsconfig.json"
11
+ },
12
+ "dependencies": {
13
+ "cors": "^2.8.5",
14
+ "dotenv": "^16.4.5",
15
+ "express": "^4.21.1",
16
+ "mongodb": "^6.21.0"
17
+ },
18
+ "devDependencies": {
19
+ "@types/cors": "^2.8.17",
20
+ "@types/express": "^4.17.21",
21
+ "@types/node": "^22.10.1",
22
+ "tsup": "^8.3.0",
23
+ "tsx": "^4.19.1",
24
+ "typescript": "^5.6.3"
25
+ }
26
+ }
@@ -0,0 +1,132 @@
1
+ import path from "node:path";
2
+ import cors from "cors";
3
+ import express from "express";
4
+ import { env } from "./config/env.js";
5
+ import { AccountStoreError, createAccountStore, type AccountStore } from "./features/accounts/accountStore.js";
6
+ import { MongoContentStore, type ContentStore } from "./features/accounts/contentStore.js";
7
+ import { createAuthRouter, type CreateAuthRouterOptions } from "./routes/auth.js";
8
+ import { healthRouter } from "./routes/health.js";
9
+ import { createProgressRouter } from "./routes/progress.js";
10
+ import { createContentRouter } from "./routes/content.js";
11
+
12
+ export interface CreateAppOptions extends CreateAuthRouterOptions {
13
+ accountStore?: AccountStore | null;
14
+ contentStore?: ContentStore | null;
15
+ apiBaseUrl?: string;
16
+ clientOrigin?: string;
17
+ corsOrigin?: any;
18
+ mode?: "development" | "embedded";
19
+ staticSiteDir?: string;
20
+ getProgress?: (userId: string) => Promise<any[]> | any[];
21
+ upsertProgress?: (userId: string, input: any) => Promise<any> | any;
22
+ }
23
+
24
+ function normalizeOrigin(origin: string) {
25
+ return origin.trim().replace(/\/+$/, "");
26
+ }
27
+
28
+ function toAllowedOriginSet(corsOrigin: any) {
29
+ if (corsOrigin === true) {
30
+ return true;
31
+ }
32
+
33
+ const origins = Array.isArray(corsOrigin)
34
+ ? corsOrigin
35
+ : typeof corsOrigin === "string"
36
+ ? corsOrigin.split(",")
37
+ : [];
38
+
39
+ return new Set(
40
+ origins
41
+ .filter((origin): origin is string => typeof origin === "string")
42
+ .map(normalizeOrigin)
43
+ .filter(Boolean)
44
+ );
45
+ }
46
+
47
+ export function createApp(options: CreateAppOptions = {}) {
48
+ const app = express();
49
+ const clientOrigin = options.clientOrigin ?? env.clientOrigin;
50
+ const corsOrigin = options.corsOrigin ?? env.corsOrigin;
51
+ const allowedOrigins = toAllowedOriginSet(corsOrigin);
52
+ const apiBaseUrl = options.apiBaseUrl ?? `http://${env.host}:${env.port}`;
53
+ const accountStore = options.accountStore ?? createAccountStore(env.mongoUri, env.mongoDbName);
54
+ const contentStore = options.contentStore ?? (env.mongoUri ? new MongoContentStore(env.mongoUri, env.mongoDbName) : null);
55
+ const authRouter = createAuthRouter({
56
+ accountStore,
57
+ clientOrigin,
58
+ googleClientId: options.googleClientId ?? env.googleClientId,
59
+ getCurrentUser: options.getCurrentUser,
60
+ onAuthenticated: options.onAuthenticated
61
+ });
62
+
63
+ app.use(
64
+ cors({
65
+ origin(origin, callback) {
66
+ if (!origin || allowedOrigins === true || allowedOrigins.has(normalizeOrigin(origin))) {
67
+ callback(null, true);
68
+ return;
69
+ }
70
+
71
+ callback(null, false);
72
+ }
73
+ })
74
+ );
75
+ app.use(express.json());
76
+ app.use(express.urlencoded({ extended: true }));
77
+
78
+ app.use("/auth", authRouter);
79
+ app.use(
80
+ "/progress",
81
+ createProgressRouter(
82
+ accountStore,
83
+ options.getCurrentUser,
84
+ options.getProgress,
85
+ options.upsertProgress
86
+ )
87
+ );
88
+ app.use("/content", createContentRouter(accountStore, contentStore));
89
+ app.use("/health", healthRouter);
90
+
91
+ app.get("/runtime/info", (_req, res) => {
92
+ res.status(200).json({
93
+ runtime: {
94
+ appUrl: clientOrigin,
95
+ apiBaseUrl,
96
+ mainAppUrl: env.mainClientOrigin,
97
+ mode: options.mode ?? "development"
98
+ }
99
+ });
100
+ });
101
+
102
+ if (options.staticSiteDir) {
103
+ app.use(express.static(options.staticSiteDir));
104
+
105
+ app.get("*", (_req, res) => {
106
+ res.sendFile(path.join(options.staticSiteDir as string, "index.html"));
107
+ });
108
+ } else {
109
+ app.get("/", (_req, res) => {
110
+ res.status(200).json({
111
+ message: "VisionOS API bootstrap is running."
112
+ });
113
+ });
114
+ }
115
+
116
+ app.use((error: unknown, _req: express.Request, res: express.Response, _next: express.NextFunction) => {
117
+ if (error instanceof AccountStoreError) {
118
+ res.status(error.status).json({
119
+ message: error.message
120
+ });
121
+ return;
122
+ }
123
+
124
+ const message = error instanceof Error ? error.message : "Unexpected VisionOS server error.";
125
+
126
+ res.status(500).json({
127
+ message
128
+ });
129
+ });
130
+
131
+ return app;
132
+ }
@@ -0,0 +1,135 @@
1
+ import dotenv from "dotenv";
2
+ import fs from "node:fs";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+
7
+ let mainClientOrigin = "http://localhost:5173";
8
+
9
+ function loadEnvFile(filePath: string) {
10
+ if (fs.existsSync(filePath)) {
11
+ const result = dotenv.config({ path: filePath });
12
+ if (result.parsed?.CLIENT_ORIGIN) {
13
+ mainClientOrigin = result.parsed.CLIENT_ORIGIN;
14
+ }
15
+ return true;
16
+ }
17
+ return false;
18
+ }
19
+
20
+ // 1. Try standard current working directory
21
+ const defaultResult = dotenv.config();
22
+ if (defaultResult.parsed?.CLIENT_ORIGIN) {
23
+ mainClientOrigin = defaultResult.parsed.CLIENT_ORIGIN;
24
+ }
25
+
26
+ // 2. Walk up parent directories of process.cwd() to find `.env` or `server/.env`
27
+ try {
28
+ let currentDir = process.cwd();
29
+ for (let i = 0; i < 10; i++) {
30
+ const rootEnv = path.join(currentDir, ".env");
31
+ const serverEnv = path.join(currentDir, "server", ".env");
32
+
33
+ if (loadEnvFile(serverEnv)) {
34
+ break;
35
+ } else if (loadEnvFile(rootEnv)) {
36
+ break;
37
+ }
38
+
39
+ const parent = path.dirname(currentDir);
40
+ if (parent === currentDir) break;
41
+ currentDir = parent;
42
+ }
43
+ } catch {
44
+ // Ignore fallback errors
45
+ }
46
+
47
+ // 3. Walk up directories starting from import.meta.url to find `.env` or `server/.env`
48
+ try {
49
+ const startDir = path.dirname(fileURLToPath(import.meta.url));
50
+ let currentDir = startDir;
51
+
52
+ for (let i = 0; i < 10; i++) {
53
+ const rootEnv = path.join(currentDir, ".env");
54
+ const serverEnv = path.join(currentDir, "server", ".env");
55
+
56
+ if (loadEnvFile(serverEnv)) {
57
+ break;
58
+ } else if (loadEnvFile(rootEnv)) {
59
+ break;
60
+ }
61
+
62
+ const parent = path.dirname(currentDir);
63
+ if (parent === currentDir) break;
64
+ currentDir = parent;
65
+ }
66
+ } catch {
67
+ // Ignore fallback errors
68
+ }
69
+
70
+ // 4. Try global user configuration directory (~/.visionos/.env)
71
+ try {
72
+ const globalEnvPath = path.join(os.homedir(), ".visionos", ".env");
73
+ if (fs.existsSync(globalEnvPath)) {
74
+ const result = dotenv.config({ path: globalEnvPath });
75
+ if (result.parsed?.CLIENT_ORIGIN) {
76
+ mainClientOrigin = result.parsed.CLIENT_ORIGIN;
77
+ }
78
+ }
79
+ } catch {
80
+ // Ignore fallback errors
81
+ }
82
+
83
+ const rawPort = Number(process.env.PORT ?? 4000);
84
+ const host = process.env.HOST ?? "127.0.0.1";
85
+ const clientOrigin = process.env.CLIENT_ORIGIN ?? "http://localhost:5173";
86
+ const localDevOrigins = ["http://localhost:5173", "http://127.0.0.1:5173"];
87
+ const deployedOrigins = ["https://vision-os-client.vercel.app", "https://visionos.onrender.com"];
88
+
89
+ function normalizeOrigin(origin?: string) {
90
+ return origin?.trim().replace(/\/+$/, "");
91
+ }
92
+
93
+ function parseOrigins(value?: string) {
94
+ return (value ?? "")
95
+ .split(",")
96
+ .map(normalizeOrigin)
97
+ .filter(Boolean);
98
+ }
99
+
100
+ function uniqueOrigins(origins: Array<string | undefined>) {
101
+ return [
102
+ ...new Set(
103
+ origins
104
+ .map(normalizeOrigin)
105
+ .filter((origin): origin is string => Boolean(origin))
106
+ )
107
+ ];
108
+ }
109
+
110
+ function resolveCorsOrigin() {
111
+ const configuredOrigins = parseOrigins(process.env.CORS_ORIGIN);
112
+
113
+ if (configuredOrigins.includes("*")) {
114
+ return true;
115
+ }
116
+
117
+ return uniqueOrigins([
118
+ ...configuredOrigins,
119
+ clientOrigin,
120
+ mainClientOrigin,
121
+ ...localDevOrigins,
122
+ ...deployedOrigins
123
+ ]);
124
+ }
125
+
126
+ export const env = {
127
+ host,
128
+ port: Number.isNaN(rawPort) ? 4000 : rawPort,
129
+ clientOrigin,
130
+ mainClientOrigin,
131
+ corsOrigin: resolveCorsOrigin(),
132
+ googleClientId: process.env.GOOGLE_CLIENT_ID,
133
+ mongoDbName: process.env.MONGODB_DB ?? "visionos",
134
+ mongoUri: process.env.MONGODB_URI
135
+ };