@envin/cli 1.1.12 → 1.1.14-canary.79c6253

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 (47) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/README.md +25 -0
  3. package/dist/cli/index.mjs +659 -849
  4. package/dist/preview/.next/BUILD_ID +1 -1
  5. package/dist/preview/.next/build-manifest.json +2 -2
  6. package/dist/preview/.next/fallback-build-manifest.json +2 -2
  7. package/dist/preview/.next/prerender-manifest.json +3 -3
  8. package/dist/preview/.next/server/app/_global-error.html +2 -2
  9. package/dist/preview/.next/server/app/_global-error.rsc +1 -1
  10. package/dist/preview/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  11. package/dist/preview/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  12. package/dist/preview/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  13. package/dist/preview/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  14. package/dist/preview/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  15. package/dist/preview/.next/server/app/_not-found.html +1 -1
  16. package/dist/preview/.next/server/app/_not-found.rsc +1 -1
  17. package/dist/preview/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  18. package/dist/preview/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  19. package/dist/preview/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  20. package/dist/preview/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  21. package/dist/preview/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  22. package/dist/preview/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  23. package/dist/preview/.next/server/app/page/server-reference-manifest.json +4 -4
  24. package/dist/preview/.next/server/app/page_client-reference-manifest.js +1 -1
  25. package/dist/preview/.next/server/chunks/ssr/[root-of-the-server]__3aaf07ce._.js +1 -1
  26. package/dist/preview/.next/server/chunks/ssr/[root-of-the-server]__3aaf07ce._.js.map +1 -1
  27. package/dist/preview/.next/server/chunks/ssr/[root-of-the-server]__e2e728e5._.js +1 -1
  28. package/dist/preview/.next/server/chunks/ssr/[root-of-the-server]__e2e728e5._.js.map +1 -1
  29. package/dist/preview/.next/server/chunks/ssr/[root-of-the-server]__e3d1c002._.js +20 -20
  30. package/dist/preview/.next/server/chunks/ssr/[root-of-the-server]__e3d1c002._.js.map +1 -1
  31. package/dist/preview/.next/server/pages/404.html +1 -1
  32. package/dist/preview/.next/server/pages/500.html +2 -2
  33. package/dist/preview/.next/server/server-reference-manifest.js +1 -1
  34. package/dist/preview/.next/server/server-reference-manifest.json +5 -5
  35. package/dist/preview/.next/static/chunks/{9bd1b84742da96a1.js → bbb5fb736577afdc.js} +1 -1
  36. package/dist/preview/.next/trace +1 -1
  37. package/dist/preview/.next/trace-build +1 -1
  38. package/package.json +6 -6
  39. package/src/app/env.ts +23 -0
  40. package/src/app/page.tsx +3 -5
  41. package/src/lib/config.ts +10 -7
  42. package/src/lib/types.ts +7 -2
  43. package/src/lib/variables/index.ts +46 -26
  44. package/tsdown.config.ts +9 -0
  45. /package/dist/preview/.next/static/{y_m2KZXoqJPNnkobTFvMs → ON209q3dIHdNhaLOJAxJ0}/_buildManifest.js +0 -0
  46. /package/dist/preview/.next/static/{y_m2KZXoqJPNnkobTFvMs → ON209q3dIHdNhaLOJAxJ0}/_clientMiddlewareManifest.json +0 -0
  47. /package/dist/preview/.next/static/{y_m2KZXoqJPNnkobTFvMs → ON209q3dIHdNhaLOJAxJ0}/_ssgManifest.js +0 -0
@@ -1,894 +1,704 @@
1
1
  #!/usr/bin/env node
2
-
3
- // src/cli/index.ts
4
2
  import { program } from "commander";
5
-
6
- // package.json
7
- var package_default = {
8
- name: "@envin/cli",
9
- version: "1.1.12",
10
- description: "Type-safe env validation with live previews",
11
- keywords: [
12
- "turbostarter",
13
- "environment variables",
14
- "validation",
15
- "zod",
16
- "arktype",
17
- "valibot"
18
- ],
19
- homepage: "https://envin.turbostarter.dev",
20
- bugs: {
21
- url: "https://github.com/turbostarter/envin/issues"
22
- },
23
- repository: {
24
- type: "git",
25
- url: "git+https://github.com/turbostarter/envin.git",
26
- directory: "packages/cli"
27
- },
28
- license: "MIT",
29
- author: "Bartosz Zagrodzki",
30
- bin: {
31
- envin: "dist/cli/index.mjs"
32
- },
33
- scripts: {
34
- build: "tsup-node && bun run build:preview",
35
- "build:preview": "node ./scripts/build-preview-server.mjs",
36
- clean: "rm -rf dist .turbo node_modules .next",
37
- dev: "tsup-node --watch",
38
- "dev:preview": "cd ../../apps/example && tsx ../../packages/cli/src/cli/index.ts dev",
39
- start: "node dist/cli/index.mjs",
40
- typecheck: "tsc --noEmit",
41
- prepack: "bun ../../scripts/replace-workspace-protocol.ts && bun ../../scripts/populate-readme.ts"
42
- },
43
- dependencies: {
44
- "@babel/parser": "^7.27.0",
45
- "@babel/traverse": "^7.27.0",
46
- "@hookform/resolvers": "^5.2.1",
47
- "@radix-ui/react-slot": "1.2.4",
48
- "radix-ui": "catalog:",
49
- "@svgr/webpack": "^8.1.0",
50
- chalk: "^5.4.1",
51
- chokidar: "^4.0.3",
52
- commander: "^13.1.0",
53
- consola: "^3.4.2",
54
- debounce: "^2.2.0",
55
- dotenv: "^16.5.0",
56
- envin: "workspace:*",
57
- esbuild: "^0.25.10",
58
- "log-symbols": "^7.0.0",
59
- "mime-types": "^3.0.1",
60
- next: "catalog:",
61
- ora: "^8.2.0",
62
- "react-hook-form": "^7.62.0",
63
- "socket.io": "^4.8.1",
64
- "socket.io-client": "^4.8.1",
65
- "tsconfig-paths": "^4.2.0",
66
- zod: "catalog:"
67
- },
68
- devDependencies: {
69
- "@babel/core": "7.26.10",
70
- "@swc/core": "1.11.21",
71
- "@tailwindcss/postcss": "catalog:",
72
- "@types/babel__core": "7.20.5",
73
- "@types/babel__traverse": "7.20.7",
74
- "@types/mime-types": "2.1.4",
75
- "@types/node": "catalog:",
76
- "@types/react": "catalog:",
77
- "@types/react-dom": "catalog:",
78
- "class-variance-authority": "catalog:",
79
- clsx: "catalog:",
80
- "lucide-react": "catalog:",
81
- postcss: "catalog:",
82
- react: "catalog:",
83
- "react-dom": "catalog:",
84
- "source-map-js": "1.2.1",
85
- "stacktrace-parser": "0.1.11",
86
- "tailwind-merge": "catalog:",
87
- tailwindcss: "catalog:",
88
- tsup: "catalog:",
89
- tsx: "^4.19.4",
90
- "tw-animate-css": "catalog:",
91
- typescript: "catalog:"
92
- }
93
- };
94
-
95
- // src/cli/commands/dev.ts
96
- import fs4 from "fs";
97
-
98
- // src/cli/utils/hot-reload/setup-hot-reloading.ts
99
- import { promises as fs3 } from "fs";
100
- import path6 from "path";
3
+ import fs, { existsSync, promises, statSync } from "node:fs";
4
+ import path from "node:path";
101
5
  import { watch } from "chokidar";
102
6
  import debounce from "debounce";
103
- import { Server as SocketServer } from "socket.io";
104
-
105
- // src/cli/utils/logger.ts
7
+ import { Server } from "socket.io";
106
8
  import { createConsola } from "consola";
107
- var logger = createConsola({});
108
-
109
- // src/cli/utils/hot-reload/create-dependency-graph.ts
110
- import { existsSync as existsSync2, promises as fs2, statSync } from "fs";
111
- import path5 from "path";
112
-
113
- // src/cli/utils/preview/start-dev-server.ts
114
- import http from "http";
115
- import path3 from "path";
116
- import url from "url";
9
+ import http from "node:http";
10
+ import url from "node:url";
117
11
  import chalk from "chalk";
118
- import logSymbols2 from "log-symbols";
12
+ import logSymbols from "log-symbols";
119
13
  import next from "next";
120
14
  import ora from "ora";
15
+ import { lookup } from "mime-types";
16
+ import { parse } from "@babel/parser";
17
+ import traverseModule from "@babel/traverse";
18
+ import { createMatchPath, loadConfig } from "tsconfig-paths";
121
19
 
