@orieljs/http 0.1.0-alpha.5 → 0.1.0-alpha.7
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/NOTICE +7 -0
- package/README.md +33 -0
- package/TRADEMARKS.md +33 -0
- package/dist/index.d.ts +58 -16
- package/dist/index.js +330 -17
- package/dist/index.js.map +1 -1
- package/package.json +6 -5
package/dist/index.js
CHANGED
|
@@ -352,6 +352,7 @@ var FormUploadedFile = class _FormUploadedFile {
|
|
|
352
352
|
|
|
353
353
|
// IncomingRequest.ts
|
|
354
354
|
import {
|
|
355
|
+
AuthFactory,
|
|
355
356
|
CurrentRoute,
|
|
356
357
|
Session as Session2,
|
|
357
358
|
ValidationFactory
|
|
@@ -685,9 +686,14 @@ var IncomingRequest = class {
|
|
|
685
686
|
filled(key) {
|
|
686
687
|
return !isBlank(Arr.get(this.all(), key));
|
|
687
688
|
}
|
|
689
|
+
/** Laravel's string(): the input as a Stringable (the default when it is missing or null): request().string('q').trim(). */
|
|
688
690
|
string(key, defaultValue = "") {
|
|
689
691
|
const value = this.input(key);
|
|
690
|
-
return value === void 0 || value === null ? defaultValue :
|
|
692
|
+
return Str2.of(value === void 0 || value === null ? defaultValue : value);
|
|
693
|
+
}
|
|
694
|
+
/** Laravel's str(): string() under its short name. */
|
|
695
|
+
str(key, defaultValue = "") {
|
|
696
|
+
return this.string(key, defaultValue);
|
|
691
697
|
}
|
|
692
698
|
/** PHP's FILTER_VALIDATE_BOOLEAN: '1', 'true', 'on', 'yes' (any case) and true are true. */
|
|
693
699
|
boolean(key, defaultValue = false) {
|
|
@@ -763,13 +769,23 @@ var IncomingRequest = class {
|
|
|
763
769
|
session() {
|
|
764
770
|
return inject3(Session2);
|
|
765
771
|
}
|
|
766
|
-
|
|
767
|
-
|
|
772
|
+
/**
|
|
773
|
+
* The signed-in user, through the resolver Auth sets, else the current scope's guard (the default, or `guard`).
|
|
774
|
+
* The guard keeps the user it found, so it is looked up once per request. Undefined for a guest or without auth.
|
|
775
|
+
*/
|
|
776
|
+
async user(guard) {
|
|
777
|
+
if (this.#userResolver !== void 0) return await this.#userResolver(guard);
|
|
778
|
+
const container = currentContainer2();
|
|
779
|
+
if (container === void 0 || !container.bound(AuthFactory)) return void 0;
|
|
780
|
+
return await container.make(AuthFactory).guard(guard).user();
|
|
768
781
|
}
|
|
769
782
|
setUserResolver(resolver) {
|
|
770
783
|
this.#userResolver = resolver;
|
|
771
784
|
}
|
|
772
|
-
/**
|
|
785
|
+
/**
|
|
786
|
+
* Validates all() through the ValidationFactory; throws its Validation error on failure. The data is typed from the
|
|
787
|
+
* rules: `validate({ title: 'required|string' })` is `{ title: string }`.
|
|
788
|
+
*/
|
|
773
789
|
validate(rules, messages, attributes) {
|
|
774
790
|
return inject3(ValidationFactory).make(this.all(), rules, messages, attributes).validated();
|
|
775
791
|
}
|
|
@@ -813,7 +829,8 @@ function url(path, query, absolute) {
|
|
|
813
829
|
const generator = inject4(UrlGenerator2);
|
|
814
830
|
return path === void 0 ? generator : generator.to(path, query, absolute);
|
|
815
831
|
}
|
|
816
|
-
function route(name,
|
|
832
|
+
function route(name, ...args) {
|
|
833
|
+
const [parameters, absolute] = args;
|
|
817
834
|
return inject4(UrlGenerator2).route(name, parameters, absolute);
|
|
818
835
|
}
|
|
819
836
|
function abort(status, message = "", headers = {}) {
|
|
@@ -1178,18 +1195,41 @@ var STATUS_TEXTS = {
|
|
|
1178
1195
|
403: "Forbidden",
|
|
1179
1196
|
404: "Not Found",
|
|
1180
1197
|
405: "Method Not Allowed",
|
|
1198
|
+
406: "Not Acceptable",
|
|
1199
|
+
407: "Proxy Authentication Required",
|
|
1181
1200
|
408: "Request Timeout",
|
|
1182
1201
|
409: "Conflict",
|
|
1183
1202
|
410: "Gone",
|
|
1203
|
+
411: "Length Required",
|
|
1204
|
+
412: "Precondition Failed",
|
|
1184
1205
|
413: "Content Too Large",
|
|
1206
|
+
414: "URI Too Long",
|
|
1185
1207
|
415: "Unsupported Media Type",
|
|
1208
|
+
416: "Range Not Satisfiable",
|
|
1209
|
+
417: "Expectation Failed",
|
|
1210
|
+
418: "I'm a teapot",
|
|
1186
1211
|
419: "Page Expired",
|
|
1212
|
+
421: "Misdirected Request",
|
|
1187
1213
|
422: "Unprocessable Content",
|
|
1214
|
+
423: "Locked",
|
|
1215
|
+
424: "Failed Dependency",
|
|
1216
|
+
425: "Too Early",
|
|
1217
|
+
426: "Upgrade Required",
|
|
1218
|
+
428: "Precondition Required",
|
|
1188
1219
|
429: "Too Many Requests",
|
|
1220
|
+
431: "Request Header Fields Too Large",
|
|
1221
|
+
451: "Unavailable For Legal Reasons",
|
|
1189
1222
|
500: "Server Error",
|
|
1223
|
+
501: "Not Implemented",
|
|
1190
1224
|
502: "Bad Gateway",
|
|
1191
1225
|
503: "Service Unavailable",
|
|
1192
|
-
504: "Gateway Timeout"
|
|
1226
|
+
504: "Gateway Timeout",
|
|
1227
|
+
505: "HTTP Version Not Supported",
|
|
1228
|
+
506: "Variant Also Negotiates",
|
|
1229
|
+
507: "Insufficient Storage",
|
|
1230
|
+
508: "Loop Detected",
|
|
1231
|
+
510: "Not Extended",
|
|
1232
|
+
511: "Network Authentication Required"
|
|
1193
1233
|
};
|
|
1194
1234
|
function statusText(status) {
|
|
1195
1235
|
return STATUS_TEXTS[status] ?? (status >= 500 ? "Server Error" : "Error");
|
|
@@ -1365,8 +1405,7 @@ var ErrorHandler = class {
|
|
|
1365
1405
|
else if (this.shouldReturnJson(http, error)) response2 = this.#renderJson(error);
|
|
1366
1406
|
else response2 = await this.#renderHtml(http, error);
|
|
1367
1407
|
const headers = error?.headers;
|
|
1368
|
-
|
|
1369
|
-
return response2;
|
|
1408
|
+
return headers === void 0 ? response2 : withHeaders(response2, headers);
|
|
1370
1409
|
}
|
|
1371
1410
|
/** An error with its own render(request) or toResponse(request) (Laravel's renderable exceptions / Responsable). */
|
|
1372
1411
|
async #renderCustom(request2, error) {
|
|
@@ -1486,6 +1525,11 @@ var ErrorHandler = class {
|
|
|
1486
1525
|
return currentContainer4() ?? this.app;
|
|
1487
1526
|
}
|
|
1488
1527
|
};
|
|
1528
|
+
function withHeaders(response2, extra) {
|
|
1529
|
+
const headers = new Headers(response2.headers);
|
|
1530
|
+
for (const [name, value] of Object.entries(extra)) headers.set(name, value);
|
|
1531
|
+
return new Response(response2.body, { status: response2.status, statusText: response2.statusText, headers });
|
|
1532
|
+
}
|
|
1489
1533
|
|
|
1490
1534
|
// ExceptionsConfiguration.ts
|
|
1491
1535
|
import { ExceptionCallbacks } from "@orieljs/contracts";
|
|
@@ -1574,9 +1618,9 @@ async function handleSafely(app, request2, publicPath) {
|
|
|
1574
1618
|
return plainServerError();
|
|
1575
1619
|
}
|
|
1576
1620
|
}
|
|
1577
|
-
function exitOnSignals(close, closed) {
|
|
1621
|
+
function exitOnSignals(close, closed, app) {
|
|
1578
1622
|
const onSignal = () => {
|
|
1579
|
-
void close().then(() => process.exit(0));
|
|
1623
|
+
void close().then(() => app.shutdown()).then(() => process.exit(0));
|
|
1580
1624
|
};
|
|
1581
1625
|
process.once("SIGINT", onSignal);
|
|
1582
1626
|
process.once("SIGTERM", onSignal);
|
|
@@ -1620,7 +1664,7 @@ async function serveBun(app, options) {
|
|
|
1620
1664
|
clearTimeout(timer);
|
|
1621
1665
|
markClosed();
|
|
1622
1666
|
})();
|
|
1623
|
-
if (options.signals) exitOnSignals(close, closed);
|
|
1667
|
+
if (options.signals) exitOnSignals(close, closed, app);
|
|
1624
1668
|
return {
|
|
1625
1669
|
runtime: "bun",
|
|
1626
1670
|
hostname: options.hostname,
|
|
@@ -1758,7 +1802,7 @@ async function serveNode(app, options) {
|
|
|
1758
1802
|
clearInterval(idle);
|
|
1759
1803
|
clearTimeout(force);
|
|
1760
1804
|
})();
|
|
1761
|
-
if (options.signals) exitOnSignals(close, closed);
|
|
1805
|
+
if (options.signals) exitOnSignals(close, closed, app);
|
|
1762
1806
|
return {
|
|
1763
1807
|
runtime: "node",
|
|
1764
1808
|
hostname: options.hostname,
|
|
@@ -1790,12 +1834,255 @@ async function serve(app, options = {}) {
|
|
|
1790
1834
|
}
|
|
1791
1835
|
|
|
1792
1836
|
// commands.ts
|
|
1793
|
-
import { existsSync } from "fs";
|
|
1837
|
+
import { existsSync as existsSync3 } from "fs";
|
|
1794
1838
|
import { mkdir, readFile as readFile2, writeFile } from "fs/promises";
|
|
1795
1839
|
import { dirname } from "path";
|
|
1796
1840
|
import {
|
|
1797
1841
|
Router as Router3
|
|
1798
1842
|
} from "@orieljs/contracts";
|
|
1843
|
+
|
|
1844
|
+
// watch/environment.ts
|
|
1845
|
+
import { existsSync, readFileSync } from "fs";
|
|
1846
|
+
import { join } from "path";
|
|
1847
|
+
import * as util from "util";
|
|
1848
|
+
var launchEnvironment = Object.freeze({ ...process.env });
|
|
1849
|
+
var BUN_ENV_FILES = [
|
|
1850
|
+
".env",
|
|
1851
|
+
".env.development",
|
|
1852
|
+
".env.production",
|
|
1853
|
+
".env.test",
|
|
1854
|
+
".env.local",
|
|
1855
|
+
".env.development.local",
|
|
1856
|
+
".env.production.local",
|
|
1857
|
+
".env.test.local"
|
|
1858
|
+
];
|
|
1859
|
+
var expand = (value, env) => value.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)\}|\$([A-Za-z_][A-Za-z0-9_]*)/g, (_match, braced, bare) => env[braced ?? bare] ?? "");
|
|
1860
|
+
function serverEnvironment(options = {}) {
|
|
1861
|
+
const launched = options.launched ?? launchEnvironment;
|
|
1862
|
+
const env = { ...launched };
|
|
1863
|
+
if (!(options.bun ?? process.versions.bun !== void 0)) return env;
|
|
1864
|
+
const parseEnv2 = "parseEnv" in options ? options.parseEnv : util.parseEnv;
|
|
1865
|
+
if (typeof parseEnv2 !== "function") return env;
|
|
1866
|
+
const cwd = options.cwd ?? process.cwd();
|
|
1867
|
+
for (const name of BUN_ENV_FILES) {
|
|
1868
|
+
const file = join(cwd, name);
|
|
1869
|
+
if (!existsSync(file)) continue;
|
|
1870
|
+
let values;
|
|
1871
|
+
try {
|
|
1872
|
+
values = parseEnv2(readFileSync(file, "utf8"));
|
|
1873
|
+
} catch {
|
|
1874
|
+
continue;
|
|
1875
|
+
}
|
|
1876
|
+
for (const [key, raw] of Object.entries(values)) {
|
|
1877
|
+
const value = launched[key];
|
|
1878
|
+
if (raw !== void 0 && value !== void 0 && (value === raw || value === expand(raw, launched))) delete env[key];
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1881
|
+
return env;
|
|
1882
|
+
}
|
|
1883
|
+
|
|
1884
|
+
// watch/files.ts
|
|
1885
|
+
import { existsSync as existsSync2, statSync, watch as watchPath } from "fs";
|
|
1886
|
+
import { join as join2 } from "path";
|
|
1887
|
+
var WATCHED_FOLDERS = ["app", "bootstrap", "config", "lang", "resources/lang", "resources/views", "routes"];
|
|
1888
|
+
var WATCHED_ROOT_FILES = ["package.json", "tsconfig.json"];
|
|
1889
|
+
var IGNORED_FOLDERS = ["bootstrap/ssr", "bootstrap/cache"];
|
|
1890
|
+
var CODE_FILE = /\.(?:[cm]?[jt]sx?|json)$/;
|
|
1891
|
+
var DECLARATION_FILE = /\.d\.[cm]?ts$/;
|
|
1892
|
+
var EDITOR_FILE = /(?:\.sw[a-px]|~|___jb_(?:tmp|old)___)$/;
|
|
1893
|
+
var isEnvironmentFile = (name) => (name === ".env" || name.startsWith(".env.")) && name !== ".env.example" && !EDITOR_FILE.test(name);
|
|
1894
|
+
function isWatchedPath(path) {
|
|
1895
|
+
const segments = path.split("/");
|
|
1896
|
+
if (segments.length === 1) return isEnvironmentFile(path) || WATCHED_ROOT_FILES.includes(path);
|
|
1897
|
+
if (!WATCHED_FOLDERS.some((folder) => path.startsWith(`${folder}/`))) return false;
|
|
1898
|
+
if (IGNORED_FOLDERS.some((folder) => path === folder || path.startsWith(`${folder}/`))) return false;
|
|
1899
|
+
if (segments.some((segment) => segment.startsWith(".") || segment === "node_modules")) return false;
|
|
1900
|
+
const name = segments.at(-1);
|
|
1901
|
+
if (DECLARATION_FILE.test(name)) return false;
|
|
1902
|
+
return CODE_FILE.test(name) || !name.includes(".");
|
|
1903
|
+
}
|
|
1904
|
+
var toSlashes = (file) => file.split("\\").join("/");
|
|
1905
|
+
function watchFiles(basePath, onChange, options = {}) {
|
|
1906
|
+
const watch = options.watch ?? ((path, watchOptions, listener) => watchPath(path, watchOptions, listener));
|
|
1907
|
+
const debounce = options.debounce ?? 200;
|
|
1908
|
+
const onError = options.onError ?? (() => {
|
|
1909
|
+
});
|
|
1910
|
+
const changed = /* @__PURE__ */ new Set();
|
|
1911
|
+
let timer;
|
|
1912
|
+
const settle = options.settle ?? 1e3;
|
|
1913
|
+
const startedAt = Date.now();
|
|
1914
|
+
const reportedLate = (path) => {
|
|
1915
|
+
if (Date.now() - startedAt >= settle) return false;
|
|
1916
|
+
try {
|
|
1917
|
+
const modified = statSync(join2(basePath, path), { throwIfNoEntry: false })?.mtimeMs;
|
|
1918
|
+
return modified === void 0 || modified < startedAt + 1;
|
|
1919
|
+
} catch {
|
|
1920
|
+
return false;
|
|
1921
|
+
}
|
|
1922
|
+
};
|
|
1923
|
+
const record = (path) => {
|
|
1924
|
+
if (!isWatchedPath(path) || reportedLate(path)) return;
|
|
1925
|
+
changed.add(path);
|
|
1926
|
+
clearTimeout(timer);
|
|
1927
|
+
timer = setTimeout(() => {
|
|
1928
|
+
const files = [...changed];
|
|
1929
|
+
changed.clear();
|
|
1930
|
+
onChange(files);
|
|
1931
|
+
}, debounce);
|
|
1932
|
+
};
|
|
1933
|
+
const handles = [];
|
|
1934
|
+
const start = (folder, recursive, toPath) => {
|
|
1935
|
+
try {
|
|
1936
|
+
const handle = watch(folder === "." ? basePath : join2(basePath, folder), { recursive }, (_event, file) => {
|
|
1937
|
+
const path = toPath(file);
|
|
1938
|
+
if (path !== void 0) record(path);
|
|
1939
|
+
});
|
|
1940
|
+
handle.on("error", (error) => onError(folder, error));
|
|
1941
|
+
handles.push(handle);
|
|
1942
|
+
} catch (error) {
|
|
1943
|
+
onError(folder, error);
|
|
1944
|
+
}
|
|
1945
|
+
};
|
|
1946
|
+
start(".", false, (file) => file === null ? void 0 : toSlashes(file));
|
|
1947
|
+
for (const folder of WATCHED_FOLDERS) {
|
|
1948
|
+
if (existsSync2(join2(basePath, folder))) start(folder, true, (file) => `${folder}/${file === null ? "" : toSlashes(file)}`);
|
|
1949
|
+
}
|
|
1950
|
+
return {
|
|
1951
|
+
close() {
|
|
1952
|
+
clearTimeout(timer);
|
|
1953
|
+
changed.clear();
|
|
1954
|
+
for (const handle of handles) handle.close();
|
|
1955
|
+
}
|
|
1956
|
+
};
|
|
1957
|
+
}
|
|
1958
|
+
|
|
1959
|
+
// watch/port.ts
|
|
1960
|
+
import { createServer as createServer2 } from "net";
|
|
1961
|
+
function claimPort(hostname, port) {
|
|
1962
|
+
return new Promise((resolve2, reject) => {
|
|
1963
|
+
const server = createServer2();
|
|
1964
|
+
server.once("error", reject);
|
|
1965
|
+
try {
|
|
1966
|
+
server.listen({ port, host: hostname, exclusive: true }, () => {
|
|
1967
|
+
const { port: claimed } = server.address();
|
|
1968
|
+
server.close(() => resolve2(claimed));
|
|
1969
|
+
});
|
|
1970
|
+
} catch (error) {
|
|
1971
|
+
reject(error);
|
|
1972
|
+
}
|
|
1973
|
+
});
|
|
1974
|
+
}
|
|
1975
|
+
var REASONS = {
|
|
1976
|
+
EADDRINUSE: "Address already in use",
|
|
1977
|
+
EACCES: "Permission denied",
|
|
1978
|
+
EADDRNOTAVAIL: "Can't assign requested address"
|
|
1979
|
+
};
|
|
1980
|
+
function listenFailure(hostname, port, error) {
|
|
1981
|
+
const { code, message } = error;
|
|
1982
|
+
return `Failed to listen on ${displayHost(hostname)}:${port} (reason: ${code !== void 0 && REASONS[code] || message})`;
|
|
1983
|
+
}
|
|
1984
|
+
|
|
1985
|
+
// watch/WatchedServer.ts
|
|
1986
|
+
import { spawn as spawnProcess } from "child_process";
|
|
1987
|
+
var WatchedServer = class {
|
|
1988
|
+
constructor(options) {
|
|
1989
|
+
this.options = options;
|
|
1990
|
+
}
|
|
1991
|
+
options;
|
|
1992
|
+
running;
|
|
1993
|
+
restarting = false;
|
|
1994
|
+
stopping = false;
|
|
1995
|
+
finish = () => {
|
|
1996
|
+
};
|
|
1997
|
+
/** Starts the server and keeps it current until stopped. Resolves 0 once the last server has exited. */
|
|
1998
|
+
run() {
|
|
1999
|
+
const done = new Promise((resolve2) => this.finish = resolve2);
|
|
2000
|
+
const stopListening = this.listenForSignals();
|
|
2001
|
+
const watcher = this.options.watch((files) => void this.changed(files));
|
|
2002
|
+
this.start();
|
|
2003
|
+
return done.finally(() => {
|
|
2004
|
+
watcher.close();
|
|
2005
|
+
stopListening();
|
|
2006
|
+
});
|
|
2007
|
+
}
|
|
2008
|
+
/** Stops the server and ends run(); called again, kills the server at once. */
|
|
2009
|
+
stop() {
|
|
2010
|
+
if (this.stopping) {
|
|
2011
|
+
this.running?.child.kill("SIGKILL");
|
|
2012
|
+
return;
|
|
2013
|
+
}
|
|
2014
|
+
this.stopping = true;
|
|
2015
|
+
void this.stopServer().then(() => this.finish(0));
|
|
2016
|
+
}
|
|
2017
|
+
async changed(files) {
|
|
2018
|
+
if (this.stopping || this.restarting || files.length === 0) return;
|
|
2019
|
+
this.restarting = true;
|
|
2020
|
+
try {
|
|
2021
|
+
this.options.info(
|
|
2022
|
+
files.some(isEnvironmentFile) ? "Environment modified. Restarting server..." : `Application change detected in [${files[0]}]. Restarting server...`
|
|
2023
|
+
);
|
|
2024
|
+
await this.stopServer();
|
|
2025
|
+
if (!this.stopping) this.start();
|
|
2026
|
+
} finally {
|
|
2027
|
+
this.restarting = false;
|
|
2028
|
+
}
|
|
2029
|
+
}
|
|
2030
|
+
start() {
|
|
2031
|
+
const [command, ...args] = this.options.command;
|
|
2032
|
+
const child = (this.options.spawn ?? spawnProcess)(command, args, {
|
|
2033
|
+
cwd: this.options.cwd,
|
|
2034
|
+
env: this.options.env,
|
|
2035
|
+
stdio: ["ignore", "inherit", "inherit"]
|
|
2036
|
+
});
|
|
2037
|
+
const running = { child, stopping: false, killed: false, exited: Promise.resolve() };
|
|
2038
|
+
running.exited = new Promise((resolve2) => {
|
|
2039
|
+
let settled = false;
|
|
2040
|
+
const settle = (report) => {
|
|
2041
|
+
if (settled) return;
|
|
2042
|
+
settled = true;
|
|
2043
|
+
if (this.running === running) this.running = void 0;
|
|
2044
|
+
if (!running.stopping && !this.stopping) this.options.error(`${report}. Waiting for changes before restarting...`);
|
|
2045
|
+
resolve2();
|
|
2046
|
+
};
|
|
2047
|
+
child.once("exit", (code, signal) => settle(signal !== null ? `The server stopped (signal ${signal})` : `The server stopped (exit code ${code})`));
|
|
2048
|
+
child.once("error", (error) => {
|
|
2049
|
+
if (child.pid === void 0) settle(`The server could not start (${error.message})`);
|
|
2050
|
+
});
|
|
2051
|
+
});
|
|
2052
|
+
this.running = running;
|
|
2053
|
+
}
|
|
2054
|
+
/** Sends SIGTERM once and waits for the exit, killing the server after stopTimeout. */
|
|
2055
|
+
async stopServer() {
|
|
2056
|
+
const running = this.running;
|
|
2057
|
+
if (running === void 0) return;
|
|
2058
|
+
if (!running.stopping) {
|
|
2059
|
+
running.stopping = true;
|
|
2060
|
+
running.child.kill("SIGTERM");
|
|
2061
|
+
}
|
|
2062
|
+
const timeout = this.options.stopTimeout ?? 15e3;
|
|
2063
|
+
const timer = setTimeout(() => {
|
|
2064
|
+
if (running.killed) return;
|
|
2065
|
+
running.killed = true;
|
|
2066
|
+
this.options.error(`The server did not stop within ${timeout / 1e3} seconds and was killed.`);
|
|
2067
|
+
running.child.kill("SIGKILL");
|
|
2068
|
+
}, timeout);
|
|
2069
|
+
try {
|
|
2070
|
+
await running.exited;
|
|
2071
|
+
} finally {
|
|
2072
|
+
clearTimeout(timer);
|
|
2073
|
+
}
|
|
2074
|
+
}
|
|
2075
|
+
listenForSignals() {
|
|
2076
|
+
const signals = this.options.signals ?? ["SIGINT", "SIGTERM", "SIGHUP"];
|
|
2077
|
+
const handler = () => this.stop();
|
|
2078
|
+
for (const signal of signals) process.on(signal, handler);
|
|
2079
|
+
return () => {
|
|
2080
|
+
for (const signal of signals) process.off(signal, handler);
|
|
2081
|
+
};
|
|
2082
|
+
}
|
|
2083
|
+
};
|
|
2084
|
+
|
|
2085
|
+
// commands.ts
|
|
1799
2086
|
function io(command) {
|
|
1800
2087
|
return {
|
|
1801
2088
|
option: (name) => {
|
|
@@ -1869,11 +2156,36 @@ ${web[1]}api: 'routes/api.ts',`);
|
|
|
1869
2156
|
${create[0]}`);
|
|
1870
2157
|
return void 0;
|
|
1871
2158
|
}
|
|
2159
|
+
async function serveWatching(app, out, port, run) {
|
|
2160
|
+
const hostname = out.option("host") ?? "127.0.0.1";
|
|
2161
|
+
const requested = port !== void 0 ? Number(port) : Number(process.env.PORT ?? process.env.SERVER_PORT ?? 8e3);
|
|
2162
|
+
let claimed;
|
|
2163
|
+
try {
|
|
2164
|
+
claimed = await claimPort(hostname, requested);
|
|
2165
|
+
} catch (error) {
|
|
2166
|
+
out.error(listenFailure(hostname, requested, error));
|
|
2167
|
+
return 1;
|
|
2168
|
+
}
|
|
2169
|
+
const script = process.argv[1];
|
|
2170
|
+
if (script === void 0) {
|
|
2171
|
+
out.error("serve --watch can't tell which script started this process, so it can't start the server.");
|
|
2172
|
+
return 1;
|
|
2173
|
+
}
|
|
2174
|
+
return run({
|
|
2175
|
+
command: [process.execPath, ...process.execArgv, script, "serve", `--host=${hostname}`, `--port=${claimed}`],
|
|
2176
|
+
cwd: process.cwd(),
|
|
2177
|
+
env: serverEnvironment(),
|
|
2178
|
+
watch: (onChange) => watchFiles(app.basePath(), onChange, { onError: (folder, error) => out.warn(`Unable to watch [${folder}] for changes: ${error.message}`) }),
|
|
2179
|
+
info: (message) => out.info(message),
|
|
2180
|
+
error: (message) => out.error(message)
|
|
2181
|
+
});
|
|
2182
|
+
}
|
|
1872
2183
|
function registerHttpCommands(kernel, app, options = {}) {
|
|
1873
2184
|
const serveApp = options.serve ?? serve;
|
|
1874
2185
|
const terminalWidth = options.terminalWidth ?? (() => process.stdout.columns ?? 120);
|
|
2186
|
+
const runWatching = options.watch ?? ((watchOptions) => new WatchedServer(watchOptions).run());
|
|
1875
2187
|
kernel.command(
|
|
1876
|
-
"serve {--host=127.0.0.1 : The host address to serve the application on} {--port= : The port to serve the application on}",
|
|
2188
|
+
"serve {--host=127.0.0.1 : The host address to serve the application on} {--port= : The port to serve the application on} {--watch : Automatically restart the server when the application is modified}",
|
|
1877
2189
|
async function() {
|
|
1878
2190
|
const out = io(this);
|
|
1879
2191
|
const port = out.option("port");
|
|
@@ -1881,6 +2193,7 @@ function registerHttpCommands(kernel, app, options = {}) {
|
|
|
1881
2193
|
out.error("The --port option must be a number.");
|
|
1882
2194
|
return 1;
|
|
1883
2195
|
}
|
|
2196
|
+
if (out.flag("watch")) return serveWatching(app, out, port, runWatching);
|
|
1884
2197
|
const server = await serveApp(app, {
|
|
1885
2198
|
hostname: out.option("host") ?? "127.0.0.1",
|
|
1886
2199
|
...port !== void 0 && { port: Number(port) },
|
|
@@ -1928,7 +2241,7 @@ function registerHttpCommands(kernel, app, options = {}) {
|
|
|
1928
2241
|
kernel.command("install:api {--force : Overwrite any existing API routes file}", async function() {
|
|
1929
2242
|
const out = io(this);
|
|
1930
2243
|
const routesFile = app.basePath("routes/api.ts");
|
|
1931
|
-
if (
|
|
2244
|
+
if (existsSync3(routesFile) && !out.flag("force")) {
|
|
1932
2245
|
out.error("API routes file already exists.");
|
|
1933
2246
|
} else {
|
|
1934
2247
|
await mkdir(dirname(routesFile), { recursive: true });
|
|
@@ -1936,7 +2249,7 @@ function registerHttpCommands(kernel, app, options = {}) {
|
|
|
1936
2249
|
out.info("Published API routes file.");
|
|
1937
2250
|
}
|
|
1938
2251
|
const bootstrapFile = app.bootstrapPath("app.ts");
|
|
1939
|
-
const source =
|
|
2252
|
+
const source = existsSync3(bootstrapFile) ? await readFile2(bootstrapFile, "utf8") : void 0;
|
|
1940
2253
|
const updated = source === void 0 ? void 0 : addApiRouting(source);
|
|
1941
2254
|
if (updated === void 0) {
|
|
1942
2255
|
out.warn(
|
|
@@ -1945,7 +2258,7 @@ function registerHttpCommands(kernel, app, options = {}) {
|
|
|
1945
2258
|
} else if (updated !== source) {
|
|
1946
2259
|
await writeFile(bootstrapFile, updated);
|
|
1947
2260
|
}
|
|
1948
|
-
out.info("API scaffolding installed.
|
|
2261
|
+
out.info("API scaffolding installed.");
|
|
1949
2262
|
return 0;
|
|
1950
2263
|
}).purpose("Create an API routes file and prepare the application for API authentication");
|
|
1951
2264
|
}
|