exisjs 0.9.1 → 0.10.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (171) hide show
  1. package/README.md +3 -0
  2. package/bin/exis.js +76 -1
  3. package/dist/config/config.d.ts.map +1 -1
  4. package/dist/config/config.js +2 -1
  5. package/dist/config/config.js.map +1 -1
  6. package/dist/config/types.d.ts +16 -2
  7. package/dist/config/types.d.ts.map +1 -1
  8. package/dist/docs/content/README.md +1 -0
  9. package/dist/docs/content/cli/commands.mdx +23 -1
  10. package/dist/docs/content/cli/usage.mdx +2 -1
  11. package/dist/docs/content/config.mdx +50 -4
  12. package/dist/docs/content/deployment.mdx +163 -0
  13. package/dist/docs/content/installation.mdx +1 -1
  14. package/dist/docs/content/introduction.mdx +3 -0
  15. package/dist/docs/content/validation.mdx +34 -12
  16. package/dist/docs/reference/config.mdx +3 -1
  17. package/dist/docs/reference/sanitize.mdx +129 -134
  18. package/dist/docs/reference/validator.mdx +12 -3
  19. package/dist/internal.d.ts +16 -0
  20. package/dist/internal.d.ts.map +1 -0
  21. package/dist/internal.js +45 -0
  22. package/dist/internal.js.map +1 -0
  23. package/dist/{cli → lib}/resolve-aliases.d.ts.map +1 -1
  24. package/dist/{cli → lib}/resolve-aliases.js.map +1 -1
  25. package/dist/lib/start-command.d.ts +11 -0
  26. package/dist/lib/start-command.d.ts.map +1 -0
  27. package/dist/lib/start-command.js +79 -0
  28. package/dist/lib/start-command.js.map +1 -0
  29. package/dist/lib/start-server.js +4 -2
  30. package/dist/lib/start-server.js.map +1 -1
  31. package/dist/middleware/backpressure.d.ts +5 -0
  32. package/dist/middleware/backpressure.d.ts.map +1 -1
  33. package/dist/middleware/backpressure.js +66 -36
  34. package/dist/middleware/backpressure.js.map +1 -1
  35. package/dist/router/route-scanner.d.ts +12 -4
  36. package/dist/router/route-scanner.d.ts.map +1 -1
  37. package/dist/router/route-scanner.js +65 -51
  38. package/dist/router/route-scanner.js.map +1 -1
  39. package/dist/sanitize/index.d.ts +29 -3
  40. package/dist/sanitize/index.d.ts.map +1 -1
  41. package/dist/sanitize/index.js +91 -32
  42. package/dist/sanitize/index.js.map +1 -1
  43. package/dist/server/adapters/bun-adapter.d.ts +1 -1
  44. package/dist/server/adapters/bun-adapter.d.ts.map +1 -1
  45. package/dist/server/adapters/bun-adapter.js.map +1 -1
  46. package/dist/server/app.d.ts.map +1 -1
  47. package/dist/server/app.js +24 -1
  48. package/dist/server/app.js.map +1 -1
  49. package/dist/server/bootstrapper.d.ts.map +1 -1
  50. package/dist/server/bootstrapper.js +4 -2
  51. package/dist/server/bootstrapper.js.map +1 -1
  52. package/dist/server/cluster.d.ts.map +1 -1
  53. package/dist/server/cluster.js +3 -0
  54. package/dist/server/cluster.js.map +1 -1
  55. package/dist/server/metrics.d.ts +43 -0
  56. package/dist/server/metrics.d.ts.map +1 -0
  57. package/dist/server/metrics.js +254 -0
  58. package/dist/server/metrics.js.map +1 -0
  59. package/dist/utils/logger.d.ts.map +1 -1
  60. package/dist/utils/logger.js +31 -3
  61. package/dist/utils/logger.js.map +1 -1
  62. package/dist/validator/builder.d.ts +32 -17
  63. package/dist/validator/builder.d.ts.map +1 -1
  64. package/dist/validator/builder.js +84 -25
  65. package/dist/validator/builder.js.map +1 -1
  66. package/dist/validator/compile.d.ts +7 -2
  67. package/dist/validator/compile.d.ts.map +1 -1
  68. package/dist/validator/compile.js +85 -54
  69. package/dist/validator/compile.js.map +1 -1
  70. package/dist/validator/engine.d.ts +58 -7
  71. package/dist/validator/engine.d.ts.map +1 -1
  72. package/dist/validator/engine.js +436 -522
  73. package/dist/validator/engine.js.map +1 -1
  74. package/dist/validator/error.d.ts.map +1 -1
  75. package/dist/validator/error.js +7 -0
  76. package/dist/validator/error.js.map +1 -1
  77. package/dist/validator/index.d.ts +4 -1
  78. package/dist/validator/index.d.ts.map +1 -1
  79. package/dist/validator/index.js +5 -1
  80. package/dist/validator/index.js.map +1 -1
  81. package/dist/validator/rules.d.ts +66 -7
  82. package/dist/validator/rules.d.ts.map +1 -1
  83. package/dist/validator/rules.js +435 -109
  84. package/dist/validator/rules.js.map +1 -1
  85. package/dist/validator/tex-types.d.ts +15 -0
  86. package/dist/validator/tex-types.d.ts.map +1 -1
  87. package/dist/validator/tex-types.js +127 -64
  88. package/dist/validator/tex-types.js.map +1 -1
  89. package/dist/validator/tex.d.ts +1 -1
  90. package/dist/validator/tex.d.ts.map +1 -1
  91. package/dist/validator/text.d.ts +2 -0
  92. package/dist/validator/text.d.ts.map +1 -1
  93. package/dist/validator/text.js +31 -13
  94. package/dist/validator/text.js.map +1 -1
  95. package/dist/validator/types.d.ts +22 -0
  96. package/dist/validator/types.d.ts.map +1 -1
  97. package/package.json +81 -9
  98. package/dist/cli/commands/build.d.ts +0 -11
  99. package/dist/cli/commands/build.d.ts.map +0 -1
  100. package/dist/cli/commands/build.js +0 -344
  101. package/dist/cli/commands/build.js.map +0 -1
  102. package/dist/cli/commands/clean.d.ts +0 -5
  103. package/dist/cli/commands/clean.d.ts.map +0 -1
  104. package/dist/cli/commands/clean.js +0 -30
  105. package/dist/cli/commands/clean.js.map +0 -1
  106. package/dist/cli/commands/dev.d.ts +0 -21
  107. package/dist/cli/commands/dev.d.ts.map +0 -1
  108. package/dist/cli/commands/dev.js +0 -842
  109. package/dist/cli/commands/dev.js.map +0 -1
  110. package/dist/cli/commands/doctor.d.ts +0 -2
  111. package/dist/cli/commands/doctor.d.ts.map +0 -1
  112. package/dist/cli/commands/doctor.js +0 -228
  113. package/dist/cli/commands/doctor.js.map +0 -1
  114. package/dist/cli/commands/exports.d.ts +0 -2
  115. package/dist/cli/commands/exports.d.ts.map +0 -1
  116. package/dist/cli/commands/exports.js +0 -48
  117. package/dist/cli/commands/exports.js.map +0 -1
  118. package/dist/cli/commands/generate.d.ts +0 -33
  119. package/dist/cli/commands/generate.d.ts.map +0 -1
  120. package/dist/cli/commands/generate.js +0 -469
  121. package/dist/cli/commands/generate.js.map +0 -1
  122. package/dist/cli/commands/init.d.ts +0 -2
  123. package/dist/cli/commands/init.d.ts.map +0 -1
  124. package/dist/cli/commands/init.js +0 -37
  125. package/dist/cli/commands/init.js.map +0 -1
  126. package/dist/cli/commands/repl.d.ts +0 -6
  127. package/dist/cli/commands/repl.d.ts.map +0 -1
  128. package/dist/cli/commands/repl.js +0 -75
  129. package/dist/cli/commands/repl.js.map +0 -1
  130. package/dist/cli/commands/run.d.ts +0 -7
  131. package/dist/cli/commands/run.d.ts.map +0 -1
  132. package/dist/cli/commands/run.js +0 -101
  133. package/dist/cli/commands/run.js.map +0 -1
  134. package/dist/cli/commands/start.d.ts +0 -8
  135. package/dist/cli/commands/start.d.ts.map +0 -1
  136. package/dist/cli/commands/start.js +0 -155
  137. package/dist/cli/commands/start.js.map +0 -1
  138. package/dist/cli/commands/test.d.ts +0 -8
  139. package/dist/cli/commands/test.d.ts.map +0 -1
  140. package/dist/cli/commands/test.js +0 -165
  141. package/dist/cli/commands/test.js.map +0 -1
  142. package/dist/cli/index.d.ts +0 -3
  143. package/dist/cli/index.d.ts.map +0 -1
  144. package/dist/cli/index.js +0 -268
  145. package/dist/cli/index.js.map +0 -1
  146. package/dist/cli/manifest.d.ts +0 -4
  147. package/dist/cli/manifest.d.ts.map +0 -1
  148. package/dist/cli/manifest.js +0 -489
  149. package/dist/cli/manifest.js.map +0 -1
  150. package/dist/cli/routes.d.ts +0 -17
  151. package/dist/cli/routes.d.ts.map +0 -1
  152. package/dist/cli/routes.js +0 -317
  153. package/dist/cli/routes.js.map +0 -1
  154. package/dist/cli/utils.d.ts +0 -24
  155. package/dist/cli/utils.d.ts.map +0 -1
  156. package/dist/cli/utils.js +0 -54
  157. package/dist/cli/utils.js.map +0 -1
  158. package/dist/lib/inspect-routes.d.ts +0 -2
  159. package/dist/lib/inspect-routes.d.ts.map +0 -1
  160. package/dist/lib/inspect-routes.js +0 -150
  161. package/dist/lib/inspect-routes.js.map +0 -1
  162. package/dist/lib/start-repl.d.ts +0 -2
  163. package/dist/lib/start-repl.d.ts.map +0 -1
  164. package/dist/lib/start-repl.js +0 -120
  165. package/dist/lib/start-repl.js.map +0 -1
  166. package/dist/sanitize/sanitize.d.ts +0 -33
  167. package/dist/sanitize/sanitize.d.ts.map +0 -1
  168. package/dist/sanitize/sanitize.js +0 -104
  169. package/dist/sanitize/sanitize.js.map +0 -1
  170. /package/dist/{cli → lib}/resolve-aliases.d.ts +0 -0
  171. /package/dist/{cli → lib}/resolve-aliases.js +0 -0