122
- // src/utils/register-spinner-autostopping.ts
123
- import logSymbols from "log-symbols";
124
- var spinners = /* @__PURE__ */ new Set();
20
+ //#region package.json
21
+ var version = "1.1.13";
22
+
23
+ //#endregion
24
+ //#region src/lib/types.ts
25
+ const Environment = {
26
+ DEVELOPMENT: "development",
27
+ PRODUCTION: "production"
28
+ };
29
+ const FILES = {
30
+ [Environment.DEVELOPMENT]: [
31
+ ".env",
32
+ ".env.local",
33
+ ".env.development",
34
+ ".env.development.local"
35
+ ],
36
+ [Environment.PRODUCTION]: [
37
+ ".env",
38
+ ".env.local",
39
+ ".env.production",
40
+ ".env.production.local"
41
+ ]
42
+ };
43
+
44
+ //#endregion
45
+ //#region src/cli/utils/logger.ts
46
+ const logger = createConsola({});
47
+
48
+ //#endregion
49
+ //#region src/utils/register-spinner-autostopping.ts
50
+ const spinners = /* @__PURE__ */ new Set();
125
51
  process.on("SIGINT", () => {
126
- spinners.forEach((spinner) => {
127
- if (spinner.isSpinning) {
128
- spinner.stop();
129
- }
130
- });
52
+ spinners.forEach((spinner) => {
53
+ if (spinner.isSpinning) spinner.stop();
54
+ });
131
55
  });
132
56
  process.on("exit", (code) => {
133
- if (code !== 0) {
134
- spinners.forEach((spinner) => {
135
- if (spinner.isSpinning) {
136
- spinner.stopAndPersist({
137
- symbol: logSymbols.error
138
- });
139
- }
140
- });
141
- }
57
+ if (code !== 0) spinners.forEach((spinner) => {
58
+ if (spinner.isSpinning) spinner.stopAndPersist({ symbol: logSymbols.error });
59
+ });
142
60
  });
