toiljs 0.0.121 → 0.0.123
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 +31 -0
- package/README.md +2 -0
- package/build/backend/.tsbuildinfo +1 -1
- package/build/cli/.tsbuildinfo +1 -1
- package/build/cli/index.js +241 -15
- package/build/compiler/.tsbuildinfo +1 -1
- package/build/compiler/toil-docs.generated.js +5 -4
- package/build/devserver/.tsbuildinfo +1 -1
- package/build/testing/.tsbuildinfo +1 -0
- package/build/testing/config.d.ts +9 -0
- package/build/testing/config.js +79 -0
- package/build/testing/setup.d.ts +1 -0
- package/build/testing/setup.js +11 -0
- package/examples/basic/client/routes/docs/[...slug].tsx +12 -0
- package/package.json +53 -22
- package/tsconfig.testing.json +13 -0
package/build/cli/index.js
CHANGED
|
@@ -3510,7 +3510,7 @@ var PREPROCESSOR_PKG = {
|
|
|
3510
3510
|
};
|
|
3511
3511
|
var TAILWIND_PKGS = ["tailwindcss", "@tailwindcss/vite"];
|
|
3512
3512
|
var PKG_VERSION = {
|
|
3513
|
-
sass: "^1.
|
|
3513
|
+
sass: "^1.105.1",
|
|
3514
3514
|
less: "^4.9.1",
|
|
3515
3515
|
stylus: "^0.64.0",
|
|
3516
3516
|
tailwindcss: "^4.3.3",
|
|
@@ -4017,6 +4017,88 @@ import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
|
4017
4017
|
var import_picocolors3 = __toESM(require_picocolors(), 1);
|
|
4018
4018
|
import { AI_HELPER_IDS, AI_HELPERS, aiHelperFiles, TOIL_DOCS, TOIL_ENV_DTS } from "toiljs/compiler";
|
|
4019
4019
|
|
|
4020
|
+
// src/cli/testing.ts
|
|
4021
|
+
var TEST_DEPENDENCIES = {
|
|
4022
|
+
vitest: "^5.0.3",
|
|
4023
|
+
"@vitest/browser-webdriverio": "^5.0.0",
|
|
4024
|
+
"@vitest/coverage-v8": "^5.0.3",
|
|
4025
|
+
webdriverio: "^10.0.2"
|
|
4026
|
+
};
|
|
4027
|
+
var TEST_SCRIPTS = {
|
|
4028
|
+
test: "toiljs test",
|
|
4029
|
+
"test:watch": "toiljs test --watch",
|
|
4030
|
+
"test:browser": "toiljs test --browser",
|
|
4031
|
+
"test:all": "toiljs test --all",
|
|
4032
|
+
"test:coverage": "toiljs test --all --coverage"
|
|
4033
|
+
};
|
|
4034
|
+
function testingFiles() {
|
|
4035
|
+
return {
|
|
4036
|
+
"vitest.config.ts": "import { defineTestConfig } from 'toiljs/vitest';\n\nexport default defineTestConfig();\n",
|
|
4037
|
+
"client/lib/Greeting.ts": [
|
|
4038
|
+
"/** Formats a greeting, treating blank input as an unnamed guest. */",
|
|
4039
|
+
"export class Greeting {",
|
|
4040
|
+
" public static message(name: string): string {",
|
|
4041
|
+
" const recipient: string = name.trim() || 'guest';",
|
|
4042
|
+
" return `Hello, ${recipient}!`;",
|
|
4043
|
+
" }",
|
|
4044
|
+
"}",
|
|
4045
|
+
""
|
|
4046
|
+
].join("\n"),
|
|
4047
|
+
"tests/Greeting.test.ts": [
|
|
4048
|
+
"import { expect, test } from 'vitest';",
|
|
4049
|
+
"import { Greeting } from '../client/lib/Greeting';",
|
|
4050
|
+
"",
|
|
4051
|
+
"test('trims the supplied name', (): void => {",
|
|
4052
|
+
" expect(Greeting.message(' Ada ')).toBe('Hello, Ada!');",
|
|
4053
|
+
"});",
|
|
4054
|
+
"",
|
|
4055
|
+
"test('greets an unnamed guest', (): void => {",
|
|
4056
|
+
" expect(Greeting.message(' ')).toBe('Hello, guest!');",
|
|
4057
|
+
"});",
|
|
4058
|
+
""
|
|
4059
|
+
].join("\n"),
|
|
4060
|
+
"client/components/TestCounter.tsx": [
|
|
4061
|
+
"import { useState, type ReactElement } from 'react';",
|
|
4062
|
+
"",
|
|
4063
|
+
"/** A small component used by the starter browser test. */",
|
|
4064
|
+
"export function TestCounter(): ReactElement {",
|
|
4065
|
+
" const [count, setCount] = useState<number>(0);",
|
|
4066
|
+
" return (",
|
|
4067
|
+
" <section>",
|
|
4068
|
+
' <Toil.Link href="/">Home</Toil.Link>',
|
|
4069
|
+
" <button onClick={(): void => setCount(count + 1)}>Count: {count}</button>",
|
|
4070
|
+
" </section>",
|
|
4071
|
+
" );",
|
|
4072
|
+
"}",
|
|
4073
|
+
""
|
|
4074
|
+
].join("\n"),
|
|
4075
|
+
"tests/TestCounter.browser.test.tsx": [
|
|
4076
|
+
"import { afterEach, expect, test } from 'vitest';",
|
|
4077
|
+
"import { page } from 'vitest/browser';",
|
|
4078
|
+
"import { createRoot, type Root } from 'react-dom/client';",
|
|
4079
|
+
"import { TestCounter } from '../client/components/TestCounter';",
|
|
4080
|
+
"",
|
|
4081
|
+
"let root: Root | null = null;",
|
|
4082
|
+
"afterEach((): void => {",
|
|
4083
|
+
" root?.unmount();",
|
|
4084
|
+
" root = null;",
|
|
4085
|
+
" document.body.replaceChildren();",
|
|
4086
|
+
"});",
|
|
4087
|
+
"",
|
|
4088
|
+
"test('increments when clicked in a real browser', async (): Promise<void> => {",
|
|
4089
|
+
" const host: HTMLDivElement = document.createElement('div');",
|
|
4090
|
+
" document.body.append(host);",
|
|
4091
|
+
" root = createRoot(host);",
|
|
4092
|
+
" root.render(<TestCounter />);",
|
|
4093
|
+
" await expect.element(page.getByRole('link', { name: 'Home' })).toHaveAttribute('href', '/');",
|
|
4094
|
+
" await page.getByRole('button', { name: 'Count: 0' }).click();",
|
|
4095
|
+
" await expect.element(page.getByRole('button', { name: 'Count: 1' })).toBeVisible();",
|
|
4096
|
+
"});",
|
|
4097
|
+
""
|
|
4098
|
+
].join("\n")
|
|
4099
|
+
};
|
|
4100
|
+
}
|
|
4101
|
+
|
|
4020
4102
|
// src/cli/validate.ts
|
|
4021
4103
|
import path3 from "node:path";
|
|
4022
4104
|
var PACKAGE_MANAGERS = ["npm", "pnpm", "yarn", "bun"];
|
|
@@ -4061,12 +4143,13 @@ async function isEmptyDir(dir) {
|
|
|
4061
4143
|
function scaffold(name, template, features, aiTools, images) {
|
|
4062
4144
|
const toilVersion = version();
|
|
4063
4145
|
const devDependencies = {
|
|
4146
|
+
...TEST_DEPENDENCIES,
|
|
4064
4147
|
"@types/react": "^19.3.0",
|
|
4065
4148
|
"@types/react-dom": "^19.3.0",
|
|
4066
|
-
oxlint: "^1.
|
|
4067
|
-
"oxlint-tsgolint": "^7.0.
|
|
4068
|
-
prettier: "^3.9.
|
|
4069
|
-
toilscript: "
|
|
4149
|
+
oxlint: "^1.87.0",
|
|
4150
|
+
"oxlint-tsgolint": "^7.0.2003",
|
|
4151
|
+
prettier: "^3.9.9",
|
|
4152
|
+
toilscript: ">=0.1.63",
|
|
4070
4153
|
typescript: "^7.0.2"
|
|
4071
4154
|
};
|
|
4072
4155
|
for (const dep of requiredPackages(features).sort()) {
|
|
@@ -4077,6 +4160,7 @@ function scaffold(name, template, features, aiTools, images) {
|
|
|
4077
4160
|
private: true,
|
|
4078
4161
|
type: "module",
|
|
4079
4162
|
scripts: {
|
|
4163
|
+
...TEST_SCRIPTS,
|
|
4080
4164
|
start: "toiljs start",
|
|
4081
4165
|
dev: "toiljs dev",
|
|
4082
4166
|
build: "toiljs build",
|
|
@@ -4093,23 +4177,22 @@ function scaffold(name, template, features, aiTools, images) {
|
|
|
4093
4177
|
devDependencies
|
|
4094
4178
|
};
|
|
4095
4179
|
const files = {
|
|
4180
|
+
...testingFiles(),
|
|
4096
4181
|
"package.json": JSON.stringify(pkg, null, 4) + "\n",
|
|
4097
|
-
"toil.config.ts":
|
|
4098
|
-
|
|
4099
|
-
export default defineConfig({
|
|
4100
|
-
client: {
|
|
4182
|
+
"toil.config.ts": "import { defineConfig } from 'toiljs/compiler';\n\nexport default defineConfig({\n" + // The app starter's session routes use the identity fields injected by built-in auth.
|
|
4183
|
+
(template === "app" ? " server: { auth: true },\n" : "") + ` client: {
|
|
4101
4184
|
// Optimize images at build time (resize/compress imported images).
|
|
4102
4185
|
images: ${String(images)},
|
|
4103
4186
|
},
|
|
4104
4187
|
});
|
|
4105
4188
|
`,
|
|
4106
|
-
"tsconfig.json": '{\n "extends": "toiljs/tsconfig",\n "compilerOptions": {\n "paths": { "shared/*": ["./shared/*"] }\n },\n "include": ["client", "shared", "emails", "toil.config.ts", "toil-env.d.ts", "toil-routes.d.ts"]\n}\n',
|
|
4189
|
+
"tsconfig.json": '{\n "extends": "toiljs/tsconfig",\n "compilerOptions": {\n "paths": { "shared/*": ["./shared/*"] }\n },\n "include": ["client", "shared", "emails", "tests", "vitest.config.ts", "toil.config.ts", "toil-env.d.ts", "toil-routes.d.ts"]\n}\n',
|
|
4107
4190
|
"oxlint.config.ts": "import toiljs from 'toiljs/oxlint';\n\nexport default toiljs;\n",
|
|
4108
4191
|
".prettierrc": '"toiljs/prettier"\n',
|
|
4109
4192
|
// Generated files don't need formatting. (toilscript server decorators like @main /
|
|
4110
4193
|
// @remote-on-functions are handled by the toiljs/prettier-plugin, so server/ is not ignored.)
|
|
4111
|
-
".prettierignore": "node_modules\nbuild\n.toil\nshared/server.ts\ntoil-env.d.ts\ntoil-routes.d.ts\nserver/_emails.ts\nserver/_ssr/\nserver/toil-server-env.d.ts\n",
|
|
4112
|
-
".gitignore": "node_modules\nbuild\n.toil\nshared/server.ts\ntoil-env.d.ts\ntoil-routes.d.ts\nserver/_ssr/\nhosts/*/_tmpl/\n# Local dev env vars/secrets (never commit)\n.env\n.env.secrets\n",
|
|
4194
|
+
".prettierignore": "node_modules\nbuild\n.toil\ncoverage\n.vitest\nshared/server.ts\ntoil-env.d.ts\ntoil-routes.d.ts\nserver/_emails.ts\nserver/_ssr/\nserver/toil-server-env.d.ts\n",
|
|
4195
|
+
".gitignore": "node_modules\nbuild\n.toil\ncoverage\n.vitest\nshared/server.ts\ntoil-env.d.ts\ntoil-routes.d.ts\nserver/_ssr/\nhosts/*/_tmpl/\n# Local dev env vars/secrets (never commit)\n.env\n.env.secrets\n",
|
|
4113
4196
|
// Use the native TypeScript 7 language server.
|
|
4114
4197
|
".vscode/settings.json": JSON.stringify(
|
|
4115
4198
|
{
|
|
@@ -4193,6 +4276,21 @@ export default defineConfig({
|
|
|
4193
4276
|
"",
|
|
4194
4277
|
" npm run build",
|
|
4195
4278
|
"",
|
|
4279
|
+
"## Test",
|
|
4280
|
+
"",
|
|
4281
|
+
"Vitest, real-browser testing, and V8 coverage are installed with the project.",
|
|
4282
|
+
"Start with `tests/Greeting.test.ts` and `tests/TestCounter.browser.test.tsx`.",
|
|
4283
|
+
"",
|
|
4284
|
+
" npm test # unit tests",
|
|
4285
|
+
" npm run test:watch # unit tests in watch mode",
|
|
4286
|
+
" npm run test:browser # real Chrome tests",
|
|
4287
|
+
" npm run test:all # unit and browser tests",
|
|
4288
|
+
" npm run test:coverage # both suites with V8 coverage",
|
|
4289
|
+
"",
|
|
4290
|
+
"Coverage reports are in `coverage/` (HTML, JSON, and LCOV).",
|
|
4291
|
+
"Chrome and its driver are managed by WebdriverIO on the first browser run.",
|
|
4292
|
+
"See `.toil/docs/frontend/testing.md` for configuration, mocks, and CI.",
|
|
4293
|
+
"",
|
|
4196
4294
|
"## Editor setup",
|
|
4197
4295
|
"",
|
|
4198
4296
|
"Run `npm install` first and use Node 26 for editor tooling.",
|
|
@@ -4547,6 +4645,8 @@ async function runCreate(opts2) {
|
|
|
4547
4645
|
await fs3.cp(appClientDir(), path4.join(targetDir, "client"), { recursive: true });
|
|
4548
4646
|
const serverAllow = /* @__PURE__ */ new Set([
|
|
4549
4647
|
"main.ts",
|
|
4648
|
+
"main.stream.ts",
|
|
4649
|
+
"SsrHelloRender.ts",
|
|
4550
4650
|
"README.md",
|
|
4551
4651
|
"tsconfig.json",
|
|
4552
4652
|
"toil-server-env.d.ts",
|
|
@@ -4554,6 +4654,7 @@ async function runCreate(opts2) {
|
|
|
4554
4654
|
"models",
|
|
4555
4655
|
"routes",
|
|
4556
4656
|
"services",
|
|
4657
|
+
"streams",
|
|
4557
4658
|
"scheduled"
|
|
4558
4659
|
]);
|
|
4559
4660
|
const serverSrc = appServerDir();
|
|
@@ -4601,6 +4702,8 @@ async function runCreate(opts2) {
|
|
|
4601
4702
|
if (!install) steps.push("npm install");
|
|
4602
4703
|
steps.push(`${accent("npm run dev")} ${dim("start the dev server")}`);
|
|
4603
4704
|
steps.push(`${accent("npm run build")} ${dim("build for production")}`);
|
|
4705
|
+
steps.push(`${accent("npm test")} ${dim("run the starter unit tests")}`);
|
|
4706
|
+
steps.push(`${accent("npm run test:browser")} ${dim("run the starter browser test")}`);
|
|
4604
4707
|
steps.push("Install the Oxc plugin for VS Code or WebStorm (README.md \u2192 Editor setup)");
|
|
4605
4708
|
steps.push("WebStorm: Settings \u2192 Tools \u2192 Oxlint \u2192 Enable type aware rules");
|
|
4606
4709
|
note(steps.map((l2) => dim(" ") + l2).join("\n"), "Next steps");
|
|
@@ -6969,7 +7072,8 @@ function buildRows(upgraded, currentDeps) {
|
|
|
6969
7072
|
function detectPackageManager2(root) {
|
|
6970
7073
|
if (fs7.existsSync(path8.join(root, "pnpm-lock.yaml"))) return { name: "pnpm", ncuName: "pnpm" };
|
|
6971
7074
|
if (fs7.existsSync(path8.join(root, "yarn.lock"))) return { name: "yarn", ncuName: "yarn" };
|
|
6972
|
-
if (fs7.existsSync(path8.join(root, "bun.
|
|
7075
|
+
if (fs7.existsSync(path8.join(root, "bun.lock")) || fs7.existsSync(path8.join(root, "bun.lockb")))
|
|
7076
|
+
return { name: "bun", ncuName: "bun" };
|
|
6973
7077
|
return { name: "npm", ncuName: "npm" };
|
|
6974
7078
|
}
|
|
6975
7079
|
function readDependencies(pkgPath) {
|
|
@@ -7330,6 +7434,115 @@ async function notifyIfOutdated(rootArg) {
|
|
|
7330
7434
|
}
|
|
7331
7435
|
}
|
|
7332
7436
|
|
|
7437
|
+
// src/cli/test.ts
|
|
7438
|
+
import fs9 from "node:fs/promises";
|
|
7439
|
+
import path10 from "node:path";
|
|
7440
|
+
import { createRequire as createRequire2 } from "node:module";
|
|
7441
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
7442
|
+
async function writeMissing(root, file, content) {
|
|
7443
|
+
const target = path10.join(root, file);
|
|
7444
|
+
await fs9.mkdir(path10.dirname(target), { recursive: true });
|
|
7445
|
+
try {
|
|
7446
|
+
await fs9.writeFile(target, content, { flag: "wx" });
|
|
7447
|
+
} catch (error) {
|
|
7448
|
+
if (!(error instanceof Error) || !("code" in error) || error.code !== "EEXIST") throw error;
|
|
7449
|
+
}
|
|
7450
|
+
}
|
|
7451
|
+
async function ensureTesting(root) {
|
|
7452
|
+
const pkgPath = path10.join(root, "package.json");
|
|
7453
|
+
const raw = JSON.parse(await fs9.readFile(pkgPath, "utf8"));
|
|
7454
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw))
|
|
7455
|
+
throw new Error("toiljs test: package.json must contain an object.");
|
|
7456
|
+
const pkg = raw;
|
|
7457
|
+
for (const field of [pkg.scripts, pkg.dependencies, pkg.devDependencies]) {
|
|
7458
|
+
if (field !== void 0 && (field === null || typeof field !== "object" || Array.isArray(field) || Object.values(field).some((value) => typeof value !== "string")))
|
|
7459
|
+
throw new Error("toiljs test: package scripts and dependencies must be string maps.");
|
|
7460
|
+
}
|
|
7461
|
+
const files = await fs9.readdir(root);
|
|
7462
|
+
const custom = files.find((file) => /^vitest\.config\.[cm]?[jt]s$/.test(file)) ?? files.find((file) => /^vite\.config\.[cm]?[jt]s$/.test(file));
|
|
7463
|
+
const source = custom === void 0 ? "" : await fs9.readFile(path10.join(root, custom), "utf8");
|
|
7464
|
+
const native = custom === void 0 || source.includes("from 'toiljs/vitest'") || source.includes('from "toiljs/vitest"');
|
|
7465
|
+
let changed = false;
|
|
7466
|
+
let install = false;
|
|
7467
|
+
const require2 = createRequire2(pkgPath);
|
|
7468
|
+
for (const [name, range] of Object.entries(TEST_DEPENDENCIES)) {
|
|
7469
|
+
if (pkg.dependencies?.[name] === void 0 && pkg.devDependencies?.[name] === void 0) {
|
|
7470
|
+
pkg.devDependencies ??= {};
|
|
7471
|
+
pkg.devDependencies[name] = range;
|
|
7472
|
+
changed = true;
|
|
7473
|
+
install = true;
|
|
7474
|
+
}
|
|
7475
|
+
try {
|
|
7476
|
+
require2.resolve(name);
|
|
7477
|
+
} catch {
|
|
7478
|
+
install = true;
|
|
7479
|
+
}
|
|
7480
|
+
}
|
|
7481
|
+
pkg.scripts ??= {};
|
|
7482
|
+
for (const [name, script] of Object.entries(TEST_SCRIPTS)) {
|
|
7483
|
+
if (pkg.scripts[name] !== void 0) continue;
|
|
7484
|
+
pkg.scripts[name] = script;
|
|
7485
|
+
changed = true;
|
|
7486
|
+
}
|
|
7487
|
+
if (changed) await fs9.writeFile(pkgPath, JSON.stringify(raw, null, 4) + "\n");
|
|
7488
|
+
if (custom === void 0) {
|
|
7489
|
+
for (const [file, content] of Object.entries(testingFiles()))
|
|
7490
|
+
await writeMissing(root, file, content);
|
|
7491
|
+
}
|
|
7492
|
+
return { install, native };
|
|
7493
|
+
}
|
|
7494
|
+
function parseTestArgs(args) {
|
|
7495
|
+
let root;
|
|
7496
|
+
let browser = false;
|
|
7497
|
+
let all = false;
|
|
7498
|
+
let watch = false;
|
|
7499
|
+
const forwarded = [];
|
|
7500
|
+
for (let index = 0; index < args.length; index++) {
|
|
7501
|
+
const arg = args[index];
|
|
7502
|
+
if (arg === "--root") {
|
|
7503
|
+
const value = args[++index];
|
|
7504
|
+
if (!value) throw new Error("toiljs test: --root requires a directory.");
|
|
7505
|
+
root = value;
|
|
7506
|
+
} else if (arg === "--browser") browser = true;
|
|
7507
|
+
else if (arg === "--all") all = true;
|
|
7508
|
+
else if (arg === "--watch" || arg === "-w") watch = true;
|
|
7509
|
+
else if (arg !== "--") forwarded.push(arg);
|
|
7510
|
+
}
|
|
7511
|
+
if (browser && all) throw new Error("toiljs test: use either --browser or --all.");
|
|
7512
|
+
return { root, browser, all, watch, arguments: forwarded };
|
|
7513
|
+
}
|
|
7514
|
+
async function runTest(options) {
|
|
7515
|
+
const root = path10.resolve(options.root ?? process.cwd());
|
|
7516
|
+
const setup = await ensureTesting(root);
|
|
7517
|
+
if (setup.install) {
|
|
7518
|
+
const pm = detectPackageManager2(root).name;
|
|
7519
|
+
process.stdout.write(
|
|
7520
|
+
`toiljs test: installing Vitest, browser testing, and V8 coverage with ${pm}.
|
|
7521
|
+
`
|
|
7522
|
+
);
|
|
7523
|
+
await run(pm, ["install"], root, { stdio: "inherit" });
|
|
7524
|
+
}
|
|
7525
|
+
const require2 = createRequire2(path10.join(root, "package.json"));
|
|
7526
|
+
const cli = path10.join(
|
|
7527
|
+
path10.dirname(require2.resolve("vitest/package.json")),
|
|
7528
|
+
"vitest.mjs"
|
|
7529
|
+
);
|
|
7530
|
+
const args = [cli, ...options.watch ? [] : ["run"]];
|
|
7531
|
+
const projectFlag = options.arguments.some(
|
|
7532
|
+
(arg) => arg === "--project" || arg.startsWith("--project=")
|
|
7533
|
+
);
|
|
7534
|
+
if (setup.native && !options.all && !projectFlag)
|
|
7535
|
+
args.push("--project", options.browser ? "browser" : "unit");
|
|
7536
|
+
if (!setup.native && options.browser) args.push("--browser");
|
|
7537
|
+
args.push(...options.arguments);
|
|
7538
|
+
const code = await new Promise((resolve, reject) => {
|
|
7539
|
+
const child = spawn2(process.execPath, args, { cwd: root, stdio: "inherit" });
|
|
7540
|
+
child.once("error", reject);
|
|
7541
|
+
child.once("exit", (status) => resolve(status ?? 1));
|
|
7542
|
+
});
|
|
7543
|
+
process.exitCode = code;
|
|
7544
|
+
}
|
|
7545
|
+
|
|
7333
7546
|
// src/cli/index.ts
|
|
7334
7547
|
var __origEmitWarning = process.emitWarning.bind(process);
|
|
7335
7548
|
process.emitWarning = ((warning, ...rest) => {
|
|
@@ -7436,6 +7649,7 @@ function printHelp() {
|
|
|
7436
7649
|
cmd("create [name]", "scaffold a new toiljs app"),
|
|
7437
7650
|
cmd("configure", "toggle styling features (Sass/Less/Stylus, Tailwind)"),
|
|
7438
7651
|
cmd("generate auth", "generate private auth keys and the matching client public key"),
|
|
7652
|
+
cmd("test", "run Vitest (auto-installs missing test tooling)"),
|
|
7439
7653
|
cmd("dev", "start the dev server with HMR"),
|
|
7440
7654
|
cmd("build", "build the optimized production bundle"),
|
|
7441
7655
|
cmd("start", "self-host the built app (hyper-express / uWS)"),
|
|
@@ -7446,7 +7660,10 @@ function printHelp() {
|
|
|
7446
7660
|
bold("Options"),
|
|
7447
7661
|
cmd("--root <dir>", "project root (default: current directory)"),
|
|
7448
7662
|
cmd("--port <n>", "dev/start: listen port (or PORT env / client.port; default 3000)"),
|
|
7449
|
-
cmd(
|
|
7663
|
+
cmd(
|
|
7664
|
+
"--host <h>",
|
|
7665
|
+
"dev/start: bind host, e.g. 0.0.0.0 (or HOST env / client.host; default 127.0.0.1)"
|
|
7666
|
+
),
|
|
7450
7667
|
cmd("--threads <n>", "start: production HTTP worker count"),
|
|
7451
7668
|
cmd("-t, --template", "create: app | minimal | agent"),
|
|
7452
7669
|
cmd("--style <name>", "create/configure: css | sass | less | stylus"),
|
|
@@ -7454,6 +7671,10 @@ function printHelp() {
|
|
|
7454
7671
|
cmd("--no-ai", "create: skip AI assistant files (CLAUDE.md, etc.)"),
|
|
7455
7672
|
cmd("-y, --yes", "create: accept defaults (non-interactive)"),
|
|
7456
7673
|
cmd("--no-install", "create: don't install dependencies"),
|
|
7674
|
+
cmd("--browser", "test: run real Chrome browser tests"),
|
|
7675
|
+
cmd("--all", "test: run unit and browser projects"),
|
|
7676
|
+
cmd("--coverage", "test: collect V8 coverage reports"),
|
|
7677
|
+
cmd("--watch", "test: watch for changes"),
|
|
7457
7678
|
cmd("--server", "build: build only the server (regenerate shared/server.ts + wasm)"),
|
|
7458
7679
|
cmd("--json", "doctor: machine-readable output"),
|
|
7459
7680
|
cmd("--fix", "doctor: auto-fix what it can (typed-RPC wiring)"),
|
|
@@ -7475,6 +7696,9 @@ async function main() {
|
|
|
7475
7696
|
const flags = parseArgs(rest);
|
|
7476
7697
|
await notifyIfOutdated(flags.root);
|
|
7477
7698
|
switch (command) {
|
|
7699
|
+
case "test":
|
|
7700
|
+
await runTest(parseTestArgs(rest));
|
|
7701
|
+
break;
|
|
7478
7702
|
case "create":
|
|
7479
7703
|
banner();
|
|
7480
7704
|
await runCreate({
|
|
@@ -7533,7 +7757,9 @@ async function main() {
|
|
|
7533
7757
|
const shownHost = wildcard || server.host === "127.0.0.1" ? "localhost" : server.host;
|
|
7534
7758
|
const exposed = server.host !== "127.0.0.1" && server.host !== "localhost" && server.host !== "::1";
|
|
7535
7759
|
process.stdout.write(
|
|
7536
|
-
accent(" \u279C ") + bold(`http://${shownHost}:${String(server.port)}`) + dim(` ws channel: ${server.wsPath}`) + (exposed ? "\n" + dim(
|
|
7760
|
+
accent(" \u279C ") + bold(`http://${shownHost}:${String(server.port)}`) + dim(` ws channel: ${server.wsPath}`) + (exposed ? "\n" + dim(
|
|
7761
|
+
` bound to ${server.host}:${String(server.port)} (exposed beyond loopback)`
|
|
7762
|
+
) : "") + "\n\n"
|
|
7537
7763
|
);
|
|
7538
7764
|
break;
|
|
7539
7765
|
}
|