@@ -1,842 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
- var __importDefault = (this && this.__importDefault) || function (mod) {
36
- return (mod && mod.__esModule) ? mod : { "default": mod };
37
- };
38
- Object.defineProperty(exports, "__esModule", { value: true });
39
- exports.RELOADABLE_CONFIG_FILES = exports.RELOADABLE_EXTENSIONS = exports.DEFAULT_WATCH_IGNORED = void 0;
40
- exports.shouldReloadOnFile = shouldReloadOnFile;
41
- exports.isDevLockHeld = isDevLockHeld;
42
- exports.devCommand = devCommand;
43
- const node_child_process_1 = require("node:child_process");
44
- const node_path_1 = __importDefault(require("node:path"));
45
- const node_fs_1 = __importDefault(require("node:fs"));
46
- const node_http_1 = __importDefault(require("node:http"));
47
- const node_net_1 = __importDefault(require("node:net"));
48
- const node_url_1 = require("node:url");
49
- const utils_1 = require("../utils");
50
- const time_1 = require("../../utils/time");
51
- const env_1 = require("../../config/env");
52
- const config_1 = require("../../config/config");
53
- exports.DEFAULT_WATCH_IGNORED = [
54
- // Dotfiles and dotfolders (.git, .vscode, .idea, etc.) except .env
55
- /(^|[/\\])\.(?!env)/,
56
- /(^|[/\\])node_modules([/\\]|$)/,
57
- /(^|[/\\])\.exis([/\\]|$)/,
58
- /(^|[/\\])dist([/\\]|$)/,
59
- /(^|[/\\])build([/\\]|$)/,
60
- /(^|[/\\])out([/\\]|$)/,
61
- /(^|[/\\])\.turbo([/\\]|$)/,
62
- /(^|[/\\])\.next([/\\]|$)/,
63
- /(^|[/\\])\.cache([/\\]|$)/,
64
- // Lock files
65
- /(^|[/\\])(package-lock\.json|pnpm-lock\.yaml|yarn\.lock|bun\.lockb|bun\.lock)$/i,
66
- // Tests & Coverage
67
- /(^|[/\\])(tests?|coverage|__tests__|\.nyc_output)([/\\]|$)/i,
68
- /\.(test|spec)\.[tj]sx?$/i,
69
- // Generated types
70
- /exis\.d\.ts$/,
71
- /\.d\.ts$/,
72
- // Runtime data, storage, downloads, artifacts, uploads, logs, temp dirs
73
- /(^|[/\\])(downloads|storage|uploads|tmp|temp|logs|data|fixtures|reports|artifacts|public)([/\\]|$)/i,
74
- // Non-source & runtime artifact file extensions
75
- /\.(rar|zip|7z|tar|gz|tgz|bz2|xz|iso)$/i, // compressed archives
76
- /\.(bak|tmp|temp|swp|swo|lock|pid)$/i, // temporary and lock files
77
- /\.(log|log\.\d+|sqlite|sqlite3|db|db-shm|db-wal|db-journal)$/i, // logs and databases
78
- /\.(png|jpe?g|gif|svg|ico|webp|avif|mp4|webm|mov|mp3|wav|ogg)$/i, // media files
79
- /\.(pdf|docx?|xlsx?|pptx?|csv|tsv)$/i, // documents & spreadsheets
80
- /\.(md|mdx|txt|rst|adoc)$/i, // markdown & plain text documentation
81
- ];
82
- exports.RELOADABLE_EXTENSIONS = new Set([
83
- '.ts',
84
- '.tsx',
85
- '.js',
86
- '.jsx',
87
- '.mjs',
88
- '.cjs',
89
- ]);
90
- exports.RELOADABLE_CONFIG_FILES = new Set([
91
- 'tsconfig.json',
92
- 'package.json',
93
- 'exis.config.ts',
94
- 'exis.config.js',
95
- 'exis.config.mjs',
96
- 'exis.config.cjs',
97
- ]);
98
- function shouldReloadOnFile(file, userIgnored = [], customExtensions) {
99
- if (!file)
100
- return false;
101
- const normalized = file.replace(/\\/g, '/');
102
- const baseName = node_path_1.default.basename(normalized);
103
- const ext = node_path_1.default.extname(normalized).toLowerCase();
104
- // 1. Check user custom ignores
105
- for (const pattern of userIgnored) {
106
- if (typeof pattern === 'string') {
107
- if (normalized.includes(pattern) || baseName === pattern)
108
- return false;
109
- }
110
- else if (pattern instanceof RegExp && pattern.test(normalized)) {
111
- return false;
112
- }
113
- }
114
- // 2. Check default ignored patterns
115
- for (const regex of exports.DEFAULT_WATCH_IGNORED) {
116
- if (typeof regex !== 'string' && regex.test(normalized))
117
- return false;
118
- }
119
- // 3. Environment files (.env, .env.local, .env.development, etc.)
120
- if (baseName === '.env' || baseName.startsWith('.env.')) {
121
- return true;
122
- }
123
- // 4. Reloadable config files
124
- if (exports.RELOADABLE_CONFIG_FILES.has(baseName)) {
125
- return true;
126
- }
127
- // 5. Custom allowed extensions if specified by user
128
- if (customExtensions && customExtensions.length > 0) {
129
- const extSet = new Set(customExtensions.map((e) => e.startsWith('.') ? e.toLowerCase() : `.${e.toLowerCase()}`));
130
- return extSet.has(ext);
131
- }
132
- // 6. Source file extensions
133
- if (exports.RELOADABLE_EXTENSIONS.has(ext)) {
134
- // Avoid re-triggering for declaration files like index.d.ts
135
- if (normalized.endsWith('.d.ts'))
136
- return false;
137
- return true;
138
- }
139
- // Any other file (e.g. .csv, .xlsx, .md, .txt, .json data file) is ignored
140
- return false;
141
- }
142
- /** True when something accepts connections on the port. */
143
- function isPortInUse(port) {
144
- return new Promise((resolve) => {
145
- const socket = node_net_1.default.connect({ port, host: '127.0.0.1' });
146
- socket.setTimeout(500);
147
- socket.once('connect', () => {
148
- socket.destroy();
149
- resolve(true);
150
- });
151
- socket.once('timeout', () => {
152
- socket.destroy();
153
- resolve(false);
154
- });
155
- socket.once('error', () => resolve(false));
156
- });
157
- }
158
- /**
159
- * Another `exisjs dev` holds this lock when its PID is alive AND it still
160
- * serves the port it recorded. A PID alone is not enough: after a crash the
161
- * OS may hand the same number to an unrelated process.
162
- */
163
- async function isDevLockHeld(lockFile) {
164
- let lock;
165
- try {
166
- lock = JSON.parse(node_fs_1.default.readFileSync(lockFile, 'utf-8'));
167
- }
168
- catch {
169
- return false;
170
- }
171
- if (!lock.pid || lock.pid === process.pid || !lock.port)
172
- return false;
173
- try {
174
- process.kill(lock.pid, 0);
175
- }
176
- catch {
177
- return false;
178
- }
179
- return isPortInUse(lock.port);
180
- }
181
- async function devCommand(options = {}) {
182
- const cwd = process.cwd();
183
- let startServerPath = '';
184
- try {
185
- startServerPath = require.resolve('../../lib/start-server');
186
- }
187
- catch {
188
- startServerPath = require.resolve('../../lib/start-server.ts');
189
- }
190
- // Detect entry file
191
- const entryFile = resolveEntry(cwd, options.entry);
192
- if (!entryFile) {
193
- (0, utils_1.error)('Could not find entry file. Expected src/http/server.ts or http/server.ts');
194
- (0, utils_1.error)('Run this command from your Exis JS project root.');
195
- process.exit(1);
196
- }
197
- const exisDir = node_path_1.default.join(cwd, '.exis');
198
- // 2. Load .env files early so process.env.PORT is available
199
- (0, env_1.loadEnv)(cwd);
200
- // 3. Read exis.config.ts to pick up user-defined port
201
- const userConfig = await (0, config_1.loadConfig)(cwd);
202
- // 4. Automatic Port Finding — priority: CLI flag > env > config > default(4000)
203
- const startPort = parseInt(options.port || process.env.PORT || String(userConfig.port) || '4000', 10);
204
- function findAvailablePort(port) {
205
- return new Promise((resolve) => {
206
- const server = node_net_1.default.createServer();
207
- server.listen(port, () => {
208
- server.once('close', () => resolve(port));
209
- server.close();
210
- });
211
- server.on('error', () => {
212
- resolve(findAvailablePort(port + 1));
213
- });
214
- });
215
- }
216
- // 1. One dev server per port: a second instance on another port (for
217
- // example pointed at a test database) is allowed
218
- const pidFile = node_path_1.default.join(exisDir, `dev-${startPort}.pid`);
219
- if (!options.force && (await isDevLockHeld(pidFile))) {
220
- console.error(`\n\x1b[31m[ExisJS] A development server for port ${startPort} is already running in another terminal.\x1b[0m\n` +
221
- 'Stop it, start this one on another port (--port), or pass --force.');
222
- process.exit(1);
223
- }
224
- const actualPort = await findAvailablePort(startPort);
225
- if (!node_fs_1.default.existsSync(exisDir))
226
- node_fs_1.default.mkdirSync(exisDir, { recursive: true });
227
- node_fs_1.default.writeFileSync(pidFile, JSON.stringify({ pid: process.pid, port: actualPort }));
228
- // Check for tsconfig (only warn for TypeScript projects)
229
- const tsconfigPath = node_path_1.default.join(cwd, 'tsconfig.json');
230
- if (!node_fs_1.default.existsSync(tsconfigPath) && entryFile.endsWith('.ts')) {
231
- (0, utils_1.warn)('No tsconfig.json found — using default TypeScript settings.');
232
- }
233
- process.env.PORT = String(actualPort);
234
- if (options.host) {
235
- process.env.HOST = options.host;
236
- }
237
- // Built for every start of the app process, so edits to .env files apply
238
- // on the next reload instead of only after restarting `exisjs dev`
239
- const buildChildEnv = () => {
240
- (0, env_1.loadEnv)(cwd, undefined, true);
241
- process.env.PORT = String(actualPort);
242
- if (options.host)
243
- process.env.HOST = options.host;
244
- return {
245
- ...process.env,
246
- NODE_ENV: 'development',
247
- FORCE_COLOR: '1',
248
- };
249
- };
250
- let version = '0.0.0';
251
- try {
252
- const pkgPath = node_path_1.default.join(__dirname, '..', '..', '..', 'package.json');
253
- version = JSON.parse(node_fs_1.default.readFileSync(pkgPath, 'utf-8')).version;
254
- }
255
- catch {
256
- /* ignore */
257
- }
258
- const primary = '\x1b[38;2;160;70;255m';
259
- const time = (0, time_1.getFormattedTime)();
260
- console.log(`\n${utils_1.c.dim}${time}${utils_1.c.reset} ${primary}[exis]${utils_1.c.reset} ${utils_1.c.dim}starting development server (v${version})...${utils_1.c.reset}`);
261
- // Hardcode Esbuild (tsx) as the Native HMR runner
262
- let tsxArgs = [require.resolve('tsx/cli')];
263
- try {
264
- const resolved = require.resolve('tsx', { paths: [cwd, __dirname] });
265
- tsxArgs = ['--import', (0, node_url_1.pathToFileURL)(resolved).href];
266
- }
267
- catch {
268
- /* fallback to tsx/cli if resolve fails */
269
- }
270
- const runner = {
271
- name: 'tsx',
272
- bin: process.execPath,
273
- args: tsxArgs,
274
- needsManualWatch: true,
275
- };
276
- let child = null;
277
- let isShuttingDown = false;
278
- let watcher = null;
279
- function cleanupPid() {
280
- try {
281
- if (node_fs_1.default.existsSync(pidFile)) {
282
- node_fs_1.default.unlinkSync(pidFile);
283
- }
284
- }
285
- catch {
286
- /* ignore */
287
- }
288
- }
289
- let forceExitTriggered = false;
290
- async function handleSessionStop(_signal) {
291
- if (isShuttingDown) {
292
- if (!forceExitTriggered) {
293
- forceExitTriggered = true;
294
- cleanupPid();
295
- if (child && child.pid) {
296
- try {
297
- if (process.platform === 'win32') {
298
- (0, node_child_process_1.spawnSync)('taskkill', ['/pid', String(child.pid), '/f', '/t'], {
299
- stdio: 'ignore',
300
- });
301
- }
302
- else {
303
- child.kill('SIGKILL');
304
- }
305
- }
306
- catch {
307
- /* ignore */
308
- }
309
- }
310
- process.exit(0);
311
- }
312
- return;
313
- }
314
- isShuttingDown = true;
315
- cleanupPid();
316
- const time = (0, time_1.getFormattedTime)();
317
- const primary = '\x1b[38;2;160;70;255m';
318
- console.log(`\n${utils_1.c.dim}${time}${utils_1.c.reset} ${primary}[exis]${utils_1.c.reset} ${utils_1.c.dim}gracefully shutting down server...${utils_1.c.reset}`);
319
- if (watcher) {
320
- try {
321
- watcher.close();
322
- }
323
- catch {
324
- /* ignore */
325
- }
326
- watcher = null;
327
- }
328
- if (global._tscProcess) {
329
- try {
330
- ;
331
- global._tscProcess.kill();
332
- }
333
- catch {
334
- /* ignore */
335
- }
336
- }
337
- if (child &&
338
- child.pid &&
339
- child.exitCode === null &&
340
- child.signalCode === null) {
341
- try {
342
- if (child.connected) {
343
- child.send({ type: 'exis:shutdown' });
344
- child.disconnect();
345
- }
346
- }
347
- catch {
348
- /* ignore */
349
- }
350
- try {
351
- if (process.platform !== 'win32') {
352
- child.kill('SIGTERM');
353
- }
354
- }
355
- catch {
356
- /* ignore */
357
- }
358
- const exitTimeout = setTimeout(() => {
359
- try {
360
- if (child && !child.killed) {
361
- if (process.platform === 'win32') {
362
- (0, node_child_process_1.spawnSync)('taskkill', ['/pid', String(child.pid), '/f', '/t'], {
363
- stdio: 'ignore',
364
- });
365
- }
366
- else {
367
- child.kill('SIGKILL');
368
- }
369
- }
370
- }
371
- catch {
372
- /* ignore */
373
- }
374
- process.exit(0);
375
- }, 2000);
376
- const onChildDone = () => {
377
- clearTimeout(exitTimeout);
378
- process.exit(0);
379
- };
380
- child.once('close', onChildDone);
381
- child.once('exit', onChildDone);
382
- }
383
- else {
384
- process.exit(0);
385
- }
386
- }
387
- process.on('SIGINT', () => handleSessionStop('SIGINT'));
388
- process.on('SIGTERM', () => handleSessionStop('SIGTERM'));
389
- let fallbackServer = null;
390
- function closeFallbackServer() {
391
- if (fallbackServer) {
392
- try {
393
- fallbackServer.close();
394
- }
395
- catch {
396
- /* ignore */
397
- }
398
- fallbackServer = null;
399
- }
400
- }
401
- let configuredPort = null;
402
- try {
403
- const configTs = node_path_1.default.join(cwd, 'exis.config.ts');
404
- const configJs = node_path_1.default.join(cwd, 'exis.config.js');
405
- const configFile = node_fs_1.default.existsSync(configTs)
406
- ? configTs
407
- : node_fs_1.default.existsSync(configJs)
408
- ? configJs
409
- : null;
410
- if (configFile) {
411
- const content = node_fs_1.default.readFileSync(configFile, 'utf8');
412
- const match = content.match(/\bport\s*:\s*(\d+)/);
413
- if (match) {
414
- configuredPort = parseInt(match[1], 10);
415
- }
416
- }
417
- }
418
- catch {
419
- /* ignore */
420
- }
421
- function startFallbackServer(errorMessage, customPort) {
422
- closeFallbackServer();
423
- const port = customPort ??
424
- (process.env.PORT ? parseInt(process.env.PORT, 10) : configuredPort) ??
425
- 4000;
426
- // ANSI colour codes belong to the terminal, not to a response
427
- const plainMessage = errorMessage.replace(new RegExp(String.fromCharCode(27) + '\\[[0-9;]*[A-Za-z]', 'g'), '');
428
- fallbackServer = node_http_1.default.createServer((req, res) => {
429
- // API clients, curl and health checks get the standard JSON error;
430
- // only browsers asking for HTML get the page
431
- if (!String(req.headers.accept || '').includes('text/html')) {
432
- res.writeHead(503, {
433
- 'Content-Type': 'application/json; charset=utf-8',
434
- 'Retry-After': '1',
435
- });
436
- res.end(JSON.stringify({
437
- success: false,
438
- statusCode: 503,
439
- error: {
440
- code: 'BUILD_ERROR',
441
- message: 'The app failed to start. Fix the error and save to reload.',
442
- details: plainMessage.trim().split('\n').slice(-40),
443
- },
444
- }));
445
- return;
446
- }
447
- res.writeHead(503, { 'Content-Type': 'text/html; charset=utf-8' });
448
- res.end(`
449
- <!DOCTYPE html>
450
- <html lang="en">
451
- <head>
452
- <meta charset="UTF-8">
453
- <title>Build Error | Exis</title>
454
- <style>
455
- body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; background-color: #fcebeb; color: #333; margin: 0; padding: 40px; }
456
- .container { max-width: 900px; margin: 0 auto; background: #fff; padding: 30px; border-radius: 8px; box-shadow: 0 10px 25px rgba(200, 0, 0, 0.1); border-left: 6px solid #e53e3e; }
457
- h1 { color: #e53e3e; margin-top: 0; font-size: 24px; }
458
- .code-block { background: #1e1e1e; color: #fc8181; padding: 20px; border-radius: 6px; overflow-x: auto; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; font-size: 14px; line-height: 1.5; white-space: pre-wrap; }
459
- </style>
460
- </head>
461
- <body>
462
- <div class="container">
463
- <h1>Build/Syntax Error</h1>
464
- <div class="code-block">${plainMessage.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')}</div>
465
- </div>
466
- </body>
467
- </html>
468
- `);
469
- });
470
- // Same interface as the app itself, never wider
471
- const host = options.host || process.env.HOST || userConfig.host || 'localhost';
472
- fallbackServer
473
- .listen(port, host, () => {
474
- console.log(`\n${utils_1.c.red}[exis] Serving build error on port ${port}${utils_1.c.reset}`);
475
- })
476
- .on('error', () => {
477
- // Port might still be in use by zombie process, just ignore
478
- });
479
- }
480
- let isRestarting = false;
481
- let pendingRestart = false;
482
- let reloadDebounceTimer = null;
483
- const queuedChangedFiles = new Set();
484
- const watchConfig = userConfig.watch || {};
485
- const devConfig = userConfig.dev || {};
486
- const userIgnored = [
487
- ...(watchConfig.ignore || []),
488
- ...(devConfig.ignored || []),
489
- ];
490
- const customExtensions = watchConfig.extensions || devConfig.extensions;
491
- const RELOAD_DEBOUNCE_MS = watchConfig.debounceMs ?? devConfig.debounceMs ?? 250;
492
- const RELOAD_TIMEOUT_MS = 3000;
493
- async function killChildGracefully() {
494
- if (!child || child.killed)
495
- return;
496
- // 1. Send IPC shutdown message so the child can run app.close() / onClose hooks
497
- try {
498
- if (child.connected) {
499
- child.send({ type: 'exis:shutdown' });
500
- }
501
- }
502
- catch {
503
- /* ignore — child may have already exited */
504
- }
505
- // 2. Disconnect the IPC channel IMMEDIATELY after sending the shutdown message.
506
- // On Windows, this is CRITICAL: it closes the parent's end of the IPC pipe
507
- // before the child starts tearing down its libuv handles, preventing the
508
- // UV_HANDLE_CLOSING assertion crash in src\win\async.c.
509
- try {
510
- if (child.connected) {
511
- child.disconnect();
512
- }
513
- }
514
- catch {
515
- /* ignore — channel may already be closed */
516
- }
517
- // 3. On non-Windows, also send SIGTERM as a direct signal
518
- try {
519
- if (process.platform !== 'win32') {
520
- child.kill('SIGTERM');
521
- }
522
- }
523
- catch {
524
- /* ignore */
525
- }
526
- if (child) {
527
- ;
528
- child._isIntentionalKill = true;
529
- }
530
- // 4. Wait for clean exit with a generous graceful timeout (3000ms).
531
- // Child will usually exit in ~50-200ms once onClose finishes.
532
- await new Promise((resolve) => {
533
- let resolved = false;
534
- const onExitOrClose = () => {
535
- if (resolved)
536
- return;
537
- resolved = true;
538
- clearTimeout(timer);
539
- resolve();
540
- };
541
- const timer = setTimeout(() => {
542
- if (resolved)
543
- return;
544
- resolved = true;
545
- // Timeout expired — force kill the child process tree
546
- if (child && !child.killed) {
547
- try {
548
- if (process.platform === 'win32') {
549
- (0, node_child_process_1.spawnSync)('taskkill', ['/pid', String(child.pid), '/f', '/t'], {
550
- stdio: 'ignore',
551
- });
552
- }
553
- else {
554
- child.kill('SIGKILL');
555
- }
556
- }
557
- catch {
558
- /* ignore */
559
- }
560
- }
561
- resolve();
562
- }, RELOAD_TIMEOUT_MS);
563
- child.once('close', onExitOrClose);
564
- child.once('exit', onExitOrClose);
565
- });
566
- }
567
- async function startProcess() {
568
- if (isRestarting) {
569
- pendingRestart = true;
570
- return;
571
- }
572
- isRestarting = true;
573
- closeFallbackServer();
574
- await killChildGracefully();
575
- child = (0, node_child_process_1.spawn)(runner.bin, [...runner.args, startServerPath], {
576
- cwd,
577
- env: {
578
- ...buildChildEnv(),
579
- __EXIS_DEV_SERVER: '1',
580
- __EXIS_IS_RESTART: global._hasStartedBefore ? '1' : '',
581
- EXIS_ENTRY_FILE: entryFile,
582
- },
583
- stdio: ['inherit', 'inherit', 'pipe', 'ipc'],
584
- shell: runner.bin === process.execPath ? false : true,
585
- });
586
- global._hasStartedBefore = true;
587
- let stderrBuffer = '';
588
- const currentSpawnedChild = child;
589
- child.stderr?.on('data', (chunk) => {
590
- const str = chunk.toString();
591
- process.stderr.write(chunk);
592
- stderrBuffer += str;
593
- // Keep only last 10000 chars to avoid memory leak
594
- if (stderrBuffer.length > 10000) {
595
- stderrBuffer = stderrBuffer.substring(stderrBuffer.length - 10000);
596
- }
597
- });
598
- child.on('error', (err) => {
599
- (0, utils_1.error)(`Failed to start process: ${err.message}`);
600
- });
601
- child.on('exit', (code, signal) => {
602
- if (currentSpawnedChild._isIntentionalKill) {
603
- return;
604
- }
605
- if (!isShuttingDown &&
606
- signal !== 'SIGTERM' &&
607
- code !== 0 &&
608
- code !== null) {
609
- if (code === 128 + 15 || code === 143) {
610
- // Normal sigterm exits
611
- return;
612
- }
613
- (0, utils_1.error)(`Process crashed with code ${code}`);
614
- // Start the fallback server so the browser doesn't hang or get "Connection Refused"
615
- startFallbackServer(stderrBuffer || 'Process crashed with no error output.');
616
- }
617
- });
618
- isRestarting = false;
619
- // If more file changes arrived while we were restarting, trigger consolidated reload
620
- if (pendingRestart) {
621
- pendingRestart = false;
622
- startProcess();
623
- }
624
- }
625
- function scheduleReload(file) {
626
- if (file) {
627
- queuedChangedFiles.add(file);
628
- }
629
- if (reloadDebounceTimer) {
630
- clearTimeout(reloadDebounceTimer);
631
- }
632
- reloadDebounceTimer = setTimeout(async () => {
633
- reloadDebounceTimer = null;
634
- const files = Array.from(queuedChangedFiles);
635
- queuedChangedFiles.clear();
636
- const time = (0, time_1.getFormattedTime)();
637
- const primary = '\x1b[38;2;160;70;255m';
638
- const fileLabel = files.length === 1
639
- ? files[0]
640
- : files.length > 1
641
- ? `${files[0]} (+${files.length - 1} other files)`
642
- : 'file changes';
643
- console.log(`${utils_1.c.dim}${time}${utils_1.c.reset} ${primary}[exis]${utils_1.c.reset} ${utils_1.c.dim}reloading due to change in ${fileLabel}${utils_1.c.reset}`);
644
- await generateManifest(cwd, '', true);
645
- startProcess();
646
- }, RELOAD_DEBOUNCE_MS);
647
- }
648
- // Handle restarts for runners that don't support watch natively
649
- if (runner.needsManualWatch && !options._disableWatch) {
650
- const chokidar = await importChokidar();
651
- if (chokidar) {
652
- const configuredWatchPaths = watchConfig.paths || devConfig.watch || [];
653
- let watchPaths;
654
- if (configuredWatchPaths.length > 0) {
655
- watchPaths = configuredWatchPaths;
656
- }
657
- else {
658
- const srcExists = node_fs_1.default.existsSync(node_path_1.default.join(cwd, 'src'));
659
- const httpExists = node_fs_1.default.existsSync(node_path_1.default.join(cwd, 'http'));
660
- if (srcExists || httpExists) {
661
- const candidates = [
662
- srcExists ? 'src' : '',
663
- httpExists ? 'http' : '',
664
- ].filter(Boolean);
665
- watchPaths = [
666
- ...candidates.map((d) => node_path_1.default.join(cwd, d)),
667
- ...[
668
- 'exis.config.ts',
669
- 'exis.config.js',
670
- 'exis.config.mjs',
671
- 'exis.config.cjs',
672
- 'tsconfig.json',
673
- 'package.json',
674
- ]
675
- .map((f) => node_path_1.default.join(cwd, f))
676
- .filter((f) => node_fs_1.default.existsSync(f)),
677
- // Watched even before they exist, so creating one reloads too
678
- ...[
679
- '.env',
680
- '.env.local',
681
- '.env.development',
682
- '.env.development.local',
683
- ].map((f) => node_path_1.default.join(cwd, f)),
684
- ];
685
- }
686
- else {
687
- watchPaths = [cwd];
688
- }
689
- }
690
- watcher = chokidar.watch(watchPaths, {
691
- cwd,
692
- ignoreInitial: true,
693
- usePolling: devConfig.usePolling ?? process.platform === 'win32',
694
- interval: devConfig.interval ?? 100,
695
- binaryInterval: devConfig.binaryInterval ?? 300,
696
- ignored: [...exports.DEFAULT_WATCH_IGNORED, ...userIgnored],
697
- awaitWriteFinish: {
698
- stabilityThreshold: 200,
699
- pollInterval: 100,
700
- },
701
- });
702
- watcher.on('error', (err) => {
703
- // Suppress non-fatal Windows file lock / permission errors (EBUSY / EPERM)
704
- if (err?.code === 'EBUSY' ||
705
- err?.code === 'EPERM' ||
706
- err?.code === 'UNKNOWN') {
707
- return;
708
- }
709
- console.warn(`\x1b[33m[exis watcher notice]\x1b[0m ${err?.message || err}`);
710
- });
711
- watcher.on('all', async (_eventName, file) => {
712
- if (!shouldReloadOnFile(file, userIgnored, customExtensions)) {
713
- return;
714
- }
715
- scheduleReload(file);
716
- });
717
- }
718
- }
719
- // Generate dev manifest for O(1) booting
720
- const { generateManifest } = await Promise.resolve().then(() => __importStar(require('../manifest')));
721
- await generateManifest(cwd, '', true);
722
- if (node_fs_1.default.existsSync(tsconfigPath)) {
723
- let tscPath;
724
- try {
725
- const req = typeof require !== 'undefined' ? require : eval('require');
726
- tscPath = req.resolve('typescript/bin/tsc', { paths: [cwd] });
727
- }
728
- catch {
729
- (0, utils_1.error)('TypeScript compiler not found in project.');
730
- (0, utils_1.error)('Install it: npm install -D typescript');
731
- process.exit(1);
732
- }
733
- const tscProcess = (0, node_child_process_1.spawn)(process.execPath, [tscPath, '--noEmit', '--watch', '--preserveWatchOutput'], { cwd, env: process.env });
734
- global._tscProcess = tscProcess;
735
- let tscErrors = [];
736
- tscProcess.stdout?.on('data', (data) => {
737
- const lines = data.toString().trim().split('\n');
738
- const getTime = () => {
739
- return (0, time_1.getFormattedTime)();
740
- };
741
- for (const line of lines) {
742
- if (!line)
743
- continue;
744
- if (line.includes('Found 0 errors')) {
745
- tscErrors = [];
746
- // Type errors are cleared — close the fallback so the real server can serve
747
- closeFallbackServer();
748
- }
749
- else if (line.includes('error TS')) {
750
- const time = getTime();
751
- console.error(`\n${utils_1.c.red}${time} ERROR:${utils_1.c.reset} ${line}`);
752
- tscErrors.push(line);
753
- }
754
- else if (line.includes('Found') && line.includes('error')) {
755
- // "Found X errors." summary line
756
- const time = getTime();
757
- console.error(`\n${utils_1.c.yellow}${time} INFO:${utils_1.c.reset} ${line}`);
758
- if (tscErrors.length > 0) {
759
- startFallbackServer(`TypeScript Compilation Errors\n${'─'.repeat(50)}\n\n` +
760
- tscErrors.join('\n'));
761
- }
762
- }
763
- else if (!line.includes('Starting compilation in watch mode') &&
764
- !line.includes('File change detected')) {
765
- const time = getTime();
766
- console.error(`${utils_1.c.yellow}${time} INFO:${utils_1.c.reset} ${line}`);
767
- }
768
- }
769
- });
770
- }
771
- startProcess();
772
- // CLI Shortcuts
773
- if (process.stdin.isTTY) {
774
- process.stdin.setEncoding('utf8');
775
- process.stdin.on('data', (data) => {
776
- const key = data.toString().trim().toLowerCase();
777
- if (key === 'h') {
778
- console.log(`
779
- ${utils_1.c.bold}Shortcuts${utils_1.c.reset}
780
- ${utils_1.c.dim}press${utils_1.c.reset} ${utils_1.c.bold}r + enter${utils_1.c.reset} ${utils_1.c.dim}to restart the server${utils_1.c.reset}
781
- ${utils_1.c.dim}press${utils_1.c.reset} ${utils_1.c.bold}u + enter${utils_1.c.reset} ${utils_1.c.dim}to show server url${utils_1.c.reset}
782
- ${utils_1.c.dim}press${utils_1.c.reset} ${utils_1.c.bold}c + enter${utils_1.c.reset} ${utils_1.c.dim}to clear console${utils_1.c.reset}
783
- ${utils_1.c.dim}press${utils_1.c.reset} ${utils_1.c.bold}q + enter${utils_1.c.reset} ${utils_1.c.dim}to quit${utils_1.c.reset}
784
- `);
785
- }
786
- else if (key === 'r') {
787
- const time = (0, time_1.getFormattedTime)();
788
- console.log(`\n${utils_1.c.dim}${time}${utils_1.c.reset} ${primary}[exis]${utils_1.c.reset} ${utils_1.c.dim}restarting server...${utils_1.c.reset}`);
789
- startProcess();
790
- }
791
- else if (key === 'c') {
792
- console.clear();
793
- }
794
- else if (key === 'q') {
795
- handleSessionStop('SIGINT');
796
- }
797
- else if (key === 'u') {
798
- // Just trigger a dummy reload so app.ts prints the URL again
799
- // Or send an IPC message, but since child.kill('SIGUSR2') isn't set up, we can just restart it
800
- startProcess();
801
- }
802
- });
803
- process.stdin.unref();
804
- }
805
- }
806
- // ─── Helpers ──────────────────────────────────────────────────────────────────
807
- function resolveEntry(cwd, custom) {
808
- if (custom) {
809
- const abs = node_path_1.default.resolve(cwd, custom);
810
- return node_fs_1.default.existsSync(abs) ? abs : null;
811
- }
812
- const srcTs = node_path_1.default.join(cwd, 'src/http/server.ts');
813
- const srcJs = node_path_1.default.join(cwd, 'src/http/server.js');
814
- const rootTs = node_path_1.default.join(cwd, 'http/server.ts');
815
- const rootJs = node_path_1.default.join(cwd, 'http/server.js');
816
- const hasSrc = node_fs_1.default.existsSync(srcTs) || node_fs_1.default.existsSync(srcJs);
817
- const hasRoot = node_fs_1.default.existsSync(rootTs) || node_fs_1.default.existsSync(rootJs);
818
- if (hasSrc && hasRoot) {
819
- console.error('\n\x1b[31m Ambiguous Entry Point Error\x1b[0m\n' +
820
- 'You have server entry files in both "src/http" and "http" directories.\n' +
821
- 'Please keep only one of them to prevent unexpected behavior.\n');
822
- process.exit(1);
823
- }
824
- if (node_fs_1.default.existsSync(srcTs))
825
- return srcTs;
826
- if (node_fs_1.default.existsSync(srcJs))
827
- return srcJs;
828
- if (node_fs_1.default.existsSync(rootTs))
829
- return rootTs;
830
- if (node_fs_1.default.existsSync(rootJs))
831
- return rootJs;
832
- return null;
833
- }
834
- async function importChokidar() {
835
- try {
836
- return await Promise.resolve().then(() => __importStar(require('chokidar')));
837
- }
838
- catch {
839
- return null;
840
- }
841
- }
842
- //# sourceMappingURL=dev.js.map