143
- var registerSpinnerAutostopping = (spinner) => {
144
- spinners.add(spinner);
61
+ const registerSpinnerAutostopping = (spinner) => {
62
+ spinners.add(spinner);
145
63
  };
146
64
 
147
- // src/cli/utils/preview/get-env-variables.ts
148
- import path from "path";
149
- var getEnvVariablesForPreviewApp = (relativePathToEnvDirectory, cwd) => {
150
- return {
151
- ENV_DIR_RELATIVE_PATH: relativePathToEnvDirectory,
152
- ENV_DIR_ABSOLUTE_PATH: path.resolve(cwd, relativePathToEnvDirectory),
153
- USER_PROJECT_LOCATION: cwd,
154
- NEXT_PUBLIC_IS_PREVIEW_DEVELOPMENT: isDev ? "true" : "false"
155
- };
65
+ //#endregion
66
+ //#region src/cli/utils/preview/get-env-variables.ts
67
+ const getEnvVariablesForPreviewApp = (relativePathToEnvDirectory, cwd, envDirAbsolutePaths, envFilePaths, envConfigPath) => {
68
+ return {
69
+ ENV_DIR_RELATIVE_PATH: relativePathToEnvDirectory,
70
+ ENV_DIR_ABSOLUTE_PATH: path.resolve(cwd, relativePathToEnvDirectory),
71
+ ENV_DIR_ABSOLUTE_PATHS: JSON.stringify(envDirAbsolutePaths),
72
+ ENV_FILE_PATHS: JSON.stringify(envFilePaths),
73
+ ENV_CONFIG_PATH: envConfigPath,
74
+ USER_PROJECT_LOCATION: cwd,
75
+ NEXT_PUBLIC_IS_PREVIEW_DEVELOPMENT: isDev ? "true" : "false"
76
+ };
156
77
  };
157
78
 
158
- // src/cli/utils/preview/serve-static-file.ts
159
- import { existsSync, promises as fs } from "fs";
160
- import path2 from "path";
161
- import { lookup } from "mime-types";
162
- var serveStaticFile = async (res, parsedUrl, staticDirRelativePath) => {
163
- const pathname = parsedUrl.pathname?.replace("/static", "./static") ?? "";
164
- const ext = path2.parse(pathname).ext;
165
- const staticBaseDir = path2.resolve(process.cwd(), staticDirRelativePath);
166
- const fileAbsolutePath = path2.resolve(staticBaseDir, pathname);
167
- if (!fileAbsolutePath.startsWith(staticBaseDir)) {
168
- res.statusCode = 403;
169
- res.end();
170
- return;
171
- }
172
- try {
173
- const fileHandle = await fs.open(fileAbsolutePath, "r");
174
- const fileData = await fs.readFile(fileHandle);
175
- res.setHeader("Content-type", lookup(ext) || "text/plain");
176
- res.end(fileData);
177
- fileHandle.close();
178
- } catch (exception) {
179
- if (!existsSync(fileAbsolutePath)) {
180
- res.statusCode = 404;
181
- res.end();
182
- } else {
183
- const sanitizedFilePath = fileAbsolutePath.replace(/\n|\r/g, "");
184
- logger.error(
185
- new Error(
186
- `Could not read file at ${sanitizedFilePath} to be served, here's the exception:`
187
- ),
188
- exception
189
- );
190
- res.statusCode = 500;
191
- res.end(
192
- "Could not read file to be served! Check your terminal for more information."
193
- );
194
- }
195
- }
79
+ //#endregion
80
+ //#region src/cli/utils/preview/serve-static-file.ts
81
+ const serveStaticFile = async (res, parsedUrl, staticDirRelativePath) => {
82
+ const pathname = parsedUrl.pathname?.replace("/static", "./static") ?? "";
83
+ const ext = path.parse(pathname).ext;
84
+ const staticBaseDir = path.resolve(process.cwd(), staticDirRelativePath);
85
+ const fileAbsolutePath = path.resolve(staticBaseDir, pathname);
86
+ if (!fileAbsolutePath.startsWith(staticBaseDir)) {
87
+ res.statusCode = 403;
88
+ res.end();
89
+ return;
90
+ }
91
+ try {
92
+ const fileHandle = await promises.open(fileAbsolutePath, "r");
93
+ const fileData = await promises.readFile(fileHandle);
94
+ res.setHeader("Content-type", lookup(ext) || "text/plain");
95
+ res.end(fileData);
96
+ fileHandle.close();
97
+ } catch (exception) {
98
+ if (!existsSync(fileAbsolutePath)) {
99
+ res.statusCode = 404;
100
+ res.end();
101
+ } else {
102
+ const sanitizedFilePath = fileAbsolutePath.replace(/\n|\r/g, "");
103
+ logger.error(/* @__PURE__ */ new Error(`Could not read file at ${sanitizedFilePath} to be served, here's the exception:`), exception);
104
+ res.statusCode = 500;
105
+ res.end("Could not read file to be served! Check your terminal for more information.");
106
+ }
107
+ }
196
108
  };
197
109
 
198
- // src/cli/utils/preview/start-dev-server.ts
199
- var devServer;
200
- var safeAsyncServerListen = (server, port) => {
201
- return new Promise((resolve) => {
202
- server.listen(port, () => {
203
- resolve({ portAlreadyInUse: false });
204
- });
205
- server.on("error", (e) => {
206
- if (e.code === "EADDRINUSE") {
207
- resolve({ portAlreadyInUse: true });
208
- }
209
- });
210
- });
110
+ //#endregion
111
+ //#region src/cli/utils/preview/start-dev-server.ts
112
+ let devServer;
113
+ const safeAsyncServerListen = (server, port) => {
114
+ return new Promise((resolve) => {
115
+ server.listen(port, () => {
116
+ resolve({ portAlreadyInUse: false });
117
+ });
118
+ server.on("error", (e) => {
119
+ if (e.code === "EADDRINUSE") resolve({ portAlreadyInUse: true });
120
+ });
121
+ });
211
122
  };
212
- var filename = url.fileURLToPath(import.meta.url);
213
- var dirname = path3.dirname(filename);
214
- var isDev = !filename.endsWith(path3.join("cli", "index.mjs"));
215
- var cliPackageLocation = isDev ? path3.resolve(dirname, "../../../..") : path3.resolve(dirname, "../..");
216
- var previewServerLocation = isDev ? path3.resolve(dirname, "../../../..") : path3.resolve(dirname, "../preview");
217
- var startDevServer = async ({
218
- envDirRelativePath,
219
- staticBaseDirRelativePath,
220
- port,
221
- verbose
222
- }) => {
223
- const [majorNodeVersion] = process.versions.node.split(".");
224
- if (majorNodeVersion && Number.parseInt(majorNodeVersion) < 18) {
225
- logger.error(
226
- `Node ${majorNodeVersion} is not supported. Please upgrade to Node 18 or higher.`
227
- );
228
- process.exit(1);
229
- }
230
- devServer = http.createServer((req, res) => {
231
- if (!req.url) {
232
- res.end(404);
233
- return;
234
- }
235
- if (verbose) {
236
- logger.debug("Creating HTTP server...", {
237
- envDirRelativePath,
238
- staticBaseDirRelativePath,
239
- port
240
- });
241
- }
242
- const parsedUrl = url.parse(req.url, true);
243
- res.setHeader(
244
- "Cache-Control",
245
- "no-cache, max-age=0, must-revalidate, no-store"
246
- );
247
- res.setHeader("Pragma", "no-cache");
248
- res.setHeader("Expires", "-1");
249
- try {
250
- if (parsedUrl.path?.includes("static/") && !parsedUrl.path.includes("_next/static/")) {
251
- void serveStaticFile(res, parsedUrl, staticBaseDirRelativePath);
252
- } else if (!isNextReady) {
253
- void nextReadyPromise.then(
254
- () => nextHandleRequest?.(req, res, parsedUrl)
255
- );
256
- } else {
257
- void nextHandleRequest?.(req, res, parsedUrl);
258
- }
259
- } catch (e) {
260
- logger.error(new Error("Error while handling request!"), e);
261
- res.writeHead(500);
262
- res.end();
263
- }
264
- });
265
- const { portAlreadyInUse } = await safeAsyncServerListen(devServer, port);
266
- if (!portAlreadyInUse) {
267
- logger.log(chalk.greenBright(`
268
- Envin ${package_default.version}`));
269
- logger.log(` Running preview at: http://localhost:${port}
270
- `);
271
- } else {
272
- const nextPortToTry = port + 1;
273
- logger.warn(`Port ${port} is already in use, trying ${nextPortToTry}...`);
274
- return startDevServer({
275
- envDirRelativePath,
276
- staticBaseDirRelativePath,
277
- port: nextPortToTry,
278
- verbose
279
- });
280
- }
281
- devServer.on("close", async () => {
282
- await app.close();
283
- });
284
- devServer.on("error", (e) => {
285
- logger.error(new Error("Preview server error!"), e);
286
- process.exit(1);
287
- });
288
- const spinner = ora({
289
- text: "Getting envin preview server ready...\n",
290
- prefixText: " "
291
- }).start();
292
- registerSpinnerAutostopping(spinner);
293
- const timeBeforeNextReady = performance.now();
294
- process.env = {
295
- NODE_ENV: "development",
296
- ...process.env,
297
- ...getEnvVariablesForPreviewApp(
298
- path3.normalize(envDirRelativePath),
299
- process.cwd()
300
- )
301
- };
302
- const app = next({
303
- // passing in env here does not get the environment variables there
304
- dev: isDev,
305
- conf: {
306
- images: {
307
- // This is to avoid the warning with sharp
308
- unoptimized: true
309
- }
310
- },
311
- hostname: "localhost",
312
- port,
313
- dir: previewServerLocation
314
- });
315
- let isNextReady = false;
316
- const nextReadyPromise = app.prepare();
317
- await nextReadyPromise;
318
- isNextReady = true;
319
- const nextHandleRequest = app.getRequestHandler();
320
- const secondsToNextReady = ((performance.now() - timeBeforeNextReady) / 1e3).toFixed(1);
321
- spinner.stopAndPersist({
322
- text: `Ready in ${secondsToNextReady}s
323
- `,
324
- symbol: logSymbols2.success
325
- });
326
- return devServer;
123
+ const filename = url.fileURLToPath(import.meta.url);
124
+ const dirname = path.dirname(filename);
125
+ const isDev = !filename.endsWith(path.join("cli", "index.mjs"));
126
+ const cliPackageLocation = isDev ? path.resolve(dirname, "../../../..") : path.resolve(dirname, "../..");
127
+ const previewServerLocation = isDev ? path.resolve(dirname, "../../../..") : path.resolve(dirname, "../preview");
128
+ const startDevServer = async ({ envDirRelativePath, staticBaseDirRelativePath, envDirAbsolutePaths, envFilePaths, envConfigPath, port, verbose }) => {
129
+ const [majorNodeVersion] = process.versions.node.split(".");
130
+ if (majorNodeVersion && Number.parseInt(majorNodeVersion) < 18) {
131
+ logger.error(`Node ${majorNodeVersion} is not supported. Please upgrade to Node 18 or higher.`);
132
+ process.exit(1);
133
+ }
134
+ devServer = http.createServer((req, res) => {
135
+ if (!req.url) {
136
+ res.end(404);
137
+ return;
138
+ }
139
+ if (verbose) logger.debug("Creating HTTP server...", {
140
+ envDirRelativePath,
141
+ staticBaseDirRelativePath,
142
+ envDirAbsolutePaths,
143
+ envFilePaths,
144
+ envConfigPath,
145
+ port
146
+ });
147
+ const parsedUrl = url.parse(req.url, true);
148
+ res.setHeader("Cache-Control", "no-cache, max-age=0, must-revalidate, no-store");
149
+ res.setHeader("Pragma", "no-cache");
150
+ res.setHeader("Expires", "-1");
151
+ try {
152
+ if (parsedUrl.path?.includes("static/") && !parsedUrl.path.includes("_next/static/")) serveStaticFile(res, parsedUrl, staticBaseDirRelativePath);
153
+ else if (!isNextReady) nextReadyPromise.then(() => nextHandleRequest?.(req, res, parsedUrl));
154
+ else nextHandleRequest?.(req, res, parsedUrl);
155
+ } catch (e) {
156
+ logger.error(/* @__PURE__ */ new Error("Error while handling request!"), e);
157
+ res.writeHead(500);
158
+ res.end();
159
+ }
160
+ });
161
+ const { portAlreadyInUse } = await safeAsyncServerListen(devServer, port);
162
+ if (!portAlreadyInUse) {
163
+ logger.log(chalk.greenBright(`\n Envin ${version}`));
164
+ logger.log(` Running preview at: http://localhost:${port}\n`);
165
+ } else {
166
+ const nextPortToTry = port + 1;
167
+ logger.warn(`Port ${port} is already in use, trying ${nextPortToTry}...`);
168
+ return startDevServer({
169
+ envDirRelativePath,
170
+ staticBaseDirRelativePath,
171
+ envDirAbsolutePaths,
172
+ envFilePaths,
173
+ envConfigPath,
174
+ port: nextPortToTry,
175
+ verbose
176
+ });
177
+ }
178
+ devServer.on("close", async () => {
179
+ await app.close();
180
+ });
181
+ devServer.on("error", (e) => {
182
+ logger.error(/* @__PURE__ */ new Error("Preview server error!"), e);
183
+ process.exit(1);
184
+ });
185
+ const spinner = ora({
186
+ text: "Getting envin preview server ready...\n",
187
+ prefixText: " "
188
+ }).start();
189
+ registerSpinnerAutostopping(spinner);
190
+ const timeBeforeNextReady = performance.now();
191
+ process.env = {
192
+ NODE_ENV: "development",
193
+ ...process.env,
194
+ ...getEnvVariablesForPreviewApp(path.normalize(envDirRelativePath), process.cwd(), envDirAbsolutePaths, envFilePaths, envConfigPath)
195
+ };
196
+ const app = next({
197
+ dev: isDev,
198
+ conf: { images: { unoptimized: true } },
199
+ hostname: "localhost",
200
+ port,
201
+ dir: previewServerLocation
202
+ });
203
+ let isNextReady = false;
204
+ const nextReadyPromise = app.prepare();
205
+ await nextReadyPromise;
206
+ isNextReady = true;
207
+ const nextHandleRequest = app.getRequestHandler();
208
+ const secondsToNextReady = ((performance.now() - timeBeforeNextReady) / 1e3).toFixed(1);
209
+ spinner.stopAndPersist({
210
+ text: `Ready in ${secondsToNextReady}s\n`,
211
+ symbol: logSymbols.success
212
+ });
213
+ return devServer;
327
214
  };
328
- var makeExitHandler = (options) => (_codeOrSignal) => {
329
- if (typeof devServer !== "undefined") {
330
- logger.log("\nShutting down dev server...");
331
- devServer.close();
332
- devServer = void 0;
333
- }
334
- if (options?.shouldKillProcess) {
335
- process.exit(options.killWithErrorCode ? 1 : 0);
336
- }
215
+ const makeExitHandler = (options) => (_codeOrSignal) => {
216
+ if (typeof devServer !== "undefined") {
217
+ logger.log("\nShutting down dev server...");
218
+ devServer.close();
219
+ devServer = void 0;
220
+ }
221
+ if (options?.shouldKillProcess) process.exit(options.killWithErrorCode ? 1 : 0);
337
222
  };
338
223
  process.on("exit", makeExitHandler());
339
- process.on(
340
- "SIGINT",
341
- makeExitHandler({ shouldKillProcess: true, killWithErrorCode: false })
342
- );
343
- process.on(
344
- "SIGUSR1",
345
- makeExitHandler({ shouldKillProcess: true, killWithErrorCode: false })
346
- );
347
- process.on(
348
- "SIGUSR2",
349
- makeExitHandler({ shouldKillProcess: true, killWithErrorCode: false })
350
- );
351
- process.on(
352
- "uncaughtException",
353
- makeExitHandler({ shouldKillProcess: true, killWithErrorCode: true })
354
- );
224
+ process.on("SIGINT", makeExitHandler({
225
+ shouldKillProcess: true,
226
+ killWithErrorCode: false
227
+ }));
228
+ process.on("SIGUSR1", makeExitHandler({
229
+ shouldKillProcess: true,
230
+ killWithErrorCode: false
231
+ }));
232
+ process.on("SIGUSR2", makeExitHandler({
233
+ shouldKillProcess: true,
234
+ killWithErrorCode: false
235
+ }));
236
+ process.on("uncaughtException", makeExitHandler({
237
+ shouldKillProcess: true,
238
+ killWithErrorCode: true
239
+ }));
355
240
 
356
- // src/cli/utils/hot-reload/get-imported-modules.ts
357
- import { parse } from "@babel/parser";
358
- import traverseModule from "@babel/traverse";
359
- var traverse = (
360
- // we keep this check here so that this still works with the dev:preview
361
- // script's use of tsx
362
- typeof traverseModule === "function" ? traverseModule : (
363
- // @ts-expect-error - this is a valid use case
364
- traverseModule.default
365
- )
366
- );
367
- var getImportedModules = (contents) => {
368
- const importedPaths = [];
369
- const parsedContents = parse(contents, {
370
- sourceType: "unambiguous",
371
- strictMode: false,
372
- errorRecovery: true,
373
- plugins: ["jsx", "typescript", "decorators"]
374
- });
375
- traverse(parsedContents, {
376
- ImportDeclaration({ node }) {
377
- importedPaths.push(node.source.value);
378
- },
379
- ExportAllDeclaration({ node }) {
380
- importedPaths.push(node.source.value);
381
- },
382
- ExportNamedDeclaration({ node }) {
383
- if (node.source) {
384
- importedPaths.push(node.source.value);
385
- }
386
- },
387
- TSExternalModuleReference({ node }) {
388
- importedPaths.push(node.expression.value);
389
- },
390
- CallExpression({ node }) {
391
- if ("name" in node.callee && node.callee.name === "require") {
392
- if (node.arguments.length === 1) {
393
- const importPathNode = node.arguments[0];
394
- if (importPathNode?.type === "StringLiteral") {
395
- importedPaths.push(importPathNode.value);
396
- }
397
- }
398
- }
399
- }
400
- });
401
- return importedPaths;
241
+ //#endregion
242
+ //#region src/cli/utils/hot-reload/get-imported-modules.ts
243
+ const traverse = typeof traverseModule === "function" ? traverseModule : traverseModule.default;
244
+ const getImportedModules = (contents) => {
245
+ const importedPaths = [];
246
+ traverse(parse(contents, {
247
+ sourceType: "unambiguous",
248
+ strictMode: false,
249
+ errorRecovery: true,
250
+ plugins: [
251
+ "jsx",
252
+ "typescript",
253
+ "decorators"
254
+ ]
255
+ }), {
256
+ ImportDeclaration({ node }) {
257
+ importedPaths.push(node.source.value);
258
+ },
259
+ ExportAllDeclaration({ node }) {
260
+ importedPaths.push(node.source.value);
261
+ },
262
+ ExportNamedDeclaration({ node }) {
263
+ if (node.source) importedPaths.push(node.source.value);
264
+ },
265
+ TSExternalModuleReference({ node }) {
266
+ importedPaths.push(node.expression.value);
267
+ },
268
+ CallExpression({ node }) {
269
+ if ("name" in node.callee && node.callee.name === "require") {
270
+ if (node.arguments.length === 1) {
271
+ const importPathNode = node.arguments[0];
272
+ if (importPathNode?.type === "StringLiteral") importedPaths.push(importPathNode.value);
273
+ }
274
+ }
275
+ }
276
+ });
277
+ return importedPaths;
402
278
  };
403
279
 
404
- // src/cli/utils/hot-reload/resolve-path-aliases.ts
405
- import path4 from "path";
406
- import { createMatchPath, loadConfig } from "tsconfig-paths";
407
- var resolvePathAliases = (importPaths, projectPath) => {
408
- const configLoadResult = loadConfig(projectPath);
409
- if (configLoadResult.resultType === "success") {
410
- const matchPath = createMatchPath(
411
- configLoadResult.absoluteBaseUrl,
412
- configLoadResult.paths
413
- );
414
- return importPaths.map((importedPath) => {
415
- const unaliasedPath = matchPath(importedPath, void 0, void 0, [
416
- ".tsx",
417
- ".ts",
418
- ".js",
419
- ".jsx",
420
- ".cjs",
421
- ".mjs"
422
- ]);
423
- if (unaliasedPath) {
424
- return `./${path4.relative(projectPath, unaliasedPath)}`;
425
- }
426
- return importedPath;
427
- });
428
- }
429
- return importPaths;
280
+ //#endregion
281
+ //#region src/cli/utils/hot-reload/resolve-path-aliases.ts
282
+ const resolvePathAliases = (importPaths, projectPath) => {
283
+ const configLoadResult = loadConfig(projectPath);
284
+ if (configLoadResult.resultType === "success") {
285
+ const matchPath = createMatchPath(configLoadResult.absoluteBaseUrl, configLoadResult.paths);
286
+ return importPaths.map((importedPath) => {
287
+ const unaliasedPath = matchPath(importedPath, void 0, void 0, [
288
+ ".tsx",
289
+ ".ts",
290
+ ".js",
291
+ ".jsx",
292
+ ".cjs",
293
+ ".mjs"
294
+ ]);
295
+ if (unaliasedPath) return `./${path.relative(projectPath, unaliasedPath)}`;
296
+ return importedPath;
297
+ });
298
+ }
299
+ return importPaths;
430
300
  };
431
301
 
432
- // src/cli/utils/hot-reload/create-dependency-graph.ts
433
- var readAllFilesInsideDirectory = async (directory) => {
434
- let allFilePaths = [];
435
- const topLevelDirents = await fs2.readdir(directory, { withFileTypes: true });
436
- for await (const dirent of topLevelDirents) {
437
- const pathToDirent = path5.join(directory, dirent.name);
438
- if (dirent.isDirectory()) {
439
- allFilePaths = allFilePaths.concat(
440
- await readAllFilesInsideDirectory(pathToDirent)
441
- );
442
- } else {
443
- allFilePaths.push(pathToDirent);
444
- }
445
- }
446
- return allFilePaths;
302
+ //#endregion
303
+ //#region src/cli/utils/hot-reload/create-dependency-graph.ts
304
+ const readAllFilesInsideDirectory = async (directory) => {
305
+ let allFilePaths = [];
306
+ const topLevelDirents = await promises.readdir(directory, { withFileTypes: true });
307
+ for await (const dirent of topLevelDirents) {
308
+ const pathToDirent = path.join(directory, dirent.name);
309
+ if (dirent.isDirectory()) allFilePaths = allFilePaths.concat(await readAllFilesInsideDirectory(pathToDirent));
310
+ else allFilePaths.push(pathToDirent);
311
+ }
312
+ return allFilePaths;
447
313
  };
448
- var isJavascriptModule = (filePath) => {
449
- const extensionName = path5.extname(filePath);
450
- return [".js", ".ts", ".jsx", ".tsx", ".mjs", ".cjs"].includes(extensionName);
314
+ const isJavascriptModule = (filePath) => {
315
+ const extensionName = path.extname(filePath);
316
+ return [
317
+ ".js",
318
+ ".ts",
319
+ ".jsx",
320
+ ".tsx",
321
+ ".mjs",
322
+ ".cjs"
323
+ ].includes(extensionName);
451
324
  };
452
- var checkFileExtensionsUntilItExists = (pathWithoutExtension) => {
453
- if (existsSync2(`${pathWithoutExtension}.ts`)) {
454
- return `${pathWithoutExtension}.ts`;
455
- }
456
- if (existsSync2(`${pathWithoutExtension}.tsx`)) {
457
- return `${pathWithoutExtension}.tsx`;
458
- }
459
- if (existsSync2(`${pathWithoutExtension}.js`)) {
460
- return `${pathWithoutExtension}.js`;
461
- }
462
- if (existsSync2(`${pathWithoutExtension}.jsx`)) {
463
- return `${pathWithoutExtension}.jsx`;
464
- }
465
- if (existsSync2(`${pathWithoutExtension}.mjs`)) {
466
- return `${pathWithoutExtension}.mjs`;
467
- }
468
- if (existsSync2(`${pathWithoutExtension}.cjs`)) {
469
- return `${pathWithoutExtension}.cjs`;
470
- }
325
+ const checkFileExtensionsUntilItExists = (pathWithoutExtension) => {
326
+ if (existsSync(`${pathWithoutExtension}.ts`)) return `${pathWithoutExtension}.ts`;
327
+ if (existsSync(`${pathWithoutExtension}.tsx`)) return `${pathWithoutExtension}.tsx`;
328
+ if (existsSync(`${pathWithoutExtension}.js`)) return `${pathWithoutExtension}.js`;
329
+ if (existsSync(`${pathWithoutExtension}.jsx`)) return `${pathWithoutExtension}.jsx`;
330
+ if (existsSync(`${pathWithoutExtension}.mjs`)) return `${pathWithoutExtension}.mjs`;
331
+ if (existsSync(`${pathWithoutExtension}.cjs`)) return `${pathWithoutExtension}.cjs`;
471
332
  };
472
- var createDependencyGraph = async (directory) => {
473
- const filePaths = await readAllFilesInsideDirectory(directory);
474
- const modulePaths = filePaths.filter(isJavascriptModule);
475
- const graph = Object.fromEntries(
476
- modulePaths.map((path7) => [
477
- path7,
478
- {
479
- path: path7,
480
- dependencyPaths: [],
481
- dependentPaths: [],
482
- moduleDependencies: []
483
- }
484
- ])
485
- );
486
- const getDependencyPaths = async (filePath) => {
487
- const contents = await fs2.readFile(filePath, "utf8");
488
- const importedPaths = isJavascriptModule(filePath) ? resolvePathAliases(getImportedModules(contents), path5.dirname(filePath)) : [];
489
- const importedPathsRelativeToDirectory = importedPaths.map(
490
- (dependencyPath) => {
491
- const isModulePath = !dependencyPath.startsWith(".");
492
- if (isModulePath || path5.isAbsolute(dependencyPath)) {
493
- return dependencyPath;
494
- }
495
- let pathToDependencyFromDirectory = path5.resolve(
496
- /*
497
- path.resolve resolves paths differently from what imports on javascript do.
498
-
499
- So if we wouldn't do this, for an email at "/path/to/email.tsx" with a dependency path of "./other-email"
500
- would end up going into /path/to/email.tsx/other-email instead of /path/to/other-email which is the
501
- one the import is meant to go to
502
- */
503
- path5.dirname(filePath),
504
- dependencyPath
505
- );
506
- let isDirectory = false;
507
- try {
508
- isDirectory = statSync(pathToDependencyFromDirectory).isDirectory();
509
- } catch (_) {
510
- }
511
- if (isDirectory) {
512
- const pathToSubDirectory = pathToDependencyFromDirectory;
513
- const pathWithExtension = checkFileExtensionsUntilItExists(
514
- `${pathToSubDirectory}/index`
515
- );
516
- if (pathWithExtension) {
517
- pathToDependencyFromDirectory = pathWithExtension;
518
- } else if (isDev) {
519
- logger.warn(
520
- `Could not find index file for directory at ${pathToDependencyFromDirectory}. This is probably going to cause issues with both hot reloading and your code.`
521
- );
522
- }
523
- }
524
- const extension = path5.extname(pathToDependencyFromDirectory);
525
- const pathWithEnsuredExtension = (() => {
526
- if (extension.length > 0 && existsSync2(pathToDependencyFromDirectory)) {
527
- return pathToDependencyFromDirectory;
528
- }
529
- return checkFileExtensionsUntilItExists(
530
- pathToDependencyFromDirectory.replace(extension, "")
531
- );
532
- })();
533
- if (pathWithEnsuredExtension) {
534
- pathToDependencyFromDirectory = pathWithEnsuredExtension;
535
- } else if (isDev) {
536
- logger.warn(
537
- `Could not find file at ${pathToDependencyFromDirectory}`
538
- );
539
- }
540
- return pathToDependencyFromDirectory;
541
- }
542
- );
543
- const moduleDependencies = importedPathsRelativeToDirectory.filter(
544
- (dependencyPath) => !dependencyPath.startsWith(".") && !path5.isAbsolute(dependencyPath)
545
- );
546
- const nonNodeModuleImportPathsRelativeToDirectory = importedPathsRelativeToDirectory.filter(
547
- (dependencyPath) => dependencyPath.startsWith(".") || path5.isAbsolute(dependencyPath)
548
- );
549
- return {
550
- dependencyPaths: nonNodeModuleImportPathsRelativeToDirectory,
551
- moduleDependencies
552
- };
553
- };
554
- const updateModuleDependenciesInGraph = async (moduleFilePath) => {
555
- if (graph[moduleFilePath] === void 0) {
556
- graph[moduleFilePath] = {
557
- path: moduleFilePath,
558
- dependencyPaths: [],
559
- dependentPaths: [],
560
- moduleDependencies: []
561
- };
562
- }
563
- const { moduleDependencies, dependencyPaths: newDependencyPaths } = await getDependencyPaths(moduleFilePath);
564
- graph[moduleFilePath].moduleDependencies = moduleDependencies;
565
- for (const dependencyPath of graph[moduleFilePath].dependencyPaths) {
566
- if (newDependencyPaths.includes(dependencyPath)) continue;
567
- const dependencyModule = graph[dependencyPath];
568
- if (dependencyModule !== void 0) {
569
- dependencyModule.dependentPaths = dependencyModule.dependentPaths.filter(
570
- (dependentPath) => dependentPath !== moduleFilePath
571
- );
572
- }
573
- }
574
- graph[moduleFilePath].dependencyPaths = newDependencyPaths;
575
- for await (const dependencyPath of newDependencyPaths) {
576
- if (graph[dependencyPath] === void 0) {
577
- await updateModuleDependenciesInGraph(dependencyPath);
578
- }
579
- const dependencyModule = graph[dependencyPath];
580
- if (dependencyModule === void 0) {
581
- throw new Error(
582
- `Loading the dependency path ${dependencyPath} did not initialize it at all. This is a bug in React Email.`
583
- );
584
- }
585
- if (!dependencyModule.dependentPaths.includes(moduleFilePath)) {
586
- dependencyModule.dependentPaths.push(moduleFilePath);
587
- }
588
- }
589
- };
590
- const removeModuleFromGraph = (filePath) => {
591
- const module = graph[filePath];
592
- if (module) {
593
- for (const dependencyPath of module.dependencyPaths) {
594
- if (graph[dependencyPath]) {
595
- graph[dependencyPath].dependentPaths = graph[dependencyPath].dependentPaths.filter(
596
- (dependentPath) => dependentPath !== filePath
597
- );
598
- }
599
- }
600
- delete graph[filePath];
601
- }
602
- };
603
- return [
604
- graph,
605
- async (event, pathToModified) => {
606
- switch (event) {
607
- case "change":
608
- if (isJavascriptModule(pathToModified)) {
609
- await updateModuleDependenciesInGraph(pathToModified);
610
- }
611
- break;
612
- case "add":
613
- if (isJavascriptModule(pathToModified)) {
614
- await updateModuleDependenciesInGraph(pathToModified);
615
- }
616
- break;
617
- case "addDir": {
618
- const filesInsideAddedDirectory = await readAllFilesInsideDirectory(pathToModified);
619
- const modulesInsideAddedDirectory = filesInsideAddedDirectory.filter(isJavascriptModule);
620
- for await (const filePath of modulesInsideAddedDirectory) {
621
- await updateModuleDependenciesInGraph(filePath);
622
- }
623
- break;
624
- }
625
- case "unlink":
626
- if (isJavascriptModule(pathToModified)) {
627
- removeModuleFromGraph(pathToModified);
628
- }
629
- break;
630
- case "unlinkDir": {
631
- const filesInsideDeletedDirectory = await readAllFilesInsideDirectory(pathToModified);
632
- const modulesInsideDeletedDirectory = filesInsideDeletedDirectory.filter(isJavascriptModule);
633
- for await (const filePath of modulesInsideDeletedDirectory) {
634
- removeModuleFromGraph(filePath);
635
- }
636
- break;
637
- }
638
- }
639
- },
640
- {
641
- resolveDependentsOf: function resolveDependentsOf(pathToModule) {
642
- const moduleEntry = graph[pathToModule];
643
- const dependentPaths = [];
644
- if (moduleEntry) {
645
- for (const dependentPath of moduleEntry.dependentPaths) {
646
- const dependentsOfDependent = resolveDependentsOf(dependentPath);
647
- dependentPaths.push(...dependentsOfDependent);
648
- dependentPaths.push(dependentPath);
649
- }
650
- }
651
- return dependentPaths;
652
- }
653
- }
654
- ];
333
+ /**
334
+ * Creates a stateful dependency graph that is structured in a way that you can get
335
+ * the dependents of a module from its path.
336
+ *
337
+ * Stateful in the sense that it provides a `getter` and an "`updater`". The updater
338
+ * will receive changes to the files, that can be perceived through some file watching mechanism,
339
+ * so that it doesn't need to recompute the entire dependency graph but only the parts changed.
340
+ */
341
+ const createDependencyGraph = async (directory) => {
342
+ const modulePaths = (await readAllFilesInsideDirectory(directory)).filter(isJavascriptModule);
343
+ const graph = Object.fromEntries(modulePaths.map((path$1) => [path$1, {
344
+ path: path$1,
345
+ dependencyPaths: [],
346
+ dependentPaths: [],
347
+ moduleDependencies: []
348
+ }]));
349
+ const getDependencyPaths = async (filePath) => {
350
+ const contents = await promises.readFile(filePath, "utf8");
351
+ const importedPathsRelativeToDirectory = (isJavascriptModule(filePath) ? resolvePathAliases(getImportedModules(contents), path.dirname(filePath)) : []).map((dependencyPath) => {
352
+ if (!dependencyPath.startsWith(".") || path.isAbsolute(dependencyPath)) return dependencyPath;
353
+ let pathToDependencyFromDirectory = path.resolve(path.dirname(filePath), dependencyPath);
354
+ let isDirectory = false;
355
+ try {
356
+ isDirectory = statSync(pathToDependencyFromDirectory).isDirectory();
357
+ } catch (_) {}
358
+ if (isDirectory) {
359
+ const pathWithExtension = checkFileExtensionsUntilItExists(`${pathToDependencyFromDirectory}/index`);
360
+ if (pathWithExtension) pathToDependencyFromDirectory = pathWithExtension;
361
+ else if (isDev) logger.warn(`Could not find index file for directory at ${pathToDependencyFromDirectory}. This is probably going to cause issues with both hot reloading and your code.`);
362
+ }
363
+ const extension = path.extname(pathToDependencyFromDirectory);
364
+ const pathWithEnsuredExtension = (() => {
365
+ if (extension.length > 0 && existsSync(pathToDependencyFromDirectory)) return pathToDependencyFromDirectory;
366
+ return checkFileExtensionsUntilItExists(pathToDependencyFromDirectory.replace(extension, ""));
367
+ })();
368
+ if (pathWithEnsuredExtension) pathToDependencyFromDirectory = pathWithEnsuredExtension;
369
+ else if (isDev) logger.warn(`Could not find file at ${pathToDependencyFromDirectory}`);
370
+ return pathToDependencyFromDirectory;
371
+ });
372
+ const moduleDependencies = importedPathsRelativeToDirectory.filter((dependencyPath) => !dependencyPath.startsWith(".") && !path.isAbsolute(dependencyPath));
373
+ return {
374
+ dependencyPaths: importedPathsRelativeToDirectory.filter((dependencyPath) => dependencyPath.startsWith(".") || path.isAbsolute(dependencyPath)),
375
+ moduleDependencies
376
+ };
377
+ };
378
+ const updateModuleDependenciesInGraph = async (moduleFilePath) => {
379
+ if (graph[moduleFilePath] === void 0) graph[moduleFilePath] = {
380
+ path: moduleFilePath,
381
+ dependencyPaths: [],
382
+ dependentPaths: [],
383
+ moduleDependencies: []
384
+ };
385
+ const { moduleDependencies, dependencyPaths: newDependencyPaths } = await getDependencyPaths(moduleFilePath);
386
+ graph[moduleFilePath].moduleDependencies = moduleDependencies;
387
+ for (const dependencyPath of graph[moduleFilePath].dependencyPaths) {
388
+ if (newDependencyPaths.includes(dependencyPath)) continue;
389
+ const dependencyModule = graph[dependencyPath];
390
+ if (dependencyModule !== void 0) dependencyModule.dependentPaths = dependencyModule.dependentPaths.filter((dependentPath) => dependentPath !== moduleFilePath);
391
+ }
392
+ graph[moduleFilePath].dependencyPaths = newDependencyPaths;
393
+ for await (const dependencyPath of newDependencyPaths) {
394
+ if (graph[dependencyPath] === void 0) await updateModuleDependenciesInGraph(dependencyPath);
395
+ const dependencyModule = graph[dependencyPath];
396
+ if (dependencyModule === void 0) throw new Error(`Loading the dependency path ${dependencyPath} did not initialize it at all. This is a bug in React Email.`);
397
+ if (!dependencyModule.dependentPaths.includes(moduleFilePath)) dependencyModule.dependentPaths.push(moduleFilePath);
398
+ }
399
+ };
400
+ const removeModuleFromGraph = (filePath) => {
401
+ const module = graph[filePath];
402
+ if (module) {
403
+ for (const dependencyPath of module.dependencyPaths) if (graph[dependencyPath]) graph[dependencyPath].dependentPaths = graph[dependencyPath].dependentPaths.filter((dependentPath) => dependentPath !== filePath);
404
+ delete graph[filePath];
405
+ }
406
+ };
407
+ return [
408
+ graph,
409
+ async (event, pathToModified) => {
410
+ switch (event) {
411
+ case "change":
412
+ if (isJavascriptModule(pathToModified)) await updateModuleDependenciesInGraph(pathToModified);
413
+ break;
414
+ case "add":
415
+ if (isJavascriptModule(pathToModified)) await updateModuleDependenciesInGraph(pathToModified);
416
+ break;
417
+ case "addDir": {
418
+ const modulesInsideAddedDirectory = (await readAllFilesInsideDirectory(pathToModified)).filter(isJavascriptModule);
419
+ for await (const filePath of modulesInsideAddedDirectory) await updateModuleDependenciesInGraph(filePath);
420
+ break;
421
+ }
422
+ case "unlink":
423
+ if (isJavascriptModule(pathToModified)) removeModuleFromGraph(pathToModified);
424
+ break;
425
+ case "unlinkDir": {
426
+ const modulesInsideDeletedDirectory = (await readAllFilesInsideDirectory(pathToModified)).filter(isJavascriptModule);
427
+ for await (const filePath of modulesInsideDeletedDirectory) removeModuleFromGraph(filePath);
428
+ break;
429
+ }
430
+ }
431
+ },
432
+ { resolveDependentsOf: function resolveDependentsOf(pathToModule) {
433
+ const moduleEntry = graph[pathToModule];
434
+ const dependentPaths = [];
435
+ if (moduleEntry) for (const dependentPath of moduleEntry.dependentPaths) {
436
+ const dependentsOfDependent = resolveDependentsOf(dependentPath);
437
+ dependentPaths.push(...dependentsOfDependent);
438
+ dependentPaths.push(dependentPath);
439
+ }
440
+ return dependentPaths;
441
+ } }
442
+ ];
655
443
  };
656
444
 
657
- // src/cli/utils/hot-reload/setup-hot-reloading.ts
658
- var IGNORED_DIR_NAMES = [
659
- "node_modules",
660
- ".git",
661
- "dist",
662
- "build",
663
- "out",
664
- "coverage",
665
- ".cache",
666
- "tmp"
445
+ //#endregion
446
+ //#region src/cli/utils/hot-reload/setup-hot-reloading.ts
447
+ const IGNORED_DIR_NAMES = [
448
+ "node_modules",
449
+ ".git",
450
+ "dist",
451
+ "build",
452
+ "out",
453
+ "coverage",
454
+ ".cache",
455
+ "tmp"
667
456
  ];
668
- var normalizePath = (p) => p.split(path6.sep).join("/");
669
- var isPathInIgnoredDir = (p) => {
670
- const normalized = normalizePath(p);
671
- return IGNORED_DIR_NAMES.some(
672
- (dir) => normalized.includes(`/${dir}/`) || normalized.endsWith(`/${dir}`) || normalized.startsWith(`${dir}/`)
673
- );
457
+ const normalizePath = (p) => p.split(path.sep).join("/");
458
+ const isPathInIgnoredDir = (p) => {
459
+ const normalized = normalizePath(p);
460
+ return IGNORED_DIR_NAMES.some((dir) => normalized.includes(`/${dir}/`) || normalized.endsWith(`/${dir}`) || normalized.startsWith(`${dir}/`));
674
461
  };
675
- var toDirGlob = (dir) => [`**/${dir}/**`, `${dir}/**`];
676
- var readGitignoreGlobs = async (baseDir) => {
677
- try {
678
- const gitignorePath = path6.join(baseDir, ".gitignore");
679
- const data = await fs3.readFile(gitignorePath, "utf8");
680
- const lines = data.split(/\r?\n/);
681
- const globs = [];
682
- for (const rawLine of lines) {
683
- const line = rawLine.trim();
684
- if (line.length === 0 || line.startsWith("#") || line.startsWith("!")) {
685
- continue;
686
- }
687
- let pattern = line.replace(/^\//, "");
688
- if (pattern.endsWith("/")) {
689
- pattern = pattern.slice(0, -1);
690
- globs.push(`**/${pattern}/**`, `${pattern}/**`);
691
- continue;
692
- }
693
- if (pattern.includes("*") || pattern.includes("?")) {
694
- globs.push(`**/${pattern}`, pattern);
695
- } else {
696
- globs.push(
697
- `**/${pattern}/**`,
698
- `${pattern}/**`,
699
- `**/${pattern}`,
700
- pattern
701
- );
702
- }
703
- }
704
- return globs;
705
- } catch {
706
- return [];
707
- }
462
+ const toDirGlob = (dir) => [`**/${dir}/**`, `${dir}/**`];
463
+ const readGitignoreGlobs = async (baseDir) => {
464
+ try {
465
+ const gitignorePath = path.join(baseDir, ".gitignore");
466
+ const lines = (await promises.readFile(gitignorePath, "utf8")).split(/\r?\n/);
467
+ const globs = [];
468
+ for (const rawLine of lines) {
469
+ const line = rawLine.trim();
470
+ if (line.length === 0 || line.startsWith("#") || line.startsWith("!")) continue;
471
+ let pattern = line.replace(/^\//, "");
472
+ if (pattern.endsWith("/")) {
473
+ pattern = pattern.slice(0, -1);
474
+ globs.push(`**/${pattern}/**`, `${pattern}/**`);
475
+ continue;
476
+ }
477
+ if (pattern.includes("*") || pattern.includes("?")) globs.push(`**/${pattern}`, pattern);
478
+ else globs.push(`**/${pattern}/**`, `${pattern}/**`, `**/${pattern}`, pattern);
479
+ }
480
+ return globs;
481
+ } catch {
482
+ return [];
483
+ }
708
484
  };
709
- var buildIgnoredGlobs = async (baseDir) => {
710
- const defaultIgnoredGlobs = IGNORED_DIR_NAMES.flatMap(toDirGlob);
711
- const gitignoreGlobs = await readGitignoreGlobs(baseDir);
712
- return [...defaultIgnoredGlobs, ...gitignoreGlobs];
485
+ const buildIgnoredGlobs = async (baseDir) => {
486
+ const defaultIgnoredGlobs = IGNORED_DIR_NAMES.flatMap(toDirGlob);
487
+ const gitignoreGlobs = await readGitignoreGlobs(baseDir);
488
+ return [...defaultIgnoredGlobs, ...gitignoreGlobs];
713
489
  };
714
- var createEnvWatcher = async (baseDir) => {
715
- const ignoredGlobs = await buildIgnoredGlobs(baseDir);
716
- return watch(["**/*.{js,ts,jsx,tsx,mjs,cjs}"], {
717
- cwd: baseDir,
718
- ignoreInitial: true,
719
- ignored: ignoredGlobs,
720
- ignorePermissionErrors: true,
721
- awaitWriteFinish: { stabilityThreshold: 50, pollInterval: 10 }
722
- });
490
+ const createEnvWatcher = async (baseDir) => {
491
+ return watch(["**/*.{js,ts,jsx,tsx,mjs,cjs}"], {
492
+ cwd: baseDir,
493
+ ignoreInitial: true,
494
+ ignored: await buildIgnoredGlobs(baseDir),
495
+ ignorePermissionErrors: true,
496
+ awaitWriteFinish: {
497
+ stabilityThreshold: 50,
498
+ pollInterval: 10
499
+ }
500
+ });
723
501
  };
724
- var addExternalFilesToWatcher = (w, files) => {
725
- for (const p of files) {
726
- if (!isPathInIgnoredDir(p)) {
727
- w.add(p);
728
- }
729
- }
502
+ const addExternalFilesToWatcher = (w, files) => {
503
+ for (const p of files) if (!isPathInIgnoredDir(p)) w.add(p);
730
504
  };
731
- var attachShutdownHandlers = (w, verbose) => {
732
- const exit = async () => {
733
- if (verbose) {
734
- logger.info("Stopping file watcher and cleaning up...");
735
- }
736
- await w.close();
737
- };
738
- process.on("SIGINT", exit);
739
- process.on("uncaughtException", exit);
505
+ const attachShutdownHandlers = (w, verbose) => {
506
+ const exit = async () => {
507
+ if (verbose) logger.info("Stopping file watcher and cleaning up...");
508
+ await w.close();
509
+ };
510
+ process.on("SIGINT", exit);
511
+ process.on("uncaughtException", exit);
740
512
  };
741
- var setupHotreloading = async ({
742
- devServer: devServer2,
743
- envDirRelativePath,
744
- verbose
745
- }) => {
746
- if (verbose) {
747
- logger.start("Initializing socket.io server for hot reloading...");
748
- }
749
- let clients = [];
750
- const io = new SocketServer(devServer2);
751
- io.on("connection", (client) => {
752
- if (verbose) {
753
- logger.debug("Client connected to hot reload socket");
754
- }
755
- clients.push(client);
756
- client.on("disconnect", () => {
757
- if (verbose) {
758
- logger.debug("Client disconnected from hot reload socket");
759
- }
760
- clients = clients.filter((item) => item !== client);
761
- });
762
- });
763
- let changes = [];
764
- const reload = debounce(() => {
765
- if (verbose) {
766
- logger.debug(
767
- "Emitting reload event to",
768
- clients.length,
769
- "clients",
770
- changes
771
- );
772
- }
773
- clients.forEach((client) => {
774
- client.emit(
775
- "reload",
776
- changes.filter(
777
- (change) => (
778
- // Ensures only changes inside the emails directory are emitted
779
- path6.resolve(absolutePathToEnvDirectory, change.filename).startsWith(absolutePathToEnvDirectory)
780
- )
781
- )
782
- );
783
- });
784
- changes = [];
785
- }, 150);
786
- const absolutePathToEnvDirectory = path6.resolve(
787
- process.cwd(),
788
- envDirRelativePath
789
- );
790
- const [dependencyGraph, updateDependencyGraph, { resolveDependentsOf }] = await createDependencyGraph(absolutePathToEnvDirectory);
791
- if (verbose) {
792
- logger.info("Dependency graph created", {
793
- modules: Object.keys(dependencyGraph).length,
794
- root: absolutePathToEnvDirectory
795
- });
796
- }
797
- const watcher = await createEnvWatcher(absolutePathToEnvDirectory);
798
- const getFilesOutsideEnvDirectory = () => Object.keys(dependencyGraph).filter(
799
- (p) => path6.relative(absolutePathToEnvDirectory, p).startsWith("..")
800
- );
801
- let filesOutsideEnvDirectory = getFilesOutsideEnvDirectory();
802
- addExternalFilesToWatcher(watcher, filesOutsideEnvDirectory);
803
- attachShutdownHandlers(watcher, verbose);
804
- watcher.on("all", async (event, relativePathToChangeTarget) => {
805
- if (verbose) {
806
- logger.debug("File system event", {
807
- event,
808
- file: relativePathToChangeTarget
809
- });
810
- }
811
- const file = relativePathToChangeTarget.split(path6.sep);
812
- if (file.length === 0) {
813
- return;
814
- }
815
- const pathToChangeTarget = path6.resolve(
816
- absolutePathToEnvDirectory,
817
- relativePathToChangeTarget
818
- );
819
- await updateDependencyGraph(event, pathToChangeTarget);
820
- const newFilesOutsideEnvDirectory = getFilesOutsideEnvDirectory();
821
- for (const p of filesOutsideEnvDirectory) {
822
- if (!newFilesOutsideEnvDirectory.includes(p)) {
823
- watcher.unwatch(p);
824
- }
825
- }
826
- for (const p of newFilesOutsideEnvDirectory) {
827
- if (!filesOutsideEnvDirectory.includes(p) && !isPathInIgnoredDir(p)) {
828
- watcher.add(p);
829
- }
830
- }
831
- filesOutsideEnvDirectory = newFilesOutsideEnvDirectory;
832
- changes.push({
833
- event,
834
- filename: relativePathToChangeTarget
835
- });
836
- for (const dependentPath of resolveDependentsOf(pathToChangeTarget)) {
837
- changes.push({
838
- event: "change",
839
- filename: path6.relative(absolutePathToEnvDirectory, dependentPath)
840
- });
841
- }
842
- reload();
843
- });
844
- return watcher;
513
+ const setupHotreloading = async ({ devServer: devServer$1, envDirRelativePath, envDirAbsolutePaths, envFilePaths, verbose }) => {
514
+ if (verbose) logger.start("Initializing socket.io server for hot reloading...");
515
+ let clients = [];
516
+ new Server(devServer$1).on("connection", (client) => {
517
+ if (verbose) logger.debug("Client connected to hot reload socket");
518
+ clients.push(client);
519
+ client.on("disconnect", () => {
520
+ if (verbose) logger.debug("Client disconnected from hot reload socket");
521
+ clients = clients.filter((item) => item !== client);
522
+ });
523
+ });
524
+ let changes = [];
525
+ const reload = debounce(() => {
526
+ if (verbose) logger.debug("Emitting reload event to", clients.length, "clients", changes);
527
+ clients.forEach((client) => {
528
+ client.emit("reload", changes.filter((change) => path.resolve(absolutePathToEnvDirectory, change.filename).startsWith(absolutePathToEnvDirectory)));
529
+ });
530
+ changes = [];
531
+ }, 150);
532
+ const absolutePathToEnvDirectory = path.resolve(process.cwd(), envDirRelativePath);
533
+ const [dependencyGraph, updateDependencyGraph, { resolveDependentsOf }] = await createDependencyGraph(absolutePathToEnvDirectory);
534
+ if (verbose) logger.info("Dependency graph created", {
535
+ modules: Object.keys(dependencyGraph).length,
536
+ root: absolutePathToEnvDirectory
537
+ });
538
+ const watcher = await createEnvWatcher(absolutePathToEnvDirectory);
539
+ const existingExtraEnvFiles = (envFilePaths.length ? envFilePaths : envDirAbsolutePaths.flatMap((dir) => [...FILES[Environment.DEVELOPMENT], ...FILES[Environment.PRODUCTION]].map((file) => path.join(dir, file)))).filter((filePath) => fs.existsSync(filePath));
540
+ const getFilesOutsideEnvDirectory = () => Object.keys(dependencyGraph).filter((p) => path.relative(absolutePathToEnvDirectory, p).startsWith(".."));
541
+ let filesOutsideEnvDirectory = getFilesOutsideEnvDirectory();
542
+ addExternalFilesToWatcher(watcher, [...filesOutsideEnvDirectory, ...existingExtraEnvFiles]);
543
+ attachShutdownHandlers(watcher, verbose);
544
+ watcher.on("all", async (event, relativePathToChangeTarget) => {
545
+ if (verbose) logger.debug("File system event", {
546
+ event,
547
+ file: relativePathToChangeTarget
548
+ });
549
+ if (relativePathToChangeTarget.split(path.sep).length === 0) return;
550
+ const pathToChangeTarget = path.resolve(absolutePathToEnvDirectory, relativePathToChangeTarget);
551
+ await updateDependencyGraph(event, pathToChangeTarget);
552
+ const newFilesOutsideEnvDirectory = getFilesOutsideEnvDirectory();
553
+ for (const p of filesOutsideEnvDirectory) if (!newFilesOutsideEnvDirectory.includes(p)) watcher.unwatch(p);
554
+ for (const p of newFilesOutsideEnvDirectory) if (!filesOutsideEnvDirectory.includes(p) && !isPathInIgnoredDir(p)) watcher.add(p);
555
+ filesOutsideEnvDirectory = newFilesOutsideEnvDirectory;
556
+ changes.push({
557
+ event,
558
+ filename: relativePathToChangeTarget
559
+ });
560
+ for (const dependentPath of resolveDependentsOf(pathToChangeTarget)) changes.push({
561
+ event: "change",
562
+ filename: path.relative(absolutePathToEnvDirectory, dependentPath)
563
+ });
564
+ reload();
565
+ });
566
+ return watcher;
845
567
  };
846
568
 
847
- // src/cli/commands/dev.ts
848
- var dev = async ({ dir: envDirRelativePath, port, verbose }) => {
849
- try {
850
- if (verbose) {
851
- logger.debug("Starting dev command...", {
852
- cwd: process.cwd(),
853
- dir: envDirRelativePath,
854
- port
855
- });
856
- }
857
- if (!fs4.existsSync(envDirRelativePath)) {
858
- logger.error(`Missing ${envDirRelativePath} folder!`);
859
- process.exit(1);
860
- }
861
- const devServer2 = await startDevServer({
862
- envDirRelativePath,
863
- staticBaseDirRelativePath: envDirRelativePath,
864
- port: Number.parseInt(port),
865
- verbose
866
- });
867
- if (verbose) {
868
- logger.start("Dev server started, setting up hot reloading...");
869
- }
870
- await setupHotreloading({
871
- devServer: devServer2,
872
- envDirRelativePath,
873
- verbose
874
- });
875
- if (verbose) {
876
- logger.success("Hot reloading setup complete");
877
- }
878
- } catch (error) {
879
- logger.error(new Error("Error while running dev!"), error);
880
- process.exit(1);
881
- }
569
+ //#endregion
570
+ //#region src/cli/commands/dev.ts
571
+ const WORKSPACE_ROOT_FILE_MARKERS = [
572
+ "pnpm-workspace.yaml",
573
+ "turbo.json",
574
+ "nx.json",
575
+ "lerna.json",
576
+ "rush.json",
577
+ "WORKSPACE",
578
+ "WORKSPACE.bazel"
579
+ ];
580
+ const findWorkspaceRoot = (startDir) => {
581
+ const readPackageJson = (candidateDir) => {
582
+ try {
583
+ const packageJsonPath = path.join(candidateDir, "package.json");
584
+ if (!fs.existsSync(packageJsonPath)) return null;
585
+ const raw = fs.readFileSync(packageJsonPath, "utf8");
586
+ return JSON.parse(raw);
587
+ } catch {
588
+ return null;
589
+ }
590
+ };
591
+ const hasWorkspacePackageJson = (candidateDir) => {
592
+ const parsed = readPackageJson(candidateDir);
593
+ return Boolean(parsed?.workspaces);
594
+ };
595
+ const hasWorkspaceMarkers = (candidateDir) => WORKSPACE_ROOT_FILE_MARKERS.some((marker) => fs.existsSync(path.join(candidateDir, marker))) || hasWorkspacePackageJson(candidateDir);
596
+ const hasMonorepoFolderHints = (candidateDir) => {
597
+ const hasApps = fs.existsSync(path.join(candidateDir, "apps"));
598
+ const hasPackages = fs.existsSync(path.join(candidateDir, "packages"));
599
+ const packageJson = readPackageJson(candidateDir);
600
+ return (hasApps || hasPackages) && Boolean(packageJson?.private ?? false);
601
+ };
602
+ let currentDir = startDir;
603
+ let bestCandidate = null;
604
+ while (true) {
605
+ if (hasWorkspaceMarkers(currentDir)) return currentDir;
606
+ if (hasMonorepoFolderHints(currentDir)) bestCandidate = currentDir;
607
+ const parentDir = path.dirname(currentDir);
608
+ if (parentDir === currentDir || fs.existsSync(path.join(currentDir, ".git")) || fs.existsSync(path.join(currentDir, "node_modules"))) return bestCandidate ?? startDir;
609
+ currentDir = parentDir;
610
+ }
611
+ };
612
+ const uniquePaths = (paths) => {
613
+ const seen = /* @__PURE__ */ new Set();
614
+ return paths.filter((candidate) => {
615
+ const normalized = path.normalize(candidate);
616
+ if (seen.has(normalized)) return false;
617
+ seen.add(normalized);
618
+ return true;
619
+ });
620
+ };
621
+ const dev = async ({ dir: envDirRelativePath, config, env, cascade = false, port, verbose }) => {
622
+ try {
623
+ if (verbose) logger.debug("Starting dev command...", {
624
+ cwd: process.cwd(),
625
+ dir: envDirRelativePath,
626
+ config,
627
+ env,
628
+ cascade,
629
+ port
630
+ });
631
+ const cwd = process.cwd();
632
+ const resolveFromCwd = (targetPath) => path.isAbsolute(targetPath) ? targetPath : path.resolve(cwd, targetPath);
633
+ const workspaceRoot = findWorkspaceRoot(cwd);
634
+ let envFilePaths = [];
635
+ let envDirPaths = [];
636
+ let primaryEnvDir = cwd;
637
+ if (env) {
638
+ const resolvedEnvPath = resolveFromCwd(env);
639
+ if (!fs.existsSync(resolvedEnvPath)) {
640
+ logger.error(`Missing ${resolvedEnvPath} path!`);
641
+ process.exit(1);
642
+ }
643
+ if (fs.statSync(resolvedEnvPath).isFile()) {
644
+ envFilePaths = [resolvedEnvPath];
645
+ primaryEnvDir = path.dirname(resolvedEnvPath);
646
+ } else {
647
+ envDirPaths = [resolvedEnvPath];
648
+ primaryEnvDir = resolvedEnvPath;
649
+ }
650
+ } else if (cascade) {
651
+ envDirPaths = uniquePaths([workspaceRoot, cwd]);
652
+ primaryEnvDir = envDirPaths[envDirPaths.length - 1] ?? cwd;
653
+ } else {
654
+ const resolvedDir = resolveFromCwd(envDirRelativePath);
655
+ envDirPaths = [resolvedDir];
656
+ primaryEnvDir = resolvedDir;
657
+ }
658
+ const resolvedEnvDirRelativePath = path.relative(cwd, primaryEnvDir) || ".";
659
+ if (!fs.existsSync(primaryEnvDir)) {
660
+ logger.error(`Missing ${primaryEnvDir} folder!`);
661
+ process.exit(1);
662
+ }
663
+ const resolveConfigPath = () => {
664
+ if (config) return resolveFromCwd(config);
665
+ if (!cascade) return path.join(primaryEnvDir, "env.config.ts");
666
+ return uniquePaths([path.join(primaryEnvDir, "env.config.ts"), path.join(workspaceRoot, "env.config.ts")]).find((candidate) => fs.existsSync(candidate));
667
+ };
668
+ const resolvedConfigPath = resolveConfigPath();
669
+ if (!resolvedConfigPath || !fs.existsSync(resolvedConfigPath)) {
670
+ logger.error("Missing env.config.ts file!");
671
+ process.exit(1);
672
+ }
673
+ const devServer$1 = await startDevServer({
674
+ envDirRelativePath: resolvedEnvDirRelativePath,
675
+ staticBaseDirRelativePath: resolvedEnvDirRelativePath,
676
+ envDirAbsolutePaths: envDirPaths.length ? envDirPaths : [primaryEnvDir],
677
+ envFilePaths,
678
+ envConfigPath: resolvedConfigPath,
679
+ port: Number.parseInt(port),
680
+ verbose
681
+ });
682
+ if (verbose) logger.start("Dev server started, setting up hot reloading...");
683
+ await setupHotreloading({
684
+ devServer: devServer$1,
685
+ envDirRelativePath: resolvedEnvDirRelativePath,
686
+ envDirAbsolutePaths: envDirPaths.length ? envDirPaths : [primaryEnvDir],
687
+ envFilePaths,
688
+ verbose
689
+ });
690
+ if (verbose) logger.success("Hot reloading setup complete");
691
+ } catch (error) {
692
+ logger.error(/* @__PURE__ */ new Error("Error while running dev!"), error);
693
+ process.exit(1);
694
+ }
882
695
  };
883
696
 
884
- // src/cli/index.ts
885
- var PACKAGE_NAME = "@envin/cli";
886
- program.name(PACKAGE_NAME).description(
887
- "A live preview of your environment variables right in your browser"
888
- ).version(package_default.version);
889
- program.command("dev").description("Starts the live preview of your environment variables").option(
890
- "-d, --dir <path>",
891
- "Directory with your envin configuration and .env files",
892
- "./"
893
- ).option("-p --port <port>", "Port to run dev server on", "3000").option("-v, --verbose", "Enable verbose logging", false).action(dev);
697
+ //#endregion
698
+ //#region src/cli/index.ts
699
+ program.name("@envin/cli").description("A live preview of your environment variables right in your browser").version(version);
700
+ program.command("dev").description("Starts the live preview of your environment variables").option("-d, --dir <path>", "Directory with your envin configuration and .env files", "./").option("-c, --config <path>", "Path to env.config.ts").option("-e, --env <path>", "Path to .env file or directory").option("--cascade", "Enable workspace env/config cascading", false).option("-p --port <port>", "Port to run dev server on", "3000").option("-v, --verbose", "Enable verbose logging", false).action(dev);
894
701
  program.parse();
702
+
703
+ //#endregion
704
+ export { };