toiljs 0.0.122 → 0.0.124
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 +27 -0
- package/README.md +2 -0
- package/build/backend/.tsbuildinfo +1 -1
- package/build/cli/.tsbuildinfo +1 -1
- package/build/cli/index.js +648 -179
- 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/package.json +53 -22
- package/tsconfig.testing.json +13 -0
package/build/cli/index.js
CHANGED
|
@@ -650,11 +650,11 @@ var require_valid = __commonJS({
|
|
|
650
650
|
"node_modules/semver/functions/valid.js"(exports, module) {
|
|
651
651
|
"use strict";
|
|
652
652
|
var parse = require_parse();
|
|
653
|
-
var
|
|
653
|
+
var valid2 = (version2, options) => {
|
|
654
654
|
const v = parse(version2, options);
|
|
655
655
|
return v ? v.version : null;
|
|
656
656
|
};
|
|
657
|
-
module.exports =
|
|
657
|
+
module.exports = valid2;
|
|
658
658
|
}
|
|
659
659
|
});
|
|
660
660
|
|
|
@@ -871,8 +871,8 @@ var require_eq = __commonJS({
|
|
|
871
871
|
"node_modules/semver/functions/eq.js"(exports, module) {
|
|
872
872
|
"use strict";
|
|
873
873
|
var compare = require_compare();
|
|
874
|
-
var
|
|
875
|
-
module.exports =
|
|
874
|
+
var eq2 = (a2, b, loose) => compare(a2, b, loose) === 0;
|
|
875
|
+
module.exports = eq2;
|
|
876
876
|
}
|
|
877
877
|
});
|
|
878
878
|
|
|
@@ -910,7 +910,7 @@ var require_lte = __commonJS({
|
|
|
910
910
|
var require_cmp = __commonJS({
|
|
911
911
|
"node_modules/semver/functions/cmp.js"(exports, module) {
|
|
912
912
|
"use strict";
|
|
913
|
-
var
|
|
913
|
+
var eq2 = require_eq();
|
|
914
914
|
var neq = require_neq();
|
|
915
915
|
var gt = require_gt();
|
|
916
916
|
var gte = require_gte();
|
|
@@ -937,7 +937,7 @@ var require_cmp = __commonJS({
|
|
|
937
937
|
case "":
|
|
938
938
|
case "=":
|
|
939
939
|
case "==":
|
|
940
|
-
return
|
|
940
|
+
return eq2(a2, b, loose);
|
|
941
941
|
case "!=":
|
|
942
942
|
return neq(a2, b, loose);
|
|
943
943
|
case ">":
|
|
@@ -1584,7 +1584,7 @@ var require_satisfies = __commonJS({
|
|
|
1584
1584
|
"node_modules/semver/functions/satisfies.js"(exports, module) {
|
|
1585
1585
|
"use strict";
|
|
1586
1586
|
var Range = require_range();
|
|
1587
|
-
var
|
|
1587
|
+
var satisfies4 = (version2, range, options) => {
|
|
1588
1588
|
try {
|
|
1589
1589
|
range = new Range(range, options);
|
|
1590
1590
|
} catch (er) {
|
|
@@ -1592,7 +1592,7 @@ var require_satisfies = __commonJS({
|
|
|
1592
1592
|
}
|
|
1593
1593
|
return range.test(version2);
|
|
1594
1594
|
};
|
|
1595
|
-
module.exports =
|
|
1595
|
+
module.exports = satisfies4;
|
|
1596
1596
|
}
|
|
1597
1597
|
});
|
|
1598
1598
|
|
|
@@ -1728,14 +1728,14 @@ var require_valid2 = __commonJS({
|
|
|
1728
1728
|
"node_modules/semver/ranges/valid.js"(exports, module) {
|
|
1729
1729
|
"use strict";
|
|
1730
1730
|
var Range = require_range();
|
|
1731
|
-
var
|
|
1731
|
+
var validRange4 = (range, options) => {
|
|
1732
1732
|
try {
|
|
1733
1733
|
return new Range(range, options).range || "*";
|
|
1734
1734
|
} catch (er) {
|
|
1735
1735
|
return null;
|
|
1736
1736
|
}
|
|
1737
1737
|
};
|
|
1738
|
-
module.exports =
|
|
1738
|
+
module.exports = validRange4;
|
|
1739
1739
|
}
|
|
1740
1740
|
});
|
|
1741
1741
|
|
|
@@ -1747,7 +1747,7 @@ var require_outside = __commonJS({
|
|
|
1747
1747
|
var Comparator = require_comparator();
|
|
1748
1748
|
var { ANY } = Comparator;
|
|
1749
1749
|
var Range = require_range();
|
|
1750
|
-
var
|
|
1750
|
+
var satisfies4 = require_satisfies();
|
|
1751
1751
|
var gt = require_gt();
|
|
1752
1752
|
var lt = require_lt();
|
|
1753
1753
|
var lte = require_lte();
|
|
@@ -1774,7 +1774,7 @@ var require_outside = __commonJS({
|
|
|
1774
1774
|
default:
|
|
1775
1775
|
throw new TypeError('Must provide a hilo val of "<" or ">"');
|
|
1776
1776
|
}
|
|
1777
|
-
if (
|
|
1777
|
+
if (satisfies4(version2, range, options)) {
|
|
1778
1778
|
return false;
|
|
1779
1779
|
}
|
|
1780
1780
|
for (let i2 = 0; i2 < range.set.length; ++i2) {
|
|
@@ -1833,12 +1833,12 @@ var require_intersects = __commonJS({
|
|
|
1833
1833
|
"node_modules/semver/ranges/intersects.js"(exports, module) {
|
|
1834
1834
|
"use strict";
|
|
1835
1835
|
var Range = require_range();
|
|
1836
|
-
var
|
|
1836
|
+
var intersects2 = (r1, r2, options) => {
|
|
1837
1837
|
r1 = new Range(r1, options);
|
|
1838
1838
|
r2 = new Range(r2, options);
|
|
1839
1839
|
return r1.intersects(r2, options);
|
|
1840
1840
|
};
|
|
1841
|
-
module.exports =
|
|
1841
|
+
module.exports = intersects2;
|
|
1842
1842
|
}
|
|
1843
1843
|
});
|
|
1844
1844
|
|
|
@@ -1846,7 +1846,7 @@ var require_intersects = __commonJS({
|
|
|
1846
1846
|
var require_simplify = __commonJS({
|
|
1847
1847
|
"node_modules/semver/ranges/simplify.js"(exports, module) {
|
|
1848
1848
|
"use strict";
|
|
1849
|
-
var
|
|
1849
|
+
var satisfies4 = require_satisfies();
|
|
1850
1850
|
var compare = require_compare();
|
|
1851
1851
|
module.exports = (versions, range, options) => {
|
|
1852
1852
|
const set = [];
|
|
@@ -1854,7 +1854,7 @@ var require_simplify = __commonJS({
|
|
|
1854
1854
|
let prev = null;
|
|
1855
1855
|
const v = versions.sort((a2, b) => compare(a2, b, options));
|
|
1856
1856
|
for (const version2 of v) {
|
|
1857
|
-
const included =
|
|
1857
|
+
const included = satisfies4(version2, range, options);
|
|
1858
1858
|
if (included) {
|
|
1859
1859
|
prev = version2;
|
|
1860
1860
|
if (!first) {
|
|
@@ -1899,9 +1899,9 @@ var require_subset = __commonJS({
|
|
|
1899
1899
|
var Range = require_range();
|
|
1900
1900
|
var Comparator = require_comparator();
|
|
1901
1901
|
var { ANY } = Comparator;
|
|
1902
|
-
var
|
|
1902
|
+
var satisfies4 = require_satisfies();
|
|
1903
1903
|
var compare = require_compare();
|
|
1904
|
-
var
|
|
1904
|
+
var subset3 = (sub, dom, options = {}) => {
|
|
1905
1905
|
if (sub === dom) {
|
|
1906
1906
|
return true;
|
|
1907
1907
|
}
|
|
@@ -1967,15 +1967,15 @@ var require_subset = __commonJS({
|
|
|
1967
1967
|
return null;
|
|
1968
1968
|
}
|
|
1969
1969
|
}
|
|
1970
|
-
for (const
|
|
1971
|
-
if (gt && !
|
|
1970
|
+
for (const eq2 of eqSet) {
|
|
1971
|
+
if (gt && !satisfies4(eq2, String(gt), options)) {
|
|
1972
1972
|
return null;
|
|
1973
1973
|
}
|
|
1974
|
-
if (lt && !
|
|
1974
|
+
if (lt && !satisfies4(eq2, String(lt), options)) {
|
|
1975
1975
|
return null;
|
|
1976
1976
|
}
|
|
1977
1977
|
for (const c2 of dom) {
|
|
1978
|
-
if (!
|
|
1978
|
+
if (!satisfies4(eq2, String(c2), options)) {
|
|
1979
1979
|
return false;
|
|
1980
1980
|
}
|
|
1981
1981
|
}
|
|
@@ -2050,7 +2050,7 @@ var require_subset = __commonJS({
|
|
|
2050
2050
|
const comp = compare(a2.semver, b.semver, options);
|
|
2051
2051
|
return comp < 0 ? a2 : comp > 0 ? b : b.operator === "<" && a2.operator === "<=" ? b : a2;
|
|
2052
2052
|
};
|
|
2053
|
-
module.exports =
|
|
2053
|
+
module.exports = subset3;
|
|
2054
2054
|
}
|
|
2055
2055
|
});
|
|
2056
2056
|
|
|
@@ -2063,7 +2063,7 @@ var require_semver2 = __commonJS({
|
|
|
2063
2063
|
var SemVer = require_semver();
|
|
2064
2064
|
var identifiers = require_identifiers();
|
|
2065
2065
|
var parse = require_parse();
|
|
2066
|
-
var
|
|
2066
|
+
var valid2 = require_valid();
|
|
2067
2067
|
var clean2 = require_clean();
|
|
2068
2068
|
var inc = require_inc();
|
|
2069
2069
|
var diff = require_diff();
|
|
@@ -2079,7 +2079,7 @@ var require_semver2 = __commonJS({
|
|
|
2079
2079
|
var rsort = require_rsort();
|
|
2080
2080
|
var gt = require_gt();
|
|
2081
2081
|
var lt = require_lt();
|
|
2082
|
-
var
|
|
2082
|
+
var eq2 = require_eq();
|
|
2083
2083
|
var neq = require_neq();
|
|
2084
2084
|
var gte = require_gte();
|
|
2085
2085
|
var lte = require_lte();
|
|
@@ -2088,21 +2088,21 @@ var require_semver2 = __commonJS({
|
|
|
2088
2088
|
var truncate = require_truncate();
|
|
2089
2089
|
var Comparator = require_comparator();
|
|
2090
2090
|
var Range = require_range();
|
|
2091
|
-
var
|
|
2091
|
+
var satisfies4 = require_satisfies();
|
|
2092
2092
|
var toComparators = require_to_comparators();
|
|
2093
2093
|
var maxSatisfying = require_max_satisfying();
|
|
2094
2094
|
var minSatisfying = require_min_satisfying();
|
|
2095
2095
|
var minVersion2 = require_min_version();
|
|
2096
|
-
var
|
|
2096
|
+
var validRange4 = require_valid2();
|
|
2097
2097
|
var outside = require_outside();
|
|
2098
2098
|
var gtr = require_gtr();
|
|
2099
2099
|
var ltr = require_ltr();
|
|
2100
|
-
var
|
|
2100
|
+
var intersects2 = require_intersects();
|
|
2101
2101
|
var simplifyRange = require_simplify();
|
|
2102
|
-
var
|
|
2102
|
+
var subset3 = require_subset();
|
|
2103
2103
|
module.exports = {
|
|
2104
2104
|
parse,
|
|
2105
|
-
valid,
|
|
2105
|
+
valid: valid2,
|
|
2106
2106
|
clean: clean2,
|
|
2107
2107
|
inc,
|
|
2108
2108
|
diff,
|
|
@@ -2118,7 +2118,7 @@ var require_semver2 = __commonJS({
|
|
|
2118
2118
|
rsort,
|
|
2119
2119
|
gt,
|
|
2120
2120
|
lt,
|
|
2121
|
-
eq,
|
|
2121
|
+
eq: eq2,
|
|
2122
2122
|
neq,
|
|
2123
2123
|
gte,
|
|
2124
2124
|
lte,
|
|
@@ -2127,18 +2127,18 @@ var require_semver2 = __commonJS({
|
|
|
2127
2127
|
truncate,
|
|
2128
2128
|
Comparator,
|
|
2129
2129
|
Range,
|
|
2130
|
-
satisfies:
|
|
2130
|
+
satisfies: satisfies4,
|
|
2131
2131
|
toComparators,
|
|
2132
2132
|
maxSatisfying,
|
|
2133
2133
|
minSatisfying,
|
|
2134
2134
|
minVersion: minVersion2,
|
|
2135
|
-
validRange:
|
|
2135
|
+
validRange: validRange4,
|
|
2136
2136
|
outside,
|
|
2137
2137
|
gtr,
|
|
2138
2138
|
ltr,
|
|
2139
|
-
intersects,
|
|
2139
|
+
intersects: intersects2,
|
|
2140
2140
|
simplifyRange,
|
|
2141
|
-
subset:
|
|
2141
|
+
subset: subset3,
|
|
2142
2142
|
SemVer,
|
|
2143
2143
|
re: internalRe.re,
|
|
2144
2144
|
src: internalRe.src,
|
|
@@ -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,6 +4177,7 @@ 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
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.
|
|
4098
4183
|
(template === "app" ? " server: { auth: true },\n" : "") + ` client: {
|
|
@@ -4101,13 +4186,13 @@ function scaffold(name, template, features, aiTools, images) {
|
|
|
4101
4186
|
},
|
|
4102
4187
|
});
|
|
4103
4188
|
`,
|
|
4104
|
-
"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',
|
|
4105
4190
|
"oxlint.config.ts": "import toiljs from 'toiljs/oxlint';\n\nexport default toiljs;\n",
|
|
4106
4191
|
".prettierrc": '"toiljs/prettier"\n',
|
|
4107
4192
|
// Generated files don't need formatting. (toilscript server decorators like @main /
|
|
4108
4193
|
// @remote-on-functions are handled by the toiljs/prettier-plugin, so server/ is not ignored.)
|
|
4109
|
-
".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",
|
|
4110
|
-
".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",
|
|
4111
4196
|
// Use the native TypeScript 7 language server.
|
|
4112
4197
|
".vscode/settings.json": JSON.stringify(
|
|
4113
4198
|
{
|
|
@@ -4191,6 +4276,21 @@ function scaffold(name, template, features, aiTools, images) {
|
|
|
4191
4276
|
"",
|
|
4192
4277
|
" npm run build",
|
|
4193
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
|
+
"",
|
|
4194
4294
|
"## Editor setup",
|
|
4195
4295
|
"",
|
|
4196
4296
|
"Run `npm install` first and use Node 26 for editor tooling.",
|
|
@@ -4425,9 +4525,9 @@ async function runCreate(opts2) {
|
|
|
4425
4525
|
name = answer.trim() || "my-toil-app";
|
|
4426
4526
|
}
|
|
4427
4527
|
}
|
|
4428
|
-
const
|
|
4429
|
-
if (
|
|
4430
|
-
cancel(
|
|
4528
|
+
const valid2 = isValidName(name);
|
|
4529
|
+
if (valid2 !== true) {
|
|
4530
|
+
cancel(valid2);
|
|
4431
4531
|
process.exit(1);
|
|
4432
4532
|
}
|
|
4433
4533
|
const targetDir = resolveProjectDir(opts2.cwd, name);
|
|
@@ -4602,6 +4702,8 @@ async function runCreate(opts2) {
|
|
|
4602
4702
|
if (!install) steps.push("npm install");
|
|
4603
4703
|
steps.push(`${accent("npm run dev")} ${dim("start the dev server")}`);
|
|
4604
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")}`);
|
|
4605
4707
|
steps.push("Install the Oxc plugin for VS Code or WebStorm (README.md \u2192 Editor setup)");
|
|
4606
4708
|
steps.push("WebStorm: Settings \u2192 Tools \u2192 Oxlint \u2192 Enable type aware rules");
|
|
4607
4709
|
note(steps.map((l2) => dim(" ") + l2).join("\n"), "Next steps");
|
|
@@ -4736,9 +4838,9 @@ function runDb(action, fileArg, opts2) {
|
|
|
4736
4838
|
}
|
|
4737
4839
|
|
|
4738
4840
|
// src/cli/doctor.ts
|
|
4739
|
-
import
|
|
4740
|
-
import { createRequire } from "node:module";
|
|
4741
|
-
import
|
|
4841
|
+
import fs6 from "node:fs";
|
|
4842
|
+
import { createRequire as createRequire2 } from "node:module";
|
|
4843
|
+
import path7 from "node:path";
|
|
4742
4844
|
import { fileURLToPath as fileURLToPath3, pathToFileURL } from "node:url";
|
|
4743
4845
|
import {
|
|
4744
4846
|
loadConfig as loadConfig2,
|
|
@@ -5148,10 +5250,240 @@ function hasFailures(summary) {
|
|
|
5148
5250
|
return summary.fail > 0;
|
|
5149
5251
|
}
|
|
5150
5252
|
|
|
5253
|
+
// src/cli/test-setup.ts
|
|
5254
|
+
import fs5 from "node:fs/promises";
|
|
5255
|
+
import path6 from "node:path";
|
|
5256
|
+
import { createRequire } from "node:module";
|
|
5257
|
+
var import_semver3 = __toESM(require_semver2(), 1);
|
|
5258
|
+
|
|
5259
|
+
// src/cli/testing-versions.ts
|
|
5260
|
+
var import_semver2 = __toESM(require_semver2(), 1);
|
|
5261
|
+
function isSupportedTestingRange(name, range) {
|
|
5262
|
+
const supported = TEST_DEPENDENCIES[name];
|
|
5263
|
+
return supported === void 0 || (0, import_semver2.validRange)(range) !== null && (0, import_semver2.subset)(range, supported);
|
|
5264
|
+
}
|
|
5265
|
+
function isTestingSource(range) {
|
|
5266
|
+
return /^(file:|workspace:|link:|git[+:]|https?:|github:|npm:)/.test(range);
|
|
5267
|
+
}
|
|
5268
|
+
|
|
5269
|
+
// src/cli/test-setup.ts
|
|
5270
|
+
async function findTestConfiguration(root) {
|
|
5271
|
+
const files = await fs5.readdir(root);
|
|
5272
|
+
const custom = files.find((file) => /^vitest\.config\.[cm]?[jt]s$/.test(file)) ?? files.find((file) => /^vite\.config\.[cm]?[jt]s$/.test(file));
|
|
5273
|
+
const source = custom === void 0 ? "" : await fs5.readFile(path6.join(root, custom), "utf8");
|
|
5274
|
+
const native = custom === void 0 || source.includes("from 'toiljs/vitest'") || source.includes('from "toiljs/vitest"');
|
|
5275
|
+
return { file: custom ?? null, native };
|
|
5276
|
+
}
|
|
5277
|
+
async function writeMissing(root, file, content) {
|
|
5278
|
+
const target = path6.join(root, file);
|
|
5279
|
+
await fs5.mkdir(path6.dirname(target), { recursive: true });
|
|
5280
|
+
try {
|
|
5281
|
+
await fs5.writeFile(target, content, { flag: "wx" });
|
|
5282
|
+
return true;
|
|
5283
|
+
} catch (error) {
|
|
5284
|
+
if (!(error instanceof Error) || !("code" in error) || error.code !== "EEXIST") throw error;
|
|
5285
|
+
return false;
|
|
5286
|
+
}
|
|
5287
|
+
}
|
|
5288
|
+
async function ensureTesting(root, repairVersions = false) {
|
|
5289
|
+
const pkgPath = path6.join(root, "package.json");
|
|
5290
|
+
const raw = JSON.parse(await fs5.readFile(pkgPath, "utf8"));
|
|
5291
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw))
|
|
5292
|
+
throw new Error("toiljs test: package.json must contain an object.");
|
|
5293
|
+
const pkg = raw;
|
|
5294
|
+
for (const field of [pkg.scripts, pkg.dependencies, pkg.devDependencies]) {
|
|
5295
|
+
if (field !== void 0 && (field === null || typeof field !== "object" || Array.isArray(field) || Object.values(field).some((value) => typeof value !== "string")))
|
|
5296
|
+
throw new Error("toiljs test: package scripts and dependencies must be string maps.");
|
|
5297
|
+
}
|
|
5298
|
+
const config = await findTestConfiguration(root);
|
|
5299
|
+
const { native } = config;
|
|
5300
|
+
const changes = [];
|
|
5301
|
+
const skipped = [];
|
|
5302
|
+
let changed = false;
|
|
5303
|
+
let install = false;
|
|
5304
|
+
const require2 = createRequire(pkgPath);
|
|
5305
|
+
for (const [name, range] of Object.entries(TEST_DEPENDENCIES)) {
|
|
5306
|
+
for (const dependencies of [pkg.dependencies, pkg.devDependencies]) {
|
|
5307
|
+
const declared = dependencies?.[name];
|
|
5308
|
+
if (repairVersions && dependencies && declared && !isSupportedTestingRange(name, declared) && !isTestingSource(declared)) {
|
|
5309
|
+
dependencies[name] = range;
|
|
5310
|
+
changes.push(`package.json (${name} ${declared} -> ${range})`);
|
|
5311
|
+
changed = true;
|
|
5312
|
+
install = true;
|
|
5313
|
+
}
|
|
5314
|
+
}
|
|
5315
|
+
if (pkg.dependencies?.[name] === void 0 && pkg.devDependencies?.[name] === void 0) {
|
|
5316
|
+
pkg.devDependencies ??= {};
|
|
5317
|
+
pkg.devDependencies[name] = range;
|
|
5318
|
+
changes.push(`package.json (add ${name})`);
|
|
5319
|
+
changed = true;
|
|
5320
|
+
install = true;
|
|
5321
|
+
}
|
|
5322
|
+
try {
|
|
5323
|
+
require2.resolve(name);
|
|
5324
|
+
} catch {
|
|
5325
|
+
install = true;
|
|
5326
|
+
}
|
|
5327
|
+
}
|
|
5328
|
+
if (repairVersions) {
|
|
5329
|
+
const runnerRange = pkg.dependencies?.vitest ?? pkg.devDependencies?.vitest;
|
|
5330
|
+
const coverageDependencies = pkg.dependencies?.["@vitest/coverage-v8"] !== void 0 ? pkg.dependencies : pkg.devDependencies;
|
|
5331
|
+
if (runnerRange && isSupportedTestingRange("vitest", runnerRange) && coverageDependencies && (0, import_semver3.validRange)(coverageDependencies["@vitest/coverage-v8"] ?? "") !== null) {
|
|
5332
|
+
const installedRunner = await testVersion(require2, "vitest");
|
|
5333
|
+
const installedCoverage = await testVersion(
|
|
5334
|
+
require2,
|
|
5335
|
+
"@vitest/coverage-v8"
|
|
5336
|
+
);
|
|
5337
|
+
const target = installedRunner && installedCoverage && installedRunner !== installedCoverage && (0, import_semver3.satisfies)(installedRunner, runnerRange) ? installedRunner : (0, import_semver3.intersects)(coverageDependencies["@vitest/coverage-v8"], runnerRange) ? coverageDependencies["@vitest/coverage-v8"] : runnerRange;
|
|
5338
|
+
if (coverageDependencies["@vitest/coverage-v8"] !== target) {
|
|
5339
|
+
coverageDependencies["@vitest/coverage-v8"] = target;
|
|
5340
|
+
changes.push("package.json (align V8 coverage with Vitest)");
|
|
5341
|
+
changed = true;
|
|
5342
|
+
install = true;
|
|
5343
|
+
}
|
|
5344
|
+
if (installedRunner && installedCoverage && installedRunner !== installedCoverage)
|
|
5345
|
+
install = true;
|
|
5346
|
+
}
|
|
5347
|
+
}
|
|
5348
|
+
pkg.scripts ??= {};
|
|
5349
|
+
for (const [name, script] of Object.entries(TEST_SCRIPTS)) {
|
|
5350
|
+
if (pkg.scripts[name]?.trim()) continue;
|
|
5351
|
+
pkg.scripts[name] = script;
|
|
5352
|
+
changes.push(`package.json (add script ${name})`);
|
|
5353
|
+
changed = true;
|
|
5354
|
+
}
|
|
5355
|
+
if (changed) await fs5.writeFile(pkgPath, JSON.stringify(raw, null, 4) + "\n");
|
|
5356
|
+
if (config.file === null) {
|
|
5357
|
+
for (const [file, content] of Object.entries(testingFiles()))
|
|
5358
|
+
if (await writeMissing(root, file, content)) changes.push(file);
|
|
5359
|
+
}
|
|
5360
|
+
if (native) await includeTestTypes(root, config.file ?? "vitest.config.ts", changes, skipped);
|
|
5361
|
+
await ignoreTestReports(root, changes);
|
|
5362
|
+
return { install, native, changed: changes, skipped };
|
|
5363
|
+
}
|
|
5364
|
+
async function includeTestTypes(root, config, changes, skipped) {
|
|
5365
|
+
const file = path6.join(root, "tsconfig.json");
|
|
5366
|
+
let source;
|
|
5367
|
+
try {
|
|
5368
|
+
source = await fs5.readFile(file, "utf8");
|
|
5369
|
+
} catch (error) {
|
|
5370
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") return;
|
|
5371
|
+
throw error;
|
|
5372
|
+
}
|
|
5373
|
+
try {
|
|
5374
|
+
const raw = JSON.parse(source);
|
|
5375
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw))
|
|
5376
|
+
throw new Error("Expected an object");
|
|
5377
|
+
const parsed = raw;
|
|
5378
|
+
if (!isStringArray(parsed.include)) {
|
|
5379
|
+
skipped.push(
|
|
5380
|
+
"tsconfig.json: include tests and the Vitest config in your existing file selection."
|
|
5381
|
+
);
|
|
5382
|
+
return;
|
|
5383
|
+
}
|
|
5384
|
+
const include = parsed.include;
|
|
5385
|
+
const missing = ["tests", config].filter(
|
|
5386
|
+
(entry) => !include.includes(entry)
|
|
5387
|
+
);
|
|
5388
|
+
if (!missing.length) return;
|
|
5389
|
+
include.push(...missing);
|
|
5390
|
+
await fs5.writeFile(file, JSON.stringify(parsed, null, 4) + "\n");
|
|
5391
|
+
changes.push("tsconfig.json (include tests and Vitest config)");
|
|
5392
|
+
} catch {
|
|
5393
|
+
skipped.push(
|
|
5394
|
+
"tsconfig.json: include tests and the Vitest config manually; custom JSON was preserved."
|
|
5395
|
+
);
|
|
5396
|
+
}
|
|
5397
|
+
}
|
|
5398
|
+
async function ignoreTestReports(root, changes) {
|
|
5399
|
+
const file = path6.join(root, ".gitignore");
|
|
5400
|
+
let source = "";
|
|
5401
|
+
try {
|
|
5402
|
+
source = await fs5.readFile(file, "utf8");
|
|
5403
|
+
} catch (error) {
|
|
5404
|
+
if (!(error instanceof Error) || !("code" in error) || error.code !== "ENOENT") throw error;
|
|
5405
|
+
}
|
|
5406
|
+
const lines = source.split(/\r?\n/).map((line) => line.trim().replace(/\/$/, ""));
|
|
5407
|
+
const missing = ["coverage", ".vitest"].filter(
|
|
5408
|
+
(entry) => !lines.includes(entry) && !lines.includes("/" + entry)
|
|
5409
|
+
);
|
|
5410
|
+
if (!missing.length) return;
|
|
5411
|
+
await fs5.writeFile(
|
|
5412
|
+
file,
|
|
5413
|
+
source + (source && !source.endsWith("\n") ? "\n" : "") + missing.join("\n") + "\n"
|
|
5414
|
+
);
|
|
5415
|
+
changes.push(".gitignore (test reports)");
|
|
5416
|
+
}
|
|
5417
|
+
async function testVersion(require2, name) {
|
|
5418
|
+
try {
|
|
5419
|
+
const raw = JSON.parse(
|
|
5420
|
+
await fs5.readFile(require2.resolve(`${name}/package.json`), "utf8")
|
|
5421
|
+
);
|
|
5422
|
+
return raw !== null && typeof raw === "object" && "version" in raw && typeof raw.version === "string" ? raw.version : null;
|
|
5423
|
+
} catch {
|
|
5424
|
+
return null;
|
|
5425
|
+
}
|
|
5426
|
+
}
|
|
5427
|
+
function isStringArray(value) {
|
|
5428
|
+
return Array.isArray(value) && value.every((entry) => typeof entry === "string");
|
|
5429
|
+
}
|
|
5430
|
+
|
|
5431
|
+
// src/cli/testing-checks.ts
|
|
5432
|
+
var import_semver4 = __toESM(require_semver2(), 1);
|
|
5433
|
+
function checkTesting(facts) {
|
|
5434
|
+
const configured = Object.keys(TEST_DEPENDENCIES).some(
|
|
5435
|
+
(name) => facts.dependencies[name] !== void 0
|
|
5436
|
+
) || facts.configuration.file?.startsWith("vitest.config.") === true;
|
|
5437
|
+
const repair = "Run `toiljs doctor --fix`, then your package manager install (or `toiljs test` to install missing tooling).";
|
|
5438
|
+
const checks = Object.entries(TEST_DEPENDENCIES).map(([name, range]) => {
|
|
5439
|
+
const installed = facts.installed[name] ?? null;
|
|
5440
|
+
const declared = facts.dependencies[name];
|
|
5441
|
+
const ok = installed !== null && declared !== void 0 && (0, import_semver4.satisfies)(installed, range) && (isSupportedTestingRange(name, declared) || isTestingSource(declared));
|
|
5442
|
+
return {
|
|
5443
|
+
id: `testing:${name}`,
|
|
5444
|
+
label: name,
|
|
5445
|
+
status: ok ? "pass" : configured ? "fail" : "warn",
|
|
5446
|
+
detail: `${installed ?? "not installed"}; declared ${declared ?? "missing"} (requires ${range})`,
|
|
5447
|
+
fix: ok ? void 0 : repair
|
|
5448
|
+
};
|
|
5449
|
+
});
|
|
5450
|
+
const runner = facts.installed.vitest ?? null;
|
|
5451
|
+
const coverage = facts.installed["@vitest/coverage-v8"] ?? null;
|
|
5452
|
+
if (runner !== null && coverage !== null) {
|
|
5453
|
+
const match = (0, import_semver4.valid)(runner) !== null && (0, import_semver4.valid)(coverage) !== null && (0, import_semver4.eq)(runner, coverage);
|
|
5454
|
+
checks.push({
|
|
5455
|
+
id: "testing:coverage-match",
|
|
5456
|
+
label: "Vitest / V8 coverage versions",
|
|
5457
|
+
status: match ? "pass" : "fail",
|
|
5458
|
+
detail: `vitest ${runner}, @vitest/coverage-v8 ${coverage}`,
|
|
5459
|
+
fix: match ? void 0 : "Install matching versions of vitest and @vitest/coverage-v8; run `toiljs update --yes` to refresh both."
|
|
5460
|
+
});
|
|
5461
|
+
}
|
|
5462
|
+
const configPresent = facts.configuration.file !== null;
|
|
5463
|
+
checks.push({
|
|
5464
|
+
id: "testing:config",
|
|
5465
|
+
label: "Vitest configuration",
|
|
5466
|
+
status: configPresent ? "pass" : "warn",
|
|
5467
|
+
detail: configPresent ? `${facts.configuration.file} (${facts.configuration.native ? "toiljs preset" : "custom, preserved"})` : "missing",
|
|
5468
|
+
fix: configPresent ? void 0 : repair
|
|
5469
|
+
});
|
|
5470
|
+
const missing = Object.keys(TEST_SCRIPTS).filter(
|
|
5471
|
+
(name) => !facts.scripts[name]?.trim()
|
|
5472
|
+
);
|
|
5473
|
+
checks.push({
|
|
5474
|
+
id: "testing:scripts",
|
|
5475
|
+
label: "Unit, browser, watch, and coverage scripts",
|
|
5476
|
+
status: missing.length ? "warn" : "pass",
|
|
5477
|
+
detail: missing.length ? `missing ${missing.join(", ")}` : "configured",
|
|
5478
|
+
fix: missing.length ? repair : void 0
|
|
5479
|
+
});
|
|
5480
|
+
return checks;
|
|
5481
|
+
}
|
|
5482
|
+
|
|
5151
5483
|
// src/cli/doctor.ts
|
|
5152
5484
|
function readJsonObject(file) {
|
|
5153
5485
|
try {
|
|
5154
|
-
const parsed = JSON.parse(
|
|
5486
|
+
const parsed = JSON.parse(fs6.readFileSync(file, "utf8"));
|
|
5155
5487
|
return typeof parsed === "object" && parsed !== null ? parsed : null;
|
|
5156
5488
|
} catch {
|
|
5157
5489
|
return null;
|
|
@@ -5165,16 +5497,16 @@ function stringRecord(value) {
|
|
|
5165
5497
|
}
|
|
5166
5498
|
function readFile(file) {
|
|
5167
5499
|
try {
|
|
5168
|
-
return
|
|
5500
|
+
return fs6.readFileSync(file, "utf8");
|
|
5169
5501
|
} catch {
|
|
5170
5502
|
return null;
|
|
5171
5503
|
}
|
|
5172
5504
|
}
|
|
5173
5505
|
function writeFile(file, content) {
|
|
5174
|
-
|
|
5506
|
+
fs6.writeFileSync(file, content);
|
|
5175
5507
|
}
|
|
5176
5508
|
function isPackageInstalled(root, name) {
|
|
5177
|
-
const require2 =
|
|
5509
|
+
const require2 = createRequire2(path7.join(root, "package.json"));
|
|
5178
5510
|
for (const id of [`${name}/package.json`, name]) {
|
|
5179
5511
|
try {
|
|
5180
5512
|
require2.resolve(id);
|
|
@@ -5183,22 +5515,22 @@ function isPackageInstalled(root, name) {
|
|
|
5183
5515
|
}
|
|
5184
5516
|
}
|
|
5185
5517
|
for (let dir = root; ; ) {
|
|
5186
|
-
if (
|
|
5187
|
-
const parent =
|
|
5518
|
+
if (fs6.existsSync(path7.join(dir, "node_modules", name, "package.json"))) return true;
|
|
5519
|
+
const parent = path7.dirname(dir);
|
|
5188
5520
|
if (parent === dir) return false;
|
|
5189
5521
|
dir = parent;
|
|
5190
5522
|
}
|
|
5191
5523
|
}
|
|
5192
5524
|
function installedVersion(root, name) {
|
|
5193
|
-
const require2 =
|
|
5525
|
+
const require2 = createRequire2(path7.join(root, "package.json"));
|
|
5194
5526
|
let pkgPath = null;
|
|
5195
5527
|
try {
|
|
5196
5528
|
pkgPath = require2.resolve(`${name}/package.json`);
|
|
5197
5529
|
} catch {
|
|
5198
5530
|
for (let dir = root; pkgPath === null; ) {
|
|
5199
|
-
const candidate =
|
|
5200
|
-
if (
|
|
5201
|
-
const parent =
|
|
5531
|
+
const candidate = path7.join(dir, "node_modules", name, "package.json");
|
|
5532
|
+
if (fs6.existsSync(candidate)) pkgPath = candidate;
|
|
5533
|
+
const parent = path7.dirname(dir);
|
|
5202
5534
|
if (parent === dir) break;
|
|
5203
5535
|
dir = parent;
|
|
5204
5536
|
}
|
|
@@ -5210,7 +5542,7 @@ function installedVersion(root, name) {
|
|
|
5210
5542
|
function applyTypeScriptFix(root) {
|
|
5211
5543
|
const changed = [];
|
|
5212
5544
|
const skipped = [];
|
|
5213
|
-
const pkgPath =
|
|
5545
|
+
const pkgPath = path7.join(root, "package.json");
|
|
5214
5546
|
const pkg = readJsonObject(pkgPath);
|
|
5215
5547
|
if (pkg === null) return { changed, skipped };
|
|
5216
5548
|
let declared = false;
|
|
@@ -5248,14 +5580,14 @@ function looksLikeSemverRange(range) {
|
|
|
5248
5580
|
return /^\s*[v^~>=<]*\s*\d+\.\d+/.test(range);
|
|
5249
5581
|
}
|
|
5250
5582
|
function gatherRpcFacts(root) {
|
|
5251
|
-
const pkg = readJsonObject(
|
|
5583
|
+
const pkg = readJsonObject(path7.join(root, "package.json"));
|
|
5252
5584
|
const scripts = pkg ? stringRecord(pkg.scripts) : {};
|
|
5253
5585
|
const deps = {
|
|
5254
5586
|
...pkg ? stringRecord(pkg.dependencies) : {},
|
|
5255
5587
|
...pkg ? stringRecord(pkg.devDependencies) : {}
|
|
5256
5588
|
};
|
|
5257
|
-
const tsconfig = readJsonObject(
|
|
5258
|
-
const gitignore = readFile(
|
|
5589
|
+
const tsconfig = readJsonObject(path7.join(root, "tsconfig.json"));
|
|
5590
|
+
const gitignore = readFile(path7.join(root, ".gitignore"));
|
|
5259
5591
|
const buildServerWired = [scripts["build:server"], scripts["build"]].some(
|
|
5260
5592
|
(s) => typeof s === "string" && (s.includes("--rpcModule") || /\btoiljs\s+build\b/.test(s))
|
|
5261
5593
|
);
|
|
@@ -5274,7 +5606,7 @@ function gatherRpcFacts(root) {
|
|
|
5274
5606
|
function serverSources(root, toilconfig) {
|
|
5275
5607
|
const dirs = /* @__PURE__ */ new Set();
|
|
5276
5608
|
const entries = Array.isArray(toilconfig?.entries) ? toilconfig.entries.filter((e) => typeof e === "string") : [];
|
|
5277
|
-
for (const e of entries) dirs.add(
|
|
5609
|
+
for (const e of entries) dirs.add(path7.dirname(path7.resolve(root, e)));
|
|
5278
5610
|
const out2 = [];
|
|
5279
5611
|
const cap = 200;
|
|
5280
5612
|
const maxDepth = 16;
|
|
@@ -5282,13 +5614,13 @@ function serverSources(root, toilconfig) {
|
|
|
5282
5614
|
if (out2.length >= cap || depth > maxDepth) return;
|
|
5283
5615
|
let listing;
|
|
5284
5616
|
try {
|
|
5285
|
-
listing =
|
|
5617
|
+
listing = fs6.readdirSync(current, { withFileTypes: true });
|
|
5286
5618
|
} catch {
|
|
5287
5619
|
return;
|
|
5288
5620
|
}
|
|
5289
5621
|
for (const entry of listing) {
|
|
5290
5622
|
if (out2.length >= cap) break;
|
|
5291
|
-
const full =
|
|
5623
|
+
const full = path7.join(current, entry.name);
|
|
5292
5624
|
if (entry.isDirectory()) {
|
|
5293
5625
|
if (entry.name !== "node_modules") visit(full, depth + 1);
|
|
5294
5626
|
} else if (entry.name.endsWith(".ts") && !entry.name.endsWith(".d.ts")) {
|
|
@@ -5312,7 +5644,7 @@ function gatherRestFacts(root, toilconfig) {
|
|
|
5312
5644
|
return { hasControllers, dispatched };
|
|
5313
5645
|
}
|
|
5314
5646
|
function secretDefined(root, key) {
|
|
5315
|
-
const raw = readFile(
|
|
5647
|
+
const raw = readFile(path7.join(root, ".env.secrets"));
|
|
5316
5648
|
if (raw === null) return false;
|
|
5317
5649
|
return new RegExp(`^\\s*(?:export\\s+)?${key}\\s*=\\s*\\S`, "m").test(raw);
|
|
5318
5650
|
}
|
|
@@ -5329,21 +5661,21 @@ function gatherAuthFacts(root, toilconfig) {
|
|
|
5329
5661
|
function serverTsconfigPath(root, toilconfig) {
|
|
5330
5662
|
const dirs = /* @__PURE__ */ new Set();
|
|
5331
5663
|
const entries = Array.isArray(toilconfig?.entries) ? toilconfig.entries.filter((e) => typeof e === "string") : [];
|
|
5332
|
-
for (const e of entries) dirs.add(
|
|
5333
|
-
if (dirs.size === 0) dirs.add(
|
|
5664
|
+
for (const e of entries) dirs.add(path7.dirname(path7.resolve(root, e)));
|
|
5665
|
+
if (dirs.size === 0) dirs.add(path7.join(root, "server"));
|
|
5334
5666
|
for (const dir of dirs) {
|
|
5335
|
-
const p =
|
|
5336
|
-
if (
|
|
5667
|
+
const p = path7.join(dir, "tsconfig.json");
|
|
5668
|
+
if (fs6.existsSync(p)) return p;
|
|
5337
5669
|
}
|
|
5338
5670
|
return null;
|
|
5339
5671
|
}
|
|
5340
5672
|
function serverMigrationsExist(root, toilconfig) {
|
|
5341
5673
|
const dirs = /* @__PURE__ */ new Set();
|
|
5342
5674
|
const entries = Array.isArray(toilconfig?.entries) ? toilconfig.entries.filter((e) => typeof e === "string") : [];
|
|
5343
|
-
for (const e of entries) dirs.add(
|
|
5344
|
-
if (dirs.size === 0) dirs.add(
|
|
5675
|
+
for (const e of entries) dirs.add(path7.dirname(path7.resolve(root, e)));
|
|
5676
|
+
if (dirs.size === 0) dirs.add(path7.join(root, "server"));
|
|
5345
5677
|
for (const dir of dirs) {
|
|
5346
|
-
if (
|
|
5678
|
+
if (fs6.existsSync(path7.join(dir, "migrations"))) return true;
|
|
5347
5679
|
}
|
|
5348
5680
|
return false;
|
|
5349
5681
|
}
|
|
@@ -5358,7 +5690,7 @@ function tsconfigHasToilPlugin(tsconfig) {
|
|
|
5358
5690
|
function applyRpcFix(root) {
|
|
5359
5691
|
const changed = [];
|
|
5360
5692
|
const skipped = [];
|
|
5361
|
-
const pkgPath =
|
|
5693
|
+
const pkgPath = path7.join(root, "package.json");
|
|
5362
5694
|
const pkgRaw = readFile(pkgPath);
|
|
5363
5695
|
const pkg = pkgRaw !== null ? readJsonObject(pkgPath) : null;
|
|
5364
5696
|
if (pkg !== null) {
|
|
@@ -5397,7 +5729,7 @@ function applyRpcFix(root) {
|
|
|
5397
5729
|
} else if (pkgRaw !== null) {
|
|
5398
5730
|
skipped.push("package.json (unparseable)");
|
|
5399
5731
|
}
|
|
5400
|
-
const tsPath =
|
|
5732
|
+
const tsPath = path7.join(root, "tsconfig.json");
|
|
5401
5733
|
const tsRaw = readFile(tsPath);
|
|
5402
5734
|
const tsconfig = tsRaw !== null ? readJsonObject(tsPath) : null;
|
|
5403
5735
|
if (tsconfig !== null) {
|
|
@@ -5436,7 +5768,7 @@ function applyRpcFix(root) {
|
|
|
5436
5768
|
} else if (tsRaw !== null) {
|
|
5437
5769
|
skipped.push('tsconfig.json (JSON with comments, add "shared" + paths by hand)');
|
|
5438
5770
|
}
|
|
5439
|
-
const giPath =
|
|
5771
|
+
const giPath = path7.join(root, ".gitignore");
|
|
5440
5772
|
const giRaw = readFile(giPath);
|
|
5441
5773
|
if (giRaw === null) {
|
|
5442
5774
|
writeFile(giPath, `${RPC_GITIGNORE_LINE}
|
|
@@ -5448,21 +5780,21 @@ function applyRpcFix(root) {
|
|
|
5448
5780
|
`);
|
|
5449
5781
|
changed.push(".gitignore");
|
|
5450
5782
|
}
|
|
5451
|
-
const serverToilconfig = readJsonObject(
|
|
5783
|
+
const serverToilconfig = readJsonObject(path7.join(root, "toilconfig.json"));
|
|
5452
5784
|
if (serverToilconfig !== null) {
|
|
5453
5785
|
const entries = Array.isArray(serverToilconfig.entries) ? serverToilconfig.entries.filter(
|
|
5454
5786
|
(e) => typeof e === "string"
|
|
5455
5787
|
) : [];
|
|
5456
5788
|
const dirs = /* @__PURE__ */ new Set();
|
|
5457
|
-
for (const e of entries) dirs.add(
|
|
5458
|
-
if (dirs.size === 0) dirs.add(
|
|
5789
|
+
for (const e of entries) dirs.add(path7.dirname(path7.resolve(root, e)));
|
|
5790
|
+
if (dirs.size === 0) dirs.add(path7.join(root, "server"));
|
|
5459
5791
|
for (const dir of dirs) {
|
|
5460
|
-
const envPath =
|
|
5792
|
+
const envPath = path7.join(dir, "toil-server-env.d.ts");
|
|
5461
5793
|
if (readFile(envPath) !== TOIL_SERVER_ENV_DTS2) {
|
|
5462
5794
|
try {
|
|
5463
|
-
|
|
5795
|
+
fs6.mkdirSync(dir, { recursive: true });
|
|
5464
5796
|
writeFile(envPath, TOIL_SERVER_ENV_DTS2);
|
|
5465
|
-
changed.push(
|
|
5797
|
+
changed.push(path7.relative(root, envPath));
|
|
5466
5798
|
} catch {
|
|
5467
5799
|
}
|
|
5468
5800
|
}
|
|
@@ -5492,7 +5824,7 @@ function prettierPluginPresent(root, pkg) {
|
|
|
5492
5824
|
return true;
|
|
5493
5825
|
}
|
|
5494
5826
|
for (const name of PRETTIER_CONFIG_FILES) {
|
|
5495
|
-
const raw = readFile(
|
|
5827
|
+
const raw = readFile(path7.join(root, name));
|
|
5496
5828
|
if (raw !== null && PRETTIER_MENTION.test(raw)) return true;
|
|
5497
5829
|
}
|
|
5498
5830
|
return false;
|
|
@@ -5501,7 +5833,7 @@ function applyPrettierFix(root, pkg) {
|
|
|
5501
5833
|
const changed = [];
|
|
5502
5834
|
const skipped = [];
|
|
5503
5835
|
if (prettierPluginPresent(root, pkg)) return { changed, skipped };
|
|
5504
|
-
const pkgPath =
|
|
5836
|
+
const pkgPath = path7.join(root, "package.json");
|
|
5505
5837
|
const pkgConfig = pkg ? asRecord(pkg.prettier) : null;
|
|
5506
5838
|
if (pkgConfig !== null) {
|
|
5507
5839
|
const full = readJsonObject(pkgPath);
|
|
@@ -5517,7 +5849,7 @@ function applyPrettierFix(root, pkg) {
|
|
|
5517
5849
|
}
|
|
5518
5850
|
}
|
|
5519
5851
|
for (const name of [".prettierrc", ".prettierrc.json"]) {
|
|
5520
|
-
const filePath =
|
|
5852
|
+
const filePath = path7.join(root, name);
|
|
5521
5853
|
const raw = readFile(filePath);
|
|
5522
5854
|
if (raw === null) continue;
|
|
5523
5855
|
const obj = readJsonObject(filePath);
|
|
@@ -5533,13 +5865,13 @@ function applyPrettierFix(root, pkg) {
|
|
|
5533
5865
|
changed.push(name);
|
|
5534
5866
|
return { changed, skipped };
|
|
5535
5867
|
}
|
|
5536
|
-
const jsConfig = PRETTIER_CONFIG_FILES.find((name) => readFile(
|
|
5868
|
+
const jsConfig = PRETTIER_CONFIG_FILES.find((name) => readFile(path7.join(root, name)) !== null);
|
|
5537
5869
|
if (jsConfig) {
|
|
5538
5870
|
skipped.push(`${jsConfig} (add "${PRETTIER_PLUGIN}" to its plugins by hand)`);
|
|
5539
5871
|
return { changed, skipped };
|
|
5540
5872
|
}
|
|
5541
5873
|
writeFile(
|
|
5542
|
-
|
|
5874
|
+
path7.join(root, ".prettierrc.json"),
|
|
5543
5875
|
JSON.stringify({ plugins: [PRETTIER_PLUGIN] }, null, 4) + "\n"
|
|
5544
5876
|
);
|
|
5545
5877
|
changed.push(".prettierrc.json");
|
|
@@ -5553,7 +5885,7 @@ function applyServerEditorFix(root, toilconfig) {
|
|
|
5553
5885
|
const parsed = readJsonObject(tsPath);
|
|
5554
5886
|
if (parsed === null)
|
|
5555
5887
|
skipped.push(
|
|
5556
|
-
`${
|
|
5888
|
+
`${path7.relative(root, tsPath)} (remove legacy ts-plugin from JSONC manually)`
|
|
5557
5889
|
);
|
|
5558
5890
|
else if (tsconfigHasToilPlugin(parsed)) {
|
|
5559
5891
|
const co = asRecord(parsed.compilerOptions);
|
|
@@ -5565,12 +5897,12 @@ function applyServerEditorFix(root, toilconfig) {
|
|
|
5565
5897
|
if (plugins.length === 0) delete co.plugins;
|
|
5566
5898
|
else co.plugins = plugins;
|
|
5567
5899
|
writeFile(tsPath, JSON.stringify(parsed, null, 4) + "\n");
|
|
5568
|
-
changed.push(
|
|
5900
|
+
changed.push(path7.relative(root, tsPath));
|
|
5569
5901
|
}
|
|
5570
5902
|
}
|
|
5571
5903
|
}
|
|
5572
|
-
const vsPath =
|
|
5573
|
-
const vs =
|
|
5904
|
+
const vsPath = path7.join(root, ".vscode/settings.json");
|
|
5905
|
+
const vs = fs6.existsSync(vsPath) ? readJsonObject(vsPath) : {};
|
|
5574
5906
|
if (vs === null)
|
|
5575
5907
|
skipped.push(".vscode/settings.json (enable js/ts.experimental.useTsgo manually)");
|
|
5576
5908
|
else {
|
|
@@ -5579,7 +5911,7 @@ function applyServerEditorFix(root, toilconfig) {
|
|
|
5579
5911
|
delete vs["typescript.enablePromptUseWorkspaceTsdk"];
|
|
5580
5912
|
vs["js/ts.experimental.useTsgo"] = true;
|
|
5581
5913
|
if (JSON.stringify(vs) !== before) {
|
|
5582
|
-
|
|
5914
|
+
fs6.mkdirSync(path7.dirname(vsPath), { recursive: true });
|
|
5583
5915
|
writeFile(vsPath, JSON.stringify(vs, null, 4) + "\n");
|
|
5584
5916
|
changed.push(".vscode/settings.json");
|
|
5585
5917
|
}
|
|
@@ -5595,8 +5927,8 @@ async function checkNativeTypeScriptConfig(root) {
|
|
|
5595
5927
|
status: "fail",
|
|
5596
5928
|
detail: "Install TypeScript 7 before validating compiler configuration."
|
|
5597
5929
|
};
|
|
5598
|
-
const configPath =
|
|
5599
|
-
if (!
|
|
5930
|
+
const configPath = path7.join(root, "tsconfig.json");
|
|
5931
|
+
if (!fs6.existsSync(configPath))
|
|
5600
5932
|
return {
|
|
5601
5933
|
...check,
|
|
5602
5934
|
status: "fail",
|
|
@@ -5604,7 +5936,7 @@ async function checkNativeTypeScriptConfig(root) {
|
|
|
5604
5936
|
fix: "Create a tsconfig.json extending toiljs/tsconfig."
|
|
5605
5937
|
};
|
|
5606
5938
|
try {
|
|
5607
|
-
const apiPath =
|
|
5939
|
+
const apiPath = createRequire2(path7.join(root, "package.json")).resolve(
|
|
5608
5940
|
"typescript/unstable/sync"
|
|
5609
5941
|
);
|
|
5610
5942
|
const native = await import(pathToFileURL(apiPath).href);
|
|
@@ -5637,9 +5969,9 @@ async function checkNativeTypeScriptConfig(root) {
|
|
|
5637
5969
|
}
|
|
5638
5970
|
}
|
|
5639
5971
|
function checkLintMigration(root) {
|
|
5640
|
-
const pkg = readJsonObject(
|
|
5972
|
+
const pkg = readJsonObject(path7.join(root, "package.json"));
|
|
5641
5973
|
const script = asRecord(pkg?.scripts)?.lint;
|
|
5642
|
-
const config = readFile(
|
|
5974
|
+
const config = readFile(path7.join(root, "oxlint.config.ts")) ?? readFile(path7.join(root, ".oxlintrc.json")) ?? "";
|
|
5643
5975
|
const configured = config.includes("toiljs/oxlint") || /["']?typeAware["']?\s*:\s*true/.test(config);
|
|
5644
5976
|
const scriptOk = typeof script === "string" && /\boxlint\b/.test(script) && !/\beslint\b/.test(script) && (script.includes("--type-aware") || configured);
|
|
5645
5977
|
const installed = installedVersion(root, "oxlint") !== null && installedVersion(root, "oxlint-tsgolint") !== null;
|
|
@@ -5652,8 +5984,8 @@ function checkLintMigration(root) {
|
|
|
5652
5984
|
};
|
|
5653
5985
|
}
|
|
5654
5986
|
function frameworkMeta() {
|
|
5655
|
-
const pkgPath =
|
|
5656
|
-
|
|
5987
|
+
const pkgPath = path7.resolve(
|
|
5988
|
+
path7.dirname(fileURLToPath3(import.meta.url)),
|
|
5657
5989
|
"..",
|
|
5658
5990
|
"..",
|
|
5659
5991
|
"package.json"
|
|
@@ -5663,10 +5995,10 @@ function frameworkMeta() {
|
|
|
5663
5995
|
const peers = pkg ? stringRecord(pkg.peerDependencies) : {};
|
|
5664
5996
|
return { node: engines.node ?? ">=24.0.0", peers };
|
|
5665
5997
|
}
|
|
5666
|
-
var LOCKFILES = ["package-lock.json", "pnpm-lock.yaml", "yarn.lock", "bun.lockb"];
|
|
5998
|
+
var LOCKFILES = ["package-lock.json", "pnpm-lock.yaml", "yarn.lock", "bun.lock", "bun.lockb"];
|
|
5667
5999
|
function readEntry(clientAbsDir) {
|
|
5668
6000
|
for (const name of ["toil.tsx", "toil.jsx", "main.tsx", "main.jsx"]) {
|
|
5669
|
-
const source = readFile(
|
|
6001
|
+
const source = readFile(path7.join(clientAbsDir, name));
|
|
5670
6002
|
if (source !== null && /toiljs\/routes|\bmount\s*\(/.test(source)) return source;
|
|
5671
6003
|
}
|
|
5672
6004
|
return null;
|
|
@@ -5677,18 +6009,18 @@ function collectSources(root, dir, cap) {
|
|
|
5677
6009
|
if (out2.length >= cap) return;
|
|
5678
6010
|
let entries;
|
|
5679
6011
|
try {
|
|
5680
|
-
entries =
|
|
6012
|
+
entries = fs6.readdirSync(current, { withFileTypes: true });
|
|
5681
6013
|
} catch {
|
|
5682
6014
|
return;
|
|
5683
6015
|
}
|
|
5684
6016
|
for (const entry of entries) {
|
|
5685
6017
|
if (out2.length >= cap) break;
|
|
5686
|
-
const full =
|
|
6018
|
+
const full = path7.join(current, entry.name);
|
|
5687
6019
|
if (entry.isDirectory()) {
|
|
5688
6020
|
if (entry.name !== "node_modules") visit(full);
|
|
5689
6021
|
} else if (/\.(tsx|jsx)$/.test(entry.name)) {
|
|
5690
6022
|
const source = readFile(full);
|
|
5691
|
-
if (source !== null) out2.push({ path:
|
|
6023
|
+
if (source !== null) out2.push({ path: path7.relative(root, full), source });
|
|
5692
6024
|
}
|
|
5693
6025
|
}
|
|
5694
6026
|
};
|
|
@@ -5724,9 +6056,9 @@ function renderHuman(groups) {
|
|
|
5724
6056
|
process.stdout.write(out2.join("\n") + "\n");
|
|
5725
6057
|
}
|
|
5726
6058
|
async function runDoctor(opts2) {
|
|
5727
|
-
const root =
|
|
6059
|
+
const root = path7.resolve(opts2.root ?? opts2.cwd);
|
|
5728
6060
|
const meta = frameworkMeta();
|
|
5729
|
-
const projectPkg = readJsonObject(
|
|
6061
|
+
const projectPkg = readJsonObject(path7.join(root, "package.json"));
|
|
5730
6062
|
const deps = {
|
|
5731
6063
|
...projectPkg ? stringRecord(projectPkg.dependencies) : {},
|
|
5732
6064
|
...projectPkg ? stringRecord(projectPkg.devDependencies) : {}
|
|
@@ -5738,11 +6070,11 @@ async function runDoctor(opts2) {
|
|
|
5738
6070
|
} catch (err) {
|
|
5739
6071
|
configError = err instanceof Error ? err.message : String(err);
|
|
5740
6072
|
}
|
|
5741
|
-
const clientAbsDir = cfg ? cfg.clientAbsDir :
|
|
5742
|
-
const routesAbsDir = cfg ? cfg.routesAbsDir :
|
|
5743
|
-
const publicDir = cfg ? cfg.publicDir :
|
|
6073
|
+
const clientAbsDir = cfg ? cfg.clientAbsDir : path7.join(root, "client");
|
|
6074
|
+
const routesAbsDir = cfg ? cfg.routesAbsDir : path7.join(clientAbsDir, "routes");
|
|
6075
|
+
const publicDir = cfg ? cfg.publicDir : path7.join(clientAbsDir, "public");
|
|
5744
6076
|
const entrySource = readEntry(clientAbsDir);
|
|
5745
|
-
const indexHtml = readFile(
|
|
6077
|
+
const indexHtml = readFile(path7.join(publicDir, "index.html"));
|
|
5746
6078
|
const routes = scanRoutes(routesAbsDir);
|
|
5747
6079
|
const mainPatterns = routes.filter((r2) => r2.slot === void 0).map((r2) => r2.pattern);
|
|
5748
6080
|
const assetIssues = findRelativeAssets(collectSources(root, clientAbsDir, 200));
|
|
@@ -5755,14 +6087,14 @@ async function runDoctor(opts2) {
|
|
|
5755
6087
|
}
|
|
5756
6088
|
const ppPkg = preprocessorImported ? PREPROCESSOR_PKG[preprocessorImported] : null;
|
|
5757
6089
|
const preprocessorInstalled = ppPkg === null || ppPkg in deps;
|
|
5758
|
-
const toilconfig = readJsonObject(
|
|
6090
|
+
const toilconfig = readJsonObject(path7.join(root, "toilconfig.json"));
|
|
5759
6091
|
const serverPresent = toilconfig !== null;
|
|
5760
6092
|
let missingEntries = [];
|
|
5761
6093
|
let toilscriptInstalled = false;
|
|
5762
6094
|
let wasmExists = false;
|
|
5763
6095
|
if (toilconfig) {
|
|
5764
6096
|
const entries = Array.isArray(toilconfig.entries) ? toilconfig.entries.filter((e) => typeof e === "string") : [];
|
|
5765
|
-
missingEntries = entries.filter((e) => !
|
|
6097
|
+
missingEntries = entries.filter((e) => !fs6.existsSync(path7.join(root, e)));
|
|
5766
6098
|
toilscriptInstalled = isPackageInstalled(root, "toilscript");
|
|
5767
6099
|
const targets = typeof toilconfig.targets === "object" && toilconfig.targets !== null ? toilconfig.targets : {};
|
|
5768
6100
|
const outFiles = [];
|
|
@@ -5772,20 +6104,20 @@ async function runDoctor(opts2) {
|
|
|
5772
6104
|
if (typeof outFile === "string") outFiles.push(outFile);
|
|
5773
6105
|
}
|
|
5774
6106
|
}
|
|
5775
|
-
wasmExists = outFiles.some((f) =>
|
|
6107
|
+
wasmExists = outFiles.some((f) => fs6.existsSync(path7.join(root, f)));
|
|
5776
6108
|
if (!wasmExists && outFiles.length === 0) {
|
|
5777
6109
|
try {
|
|
5778
|
-
wasmExists =
|
|
6110
|
+
wasmExists = fs6.readdirSync(path7.join(root, "build", "server")).some((f) => f.endsWith(".wasm"));
|
|
5779
6111
|
} catch {
|
|
5780
6112
|
wasmExists = false;
|
|
5781
6113
|
}
|
|
5782
6114
|
}
|
|
5783
6115
|
}
|
|
5784
6116
|
const peerName = (n3) => checkPeer(n3, deps[n3] ?? null, meta.peers[n3] ?? "*");
|
|
5785
|
-
const peerChecks = Object.keys(meta.peers).filter((n3) => n3 !== "typescript").map(peerName);
|
|
6117
|
+
const peerChecks = Object.keys(meta.peers).filter((n3) => n3 !== "typescript" && !Object.hasOwn(TEST_DEPENDENCIES, n3)).map(peerName);
|
|
5786
6118
|
const typeScriptFix = opts2.fix ? applyTypeScriptFix(root) : null;
|
|
5787
6119
|
const declaredTypeScript = (() => {
|
|
5788
|
-
const pkg = readJsonObject(
|
|
6120
|
+
const pkg = readJsonObject(path7.join(root, "package.json"));
|
|
5789
6121
|
if (pkg === null) return deps.typescript ?? null;
|
|
5790
6122
|
const ranges = [
|
|
5791
6123
|
"dependencies",
|
|
@@ -5807,12 +6139,39 @@ async function runDoctor(opts2) {
|
|
|
5807
6139
|
const authFacts = gatherAuthFacts(root, toilconfig);
|
|
5808
6140
|
const prettierPresent = prettierPluginPresent(
|
|
5809
6141
|
root,
|
|
5810
|
-
readJsonObject(
|
|
6142
|
+
readJsonObject(path7.join(root, "package.json"))
|
|
5811
6143
|
);
|
|
5812
6144
|
const serverTsPath = serverPresent ? serverTsconfigPath(root, toilconfig) : null;
|
|
5813
6145
|
const serverTsParsed = serverTsPath ? readJsonObject(serverTsPath) : null;
|
|
5814
6146
|
const serverTsPluginPresent = serverTsPath === null || serverTsParsed === null ? false : tsconfigHasToilPlugin(serverTsParsed);
|
|
5815
|
-
|
|
6147
|
+
let testingFix = null;
|
|
6148
|
+
let testingChecks;
|
|
6149
|
+
try {
|
|
6150
|
+
if (opts2.fix && projectPkg !== null) testingFix = await ensureTesting(root, true);
|
|
6151
|
+
const testingPkg = readJsonObject(path7.join(root, "package.json"));
|
|
6152
|
+
testingChecks = checkTesting({
|
|
6153
|
+
dependencies: {
|
|
6154
|
+
...stringRecord(testingPkg?.dependencies),
|
|
6155
|
+
...stringRecord(testingPkg?.devDependencies)
|
|
6156
|
+
},
|
|
6157
|
+
scripts: stringRecord(testingPkg?.scripts),
|
|
6158
|
+
installed: Object.fromEntries(
|
|
6159
|
+
Object.keys(TEST_DEPENDENCIES).map((name) => [name, installedVersion(root, name)])
|
|
6160
|
+
),
|
|
6161
|
+
configuration: await findTestConfiguration(root)
|
|
6162
|
+
});
|
|
6163
|
+
} catch (error) {
|
|
6164
|
+
testingChecks = [
|
|
6165
|
+
{
|
|
6166
|
+
id: "testing:setup",
|
|
6167
|
+
label: "Testing setup",
|
|
6168
|
+
status: "fail",
|
|
6169
|
+
detail: error instanceof Error ? error.message : String(error),
|
|
6170
|
+
fix: "Repair package.json or unreadable test configuration, then run `toiljs doctor --fix`."
|
|
6171
|
+
}
|
|
6172
|
+
];
|
|
6173
|
+
}
|
|
6174
|
+
const applied = [typeScriptFix, rpcFix, prettierFix, editorFix, testingFix].filter(
|
|
5816
6175
|
(fix) => fix !== null
|
|
5817
6176
|
);
|
|
5818
6177
|
const serverFix = applied.length > 0 ? {
|
|
@@ -5829,7 +6188,7 @@ async function runDoctor(opts2) {
|
|
|
5829
6188
|
typeScriptCheck,
|
|
5830
6189
|
checkLintMigration(root),
|
|
5831
6190
|
await checkNativeTypeScriptConfig(root),
|
|
5832
|
-
checkPackageManager(LOCKFILES.filter((f) =>
|
|
6191
|
+
checkPackageManager(LOCKFILES.filter((f) => fs6.existsSync(path7.join(root, f))))
|
|
5833
6192
|
]
|
|
5834
6193
|
},
|
|
5835
6194
|
{
|
|
@@ -5838,13 +6197,13 @@ async function runDoctor(opts2) {
|
|
|
5838
6197
|
checkDir(
|
|
5839
6198
|
"client-dir",
|
|
5840
6199
|
"client/ directory",
|
|
5841
|
-
|
|
6200
|
+
fs6.existsSync(clientAbsDir),
|
|
5842
6201
|
"Create a client/ directory for your app."
|
|
5843
6202
|
),
|
|
5844
6203
|
checkDir(
|
|
5845
6204
|
"routes-dir",
|
|
5846
6205
|
"routes/ directory",
|
|
5847
|
-
|
|
6206
|
+
fs6.existsSync(routesAbsDir),
|
|
5848
6207
|
"Create client/routes/ and add an index.tsx."
|
|
5849
6208
|
),
|
|
5850
6209
|
checkRootElement(indexHtml),
|
|
@@ -5855,6 +6214,10 @@ async function runDoctor(opts2) {
|
|
|
5855
6214
|
checkDevScripts(projectPkg ? stringRecord(projectPkg.scripts) : {})
|
|
5856
6215
|
]
|
|
5857
6216
|
},
|
|
6217
|
+
{
|
|
6218
|
+
title: "Testing",
|
|
6219
|
+
checks: testingChecks
|
|
6220
|
+
},
|
|
5858
6221
|
{
|
|
5859
6222
|
title: "Config + assets",
|
|
5860
6223
|
checks: [
|
|
@@ -5915,8 +6278,8 @@ function renderFix(result) {
|
|
|
5915
6278
|
|
|
5916
6279
|
// src/cli/generate.ts
|
|
5917
6280
|
import { randomBytes as randomBytes3, timingSafeEqual } from "node:crypto";
|
|
5918
|
-
import
|
|
5919
|
-
import
|
|
6281
|
+
import fs7 from "node:fs";
|
|
6282
|
+
import path8 from "node:path";
|
|
5920
6283
|
import { parseEnv } from "node:util";
|
|
5921
6284
|
|
|
5922
6285
|
// node_modules/@noble/hashes/_u64.js
|
|
@@ -6829,10 +7192,10 @@ var ml_kem768 = /* @__PURE__ */ (() => mk(PARAMS[768]))();
|
|
|
6829
7192
|
var PRIVATE_KEYS = ["AUTH_SESSION_SECRET", "AUTH_OPRF_SEED", "AUTH_KEM_SK"];
|
|
6830
7193
|
var PUBLIC_KEY = "VITE_AUTH_KEM_PUBLIC_KEY";
|
|
6831
7194
|
function read(file) {
|
|
6832
|
-
const stat =
|
|
7195
|
+
const stat = fs7.lstatSync(file, { throwIfNoEntry: false });
|
|
6833
7196
|
if (!stat) return "";
|
|
6834
7197
|
if (!stat.isFile()) throw new Error(`Expected a regular file: ${file}`);
|
|
6835
|
-
return
|
|
7198
|
+
return fs7.readFileSync(file, "utf8");
|
|
6836
7199
|
}
|
|
6837
7200
|
function append(text2, lines) {
|
|
6838
7201
|
if (!lines.length) return text2;
|
|
@@ -6855,10 +7218,10 @@ function publicKeyFor(secret) {
|
|
|
6855
7218
|
}
|
|
6856
7219
|
function runGenerate(target, opts2 = {}) {
|
|
6857
7220
|
if (target !== "auth") throw new Error("Usage: toiljs generate auth [--root <dir>]");
|
|
6858
|
-
const root =
|
|
6859
|
-
const envFile =
|
|
6860
|
-
const secretsFile =
|
|
6861
|
-
const ignoreFile =
|
|
7221
|
+
const root = path8.resolve(opts2.root ?? process.cwd());
|
|
7222
|
+
const envFile = path8.join(root, ".env");
|
|
7223
|
+
const secretsFile = path8.join(root, ".env.secrets");
|
|
7224
|
+
const ignoreFile = path8.join(root, ".gitignore");
|
|
6862
7225
|
const envText = read(envFile);
|
|
6863
7226
|
const secretsText = read(secretsFile);
|
|
6864
7227
|
const ignoreText = read(ignoreFile);
|
|
@@ -6896,12 +7259,12 @@ function runGenerate(target, opts2 = {}) {
|
|
|
6896
7259
|
const publicAdditions = Object.hasOwn(env, PUBLIC_KEY) ? [] : [`${PUBLIC_KEY}=${publicKey}`];
|
|
6897
7260
|
const ignoreLines = new Set(ignoreText.split(/\r?\n/));
|
|
6898
7261
|
const ignoreAdditions = ["/.env", "/.env.secrets"].filter((line) => !ignoreLines.has(line));
|
|
6899
|
-
if (ignoreAdditions.length)
|
|
6900
|
-
if (
|
|
7262
|
+
if (ignoreAdditions.length) fs7.writeFileSync(ignoreFile, append(ignoreText, ignoreAdditions));
|
|
7263
|
+
if (fs7.existsSync(secretsFile)) fs7.chmodSync(secretsFile, 384);
|
|
6901
7264
|
if (additions.length)
|
|
6902
|
-
|
|
7265
|
+
fs7.writeFileSync(secretsFile, append(secretsText, additions), { mode: 384 });
|
|
6903
7266
|
if (publicAdditions.length)
|
|
6904
|
-
|
|
7267
|
+
fs7.writeFileSync(envFile, append(envText, publicAdditions), { mode: 384 });
|
|
6905
7268
|
process.stdout.write(
|
|
6906
7269
|
[
|
|
6907
7270
|
`Authentication keys ready in ${root}. Existing credentials were preserved.`,
|
|
@@ -6918,14 +7281,14 @@ function runGenerate(target, opts2 = {}) {
|
|
|
6918
7281
|
}
|
|
6919
7282
|
|
|
6920
7283
|
// src/cli/notify.ts
|
|
6921
|
-
import
|
|
7284
|
+
import fs9 from "node:fs";
|
|
6922
7285
|
import os from "node:os";
|
|
6923
|
-
import
|
|
7286
|
+
import path10 from "node:path";
|
|
6924
7287
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
6925
7288
|
|
|
6926
7289
|
// src/cli/update.ts
|
|
6927
|
-
import
|
|
6928
|
-
import
|
|
7290
|
+
import fs8 from "node:fs";
|
|
7291
|
+
import path9 from "node:path";
|
|
6929
7292
|
|
|
6930
7293
|
// src/cli/updates.ts
|
|
6931
7294
|
function parseVersion2(v) {
|
|
@@ -6968,13 +7331,14 @@ function buildRows(upgraded, currentDeps) {
|
|
|
6968
7331
|
|
|
6969
7332
|
// src/cli/update.ts
|
|
6970
7333
|
function detectPackageManager2(root) {
|
|
6971
|
-
if (
|
|
6972
|
-
if (
|
|
6973
|
-
if (
|
|
7334
|
+
if (fs8.existsSync(path9.join(root, "pnpm-lock.yaml"))) return { name: "pnpm", ncuName: "pnpm" };
|
|
7335
|
+
if (fs8.existsSync(path9.join(root, "yarn.lock"))) return { name: "yarn", ncuName: "yarn" };
|
|
7336
|
+
if (fs8.existsSync(path9.join(root, "bun.lock")) || fs8.existsSync(path9.join(root, "bun.lockb")))
|
|
7337
|
+
return { name: "bun", ncuName: "bun" };
|
|
6974
7338
|
return { name: "npm", ncuName: "npm" };
|
|
6975
7339
|
}
|
|
6976
7340
|
function readDependencies(pkgPath) {
|
|
6977
|
-
const parsed = JSON.parse(
|
|
7341
|
+
const parsed = JSON.parse(fs8.readFileSync(pkgPath, "utf8"));
|
|
6978
7342
|
if (typeof parsed !== "object" || parsed === null) return {};
|
|
6979
7343
|
const pkg = parsed;
|
|
6980
7344
|
const merge = (v) => {
|
|
@@ -7002,26 +7366,26 @@ function serverDirs(root) {
|
|
|
7002
7366
|
const dirs = /* @__PURE__ */ new Set();
|
|
7003
7367
|
try {
|
|
7004
7368
|
const parsed = JSON.parse(
|
|
7005
|
-
|
|
7369
|
+
fs8.readFileSync(path9.join(root, "toilconfig.json"), "utf8")
|
|
7006
7370
|
);
|
|
7007
7371
|
const entries = typeof parsed === "object" && parsed !== null && Array.isArray(parsed.entries) ? parsed.entries.filter(
|
|
7008
7372
|
(e) => typeof e === "string"
|
|
7009
7373
|
) : [];
|
|
7010
|
-
for (const e of entries) dirs.add(
|
|
7374
|
+
for (const e of entries) dirs.add(path9.dirname(path9.resolve(root, e)));
|
|
7011
7375
|
} catch {
|
|
7012
7376
|
}
|
|
7013
|
-
if (dirs.size === 0) dirs.add(
|
|
7377
|
+
if (dirs.size === 0) dirs.add(path9.join(root, "server"));
|
|
7014
7378
|
return [...dirs];
|
|
7015
7379
|
}
|
|
7016
7380
|
function ensureMigrationsDirs(root) {
|
|
7017
7381
|
const created = [];
|
|
7018
7382
|
for (const dir of serverDirs(root)) {
|
|
7019
|
-
if (!
|
|
7020
|
-
const migrations =
|
|
7021
|
-
if (
|
|
7022
|
-
|
|
7023
|
-
|
|
7024
|
-
created.push(
|
|
7383
|
+
if (!fs8.existsSync(dir)) continue;
|
|
7384
|
+
const migrations = path9.join(dir, "migrations");
|
|
7385
|
+
if (fs8.existsSync(migrations)) continue;
|
|
7386
|
+
fs8.mkdirSync(migrations, { recursive: true });
|
|
7387
|
+
fs8.writeFileSync(path9.join(migrations, "README.md"), MIGRATIONS_README);
|
|
7388
|
+
created.push(path9.relative(root, migrations) || "migrations");
|
|
7025
7389
|
}
|
|
7026
7390
|
return created;
|
|
7027
7391
|
}
|
|
@@ -7037,16 +7401,16 @@ function rowLine(row) {
|
|
|
7037
7401
|
function noteWithheld(names) {
|
|
7038
7402
|
note(
|
|
7039
7403
|
names.map((n3) => `${dim("-")} ${n3}`).join("\n") + "\n\n" + dim(
|
|
7040
|
-
"Held back:
|
|
7404
|
+
"Held back: compiler updates must stay within TypeScript 7; native testing updates must stay within the supported Vitest and WebdriverIO versions."
|
|
7041
7405
|
),
|
|
7042
7406
|
warn("Not upgraded")
|
|
7043
7407
|
);
|
|
7044
7408
|
}
|
|
7045
7409
|
var TARGETS = /* @__PURE__ */ new Set(["latest", "minor", "patch", "newest", "greatest"]);
|
|
7046
7410
|
async function runUpdate(opts2) {
|
|
7047
|
-
const root =
|
|
7048
|
-
const pkgPath =
|
|
7049
|
-
if (!
|
|
7411
|
+
const root = path9.resolve(opts2.root ?? opts2.cwd);
|
|
7412
|
+
const pkgPath = path9.join(root, "package.json");
|
|
7413
|
+
if (!fs8.existsSync(pkgPath)) {
|
|
7050
7414
|
throw new Error("No package.json here. Run from your project root or pass --root <dir>.");
|
|
7051
7415
|
}
|
|
7052
7416
|
const currentDeps = readDependencies(pkgPath);
|
|
@@ -7079,7 +7443,13 @@ async function runUpdate(opts2) {
|
|
|
7079
7443
|
return;
|
|
7080
7444
|
}
|
|
7081
7445
|
const upgraded = parseNcuJson(res.stdout);
|
|
7082
|
-
const
|
|
7446
|
+
const testConfig = await findTestConfiguration(root);
|
|
7447
|
+
const withheld = [
|
|
7448
|
+
...withheldUpgrades(upgraded),
|
|
7449
|
+
...Object.keys(upgraded).filter(
|
|
7450
|
+
(name) => testConfig.native && !isSupportedTestingRange(name, upgraded[name])
|
|
7451
|
+
)
|
|
7452
|
+
];
|
|
7083
7453
|
for (const name of withheld) delete upgraded[name];
|
|
7084
7454
|
if (!isSupportedTypeScriptRange(currentDeps.typescript ?? "") && !upgraded.typescript) {
|
|
7085
7455
|
const versions = await capture("npm", ["view", "typescript@7", "version", "--json"], root);
|
|
@@ -7096,7 +7466,20 @@ async function runUpdate(opts2) {
|
|
|
7096
7466
|
if (rows.length === 0) {
|
|
7097
7467
|
s.stop("Everything is up to date");
|
|
7098
7468
|
if (withheld.length > 0) noteWithheld(withheld);
|
|
7099
|
-
|
|
7469
|
+
if (opts2.yes) {
|
|
7470
|
+
const testing2 = await ensureTesting(root, true);
|
|
7471
|
+
if (testing2.changed.length) note(testing2.changed.join("\n"), "Testing setup");
|
|
7472
|
+
if (testing2.skipped.length) note(testing2.skipped.join("\n"), "Manual changes");
|
|
7473
|
+
if (testing2.install && !await installUpdates(pm.name, root)) return;
|
|
7474
|
+
outro(
|
|
7475
|
+
success(testing2.changed.length ? "Testing setup updated." : "Nothing to update.")
|
|
7476
|
+
);
|
|
7477
|
+
} else
|
|
7478
|
+
outro(
|
|
7479
|
+
success(
|
|
7480
|
+
"Nothing to update. Use `toiljs doctor --fix` or `toiljs update --yes` to add missing testing setup."
|
|
7481
|
+
)
|
|
7482
|
+
);
|
|
7100
7483
|
return;
|
|
7101
7484
|
}
|
|
7102
7485
|
s.stop(`${String(rows.length)} update${rows.length === 1 ? "" : "s"} available`);
|
|
@@ -7131,7 +7514,17 @@ async function runUpdate(opts2) {
|
|
|
7131
7514
|
outro(dim("No packages selected."));
|
|
7132
7515
|
return;
|
|
7133
7516
|
}
|
|
7134
|
-
|
|
7517
|
+
if (testConfig.native) {
|
|
7518
|
+
const paired = selected.find(
|
|
7519
|
+
(name) => name === "vitest" || name === "@vitest/coverage-v8"
|
|
7520
|
+
);
|
|
7521
|
+
if (paired) {
|
|
7522
|
+
const counterpart = paired === "vitest" ? "@vitest/coverage-v8" : "vitest";
|
|
7523
|
+
upgraded[counterpart] = upgraded[paired];
|
|
7524
|
+
if (!selected.includes(counterpart)) selected.push(counterpart);
|
|
7525
|
+
}
|
|
7526
|
+
}
|
|
7527
|
+
const pkg = JSON.parse(fs8.readFileSync(pkgPath, "utf8"));
|
|
7135
7528
|
const targetTypeScript = selected.includes("typescript") ? upgraded.typescript : currentDeps.typescript;
|
|
7136
7529
|
if (!targetTypeScript || !isSupportedTypeScriptRange(targetTypeScript)) {
|
|
7137
7530
|
throw new Error("Select the TypeScript 7 migration before installing updates.");
|
|
@@ -7157,23 +7550,30 @@ async function runUpdate(opts2) {
|
|
|
7157
7550
|
pkg.devDependencies = deps;
|
|
7158
7551
|
}
|
|
7159
7552
|
}
|
|
7160
|
-
|
|
7553
|
+
fs8.writeFileSync(pkgPath, JSON.stringify(pkg, null, 4) + "\n");
|
|
7161
7554
|
s.stop("package.json updated");
|
|
7162
|
-
|
|
7555
|
+
const testing = await ensureTesting(root, true);
|
|
7556
|
+
if (testing.changed.length) note(testing.changed.join("\n"), "Testing setup");
|
|
7557
|
+
if (testing.skipped.length) note(testing.skipped.join("\n"), "Manual changes");
|
|
7558
|
+
if (!await installUpdates(pm.name, root)) return;
|
|
7559
|
+
outro(
|
|
7560
|
+
success(`Updated ${String(selected.length)} package${selected.length === 1 ? "" : "s"}.`)
|
|
7561
|
+
);
|
|
7562
|
+
}
|
|
7563
|
+
async function installUpdates(pm, root) {
|
|
7564
|
+
note(dim(`Running ${pm} install\u2026`), "Install");
|
|
7163
7565
|
try {
|
|
7164
|
-
await run(pm
|
|
7566
|
+
await run(pm, ["install"], root, { stdio: "inherit" });
|
|
7165
7567
|
} catch {
|
|
7166
7568
|
outro(
|
|
7167
7569
|
danger(
|
|
7168
|
-
`${pm
|
|
7570
|
+
`${pm} install failed \u2014 package.json was updated to the new versions, but the install did not finish. Fix the error printed above, then run \`${pm} install\`.`
|
|
7169
7571
|
)
|
|
7170
7572
|
);
|
|
7171
7573
|
process.exitCode = 1;
|
|
7172
|
-
return;
|
|
7574
|
+
return false;
|
|
7173
7575
|
}
|
|
7174
|
-
|
|
7175
|
-
success(`Updated ${String(selected.length)} package${selected.length === 1 ? "" : "s"}.`)
|
|
7176
|
-
);
|
|
7576
|
+
return true;
|
|
7177
7577
|
}
|
|
7178
7578
|
|
|
7179
7579
|
// src/cli/version-check.ts
|
|
@@ -7248,8 +7648,8 @@ function findOutdated(latest, projectVersion, cliVersion, cliIsLocal, pm) {
|
|
|
7248
7648
|
var REGISTRY_URL = "https://registry.npmjs.org/toiljs/latest";
|
|
7249
7649
|
var FETCH_TIMEOUT_MS = 2e3;
|
|
7250
7650
|
function cacheFile() {
|
|
7251
|
-
const base = process.env.XDG_CACHE_HOME ??
|
|
7252
|
-
return
|
|
7651
|
+
const base = process.env.XDG_CACHE_HOME ?? path10.join(os.homedir(), ".cache");
|
|
7652
|
+
return path10.join(base, "toiljs", "update-check.json");
|
|
7253
7653
|
}
|
|
7254
7654
|
async function fetchLatest() {
|
|
7255
7655
|
const ctrl = new AbortController();
|
|
@@ -7275,22 +7675,22 @@ async function fetchLatest() {
|
|
|
7275
7675
|
async function resolveLatest() {
|
|
7276
7676
|
const file = cacheFile();
|
|
7277
7677
|
try {
|
|
7278
|
-
const cached = parseCheckCache(
|
|
7678
|
+
const cached = parseCheckCache(fs9.readFileSync(file, "utf8"));
|
|
7279
7679
|
if (cached && isCacheFresh(cached, Date.now())) return cached.latest;
|
|
7280
7680
|
} catch {
|
|
7281
7681
|
}
|
|
7282
7682
|
const latest = await fetchLatest();
|
|
7283
7683
|
try {
|
|
7284
|
-
|
|
7285
|
-
|
|
7684
|
+
fs9.mkdirSync(path10.dirname(file), { recursive: true });
|
|
7685
|
+
fs9.writeFileSync(file, JSON.stringify({ latest, checkedAt: Date.now() }));
|
|
7286
7686
|
} catch {
|
|
7287
7687
|
}
|
|
7288
7688
|
return latest;
|
|
7289
7689
|
}
|
|
7290
7690
|
function projectToiljsVersion(root) {
|
|
7291
7691
|
try {
|
|
7292
|
-
const raw =
|
|
7293
|
-
|
|
7692
|
+
const raw = fs9.readFileSync(
|
|
7693
|
+
path10.join(root, "node_modules", "toiljs", "package.json"),
|
|
7294
7694
|
"utf8"
|
|
7295
7695
|
);
|
|
7296
7696
|
const parsed = JSON.parse(raw);
|
|
@@ -7315,9 +7715,9 @@ async function notifyIfOutdated(rootArg) {
|
|
|
7315
7715
|
if (env.TOILJS_NO_UPDATE_CHECK || env.NO_UPDATE_NOTIFIER || env.CI) return;
|
|
7316
7716
|
const latest = await resolveLatest();
|
|
7317
7717
|
if (!latest) return;
|
|
7318
|
-
const root =
|
|
7319
|
-
const cliDir =
|
|
7320
|
-
const cliIsLocal = cliDir.startsWith(
|
|
7718
|
+
const root = path10.resolve(rootArg ?? process.cwd());
|
|
7719
|
+
const cliDir = path10.dirname(fileURLToPath4(import.meta.url));
|
|
7720
|
+
const cliIsLocal = cliDir.startsWith(path10.join(root, "node_modules") + path10.sep);
|
|
7321
7721
|
const rows = findOutdated(
|
|
7322
7722
|
latest,
|
|
7323
7723
|
projectToiljsVersion(root),
|
|
@@ -7331,6 +7731,62 @@ async function notifyIfOutdated(rootArg) {
|
|
|
7331
7731
|
}
|
|
7332
7732
|
}
|
|
7333
7733
|
|
|
7734
|
+
// src/cli/test.ts
|
|
7735
|
+
import path11 from "node:path";
|
|
7736
|
+
import { createRequire as createRequire3 } from "node:module";
|
|
7737
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
7738
|
+
function parseTestArgs(args) {
|
|
7739
|
+
let root;
|
|
7740
|
+
let browser = false;
|
|
7741
|
+
let all = false;
|
|
7742
|
+
let watch = false;
|
|
7743
|
+
const forwarded = [];
|
|
7744
|
+
for (let index = 0; index < args.length; index++) {
|
|
7745
|
+
const arg = args[index];
|
|
7746
|
+
if (arg === "--root") {
|
|
7747
|
+
const value = args[++index];
|
|
7748
|
+
if (!value) throw new Error("toiljs test: --root requires a directory.");
|
|
7749
|
+
root = value;
|
|
7750
|
+
} else if (arg === "--browser") browser = true;
|
|
7751
|
+
else if (arg === "--all") all = true;
|
|
7752
|
+
else if (arg === "--watch" || arg === "-w") watch = true;
|
|
7753
|
+
else if (arg !== "--") forwarded.push(arg);
|
|
7754
|
+
}
|
|
7755
|
+
if (browser && all) throw new Error("toiljs test: use either --browser or --all.");
|
|
7756
|
+
return { root, browser, all, watch, arguments: forwarded };
|
|
7757
|
+
}
|
|
7758
|
+
async function runTest(options) {
|
|
7759
|
+
const root = path11.resolve(options.root ?? process.cwd());
|
|
7760
|
+
const setup = await ensureTesting(root);
|
|
7761
|
+
if (setup.install) {
|
|
7762
|
+
const pm = detectPackageManager2(root).name;
|
|
7763
|
+
process.stdout.write(
|
|
7764
|
+
`toiljs test: installing Vitest, browser testing, and V8 coverage with ${pm}.
|
|
7765
|
+
`
|
|
7766
|
+
);
|
|
7767
|
+
await run(pm, ["install"], root, { stdio: "inherit" });
|
|
7768
|
+
}
|
|
7769
|
+
const require2 = createRequire3(path11.join(root, "package.json"));
|
|
7770
|
+
const cli = path11.join(
|
|
7771
|
+
path11.dirname(require2.resolve("vitest/package.json")),
|
|
7772
|
+
"vitest.mjs"
|
|
7773
|
+
);
|
|
7774
|
+
const args = [cli, ...options.watch ? [] : ["run"]];
|
|
7775
|
+
const projectFlag = options.arguments.some(
|
|
7776
|
+
(arg) => arg === "--project" || arg.startsWith("--project=")
|
|
7777
|
+
);
|
|
7778
|
+
if (setup.native && !options.all && !projectFlag)
|
|
7779
|
+
args.push("--project", options.browser ? "browser" : "unit");
|
|
7780
|
+
if (!setup.native && options.browser) args.push("--browser");
|
|
7781
|
+
args.push(...options.arguments);
|
|
7782
|
+
const code = await new Promise((resolve, reject) => {
|
|
7783
|
+
const child = spawn2(process.execPath, args, { cwd: root, stdio: "inherit" });
|
|
7784
|
+
child.once("error", reject);
|
|
7785
|
+
child.once("exit", (status) => resolve(status ?? 1));
|
|
7786
|
+
});
|
|
7787
|
+
process.exitCode = code;
|
|
7788
|
+
}
|
|
7789
|
+
|
|
7334
7790
|
// src/cli/index.ts
|
|
7335
7791
|
var __origEmitWarning = process.emitWarning.bind(process);
|
|
7336
7792
|
process.emitWarning = ((warning, ...rest) => {
|
|
@@ -7437,6 +7893,7 @@ function printHelp() {
|
|
|
7437
7893
|
cmd("create [name]", "scaffold a new toiljs app"),
|
|
7438
7894
|
cmd("configure", "toggle styling features (Sass/Less/Stylus, Tailwind)"),
|
|
7439
7895
|
cmd("generate auth", "generate private auth keys and the matching client public key"),
|
|
7896
|
+
cmd("test", "run Vitest (auto-installs missing test tooling)"),
|
|
7440
7897
|
cmd("dev", "start the dev server with HMR"),
|
|
7441
7898
|
cmd("build", "build the optimized production bundle"),
|
|
7442
7899
|
cmd("start", "self-host the built app (hyper-express / uWS)"),
|
|
@@ -7447,7 +7904,10 @@ function printHelp() {
|
|
|
7447
7904
|
bold("Options"),
|
|
7448
7905
|
cmd("--root <dir>", "project root (default: current directory)"),
|
|
7449
7906
|
cmd("--port <n>", "dev/start: listen port (or PORT env / client.port; default 3000)"),
|
|
7450
|
-
cmd(
|
|
7907
|
+
cmd(
|
|
7908
|
+
"--host <h>",
|
|
7909
|
+
"dev/start: bind host, e.g. 0.0.0.0 (or HOST env / client.host; default 127.0.0.1)"
|
|
7910
|
+
),
|
|
7451
7911
|
cmd("--threads <n>", "start: production HTTP worker count"),
|
|
7452
7912
|
cmd("-t, --template", "create: app | minimal | agent"),
|
|
7453
7913
|
cmd("--style <name>", "create/configure: css | sass | less | stylus"),
|
|
@@ -7455,9 +7915,13 @@ function printHelp() {
|
|
|
7455
7915
|
cmd("--no-ai", "create: skip AI assistant files (CLAUDE.md, etc.)"),
|
|
7456
7916
|
cmd("-y, --yes", "create: accept defaults (non-interactive)"),
|
|
7457
7917
|
cmd("--no-install", "create: don't install dependencies"),
|
|
7918
|
+
cmd("--browser", "test: run real Chrome browser tests"),
|
|
7919
|
+
cmd("--all", "test: run unit and browser projects"),
|
|
7920
|
+
cmd("--coverage", "test: collect V8 coverage reports"),
|
|
7921
|
+
cmd("--watch", "test: watch for changes"),
|
|
7458
7922
|
cmd("--server", "build: build only the server (regenerate shared/server.ts + wasm)"),
|
|
7459
7923
|
cmd("--json", "doctor: machine-readable output"),
|
|
7460
|
-
cmd("--fix", "doctor:
|
|
7924
|
+
cmd("--fix", "doctor: repair testing, compiler, and server wiring"),
|
|
7461
7925
|
cmd("--target <t>", "update: latest | minor | patch | newest | greatest"),
|
|
7462
7926
|
cmd("-v, --version", "print the toiljs version"),
|
|
7463
7927
|
"",
|
|
@@ -7476,6 +7940,9 @@ async function main() {
|
|
|
7476
7940
|
const flags = parseArgs(rest);
|
|
7477
7941
|
await notifyIfOutdated(flags.root);
|
|
7478
7942
|
switch (command) {
|
|
7943
|
+
case "test":
|
|
7944
|
+
await runTest(parseTestArgs(rest));
|
|
7945
|
+
break;
|
|
7479
7946
|
case "create":
|
|
7480
7947
|
banner();
|
|
7481
7948
|
await runCreate({
|
|
@@ -7534,7 +8001,9 @@ async function main() {
|
|
|
7534
8001
|
const shownHost = wildcard || server.host === "127.0.0.1" ? "localhost" : server.host;
|
|
7535
8002
|
const exposed = server.host !== "127.0.0.1" && server.host !== "localhost" && server.host !== "::1";
|
|
7536
8003
|
process.stdout.write(
|
|
7537
|
-
accent(" \u279C ") + bold(`http://${shownHost}:${String(server.port)}`) + dim(` ws channel: ${server.wsPath}`) + (exposed ? "\n" + dim(
|
|
8004
|
+
accent(" \u279C ") + bold(`http://${shownHost}:${String(server.port)}`) + dim(` ws channel: ${server.wsPath}`) + (exposed ? "\n" + dim(
|
|
8005
|
+
` bound to ${server.host}:${String(server.port)} (exposed beyond loopback)`
|
|
8006
|
+
) : "") + "\n\n"
|
|
7538
8007
|
);
|
|
7539
8008
|
break;
|
|
7540
8009
|
}
|