catalyst-core 1.0.2-canary.1 → 1.0.2
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/bin/catalyst.js +105 -1
- package/changelog.md +34 -0
- package/dist/cli/README.md +119 -0
- package/dist/cli/appConfig.js +51 -0
- package/dist/cli/buildErrors.js +170 -0
- package/dist/cli/buildWarnings.js +28 -0
- package/dist/cli/devOutput.js +524 -0
- package/dist/cli/diagnostic.js +388 -0
- package/dist/cli/hints.js +69 -0
- package/dist/cli/package.json +3 -0
- package/dist/cli/spinner.js +141 -0
- package/dist/cli/theme.js +155 -0
- package/dist/errors/index.js +7 -3
- package/dist/logger.js +61 -6
- package/dist/native/androidProject/app/build.gradle.kts +2 -1
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/CustomWebview.kt +2 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/MainActivity.kt +9 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/SplashActivity.kt +65 -34
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/SplashOverlay.kt +109 -0
- package/dist/native/androidSetup.js +46 -57
- package/dist/native/buildAndroid/assets.js +22 -13
- package/dist/native/buildAndroid/build.js +98 -26
- package/dist/native/buildAndroid/config.js +43 -12
- package/dist/native/buildAndroid/index.js +49 -191
- package/dist/native/buildAppAndroid.js +19 -8
- package/dist/native/buildAppIos.js +35 -16
- package/dist/native/buildIos/assets.js +1 -6
- package/dist/native/buildIos/build.js +66 -95
- package/dist/native/buildIos/config.js +26 -8
- package/dist/native/buildIos/index.js +143 -64
- package/dist/native/buildIos/plugins.js +12 -15
- package/dist/native/internalPluginUtils.js +0 -1
- package/dist/native/iosnativeWebView/CoreLogic/Sources/CatalystCoreLogic/Constants/ConfigConstants.swift +2 -0
- package/dist/native/iosnativeWebView/Sources/Core/WebView/SplashHTMLView.swift +31 -0
- package/dist/native/iosnativeWebView/Sources/Core/WebView/SplashView.swift +17 -1
- package/dist/native/iosnativeWebView/Sources/Core/WebView/SplashViewModel.swift +41 -1
- package/dist/native/iosnativeWebView/Sources/Core/WebView/WebViewModel.swift +2 -0
- package/dist/native/iosnativeWebView/Sources/Core/WebView/WebViewNavigationDelegate.swift +3 -0
- package/dist/native/pluginComposerAndroid.js +2 -3
- package/dist/native/renameAndroidProject.js +42 -20
- package/dist/native/setupEmulatorIos.js +28 -49
- package/dist/native/setupServer.js +12 -15
- package/dist/native/terminalProgress.js +186 -147
- package/dist/native/utils.js +340 -54
- package/dist/otel.js +15 -9
- package/dist/scripts/build.js +313 -42
- package/dist/scripts/generateOfflineManifest.js +11 -1
- package/dist/scripts/loadEnvironmentVariables.js +24 -2
- package/dist/scripts/preServerInit.js +32 -10
- package/dist/scripts/preflight.js +35 -11
- package/dist/scripts/scriptUtils.js +34 -0
- package/dist/scripts/serve.js +64 -15
- package/dist/scripts/start.js +58 -21
- package/dist/server/expressServer.js +167 -32
- package/dist/server/renderer/handler.jsx +87 -11
- package/dist/server/utils/validator.js +14 -11
- package/dist/vite/manifest-categorization-plugin.js +7 -5
- package/dist/vite/resolveDevServerConfig.js +9 -2
- package/dist/vite/vite.config.client.js +18 -0
- package/dist/vite/vite.config.js +73 -13
- package/dist/vite/vite.config.server.js +21 -0
- package/dist/web-router/components/MetaTag.jsx +15 -2
- package/dist/web-router/components/MetaTag.test.jsx +71 -2
- package/dist/webmcp/WebMcpProvider.jsx +3 -2
- package/dist/webmcp/useTool.js +4 -1
- package/mcp_v2/conversion-tasks.json +6 -5
- package/mcp_v2/knowledge-base.json +124 -7
- package/mcp_v2/test/config.test.ts +59 -0
- package/mcp_v2/tools/build.js +6 -6
- package/mcp_v2/tools/config.js +35 -18
- package/mcp_v2/tools/conversion.js +7 -13
- package/package.json +5 -5
- package/test/cli/buildErrors.test.ts +173 -0
- package/test/cli/buildWarnings.test.cjs +62 -0
- package/test/cli/devOutput.test.ts +549 -0
- package/test/cli/diagnostic.test.ts +434 -0
- package/test/cli/output.test.cjs +90 -0
- package/test/cli/spinner.test.ts +177 -0
- package/test/cli/theme.test.ts +163 -0
- package/test/native/redactSecrets.test.ts +72 -0
- package/test/native/utils.test.ts +532 -0
- package/test/scripts/build.test.ts +275 -0
- package/test/scripts/serve.test.ts +102 -0
- package/test/server/fixtures/template/src/js/routes/index.jsx +8 -0
- package/test/server/handler.test.ts +17 -0
- package/test/server/logger.test.ts +185 -0
- package/test/server/otel.test.ts +556 -0
- package/test/server/validator.test.ts +19 -8
- package/dist/scripts/checkVersion.js +0 -3
- package/dist/scripts/cleanCache.js +0 -30
|
@@ -2,6 +2,7 @@ import fs from "fs"
|
|
|
2
2
|
import path from "path"
|
|
3
3
|
import { fileURLToPath } from "url"
|
|
4
4
|
import util from "node:util"
|
|
5
|
+
import { pathToFileURL } from "url"
|
|
5
6
|
import pkg from "ansis"
|
|
6
7
|
const { gray, cyan } = pkg
|
|
7
8
|
|
|
@@ -49,6 +50,39 @@ export const printBundleInformation = () => {
|
|
|
49
50
|
})
|
|
50
51
|
}
|
|
51
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Propagate a spawnSync result to this process's exit code.
|
|
55
|
+
*
|
|
56
|
+
* Without this the wrapper always exits 0, so a server that failed to boot
|
|
57
|
+
* still reads as a successful run to CI and to shell `&&` chains.
|
|
58
|
+
*
|
|
59
|
+
* @param {import('child_process').SpawnSyncReturns<Buffer>} result
|
|
60
|
+
* @param {string} label - what was being started, for the error message
|
|
61
|
+
*/
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* A `--import` argument that registers a module loader.
|
|
65
|
+
*
|
|
66
|
+
* `--loader` is deprecated and makes Node print a four-line
|
|
67
|
+
* ExperimentalWarning to stderr for every process that uses it -- which meant
|
|
68
|
+
* 18 lines of warnings in a single `catalyst build`, since the flag is passed
|
|
69
|
+
* to both bundles and the manifest step. register() is the supported form and
|
|
70
|
+
* is silent.
|
|
71
|
+
*
|
|
72
|
+
* @param {string} loaderPath - absolute path to the loader module
|
|
73
|
+
*/
|
|
74
|
+
export function loaderImportArg(loaderPath) {
|
|
75
|
+
const url = pathToFileURL(loaderPath).href
|
|
76
|
+
return (
|
|
77
|
+
`data:text/javascript,` +
|
|
78
|
+
encodeURIComponent(
|
|
79
|
+
`import { register } from "node:module";` +
|
|
80
|
+
`import { pathToFileURL } from "node:url";` +
|
|
81
|
+
`register(${JSON.stringify(url)}, pathToFileURL("./"));`
|
|
82
|
+
)
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
|
|
52
86
|
export function arrayToObject(array) {
|
|
53
87
|
const obj = {}
|
|
54
88
|
array.forEach((item) => {
|
package/dist/scripts/serve.js
CHANGED
|
@@ -1,21 +1,33 @@
|
|
|
1
1
|
import path from "path"
|
|
2
|
-
import {
|
|
3
|
-
import { arrayToObject, resolveOutputMode } from "./scriptUtils.js"
|
|
4
|
-
import { runStaticPreflightOrExit, checkBuildMatchesConfig } from "./preflight.js"
|
|
5
|
-
import { formatError } from "../errors/index.js"
|
|
2
|
+
import { spawn } from "child_process"
|
|
3
|
+
import { arrayToObject, loaderImportArg, resolveOutputMode } from "./scriptUtils.js"
|
|
4
|
+
import { runStaticPreflightOrExit, checkBuildMatchesConfig, renderRegistryError } from "./preflight.js"
|
|
6
5
|
import { fileURLToPath } from "url"
|
|
7
6
|
import { dirname } from "path"
|
|
8
|
-
import { readFileSync } from "fs"
|
|
7
|
+
import { existsSync, readFileSync } from "fs"
|
|
8
|
+
import { createRequire } from "module"
|
|
9
9
|
|
|
10
10
|
const __filename = fileURLToPath(import.meta.url)
|
|
11
11
|
const __dirname = dirname(__filename)
|
|
12
|
+
|
|
13
|
+
const requireCjs = createRequire(import.meta.url)
|
|
14
|
+
const { diagnostic } = requireCjs("../cli/diagnostic.js")
|
|
15
|
+
const { superviseServer, ensurePortFree } = requireCjs("../cli/devOutput.js")
|
|
16
|
+
const { loadAppConfig } = requireCjs("../cli/appConfig.js")
|
|
17
|
+
|
|
12
18
|
const loaderPath = path.resolve(__dirname, "../../dist/vite/node-loader.mjs")
|
|
13
19
|
const preInitPath = path.resolve(__dirname, "preServerInit.js")
|
|
14
20
|
|
|
15
21
|
/**
|
|
16
|
-
* @description -
|
|
22
|
+
* @description - serves the production build
|
|
23
|
+
*
|
|
24
|
+
* Output is inherited, not piped through the `start` formatter. Production
|
|
25
|
+
* output is read by log shippers (pm2, Docker, Datadog) one JSON line at a
|
|
26
|
+
* time, so every byte the app writes must arrive unaltered and on the stream
|
|
27
|
+
* it was written to. CATALYST_WRAPPED stays unset, so the server prints its
|
|
28
|
+
* banner plainly with no fences to consume.
|
|
17
29
|
*/
|
|
18
|
-
function startProd() {
|
|
30
|
+
async function startProd() {
|
|
19
31
|
// Fail fast on a misconfigured app before spawning the production server.
|
|
20
32
|
runStaticPreflightOrExit()
|
|
21
33
|
|
|
@@ -24,27 +36,62 @@ function startProd() {
|
|
|
24
36
|
const outputMode = resolveOutputMode(process.argv)
|
|
25
37
|
const dirname = path.resolve(__dirname, "../../")
|
|
26
38
|
|
|
39
|
+
// Read package.json
|
|
40
|
+
const packageJson = JSON.parse(readFileSync(path.join(process.env.PWD, "package.json"), "utf-8"))
|
|
41
|
+
const { name } = packageJson
|
|
42
|
+
|
|
43
|
+
// Serving a build that was never made produces a stack trace from deep
|
|
44
|
+
// inside the server. Say the useful thing instead.
|
|
45
|
+
// From config.json, not the env: config values are promoted to env only
|
|
46
|
+
// inside the child, so reading env here always saw the default.
|
|
47
|
+
const appConfig = loadAppConfig(process.env.PWD || process.cwd())
|
|
48
|
+
const buildDir = path.join(
|
|
49
|
+
process.env.PWD,
|
|
50
|
+
appConfig.BUILD_OUTPUT_PATH || process.env.BUILD_OUTPUT_PATH || "build"
|
|
51
|
+
)
|
|
52
|
+
if (!existsSync(buildDir)) {
|
|
53
|
+
process.stderr.write(
|
|
54
|
+
diagnostic({
|
|
55
|
+
message: `No production build in ${path.relative(process.env.PWD, buildDir) || buildDir}/`,
|
|
56
|
+
scope: "server",
|
|
57
|
+
hint: "Run catalyst build first, then catalyst serve.",
|
|
58
|
+
})
|
|
59
|
+
)
|
|
60
|
+
process.exit(1)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// After the missing-build check, so a missing build gets that message
|
|
64
|
+
// rather than a "skipping the check" warning first.
|
|
27
65
|
const staleBuild = checkBuildMatchesConfig(undefined, argumentsObject)
|
|
28
66
|
if (staleBuild) {
|
|
29
|
-
|
|
67
|
+
process.stderr.write(renderRegistryError(staleBuild, outputMode, { scope: "server" }))
|
|
30
68
|
process.exit(1)
|
|
31
69
|
}
|
|
32
70
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
71
|
+
await ensurePortFree({
|
|
72
|
+
port: argumentsObject.NODE_SERVER_PORT || appConfig.NODE_SERVER_PORT,
|
|
73
|
+
host: argumentsObject.NODE_SERVER_HOSTNAME || appConfig.NODE_SERVER_HOSTNAME,
|
|
74
|
+
scope: "server",
|
|
75
|
+
})
|
|
38
76
|
|
|
39
77
|
const inspectArgs = commandLineArguments.includes("--inspect") ? ["--inspect"] : []
|
|
40
|
-
|
|
78
|
+
const child = spawn(
|
|
41
79
|
"node",
|
|
42
|
-
[
|
|
80
|
+
[
|
|
81
|
+
...inspectArgs,
|
|
82
|
+
"--import",
|
|
83
|
+
preInitPath,
|
|
84
|
+
"--import",
|
|
85
|
+
loaderImportArg(loaderPath),
|
|
86
|
+
"./dist/server/expressServer.js",
|
|
87
|
+
],
|
|
43
88
|
{
|
|
44
89
|
cwd: dirname,
|
|
45
90
|
stdio: "inherit",
|
|
46
91
|
env: {
|
|
47
92
|
...process.env,
|
|
93
|
+
// When the command was run, so "Ready in" counts the whole wait.
|
|
94
|
+
CATALYST_STARTED_AT: String(Math.round(performance.timeOrigin)),
|
|
48
95
|
src_path: process.env.PWD,
|
|
49
96
|
NODE_ENV: "production",
|
|
50
97
|
APPLICATION: name || "catalyst_app",
|
|
@@ -59,6 +106,8 @@ function startProd() {
|
|
|
59
106
|
},
|
|
60
107
|
}
|
|
61
108
|
)
|
|
109
|
+
|
|
110
|
+
superviseServer(child, { scope: "server", label: "production server", passthrough: true })
|
|
62
111
|
}
|
|
63
112
|
|
|
64
113
|
startProd()
|
package/dist/scripts/start.js
CHANGED
|
@@ -1,20 +1,32 @@
|
|
|
1
1
|
import path from "path"
|
|
2
|
-
import {
|
|
3
|
-
import { arrayToObject, resolveOutputMode } from "./scriptUtils.js"
|
|
2
|
+
import { spawn } from "child_process"
|
|
3
|
+
import { arrayToObject, loaderImportArg, resolveOutputMode } from "./scriptUtils.js"
|
|
4
4
|
import { runStaticPreflightOrExit } from "./preflight.js"
|
|
5
5
|
import { fileURLToPath } from "url"
|
|
6
6
|
import { dirname } from "path"
|
|
7
7
|
import { readFileSync } from "fs"
|
|
8
|
+
import { createRequire } from "module"
|
|
8
9
|
|
|
9
10
|
const __filename = fileURLToPath(import.meta.url)
|
|
10
11
|
const __dirname = dirname(__filename)
|
|
12
|
+
|
|
13
|
+
const requireCjs = createRequire(import.meta.url)
|
|
14
|
+
const { superviseServer, ensurePortFree } = requireCjs("../cli/devOutput.js")
|
|
15
|
+
const { loadAppConfig } = requireCjs("../cli/appConfig.js")
|
|
16
|
+
|
|
11
17
|
const loaderPath = path.resolve(__dirname, "../../dist/vite/node-loader.mjs")
|
|
12
18
|
const preInitPath = path.resolve(__dirname, "preServerInit.js")
|
|
13
19
|
|
|
14
20
|
/**
|
|
15
|
-
* @description - starts
|
|
21
|
+
* @description - starts the dev server
|
|
22
|
+
*
|
|
23
|
+
* stdio is piped rather than inherited so this command owns every line it
|
|
24
|
+
* prints. The server child speaks in three unrelated voices -- Node process
|
|
25
|
+
* warnings, Vite notices, and our own banner -- and inheriting stdio meant
|
|
26
|
+
* they interleaved raw, with the banner arriving last and reading as an
|
|
27
|
+
* afterthought.
|
|
16
28
|
*/
|
|
17
|
-
function start() {
|
|
29
|
+
async function start() {
|
|
18
30
|
// Fail fast on a misconfigured app (missing/invalid config.json, package.json,
|
|
19
31
|
// or moduleAliases) with a coded, doc-linked error before spawning the server.
|
|
20
32
|
runStaticPreflightOrExit()
|
|
@@ -28,24 +40,49 @@ function start() {
|
|
|
28
40
|
const packageJson = JSON.parse(readFileSync(path.join(process.env.PWD, "package.json"), "utf-8"))
|
|
29
41
|
const { name } = packageJson
|
|
30
42
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
src_path: process.env.PWD,
|
|
37
|
-
NODE_ENV: "development",
|
|
38
|
-
APPLICATION: name || "catalyst_app",
|
|
39
|
-
CATALYST_OUTPUT_MODE: outputMode,
|
|
40
|
-
...argumentsObject,
|
|
41
|
-
filterKeys: JSON.stringify([
|
|
42
|
-
"src_path",
|
|
43
|
-
"NODE_ENV",
|
|
44
|
-
"APPLICATION",
|
|
45
|
-
...Object.keys(argumentsObject),
|
|
46
|
-
]),
|
|
47
|
-
},
|
|
43
|
+
const appConfig = loadAppConfig(process.env.PWD || process.cwd())
|
|
44
|
+
await ensurePortFree({
|
|
45
|
+
port: argumentsObject.NODE_SERVER_PORT || appConfig.NODE_SERVER_PORT,
|
|
46
|
+
host: argumentsObject.NODE_SERVER_HOSTNAME || appConfig.NODE_SERVER_HOSTNAME,
|
|
47
|
+
scope: "dev server",
|
|
48
48
|
})
|
|
49
|
+
|
|
50
|
+
const child = spawn(
|
|
51
|
+
"node",
|
|
52
|
+
["--import", preInitPath, "--import", loaderImportArg(loaderPath), "./dist/server/expressServer.js"],
|
|
53
|
+
{
|
|
54
|
+
cwd: dirname,
|
|
55
|
+
stdio: ["inherit", "pipe", "pipe"],
|
|
56
|
+
env: {
|
|
57
|
+
...process.env,
|
|
58
|
+
// Piping the child's stdout makes it a non-TTY, so colour
|
|
59
|
+
// libraries in the child disable themselves and the banner
|
|
60
|
+
// arrives grey. Pass our own terminal's answer down, so the
|
|
61
|
+
// child colours only when there is a real terminal to colour
|
|
62
|
+
// for -- and a redirect to a file still comes out plain.
|
|
63
|
+
...(process.stdout.isTTY && !process.env.NO_COLOR ? { FORCE_COLOR: "1" } : {}),
|
|
64
|
+
// Tells the server its output is being consumed, so it may
|
|
65
|
+
// fence the banner. Unset when run directly.
|
|
66
|
+
CATALYST_WRAPPED: "1",
|
|
67
|
+
// When the command was run, so "Ready in" counts the whole wait.
|
|
68
|
+
CATALYST_STARTED_AT: String(Math.round(performance.timeOrigin)),
|
|
69
|
+
src_path: process.env.PWD,
|
|
70
|
+
NODE_ENV: "development",
|
|
71
|
+
APPLICATION: name || "catalyst_app",
|
|
72
|
+
CATALYST_OUTPUT_MODE: outputMode,
|
|
73
|
+
...argumentsObject,
|
|
74
|
+
filterKeys: JSON.stringify([
|
|
75
|
+
"CATALYST_WRAPPED",
|
|
76
|
+
"src_path",
|
|
77
|
+
"NODE_ENV",
|
|
78
|
+
"APPLICATION",
|
|
79
|
+
...Object.keys(argumentsObject),
|
|
80
|
+
]),
|
|
81
|
+
},
|
|
82
|
+
}
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
superviseServer(child, { scope: "dev server", label: "development server" })
|
|
49
86
|
}
|
|
50
87
|
|
|
51
88
|
start()
|
|
@@ -5,18 +5,106 @@ import compression from "compression";
|
|
|
5
5
|
import cookieParser from "cookie-parser";
|
|
6
6
|
import expressStaticGzip from "express-static-gzip";
|
|
7
7
|
import { createServer as createViteServer } from "vite";
|
|
8
|
-
import
|
|
9
|
-
import pc from "ansis";
|
|
8
|
+
import os from "node:os";
|
|
10
9
|
import fs from "fs";
|
|
11
|
-
const {
|
|
12
|
-
cyan,
|
|
13
|
-
yellow,
|
|
14
|
-
green
|
|
15
|
-
} = pc;
|
|
16
10
|
import { toMountPathPrefix } from "../vite/resolveDevServerConfig.js";
|
|
17
11
|
import { validateMiddleware, handleError, safeCall } from "./utils/validator.js";
|
|
18
12
|
import { botDetectionMiddleware } from "./utils/botDetectionMiddleware.js";
|
|
19
13
|
import { cjsRequire } from "./utils/cjsRequire.js";
|
|
14
|
+
const {
|
|
15
|
+
header,
|
|
16
|
+
row,
|
|
17
|
+
glyph,
|
|
18
|
+
t,
|
|
19
|
+
GUTTER,
|
|
20
|
+
duration
|
|
21
|
+
} = cjsRequire("../../cli/theme.js");
|
|
22
|
+
const {
|
|
23
|
+
diagnostic,
|
|
24
|
+
runtimeFailure
|
|
25
|
+
} = cjsRequire("../../cli/diagnostic.js");
|
|
26
|
+
const {
|
|
27
|
+
hintFor
|
|
28
|
+
} = cjsRequire("../../cli/hints.js");
|
|
29
|
+
|
|
30
|
+
// "Ready in" should measure what the person waited for: from the moment they
|
|
31
|
+
// ran the command, not from when this child process happened to load. The CLI
|
|
32
|
+
// stamps that moment; run directly, module load is the best we have.
|
|
33
|
+
const serverStartedAt = Number(process.env.CATALYST_STARTED_AT) || Date.now();
|
|
34
|
+
const outputMode = process.env.CATALYST_OUTPUT_MODE || "default";
|
|
35
|
+
const verbose = outputMode === "verbose" || outputMode === "debug";
|
|
36
|
+
|
|
37
|
+
// Set in development once Vite is up: maps a stack from Vite's transformed SSR
|
|
38
|
+
// modules back to the source lines the user wrote.
|
|
39
|
+
let fixStacktrace = null;
|
|
40
|
+
const stackFixed = new WeakSet();
|
|
41
|
+
|
|
42
|
+
// Reloading a broken page re-throws the same error on every request. After the
|
|
43
|
+
// first full report, a repeat gets one line until something different fails.
|
|
44
|
+
let lastReport = null;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Report a runtime error in the house style. Exposed on globalThis because the
|
|
48
|
+
* SSR handler is bundled into the app's build and cannot import this module's
|
|
49
|
+
* CLI helpers by path; it calls this when present.
|
|
50
|
+
*/
|
|
51
|
+
function reportRuntimeError({
|
|
52
|
+
error,
|
|
53
|
+
code,
|
|
54
|
+
docs,
|
|
55
|
+
scope
|
|
56
|
+
}) {
|
|
57
|
+
if (fixStacktrace && error && typeof error === "object" && !stackFixed.has(error)) {
|
|
58
|
+
stackFixed.add(error);
|
|
59
|
+
try {
|
|
60
|
+
fixStacktrace(error);
|
|
61
|
+
} catch {
|
|
62
|
+
// A stack we cannot map is still worth printing as-is.
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const fingerprint = `${code}|${error?.message ?? String(error)}|${String(error?.stack || "").split("\n")[1] || ""}`;
|
|
66
|
+
if (fingerprint === lastReport && outputMode === "default") {
|
|
67
|
+
process.stderr.write(`${GUTTER}${t.bad(glyph.fail)} ${t.dim(`Same error again${scope ? ` ${scope}` : ""}`)}\n`);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
lastReport = fingerprint;
|
|
71
|
+
process.stderr.write(runtimeFailure({
|
|
72
|
+
error,
|
|
73
|
+
code,
|
|
74
|
+
docs,
|
|
75
|
+
scope,
|
|
76
|
+
mode: outputMode,
|
|
77
|
+
env: outputMode === "debug" ? {
|
|
78
|
+
node: process.version,
|
|
79
|
+
platform: process.platform
|
|
80
|
+
} : undefined,
|
|
81
|
+
cwd: process.env.src_path || process.cwd()
|
|
82
|
+
}));
|
|
83
|
+
}
|
|
84
|
+
;
|
|
85
|
+
globalThis.__catalystReportRuntimeError = reportRuntimeError;
|
|
86
|
+
|
|
87
|
+
// Invisible fences around the banner block. The wrapper strips these; if the
|
|
88
|
+
// server is run directly they are two blank-looking lines.
|
|
89
|
+
const {
|
|
90
|
+
BANNER_START,
|
|
91
|
+
BANNER_END,
|
|
92
|
+
NEXT_STEP
|
|
93
|
+
} = cjsRequire("../../cli/devOutput.js");
|
|
94
|
+
|
|
95
|
+
/** First non-internal IPv4 address, so the banner can offer a LAN URL. */
|
|
96
|
+
function lanAddress() {
|
|
97
|
+
try {
|
|
98
|
+
for (const interfaces of Object.values(os.networkInterfaces())) {
|
|
99
|
+
for (const entry of interfaces || []) {
|
|
100
|
+
if (entry.family === "IPv4" && !entry.internal) return entry.address;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
} catch (error) {
|
|
104
|
+
// Not worth failing a server start over.
|
|
105
|
+
}
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
20
108
|
const {
|
|
21
109
|
addMiddlewares
|
|
22
110
|
} = await import(path.join(process.env.src_path, "server/server.js"));
|
|
@@ -31,7 +119,8 @@ function mountAIRouter(app) {
|
|
|
31
119
|
} catch (resolveErr) {
|
|
32
120
|
if (resolveErr.code !== "MODULE_NOT_FOUND") {
|
|
33
121
|
console.error("[catalyst-core/ai] Unexpected error resolving catalyst-ai:", resolveErr);
|
|
34
|
-
} else {
|
|
122
|
+
} else if (verbose) {
|
|
123
|
+
// Not installing an optional package is not news on every start.
|
|
35
124
|
console.debug("[catalyst-core/ai] catalyst-ai not installed — AI routes unavailable");
|
|
36
125
|
}
|
|
37
126
|
return;
|
|
@@ -54,7 +143,6 @@ function mountAIRouter(app) {
|
|
|
54
143
|
return;
|
|
55
144
|
}
|
|
56
145
|
const aiBasePath = aiConfig.basePath || "/ai";
|
|
57
|
-
console.log(`[catalyst-core/ai] mounting AI router at ${aiBasePath}`);
|
|
58
146
|
app.use(aiBasePath, aiRouter);
|
|
59
147
|
} catch (mountErr) {
|
|
60
148
|
console.error("[catalyst-core/ai] Failed to mount AI router:", mountErr);
|
|
@@ -97,15 +185,34 @@ function safeStringify(err) {
|
|
|
97
185
|
}
|
|
98
186
|
}
|
|
99
187
|
process.on("uncaughtException", (err, origin) => {
|
|
100
|
-
|
|
101
|
-
|
|
188
|
+
// A server that cannot bind its port is dead -- swallowing that left the
|
|
189
|
+
// process alive but useless, so `catalyst start` hung with a blank screen.
|
|
190
|
+
// Report it the way every other failure is reported, then exit.
|
|
191
|
+
const code = err?.code;
|
|
192
|
+
if (code === "EADDRINUSE" || code === "EACCES") {
|
|
193
|
+
const known = hintFor(err.message);
|
|
194
|
+
process.stderr.write(diagnostic({
|
|
195
|
+
message: err.message,
|
|
196
|
+
scope: "dev server",
|
|
197
|
+
hint: known?.hint,
|
|
198
|
+
docs: known?.docs
|
|
199
|
+
}));
|
|
200
|
+
process.exit(1);
|
|
201
|
+
}
|
|
202
|
+
reportRuntimeError({
|
|
203
|
+
error: err,
|
|
204
|
+
scope: origin === "unhandledRejection" ? "unhandled rejection" : "uncaught exception"
|
|
205
|
+
});
|
|
102
206
|
});
|
|
103
|
-
process.on("
|
|
104
|
-
|
|
207
|
+
process.on("unhandledRejection", err => {
|
|
208
|
+
// A rejection with a non-Error reason still deserves a readable line.
|
|
209
|
+
const error = err instanceof Error ? err : new Error(safeStringify(err) || String(err));
|
|
210
|
+
reportRuntimeError({
|
|
211
|
+
error,
|
|
212
|
+
scope: "unhandled rejection"
|
|
213
|
+
});
|
|
105
214
|
});
|
|
106
|
-
process.on("unhandledRejection", err => console.log("unhandledRejection in Catalyst", err?.stack || safeStringify(err)));
|
|
107
215
|
process.on("SIGINT", function () {
|
|
108
|
-
console.log("SIGINT");
|
|
109
216
|
process.exit(0);
|
|
110
217
|
});
|
|
111
218
|
process.on("message", function (msg) {
|
|
@@ -201,6 +308,7 @@ async function createServer() {
|
|
|
201
308
|
appType: "custom",
|
|
202
309
|
root: process.env.src_path
|
|
203
310
|
});
|
|
311
|
+
fixStacktrace = error => vite.ssrFixStacktrace(error);
|
|
204
312
|
|
|
205
313
|
// Expose Vite's resolved base to the SSR renderer so the URLs it injects
|
|
206
314
|
// (client entry, react-refresh preamble) match the path Vite serves under.
|
|
@@ -241,11 +349,13 @@ async function createServer() {
|
|
|
241
349
|
res.status(500).send("Error loading renderer");
|
|
242
350
|
}
|
|
243
351
|
} catch (err) {
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
352
|
+
// Reached when the renderer itself fails to load or throws before
|
|
353
|
+
// its own error handling -- e.g. a syntax error in an app module.
|
|
354
|
+
reportRuntimeError({
|
|
355
|
+
error: err,
|
|
356
|
+
scope: `${req.method} ${req.originalUrl}`
|
|
357
|
+
});
|
|
358
|
+
if (!res.headersSent) res.status(500).send("Internal Server Error");
|
|
249
359
|
}
|
|
250
360
|
});
|
|
251
361
|
app.listen({
|
|
@@ -253,26 +363,51 @@ async function createServer() {
|
|
|
253
363
|
host
|
|
254
364
|
}, error => {
|
|
255
365
|
const {
|
|
256
|
-
APPLICATION,
|
|
257
366
|
NODE_SERVER_HOSTNAME,
|
|
258
367
|
NODE_SERVER_PORT
|
|
259
368
|
} = process.env;
|
|
369
|
+
const isDev = process.env.NODE_ENV === "development";
|
|
260
370
|
if (error) {
|
|
261
|
-
|
|
371
|
+
const known = hintFor(error.message);
|
|
372
|
+
process.stderr.write(diagnostic({
|
|
373
|
+
message: `Could not start the server: ${error.message}`,
|
|
374
|
+
scope: isDev ? "dev server" : "server",
|
|
375
|
+
hint: known?.hint,
|
|
376
|
+
docs: known?.docs
|
|
377
|
+
}));
|
|
262
378
|
safeCall(onServerError);
|
|
263
379
|
return;
|
|
264
380
|
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
381
|
+
|
|
382
|
+
// Reached from the listen callback, so this means a port was bound --
|
|
383
|
+
// it says nothing about whether anything compiled. Claiming a
|
|
384
|
+
// successful compile here was an outright lie when the bundle failed.
|
|
385
|
+
const elapsed = Date.now() - serverStartedAt;
|
|
386
|
+
|
|
387
|
+
// Fence the banner so the wrapper can forward it verbatim instead of
|
|
388
|
+
// guessing from its text -- colour codes made pattern-matching the
|
|
389
|
+
// lines unreliable, and the guesswork mis-filed the rule and the URL.
|
|
390
|
+
const wrapped = process.env.CATALYST_WRAPPED === "1";
|
|
391
|
+
if (wrapped) process.stdout.write(`${BANNER_START}\n`);
|
|
392
|
+
process.stdout.write(header(`catalyst ${isDev ? "start" : "serve"}`, isDev ? "development" : "production"));
|
|
393
|
+
console.log(`${GUTTER}${t.ok(glyph.done)} Ready in ${t.bold(duration(elapsed))}\n`);
|
|
394
|
+
|
|
395
|
+
// An app can bind to a LAN address rather than localhost, in which case
|
|
396
|
+
// "Local" and "Network" would print the same URL twice. Show the bound
|
|
397
|
+
// address as Local, and only offer a Network line when it differs.
|
|
398
|
+
const boundHost = NODE_SERVER_HOSTNAME || "localhost";
|
|
399
|
+
console.log(row("Local", t.accent(`http://${boundHost}:${NODE_SERVER_PORT}`)));
|
|
400
|
+
const lan = lanAddress();
|
|
401
|
+
const reachableEverywhere = boundHost === "0.0.0.0" || boundHost === "::";
|
|
402
|
+
if (lan && lan !== boundHost && (reachableEverywhere || boundHost === "localhost")) {
|
|
403
|
+
console.log(row("Network", t.dim(`http://${lan}:${NODE_SERVER_PORT}`)));
|
|
273
404
|
}
|
|
274
|
-
|
|
275
|
-
|
|
405
|
+
|
|
406
|
+
// The next-step line is the one thing to act on, so it is handed to the
|
|
407
|
+
// wrapper fenced rather than printed here -- the wrapper prints it after
|
|
408
|
+
// any notices, which would otherwise bury it.
|
|
409
|
+
process.stdout.write(`${wrapped ? NEXT_STEP : ""}${GUTTER}${t.dim(isDev ? "Watching for changes. " : "Serving ")}` + `${isDev ? "" : t.accent(`${process.env.BUILD_OUTPUT_PATH || "build"}/`)}` + `${t.dim(isDev ? "Press Ctrl+C to stop." : ". Press Ctrl+C to stop.")}\n`);
|
|
410
|
+
if (wrapped) process.stdout.write(`${BANNER_END}\n`);
|
|
276
411
|
});
|
|
277
412
|
}
|
|
278
413
|
createServer().catch(err => {
|