@envin/cli 1.1.12 → 1.1.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/dist/cli/index.mjs +532 -849
- package/dist/preview/.next/BUILD_ID +1 -1
- package/dist/preview/.next/build-manifest.json +2 -2
- package/dist/preview/.next/fallback-build-manifest.json +2 -2
- package/dist/preview/.next/prerender-manifest.json +3 -3
- package/dist/preview/.next/server/app/_global-error.html +2 -2
- package/dist/preview/.next/server/app/_global-error.rsc +1 -1
- package/dist/preview/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/dist/preview/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/preview/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/preview/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/preview/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/preview/.next/server/app/_not-found.html +1 -1
- package/dist/preview/.next/server/app/_not-found.rsc +1 -1
- package/dist/preview/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/preview/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/preview/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/preview/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/preview/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/preview/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/preview/.next/server/app/page/server-reference-manifest.json +4 -4
- package/dist/preview/.next/server/app/page_client-reference-manifest.js +1 -1
- package/dist/preview/.next/server/chunks/ssr/[root-of-the-server]__3aaf07ce._.js +1 -1
- package/dist/preview/.next/server/chunks/ssr/[root-of-the-server]__3aaf07ce._.js.map +1 -1
- package/dist/preview/.next/server/chunks/ssr/[root-of-the-server]__e2e728e5._.js +1 -1
- package/dist/preview/.next/server/chunks/ssr/[root-of-the-server]__e2e728e5._.js.map +1 -1
- package/dist/preview/.next/server/chunks/ssr/[root-of-the-server]__e3d1c002._.js +3 -3
- package/dist/preview/.next/server/chunks/ssr/[root-of-the-server]__e3d1c002._.js.map +1 -1
- package/dist/preview/.next/server/pages/404.html +1 -1
- package/dist/preview/.next/server/pages/500.html +2 -2
- package/dist/preview/.next/server/server-reference-manifest.js +1 -1
- package/dist/preview/.next/server/server-reference-manifest.json +5 -5
- package/dist/preview/.next/static/chunks/{9bd1b84742da96a1.js → a933bffba6c37967.js} +1 -1
- package/dist/preview/.next/trace +1 -1
- package/dist/preview/.next/trace-build +1 -1
- package/package.json +6 -6
- package/tsdown.config.ts +9 -0
- /package/dist/preview/.next/static/{y_m2KZXoqJPNnkobTFvMs → 2pe0g3qnZD70MXuT6-2Ym}/_buildManifest.js +0 -0
- /package/dist/preview/.next/static/{y_m2KZXoqJPNnkobTFvMs → 2pe0g3qnZD70MXuT6-2Ym}/_clientMiddlewareManifest.json +0 -0
- /package/dist/preview/.next/static/{y_m2KZXoqJPNnkobTFvMs → 2pe0g3qnZD70MXuT6-2Ym}/_ssgManifest.js +0 -0
package/dist/cli/index.mjs
CHANGED
|
@@ -1,894 +1,577 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
// src/cli/index.ts
|
|
4
2
|
import { program } from "commander";
|
|
5
|
-
|
|
6
|
-
|
|
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
|
|
104
|
-
|
|
105
|
-
// src/cli/utils/logger.ts
|
|
7
|
+
import { Server } from "socket.io";
|
|
106
8
|
import { createConsola } from "consola";
|
|
107
|
-
|
|
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
|
|
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
|
-
|
|
123
|
-
|
|
124
|
-
|
|
20
|
+
//#region package.json
|
|
21
|
+
var version = "1.1.13";
|
|
22
|
+
|
|
23
|
+
//#endregion
|
|
24
|
+
//#region src/cli/utils/logger.ts
|
|
25
|
+
const logger = createConsola({});
|
|
26
|
+
|
|
27
|
+
//#endregion
|
|
28
|
+
//#region src/utils/register-spinner-autostopping.ts
|
|
29
|
+
const spinners = /* @__PURE__ */ new Set();
|
|
125
30
|
process.on("SIGINT", () => {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
}
|
|
130
|
-
});
|
|
31
|
+
spinners.forEach((spinner) => {
|
|
32
|
+
if (spinner.isSpinning) spinner.stop();
|
|
33
|
+
});
|
|
131
34
|
});
|
|
132
35
|
process.on("exit", (code) => {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
spinner.stopAndPersist({
|
|
137
|
-
symbol: logSymbols.error
|
|
138
|
-
});
|
|
139
|
-
}
|
|
140
|
-
});
|
|
141
|
-
}
|
|
36
|
+
if (code !== 0) spinners.forEach((spinner) => {
|
|
37
|
+
if (spinner.isSpinning) spinner.stopAndPersist({ symbol: logSymbols.error });
|
|
38
|
+
});
|
|
142
39
|
});
|
|
143
|
-
|
|
144
|
-
|
|
40
|
+
const registerSpinnerAutostopping = (spinner) => {
|
|
41
|
+
spinners.add(spinner);
|
|
145
42
|
};
|
|
146
43
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
44
|
+
//#endregion
|
|
45
|
+
//#region src/cli/utils/preview/get-env-variables.ts
|
|
46
|
+
const getEnvVariablesForPreviewApp = (relativePathToEnvDirectory, cwd) => {
|
|
47
|
+
return {
|
|
48
|
+
ENV_DIR_RELATIVE_PATH: relativePathToEnvDirectory,
|
|
49
|
+
ENV_DIR_ABSOLUTE_PATH: path.resolve(cwd, relativePathToEnvDirectory),
|
|
50
|
+
USER_PROJECT_LOCATION: cwd,
|
|
51
|
+
NEXT_PUBLIC_IS_PREVIEW_DEVELOPMENT: isDev ? "true" : "false"
|
|
52
|
+
};
|
|
156
53
|
};
|
|
157
54
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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
|
-
}
|
|
55
|
+
//#endregion
|
|
56
|
+
//#region src/cli/utils/preview/serve-static-file.ts
|
|
57
|
+
const serveStaticFile = async (res, parsedUrl, staticDirRelativePath) => {
|
|
58
|
+
const pathname = parsedUrl.pathname?.replace("/static", "./static") ?? "";
|
|
59
|
+
const ext = path.parse(pathname).ext;
|
|
60
|
+
const staticBaseDir = path.resolve(process.cwd(), staticDirRelativePath);
|
|
61
|
+
const fileAbsolutePath = path.resolve(staticBaseDir, pathname);
|
|
62
|
+
if (!fileAbsolutePath.startsWith(staticBaseDir)) {
|
|
63
|
+
res.statusCode = 403;
|
|
64
|
+
res.end();
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
try {
|
|
68
|
+
const fileHandle = await promises.open(fileAbsolutePath, "r");
|
|
69
|
+
const fileData = await promises.readFile(fileHandle);
|
|
70
|
+
res.setHeader("Content-type", lookup(ext) || "text/plain");
|
|
71
|
+
res.end(fileData);
|
|
72
|
+
fileHandle.close();
|
|
73
|
+
} catch (exception) {
|
|
74
|
+
if (!existsSync(fileAbsolutePath)) {
|
|
75
|
+
res.statusCode = 404;
|
|
76
|
+
res.end();
|
|
77
|
+
} else {
|
|
78
|
+
const sanitizedFilePath = fileAbsolutePath.replace(/\n|\r/g, "");
|
|
79
|
+
logger.error(/* @__PURE__ */ new Error(`Could not read file at ${sanitizedFilePath} to be served, here's the exception:`), exception);
|
|
80
|
+
res.statusCode = 500;
|
|
81
|
+
res.end("Could not read file to be served! Check your terminal for more information.");
|
|
82
|
+
}
|
|
83
|
+
}
|
|
196
84
|
};
|
|
197
85
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
});
|
|
86
|
+
//#endregion
|
|
87
|
+
//#region src/cli/utils/preview/start-dev-server.ts
|
|
88
|
+
let devServer;
|
|
89
|
+
const safeAsyncServerListen = (server, port) => {
|
|
90
|
+
return new Promise((resolve) => {
|
|
91
|
+
server.listen(port, () => {
|
|
92
|
+
resolve({ portAlreadyInUse: false });
|
|
93
|
+
});
|
|
94
|
+
server.on("error", (e) => {
|
|
95
|
+
if (e.code === "EADDRINUSE") resolve({ portAlreadyInUse: true });
|
|
96
|
+
});
|
|
97
|
+
});
|
|
211
98
|
};
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
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;
|
|
99
|
+
const filename = url.fileURLToPath(import.meta.url);
|
|
100
|
+
const dirname = path.dirname(filename);
|
|
101
|
+
const isDev = !filename.endsWith(path.join("cli", "index.mjs"));
|
|
102
|
+
const cliPackageLocation = isDev ? path.resolve(dirname, "../../../..") : path.resolve(dirname, "../..");
|
|
103
|
+
const previewServerLocation = isDev ? path.resolve(dirname, "../../../..") : path.resolve(dirname, "../preview");
|
|
104
|
+
const startDevServer = async ({ envDirRelativePath, staticBaseDirRelativePath, port, verbose }) => {
|
|
105
|
+
const [majorNodeVersion] = process.versions.node.split(".");
|
|
106
|
+
if (majorNodeVersion && Number.parseInt(majorNodeVersion) < 18) {
|
|
107
|
+
logger.error(`Node ${majorNodeVersion} is not supported. Please upgrade to Node 18 or higher.`);
|
|
108
|
+
process.exit(1);
|
|
109
|
+
}
|
|
110
|
+
devServer = http.createServer((req, res) => {
|
|
111
|
+
if (!req.url) {
|
|
112
|
+
res.end(404);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
if (verbose) logger.debug("Creating HTTP server...", {
|
|
116
|
+
envDirRelativePath,
|
|
117
|
+
staticBaseDirRelativePath,
|
|
118
|
+
port
|
|
119
|
+
});
|
|
120
|
+
const parsedUrl = url.parse(req.url, true);
|
|
121
|
+
res.setHeader("Cache-Control", "no-cache, max-age=0, must-revalidate, no-store");
|
|
122
|
+
res.setHeader("Pragma", "no-cache");
|
|
123
|
+
res.setHeader("Expires", "-1");
|
|
124
|
+
try {
|
|
125
|
+
if (parsedUrl.path?.includes("static/") && !parsedUrl.path.includes("_next/static/")) serveStaticFile(res, parsedUrl, staticBaseDirRelativePath);
|
|
126
|
+
else if (!isNextReady) nextReadyPromise.then(() => nextHandleRequest?.(req, res, parsedUrl));
|
|
127
|
+
else nextHandleRequest?.(req, res, parsedUrl);
|
|
128
|
+
} catch (e) {
|
|
129
|
+
logger.error(/* @__PURE__ */ new Error("Error while handling request!"), e);
|
|
130
|
+
res.writeHead(500);
|
|
131
|
+
res.end();
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
const { portAlreadyInUse } = await safeAsyncServerListen(devServer, port);
|
|
135
|
+
if (!portAlreadyInUse) {
|
|
136
|
+
logger.log(chalk.greenBright(`\n Envin ${version}`));
|
|
137
|
+
logger.log(` Running preview at: http://localhost:${port}\n`);
|
|
138
|
+
} else {
|
|
139
|
+
const nextPortToTry = port + 1;
|
|
140
|
+
logger.warn(`Port ${port} is already in use, trying ${nextPortToTry}...`);
|
|
141
|
+
return startDevServer({
|
|
142
|
+
envDirRelativePath,
|
|
143
|
+
staticBaseDirRelativePath,
|
|
144
|
+
port: nextPortToTry,
|
|
145
|
+
verbose
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
devServer.on("close", async () => {
|
|
149
|
+
await app.close();
|
|
150
|
+
});
|
|
151
|
+
devServer.on("error", (e) => {
|
|
152
|
+
logger.error(/* @__PURE__ */ new Error("Preview server error!"), e);
|
|
153
|
+
process.exit(1);
|
|
154
|
+
});
|
|
155
|
+
const spinner = ora({
|
|
156
|
+
text: "Getting envin preview server ready...\n",
|
|
157
|
+
prefixText: " "
|
|
158
|
+
}).start();
|
|
159
|
+
registerSpinnerAutostopping(spinner);
|
|
160
|
+
const timeBeforeNextReady = performance.now();
|
|
161
|
+
process.env = {
|
|
162
|
+
NODE_ENV: "development",
|
|
163
|
+
...process.env,
|
|
164
|
+
...getEnvVariablesForPreviewApp(path.normalize(envDirRelativePath), process.cwd())
|
|
165
|
+
};
|
|
166
|
+
const app = next({
|
|
167
|
+
dev: isDev,
|
|
168
|
+
conf: { images: { unoptimized: true } },
|
|
169
|
+
hostname: "localhost",
|
|
170
|
+
port,
|
|
171
|
+
dir: previewServerLocation
|
|
172
|
+
});
|
|
173
|
+
let isNextReady = false;
|
|
174
|
+
const nextReadyPromise = app.prepare();
|
|
175
|
+
await nextReadyPromise;
|
|
176
|
+
isNextReady = true;
|
|
177
|
+
const nextHandleRequest = app.getRequestHandler();
|
|
178
|
+
const secondsToNextReady = ((performance.now() - timeBeforeNextReady) / 1e3).toFixed(1);
|
|
179
|
+
spinner.stopAndPersist({
|
|
180
|
+
text: `Ready in ${secondsToNextReady}s\n`,
|
|
181
|
+
symbol: logSymbols.success
|
|
182
|
+
});
|
|
183
|
+
return devServer;
|
|
327
184
|
};
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
process.exit(options.killWithErrorCode ? 1 : 0);
|
|
336
|
-
}
|
|
185
|
+
const makeExitHandler = (options) => (_codeOrSignal) => {
|
|
186
|
+
if (typeof devServer !== "undefined") {
|
|
187
|
+
logger.log("\nShutting down dev server...");
|
|
188
|
+
devServer.close();
|
|
189
|
+
devServer = void 0;
|
|
190
|
+
}
|
|
191
|
+
if (options?.shouldKillProcess) process.exit(options.killWithErrorCode ? 1 : 0);
|
|
337
192
|
};
|
|
338
193
|
process.on("exit", makeExitHandler());
|
|
339
|
-
process.on(
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
);
|
|
343
|
-
process.on(
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
);
|
|
347
|
-
process.on(
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
);
|
|
351
|
-
process.on(
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
);
|
|
194
|
+
process.on("SIGINT", makeExitHandler({
|
|
195
|
+
shouldKillProcess: true,
|
|
196
|
+
killWithErrorCode: false
|
|
197
|
+
}));
|
|
198
|
+
process.on("SIGUSR1", makeExitHandler({
|
|
199
|
+
shouldKillProcess: true,
|
|
200
|
+
killWithErrorCode: false
|
|
201
|
+
}));
|
|
202
|
+
process.on("SIGUSR2", makeExitHandler({
|
|
203
|
+
shouldKillProcess: true,
|
|
204
|
+
killWithErrorCode: false
|
|
205
|
+
}));
|
|
206
|
+
process.on("uncaughtException", makeExitHandler({
|
|
207
|
+
shouldKillProcess: true,
|
|
208
|
+
killWithErrorCode: true
|
|
209
|
+
}));
|
|
355
210
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
const importPathNode = node.arguments[0];
|
|
394
|
-
if (importPathNode?.type === "StringLiteral") {
|
|
395
|
-
importedPaths.push(importPathNode.value);
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
});
|
|
401
|
-
return importedPaths;
|
|
211
|
+
//#endregion
|
|
212
|
+
//#region src/cli/utils/hot-reload/get-imported-modules.ts
|
|
213
|
+
const traverse = typeof traverseModule === "function" ? traverseModule : traverseModule.default;
|
|
214
|
+
const getImportedModules = (contents) => {
|
|
215
|
+
const importedPaths = [];
|
|
216
|
+
traverse(parse(contents, {
|
|
217
|
+
sourceType: "unambiguous",
|
|
218
|
+
strictMode: false,
|
|
219
|
+
errorRecovery: true,
|
|
220
|
+
plugins: [
|
|
221
|
+
"jsx",
|
|
222
|
+
"typescript",
|
|
223
|
+
"decorators"
|
|
224
|
+
]
|
|
225
|
+
}), {
|
|
226
|
+
ImportDeclaration({ node }) {
|
|
227
|
+
importedPaths.push(node.source.value);
|
|
228
|
+
},
|
|
229
|
+
ExportAllDeclaration({ node }) {
|
|
230
|
+
importedPaths.push(node.source.value);
|
|
231
|
+
},
|
|
232
|
+
ExportNamedDeclaration({ node }) {
|
|
233
|
+
if (node.source) importedPaths.push(node.source.value);
|
|
234
|
+
},
|
|
235
|
+
TSExternalModuleReference({ node }) {
|
|
236
|
+
importedPaths.push(node.expression.value);
|
|
237
|
+
},
|
|
238
|
+
CallExpression({ node }) {
|
|
239
|
+
if ("name" in node.callee && node.callee.name === "require") {
|
|
240
|
+
if (node.arguments.length === 1) {
|
|
241
|
+
const importPathNode = node.arguments[0];
|
|
242
|
+
if (importPathNode?.type === "StringLiteral") importedPaths.push(importPathNode.value);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
return importedPaths;
|
|
402
248
|
};
|
|
403
249
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
return `./${path4.relative(projectPath, unaliasedPath)}`;
|
|
425
|
-
}
|
|
426
|
-
return importedPath;
|
|
427
|
-
});
|
|
428
|
-
}
|
|
429
|
-
return importPaths;
|
|
250
|
+
//#endregion
|
|
251
|
+
//#region src/cli/utils/hot-reload/resolve-path-aliases.ts
|
|
252
|
+
const resolvePathAliases = (importPaths, projectPath) => {
|
|
253
|
+
const configLoadResult = loadConfig(projectPath);
|
|
254
|
+
if (configLoadResult.resultType === "success") {
|
|
255
|
+
const matchPath = createMatchPath(configLoadResult.absoluteBaseUrl, configLoadResult.paths);
|
|
256
|
+
return importPaths.map((importedPath) => {
|
|
257
|
+
const unaliasedPath = matchPath(importedPath, void 0, void 0, [
|
|
258
|
+
".tsx",
|
|
259
|
+
".ts",
|
|
260
|
+
".js",
|
|
261
|
+
".jsx",
|
|
262
|
+
".cjs",
|
|
263
|
+
".mjs"
|
|
264
|
+
]);
|
|
265
|
+
if (unaliasedPath) return `./${path.relative(projectPath, unaliasedPath)}`;
|
|
266
|
+
return importedPath;
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
return importPaths;
|
|
430
270
|
};
|
|
431
271
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
allFilePaths.push(pathToDirent);
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
return allFilePaths;
|
|
272
|
+
//#endregion
|
|
273
|
+
//#region src/cli/utils/hot-reload/create-dependency-graph.ts
|
|
274
|
+
const readAllFilesInsideDirectory = async (directory) => {
|
|
275
|
+
let allFilePaths = [];
|
|
276
|
+
const topLevelDirents = await promises.readdir(directory, { withFileTypes: true });
|
|
277
|
+
for await (const dirent of topLevelDirents) {
|
|
278
|
+
const pathToDirent = path.join(directory, dirent.name);
|
|
279
|
+
if (dirent.isDirectory()) allFilePaths = allFilePaths.concat(await readAllFilesInsideDirectory(pathToDirent));
|
|
280
|
+
else allFilePaths.push(pathToDirent);
|
|
281
|
+
}
|
|
282
|
+
return allFilePaths;
|
|
447
283
|
};
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
284
|
+
const isJavascriptModule = (filePath) => {
|
|
285
|
+
const extensionName = path.extname(filePath);
|
|
286
|
+
return [
|
|
287
|
+
".js",
|
|
288
|
+
".ts",
|
|
289
|
+
".jsx",
|
|
290
|
+
".tsx",
|
|
291
|
+
".mjs",
|
|
292
|
+
".cjs"
|
|
293
|
+
].includes(extensionName);
|
|
451
294
|
};
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
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
|
-
}
|
|
295
|
+
const checkFileExtensionsUntilItExists = (pathWithoutExtension) => {
|
|
296
|
+
if (existsSync(`${pathWithoutExtension}.ts`)) return `${pathWithoutExtension}.ts`;
|
|
297
|
+
if (existsSync(`${pathWithoutExtension}.tsx`)) return `${pathWithoutExtension}.tsx`;
|
|
298
|
+
if (existsSync(`${pathWithoutExtension}.js`)) return `${pathWithoutExtension}.js`;
|
|
299
|
+
if (existsSync(`${pathWithoutExtension}.jsx`)) return `${pathWithoutExtension}.jsx`;
|
|
300
|
+
if (existsSync(`${pathWithoutExtension}.mjs`)) return `${pathWithoutExtension}.mjs`;
|
|
301
|
+
if (existsSync(`${pathWithoutExtension}.cjs`)) return `${pathWithoutExtension}.cjs`;
|
|
471
302
|
};
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
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
|
-
];
|
|
303
|
+
/**
|
|
304
|
+
* Creates a stateful dependency graph that is structured in a way that you can get
|
|
305
|
+
* the dependents of a module from its path.
|
|
306
|
+
*
|
|
307
|
+
* Stateful in the sense that it provides a `getter` and an "`updater`". The updater
|
|
308
|
+
* will receive changes to the files, that can be perceived through some file watching mechanism,
|
|
309
|
+
* so that it doesn't need to recompute the entire dependency graph but only the parts changed.
|
|
310
|
+
*/
|
|
311
|
+
const createDependencyGraph = async (directory) => {
|
|
312
|
+
const modulePaths = (await readAllFilesInsideDirectory(directory)).filter(isJavascriptModule);
|
|
313
|
+
const graph = Object.fromEntries(modulePaths.map((path$1) => [path$1, {
|
|
314
|
+
path: path$1,
|
|
315
|
+
dependencyPaths: [],
|
|
316
|
+
dependentPaths: [],
|
|
317
|
+
moduleDependencies: []
|
|
318
|
+
}]));
|
|
319
|
+
const getDependencyPaths = async (filePath) => {
|
|
320
|
+
const contents = await promises.readFile(filePath, "utf8");
|
|
321
|
+
const importedPathsRelativeToDirectory = (isJavascriptModule(filePath) ? resolvePathAliases(getImportedModules(contents), path.dirname(filePath)) : []).map((dependencyPath) => {
|
|
322
|
+
if (!dependencyPath.startsWith(".") || path.isAbsolute(dependencyPath)) return dependencyPath;
|
|
323
|
+
let pathToDependencyFromDirectory = path.resolve(path.dirname(filePath), dependencyPath);
|
|
324
|
+
let isDirectory = false;
|
|
325
|
+
try {
|
|
326
|
+
isDirectory = statSync(pathToDependencyFromDirectory).isDirectory();
|
|
327
|
+
} catch (_) {}
|
|
328
|
+
if (isDirectory) {
|
|
329
|
+
const pathWithExtension = checkFileExtensionsUntilItExists(`${pathToDependencyFromDirectory}/index`);
|
|
330
|
+
if (pathWithExtension) pathToDependencyFromDirectory = pathWithExtension;
|
|
331
|
+
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.`);
|
|
332
|
+
}
|
|
333
|
+
const extension = path.extname(pathToDependencyFromDirectory);
|
|
334
|
+
const pathWithEnsuredExtension = (() => {
|
|
335
|
+
if (extension.length > 0 && existsSync(pathToDependencyFromDirectory)) return pathToDependencyFromDirectory;
|
|
336
|
+
return checkFileExtensionsUntilItExists(pathToDependencyFromDirectory.replace(extension, ""));
|
|
337
|
+
})();
|
|
338
|
+
if (pathWithEnsuredExtension) pathToDependencyFromDirectory = pathWithEnsuredExtension;
|
|
339
|
+
else if (isDev) logger.warn(`Could not find file at ${pathToDependencyFromDirectory}`);
|
|
340
|
+
return pathToDependencyFromDirectory;
|
|
341
|
+
});
|
|
342
|
+
const moduleDependencies = importedPathsRelativeToDirectory.filter((dependencyPath) => !dependencyPath.startsWith(".") && !path.isAbsolute(dependencyPath));
|
|
343
|
+
return {
|
|
344
|
+
dependencyPaths: importedPathsRelativeToDirectory.filter((dependencyPath) => dependencyPath.startsWith(".") || path.isAbsolute(dependencyPath)),
|
|
345
|
+
moduleDependencies
|
|
346
|
+
};
|
|
347
|
+
};
|
|
348
|
+
const updateModuleDependenciesInGraph = async (moduleFilePath) => {
|
|
349
|
+
if (graph[moduleFilePath] === void 0) graph[moduleFilePath] = {
|
|
350
|
+
path: moduleFilePath,
|
|
351
|
+
dependencyPaths: [],
|
|
352
|
+
dependentPaths: [],
|
|
353
|
+
moduleDependencies: []
|
|
354
|
+
};
|
|
355
|
+
const { moduleDependencies, dependencyPaths: newDependencyPaths } = await getDependencyPaths(moduleFilePath);
|
|
356
|
+
graph[moduleFilePath].moduleDependencies = moduleDependencies;
|
|
357
|
+
for (const dependencyPath of graph[moduleFilePath].dependencyPaths) {
|
|
358
|
+
if (newDependencyPaths.includes(dependencyPath)) continue;
|
|
359
|
+
const dependencyModule = graph[dependencyPath];
|
|
360
|
+
if (dependencyModule !== void 0) dependencyModule.dependentPaths = dependencyModule.dependentPaths.filter((dependentPath) => dependentPath !== moduleFilePath);
|
|
361
|
+
}
|
|
362
|
+
graph[moduleFilePath].dependencyPaths = newDependencyPaths;
|
|
363
|
+
for await (const dependencyPath of newDependencyPaths) {
|
|
364
|
+
if (graph[dependencyPath] === void 0) await updateModuleDependenciesInGraph(dependencyPath);
|
|
365
|
+
const dependencyModule = graph[dependencyPath];
|
|
366
|
+
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.`);
|
|
367
|
+
if (!dependencyModule.dependentPaths.includes(moduleFilePath)) dependencyModule.dependentPaths.push(moduleFilePath);
|
|
368
|
+
}
|
|
369
|
+
};
|
|
370
|
+
const removeModuleFromGraph = (filePath) => {
|
|
371
|
+
const module = graph[filePath];
|
|
372
|
+
if (module) {
|
|
373
|
+
for (const dependencyPath of module.dependencyPaths) if (graph[dependencyPath]) graph[dependencyPath].dependentPaths = graph[dependencyPath].dependentPaths.filter((dependentPath) => dependentPath !== filePath);
|
|
374
|
+
delete graph[filePath];
|
|
375
|
+
}
|
|
376
|
+
};
|
|
377
|
+
return [
|
|
378
|
+
graph,
|
|
379
|
+
async (event, pathToModified) => {
|
|
380
|
+
switch (event) {
|
|
381
|
+
case "change":
|
|
382
|
+
if (isJavascriptModule(pathToModified)) await updateModuleDependenciesInGraph(pathToModified);
|
|
383
|
+
break;
|
|
384
|
+
case "add":
|
|
385
|
+
if (isJavascriptModule(pathToModified)) await updateModuleDependenciesInGraph(pathToModified);
|
|
386
|
+
break;
|
|
387
|
+
case "addDir": {
|
|
388
|
+
const modulesInsideAddedDirectory = (await readAllFilesInsideDirectory(pathToModified)).filter(isJavascriptModule);
|
|
389
|
+
for await (const filePath of modulesInsideAddedDirectory) await updateModuleDependenciesInGraph(filePath);
|
|
390
|
+
break;
|
|
391
|
+
}
|
|
392
|
+
case "unlink":
|
|
393
|
+
if (isJavascriptModule(pathToModified)) removeModuleFromGraph(pathToModified);
|
|
394
|
+
break;
|
|
395
|
+
case "unlinkDir": {
|
|
396
|
+
const modulesInsideDeletedDirectory = (await readAllFilesInsideDirectory(pathToModified)).filter(isJavascriptModule);
|
|
397
|
+
for await (const filePath of modulesInsideDeletedDirectory) removeModuleFromGraph(filePath);
|
|
398
|
+
break;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
},
|
|
402
|
+
{ resolveDependentsOf: function resolveDependentsOf(pathToModule) {
|
|
403
|
+
const moduleEntry = graph[pathToModule];
|
|
404
|
+
const dependentPaths = [];
|
|
405
|
+
if (moduleEntry) for (const dependentPath of moduleEntry.dependentPaths) {
|
|
406
|
+
const dependentsOfDependent = resolveDependentsOf(dependentPath);
|
|
407
|
+
dependentPaths.push(...dependentsOfDependent);
|
|
408
|
+
dependentPaths.push(dependentPath);
|
|
409
|
+
}
|
|
410
|
+
return dependentPaths;
|
|
411
|
+
} }
|
|
412
|
+
];
|
|
655
413
|
};
|
|
656
414
|
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
415
|
+
//#endregion
|
|
416
|
+
//#region src/cli/utils/hot-reload/setup-hot-reloading.ts
|
|
417
|
+
const IGNORED_DIR_NAMES = [
|
|
418
|
+
"node_modules",
|
|
419
|
+
".git",
|
|
420
|
+
"dist",
|
|
421
|
+
"build",
|
|
422
|
+
"out",
|
|
423
|
+
"coverage",
|
|
424
|
+
".cache",
|
|
425
|
+
"tmp"
|
|
667
426
|
];
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
(dir) => normalized.includes(`/${dir}/`) || normalized.endsWith(`/${dir}`) || normalized.startsWith(`${dir}/`)
|
|
673
|
-
);
|
|
427
|
+
const normalizePath = (p) => p.split(path.sep).join("/");
|
|
428
|
+
const isPathInIgnoredDir = (p) => {
|
|
429
|
+
const normalized = normalizePath(p);
|
|
430
|
+
return IGNORED_DIR_NAMES.some((dir) => normalized.includes(`/${dir}/`) || normalized.endsWith(`/${dir}`) || normalized.startsWith(`${dir}/`));
|
|
674
431
|
};
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
`**/${pattern}/**`,
|
|
698
|
-
`${pattern}/**`,
|
|
699
|
-
`**/${pattern}`,
|
|
700
|
-
pattern
|
|
701
|
-
);
|
|
702
|
-
}
|
|
703
|
-
}
|
|
704
|
-
return globs;
|
|
705
|
-
} catch {
|
|
706
|
-
return [];
|
|
707
|
-
}
|
|
432
|
+
const toDirGlob = (dir) => [`**/${dir}/**`, `${dir}/**`];
|
|
433
|
+
const readGitignoreGlobs = async (baseDir) => {
|
|
434
|
+
try {
|
|
435
|
+
const gitignorePath = path.join(baseDir, ".gitignore");
|
|
436
|
+
const lines = (await promises.readFile(gitignorePath, "utf8")).split(/\r?\n/);
|
|
437
|
+
const globs = [];
|
|
438
|
+
for (const rawLine of lines) {
|
|
439
|
+
const line = rawLine.trim();
|
|
440
|
+
if (line.length === 0 || line.startsWith("#") || line.startsWith("!")) continue;
|
|
441
|
+
let pattern = line.replace(/^\//, "");
|
|
442
|
+
if (pattern.endsWith("/")) {
|
|
443
|
+
pattern = pattern.slice(0, -1);
|
|
444
|
+
globs.push(`**/${pattern}/**`, `${pattern}/**`);
|
|
445
|
+
continue;
|
|
446
|
+
}
|
|
447
|
+
if (pattern.includes("*") || pattern.includes("?")) globs.push(`**/${pattern}`, pattern);
|
|
448
|
+
else globs.push(`**/${pattern}/**`, `${pattern}/**`, `**/${pattern}`, pattern);
|
|
449
|
+
}
|
|
450
|
+
return globs;
|
|
451
|
+
} catch {
|
|
452
|
+
return [];
|
|
453
|
+
}
|
|
708
454
|
};
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
455
|
+
const buildIgnoredGlobs = async (baseDir) => {
|
|
456
|
+
const defaultIgnoredGlobs = IGNORED_DIR_NAMES.flatMap(toDirGlob);
|
|
457
|
+
const gitignoreGlobs = await readGitignoreGlobs(baseDir);
|
|
458
|
+
return [...defaultIgnoredGlobs, ...gitignoreGlobs];
|
|
713
459
|
};
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
460
|
+
const createEnvWatcher = async (baseDir) => {
|
|
461
|
+
return watch(["**/*.{js,ts,jsx,tsx,mjs,cjs}"], {
|
|
462
|
+
cwd: baseDir,
|
|
463
|
+
ignoreInitial: true,
|
|
464
|
+
ignored: await buildIgnoredGlobs(baseDir),
|
|
465
|
+
ignorePermissionErrors: true,
|
|
466
|
+
awaitWriteFinish: {
|
|
467
|
+
stabilityThreshold: 50,
|
|
468
|
+
pollInterval: 10
|
|
469
|
+
}
|
|
470
|
+
});
|
|
723
471
|
};
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
if (!isPathInIgnoredDir(p)) {
|
|
727
|
-
w.add(p);
|
|
728
|
-
}
|
|
729
|
-
}
|
|
472
|
+
const addExternalFilesToWatcher = (w, files) => {
|
|
473
|
+
for (const p of files) if (!isPathInIgnoredDir(p)) w.add(p);
|
|
730
474
|
};
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
process.on("SIGINT", exit);
|
|
739
|
-
process.on("uncaughtException", exit);
|
|
475
|
+
const attachShutdownHandlers = (w, verbose) => {
|
|
476
|
+
const exit = async () => {
|
|
477
|
+
if (verbose) logger.info("Stopping file watcher and cleaning up...");
|
|
478
|
+
await w.close();
|
|
479
|
+
};
|
|
480
|
+
process.on("SIGINT", exit);
|
|
481
|
+
process.on("uncaughtException", exit);
|
|
740
482
|
};
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
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;
|
|
483
|
+
const setupHotreloading = async ({ devServer: devServer$1, envDirRelativePath, verbose }) => {
|
|
484
|
+
if (verbose) logger.start("Initializing socket.io server for hot reloading...");
|
|
485
|
+
let clients = [];
|
|
486
|
+
new Server(devServer$1).on("connection", (client) => {
|
|
487
|
+
if (verbose) logger.debug("Client connected to hot reload socket");
|
|
488
|
+
clients.push(client);
|
|
489
|
+
client.on("disconnect", () => {
|
|
490
|
+
if (verbose) logger.debug("Client disconnected from hot reload socket");
|
|
491
|
+
clients = clients.filter((item) => item !== client);
|
|
492
|
+
});
|
|
493
|
+
});
|
|
494
|
+
let changes = [];
|
|
495
|
+
const reload = debounce(() => {
|
|
496
|
+
if (verbose) logger.debug("Emitting reload event to", clients.length, "clients", changes);
|
|
497
|
+
clients.forEach((client) => {
|
|
498
|
+
client.emit("reload", changes.filter((change) => path.resolve(absolutePathToEnvDirectory, change.filename).startsWith(absolutePathToEnvDirectory)));
|
|
499
|
+
});
|
|
500
|
+
changes = [];
|
|
501
|
+
}, 150);
|
|
502
|
+
const absolutePathToEnvDirectory = path.resolve(process.cwd(), envDirRelativePath);
|
|
503
|
+
const [dependencyGraph, updateDependencyGraph, { resolveDependentsOf }] = await createDependencyGraph(absolutePathToEnvDirectory);
|
|
504
|
+
if (verbose) logger.info("Dependency graph created", {
|
|
505
|
+
modules: Object.keys(dependencyGraph).length,
|
|
506
|
+
root: absolutePathToEnvDirectory
|
|
507
|
+
});
|
|
508
|
+
const watcher = await createEnvWatcher(absolutePathToEnvDirectory);
|
|
509
|
+
const getFilesOutsideEnvDirectory = () => Object.keys(dependencyGraph).filter((p) => path.relative(absolutePathToEnvDirectory, p).startsWith(".."));
|
|
510
|
+
let filesOutsideEnvDirectory = getFilesOutsideEnvDirectory();
|
|
511
|
+
addExternalFilesToWatcher(watcher, filesOutsideEnvDirectory);
|
|
512
|
+
attachShutdownHandlers(watcher, verbose);
|
|
513
|
+
watcher.on("all", async (event, relativePathToChangeTarget) => {
|
|
514
|
+
if (verbose) logger.debug("File system event", {
|
|
515
|
+
event,
|
|
516
|
+
file: relativePathToChangeTarget
|
|
517
|
+
});
|
|
518
|
+
if (relativePathToChangeTarget.split(path.sep).length === 0) return;
|
|
519
|
+
const pathToChangeTarget = path.resolve(absolutePathToEnvDirectory, relativePathToChangeTarget);
|
|
520
|
+
await updateDependencyGraph(event, pathToChangeTarget);
|
|
521
|
+
const newFilesOutsideEnvDirectory = getFilesOutsideEnvDirectory();
|
|
522
|
+
for (const p of filesOutsideEnvDirectory) if (!newFilesOutsideEnvDirectory.includes(p)) watcher.unwatch(p);
|
|
523
|
+
for (const p of newFilesOutsideEnvDirectory) if (!filesOutsideEnvDirectory.includes(p) && !isPathInIgnoredDir(p)) watcher.add(p);
|
|
524
|
+
filesOutsideEnvDirectory = newFilesOutsideEnvDirectory;
|
|
525
|
+
changes.push({
|
|
526
|
+
event,
|
|
527
|
+
filename: relativePathToChangeTarget
|
|
528
|
+
});
|
|
529
|
+
for (const dependentPath of resolveDependentsOf(pathToChangeTarget)) changes.push({
|
|
530
|
+
event: "change",
|
|
531
|
+
filename: path.relative(absolutePathToEnvDirectory, dependentPath)
|
|
532
|
+
});
|
|
533
|
+
reload();
|
|
534
|
+
});
|
|
535
|
+
return watcher;
|
|
845
536
|
};
|
|
846
537
|
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
}
|
|
878
|
-
} catch (error) {
|
|
879
|
-
logger.error(new Error("Error while running dev!"), error);
|
|
880
|
-
process.exit(1);
|
|
881
|
-
}
|
|
538
|
+
//#endregion
|
|
539
|
+
//#region src/cli/commands/dev.ts
|
|
540
|
+
const dev = async ({ dir: envDirRelativePath, port, verbose }) => {
|
|
541
|
+
try {
|
|
542
|
+
if (verbose) logger.debug("Starting dev command...", {
|
|
543
|
+
cwd: process.cwd(),
|
|
544
|
+
dir: envDirRelativePath,
|
|
545
|
+
port
|
|
546
|
+
});
|
|
547
|
+
if (!fs.existsSync(envDirRelativePath)) {
|
|
548
|
+
logger.error(`Missing ${envDirRelativePath} folder!`);
|
|
549
|
+
process.exit(1);
|
|
550
|
+
}
|
|
551
|
+
const devServer$1 = await startDevServer({
|
|
552
|
+
envDirRelativePath,
|
|
553
|
+
staticBaseDirRelativePath: envDirRelativePath,
|
|
554
|
+
port: Number.parseInt(port),
|
|
555
|
+
verbose
|
|
556
|
+
});
|
|
557
|
+
if (verbose) logger.start("Dev server started, setting up hot reloading...");
|
|
558
|
+
await setupHotreloading({
|
|
559
|
+
devServer: devServer$1,
|
|
560
|
+
envDirRelativePath,
|
|
561
|
+
verbose
|
|
562
|
+
});
|
|
563
|
+
if (verbose) logger.success("Hot reloading setup complete");
|
|
564
|
+
} catch (error) {
|
|
565
|
+
logger.error(/* @__PURE__ */ new Error("Error while running dev!"), error);
|
|
566
|
+
process.exit(1);
|
|
567
|
+
}
|
|
882
568
|
};
|
|
883
569
|
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
program.name(
|
|
887
|
-
|
|
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);
|
|
570
|
+
//#endregion
|
|
571
|
+
//#region src/cli/index.ts
|
|
572
|
+
program.name("@envin/cli").description("A live preview of your environment variables right in your browser").version(version);
|
|
573
|
+
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("-p --port <port>", "Port to run dev server on", "3000").option("-v, --verbose", "Enable verbose logging", false).action(dev);
|
|
894
574
|
program.parse();
|
|
575
|
+
|
|
576
|
+
//#endregion
|
|
577
|
+
export { };
|