toiljs 0.0.55 → 0.0.57

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +72 -14
  3. package/build/backend/.tsbuildinfo +1 -1
  4. package/build/cli/.tsbuildinfo +1 -1
  5. package/build/cli/index.js +293 -142
  6. package/build/client/.tsbuildinfo +1 -1
  7. package/build/client/auth.js +1 -1
  8. package/build/client/components/Image.d.ts +1 -1
  9. package/build/client/dev/devtools.js +4 -2
  10. package/build/client/index.d.ts +2 -2
  11. package/build/client/index.js +2 -2
  12. package/build/client/routing/Router.js +1 -1
  13. package/build/client/routing/hooks.js +2 -2
  14. package/build/client/routing/mount.js +1 -1
  15. package/build/compiler/.tsbuildinfo +1 -1
  16. package/build/compiler/docs.js +1 -1
  17. package/build/compiler/seo.js +1 -3
  18. package/build/compiler/template-build.d.ts +5 -2
  19. package/build/compiler/template-build.js +19 -7
  20. package/build/devserver/.tsbuildinfo +1 -1
  21. package/build/devserver/cache.js +0 -0
  22. package/build/devserver/crypto.js +45 -17
  23. package/build/devserver/database.d.ts +1 -1
  24. package/build/devserver/database.js +84 -0
  25. package/build/devserver/email/caps.js +0 -0
  26. package/build/devserver/email/config.js +7 -2
  27. package/build/devserver/email/validate.js +1 -4
  28. package/build/devserver/host.js +18 -1
  29. package/build/devserver/index.d.ts +1 -1
  30. package/build/devserver/index.js +3 -2
  31. package/build/devserver/module.js +51 -12
  32. package/build/devserver/proxy.js +2 -1
  33. package/build/io/.tsbuildinfo +1 -1
  34. package/build/io/codec.d.ts +5 -5
  35. package/build/io/codec.js +193 -77
  36. package/examples/basic/client/components/HoneycombBackground.tsx +1 -1
  37. package/examples/basic/client/public/images/logo.svg +37 -34
  38. package/examples/basic/client/public/index.html +14 -14
  39. package/examples/basic/client/routes/auth.tsx +18 -10
  40. package/examples/basic/client/routes/cookies.tsx +15 -24
  41. package/examples/basic/client/routes/crypto.tsx +4 -5
  42. package/examples/basic/client/routes/features/template/template.tsx +1 -1
  43. package/examples/basic/client/routes/hello.tsx +1 -1
  44. package/examples/basic/client/routes/pq.tsx +14 -14
  45. package/examples/basic/client/routes/rest.tsx +1 -3
  46. package/examples/basic/client/styles/main.css +25 -22
  47. package/examples/basic/client/toil.tsx +1 -1
  48. package/examples/basic/server/README.md +8 -8
  49. package/examples/basic/server/core/AppHandler.ts +4 -7
  50. package/examples/basic/server/routes/Auth.ts +13 -10
  51. package/examples/basic/server/routes/EnvDemo.ts +9 -3
  52. package/examples/basic/server/routes/Guestbook.ts +2 -4
  53. package/package.json +26 -26
  54. package/src/backend/index.ts +4 -2
  55. package/src/cli/create.ts +19 -4
  56. package/src/cli/diagnostics.ts +48 -0
  57. package/src/cli/doctor.ts +155 -9
  58. package/src/cli/notify.ts +1 -6
  59. package/src/cli/ui.ts +3 -3
  60. package/src/cli/version-check.ts +5 -1
  61. package/src/client/auth.ts +33 -10
  62. package/src/client/components/Form.tsx +2 -2
  63. package/src/client/components/Image.tsx +1 -1
  64. package/src/client/components/Script.tsx +1 -1
  65. package/src/client/components/Slot.tsx +1 -1
  66. package/src/client/dev/devtools.tsx +126 -55
  67. package/src/client/dev/error-overlay.tsx +7 -1
  68. package/src/client/head/metadata.ts +1 -1
  69. package/src/client/index.ts +13 -2
  70. package/src/client/routing/Router.tsx +2 -2
  71. package/src/client/routing/error-boundary.tsx +1 -1
  72. package/src/client/routing/hooks.ts +5 -3
  73. package/src/client/routing/loader.ts +2 -2
  74. package/src/client/routing/mount.tsx +5 -6
  75. package/src/compiler/docs.ts +1 -1
  76. package/src/compiler/email-preview.ts +1 -1
  77. package/src/compiler/generate.ts +1 -1
  78. package/src/compiler/seo.ts +1 -3
  79. package/src/compiler/ssg.ts +10 -4
  80. package/src/compiler/template-build.ts +43 -11
  81. package/src/compiler/template.ts +1 -4
  82. package/src/compiler/vite.ts +1 -1
  83. package/src/devserver/cache.ts +0 -0
  84. package/src/devserver/crypto.ts +140 -51
  85. package/src/devserver/database.ts +168 -9
  86. package/src/devserver/dotenv.ts +10 -2
  87. package/src/devserver/email/caps.ts +0 -0
  88. package/src/devserver/email/config.ts +8 -2
  89. package/src/devserver/email/index.ts +3 -3
  90. package/src/devserver/email/validate.ts +1 -4
  91. package/src/devserver/envelope.ts +3 -3
  92. package/src/devserver/host.ts +46 -6
  93. package/src/devserver/index.ts +15 -6
  94. package/src/devserver/module.ts +56 -14
  95. package/src/devserver/proxy.ts +5 -7
  96. package/src/io/codec.ts +226 -83
  97. package/test/devserver-database.test.ts +60 -0
  98. package/test/devserver-secrets.test.ts +59 -0
  99. package/test/doctor.test.ts +30 -0
@@ -609,8 +609,8 @@ function block({
609
609
  return;
610
610
  }
611
611
  if (!o2) return;
612
- const i2 = a3 === "return" ? 0 : -1, m = a3 === "return" ? -1 : 0;
613
- l.moveCursor(r2, i2, m, () => {
612
+ const i2 = a3 === "return" ? 0 : -1, m2 = a3 === "return" ? -1 : 0;
613
+ l.moveCursor(r2, i2, m2, () => {
614
614
  l.clearLine(r2, 1, () => {
615
615
  e.once("keypress", n2);
616
616
  });
@@ -628,9 +628,9 @@ function wrapTextWithPrefix(e, r2, o2, t2 = o2, s = o2, n2) {
628
628
  hard: true,
629
629
  trim: false
630
630
  }).split(`
631
- `).map((c2, i2, m) => {
632
- const d2 = n2 ? n2(c2, i2) : c2;
633
- return i2 === 0 ? `${t2}${d2}` : i2 === m.length - 1 ? `${s}${d2}` : `${o2}${d2}`;
631
+ `).map((c2, i2, m2) => {
632
+ const d = n2 ? n2(c2, i2) : c2;
633
+ return i2 === 0 ? `${t2}${d}` : i2 === m2.length - 1 ? `${s}${d}` : `${o2}${d}`;
634
634
  }).join(`
635
635
  `);
636
636
  }
@@ -941,7 +941,7 @@ function isUnicodeSupported() {
941
941
  }
942
942
  var unicode = isUnicodeSupported();
943
943
  var isCI = () => process.env.CI === "true";
944
- var unicodeOr = (e, o2) => unicode ? e : o2;
944
+ var unicodeOr = (o2, e) => unicode ? o2 : e;
945
945
  var S_STEP_ACTIVE = unicodeOr("\u25C6", "*");
946
946
  var S_STEP_CANCEL = unicodeOr("\u25A0", "x");
947
947
  var S_STEP_ERROR = unicodeOr("\u25B2", "x");
@@ -967,8 +967,8 @@ var S_INFO = unicodeOr("\u25CF", "\u2022");
967
967
  var S_SUCCESS = unicodeOr("\u25C6", "*");
968
968
  var S_WARN = unicodeOr("\u25B2", "!");
969
969
  var S_ERROR = unicodeOr("\u25A0", "x");
970
- var symbol = (e) => {
971
- switch (e) {
970
+ var symbol = (o2) => {
971
+ switch (o2) {
972
972
  case "initial":
973
973
  case "active":
974
974
  return styleText2("cyan", S_STEP_ACTIVE);
@@ -980,8 +980,8 @@ var symbol = (e) => {
980
980
  return styleText2("green", S_STEP_SUBMIT);
981
981
  }
982
982
  };
983
- var symbolBar = (e) => {
984
- switch (e) {
983
+ var symbolBar = (o2) => {
984
+ switch (o2) {
985
985
  case "initial":
986
986
  case "active":
987
987
  return styleText2("cyan", S_BAR);
@@ -993,6 +993,10 @@ var symbolBar = (e) => {
993
993
  return styleText2("green", S_BAR);
994
994
  }
995
995
  };
996
+ function formatInstructionFooter(o2, e) {
997
+ const r2 = [`${e ? `${styleText2("cyan", S_BAR)} ` : ""}${o2.join(" \u2022 ")}`];
998
+ return e && r2.push(styleText2("cyan", S_BAR_END)), r2;
999
+ }
996
1000
  var E$1 = (l2, o2, g, c2, h2, O = false) => {
997
1001
  let r2 = o2, w = 0;
998
1002
  if (O)
@@ -1012,19 +1016,19 @@ var limitOptions = ({
1012
1016
  columnPadding: O = 0,
1013
1017
  rowPadding: r2 = 4
1014
1018
  }) => {
1015
- const i2 = getColumns(c2) - O, I = getRows(c2), C2 = styleText2("dim", "..."), x = Math.max(I - r2, 0), m = Math.max(Math.min(h2, x), 5);
1019
+ const i2 = getColumns(c2) - O, I = getRows(c2), C2 = styleText2("dim", "..."), x = Math.max(I - r2, 0), m2 = Math.max(Math.min(h2, x), 5);
1016
1020
  let p = 0;
1017
- l2 >= m - 3 && (p = Math.max(
1018
- Math.min(l2 - m + 3, o2.length - m),
1021
+ l2 >= m2 - 3 && (p = Math.max(
1022
+ Math.min(l2 - m2 + 3, o2.length - m2),
1019
1023
  0
1020
1024
  ));
1021
- let f = m < o2.length && p > 0, u3 = m < o2.length && p + m < o2.length;
1025
+ let f = m2 < o2.length && p > 0, u3 = m2 < o2.length && p + m2 < o2.length;
1022
1026
  const W2 = Math.min(
1023
- p + m,
1027
+ p + m2,
1024
1028
  o2.length
1025
1029
  ), e = [];
1026
- let d2 = 0;
1027
- f && d2++, u3 && d2++;
1030
+ let d = 0;
1031
+ f && d++, u3 && d++;
1028
1032
  const v = p + (f ? 1 : 0), P = W2 - (u3 ? 1 : 0);
1029
1033
  for (let t2 = v; t2 < P; t2++) {
1030
1034
  const n2 = wrapAnsi(g(o2[t2], t2 === l2), i2, {
@@ -1032,10 +1036,10 @@ var limitOptions = ({
1032
1036
  trim: false
1033
1037
  }).split(`
1034
1038
  `);
1035
- e.push(n2), d2 += n2.length;
1039
+ e.push(n2), d += n2.length;
1036
1040
  }
1037
- if (d2 > x) {
1038
- let t2 = 0, n2 = 0, s = d2;
1041
+ if (d > x) {
1042
+ let t2 = 0, n2 = 0, s = d;
1039
1043
  const M = l2 - v;
1040
1044
  let a3 = x;
1041
1045
  const T = () => E$1(e, s, 0, M, a3), L = () => E$1(
@@ -1095,42 +1099,29 @@ ${g}
1095
1099
  }
1096
1100
  }).prompt();
1097
1101
  };
1098
- var cancel = (o2 = "", t2) => {
1099
- const i2 = t2?.output ?? process.stdout, e = t2?.withGuide ?? settings.withGuide ? `${styleText2("gray", S_BAR_END)} ` : "";
1100
- i2.write(`${e}${styleText2("red", o2)}
1101
-
1102
- `);
1103
- };
1104
- var intro = (o2 = "", t2) => {
1105
- const i2 = t2?.output ?? process.stdout, e = t2?.withGuide ?? settings.withGuide ? `${styleText2("gray", S_BAR_START)} ` : "";
1106
- i2.write(`${e}${o2}
1107
- `);
1108
- };
1109
- var outro = (o2 = "", t2) => {
1110
- const i2 = t2?.output ?? process.stdout, e = t2?.withGuide ?? settings.withGuide ? `${styleText2("gray", S_BAR)}
1111
- ${styleText2("gray", S_BAR_END)} ` : "";
1112
- i2.write(`${e}${o2}
1113
-
1114
- `);
1115
- };
1116
- var d = (n2, a3) => n2.split(`
1117
- `).map((m) => a3(m)).join(`
1102
+ var MULTISELECT_INSTRUCTIONS = [
1103
+ `${styleText2("dim", "\u2191/\u2193")} to navigate`,
1104
+ `${styleText2("dim", "Space:")} select`,
1105
+ `${styleText2("dim", "Enter:")} confirm`
1106
+ ];
1107
+ var m = (n2, o2) => n2.split(`
1108
+ `).map((d) => o2(d)).join(`
1118
1109
  `);
1119
1110
  var multiselect = (n2) => {
1120
- const a3 = (t2, o2) => {
1111
+ const o2 = (t2, a3) => {
1121
1112
  const r2 = t2.label ?? String(t2.value);
1122
- return o2 === "disabled" ? `${styleText2("gray", S_CHECKBOX_INACTIVE)} ${d(r2, (l2) => styleText2(["strikethrough", "gray"], l2))}${t2.hint ? ` ${styleText2("dim", `(${t2.hint ?? "disabled"})`)}` : ""}` : o2 === "active" ? `${styleText2("cyan", S_CHECKBOX_ACTIVE)} ${r2}${t2.hint ? ` ${styleText2("dim", `(${t2.hint})`)}` : ""}` : o2 === "selected" ? `${styleText2("green", S_CHECKBOX_SELECTED)} ${d(r2, (l2) => styleText2("dim", l2))}${t2.hint ? ` ${styleText2("dim", `(${t2.hint})`)}` : ""}` : o2 === "cancelled" ? `${d(r2, (l2) => styleText2(["strikethrough", "dim"], l2))}` : o2 === "active-selected" ? `${styleText2("green", S_CHECKBOX_SELECTED)} ${r2}${t2.hint ? ` ${styleText2("dim", `(${t2.hint})`)}` : ""}` : o2 === "submitted" ? `${d(r2, (l2) => styleText2("dim", l2))}` : `${styleText2("dim", S_CHECKBOX_INACTIVE)} ${d(r2, (l2) => styleText2("dim", l2))}`;
1123
- }, m = n2.required ?? true;
1113
+ return a3 === "disabled" ? `${styleText2("gray", S_CHECKBOX_INACTIVE)} ${m(r2, (l2) => styleText2(["strikethrough", "gray"], l2))}${t2.hint ? ` ${styleText2("dim", `(${t2.hint ?? "disabled"})`)}` : ""}` : a3 === "active" ? `${styleText2("cyan", S_CHECKBOX_ACTIVE)} ${r2}${t2.hint ? ` ${styleText2("dim", `(${t2.hint})`)}` : ""}` : a3 === "selected" ? `${styleText2("green", S_CHECKBOX_SELECTED)} ${m(r2, (l2) => styleText2("dim", l2))}${t2.hint ? ` ${styleText2("dim", `(${t2.hint})`)}` : ""}` : a3 === "cancelled" ? `${m(r2, (l2) => styleText2(["strikethrough", "dim"], l2))}` : a3 === "active-selected" ? `${styleText2("green", S_CHECKBOX_SELECTED)} ${r2}${t2.hint ? ` ${styleText2("dim", `(${t2.hint})`)}` : ""}` : a3 === "submitted" ? `${m(r2, (l2) => styleText2("dim", l2))}` : `${styleText2("dim", S_CHECKBOX_INACTIVE)} ${m(r2, (l2) => styleText2("dim", l2))}`;
1114
+ }, d = n2.required ?? true;
1124
1115
  return new a$1({
1125
1116
  options: n2.options,
1126
1117
  signal: n2.signal,
1127
1118
  input: n2.input,
1128
1119
  output: n2.output,
1129
1120
  initialValues: n2.initialValues,
1130
- required: m,
1121
+ required: d,
1131
1122
  cursorAt: n2.cursorAt,
1132
1123
  validate(t2) {
1133
- if (m && (t2 === void 0 || t2.length === 0))
1124
+ if (d && (t2 === void 0 || t2.length === 0))
1134
1125
  return `Please select at least one option.
1135
1126
  ${styleText2(
1136
1127
  "reset",
@@ -1144,22 +1135,22 @@ ${styleText2(
1144
1135
  )}`;
1145
1136
  },
1146
1137
  render() {
1147
- const t2 = n2.withGuide ?? settings.withGuide, o2 = wrapTextWithPrefix(
1138
+ const t2 = n2.withGuide ?? settings.withGuide, a3 = wrapTextWithPrefix(
1148
1139
  n2.output,
1149
1140
  n2.message,
1150
1141
  t2 ? `${symbolBar(this.state)} ` : "",
1151
1142
  `${symbol(this.state)} `
1152
1143
  ), r2 = `${t2 ? `${styleText2("gray", S_BAR)}
1153
- ` : ""}${o2}
1154
- `, l2 = this.value ?? [], g = (i2, u3) => {
1144
+ ` : ""}${a3}
1145
+ `, l2 = this.value ?? [], p = (i2, u3) => {
1155
1146
  if (i2.disabled)
1156
- return a3(i2, "disabled");
1147
+ return o2(i2, "disabled");
1157
1148
  const s = l2.includes(i2.value);
1158
- return u3 && s ? a3(i2, "active-selected") : s ? a3(i2, "selected") : a3(i2, u3 ? "active" : "inactive");
1149
+ return u3 && s ? o2(i2, "active-selected") : s ? o2(i2, "selected") : o2(i2, u3 ? "active" : "inactive");
1159
1150
  };
1160
1151
  switch (this.state) {
1161
1152
  case "submit": {
1162
- const i2 = this.options.filter(({ value: s }) => l2.includes(s)).map((s) => a3(s, "submitted")).join(styleText2("dim", ", ")) || styleText2("dim", "none"), u3 = wrapTextWithPrefix(
1153
+ const i2 = this.options.filter(({ value: s }) => l2.includes(s)).map((s) => o2(s, "submitted")).join(styleText2("dim", ", ")) || styleText2("dim", "none"), u3 = wrapTextWithPrefix(
1163
1154
  n2.output,
1164
1155
  i2,
1165
1156
  t2 ? `${styleText2("gray", S_BAR)} ` : ""
@@ -1167,7 +1158,7 @@ ${styleText2(
1167
1158
  return `${r2}${u3}`;
1168
1159
  }
1169
1160
  case "cancel": {
1170
- const i2 = this.options.filter(({ value: s }) => l2.includes(s)).map((s) => a3(s, "cancelled")).join(styleText2("dim", ", "));
1161
+ const i2 = this.options.filter(({ value: s }) => l2.includes(s)).map((s) => o2(s, "cancelled")).join(styleText2("dim", ", "));
1171
1162
  if (i2.trim() === "")
1172
1163
  return `${r2}${styleText2("gray", S_BAR)}`;
1173
1164
  const u3 = wrapTextWithPrefix(
@@ -1181,10 +1172,10 @@ ${styleText2("gray", S_BAR)}` : ""}`;
1181
1172
  case "error": {
1182
1173
  const i2 = t2 ? `${styleText2("yellow", S_BAR)} ` : "", u3 = this.error.split(`
1183
1174
  `).map(
1184
- (h2, x) => x === 0 ? `${t2 ? `${styleText2("yellow", S_BAR_END)} ` : ""}${styleText2("yellow", h2)}` : ` ${h2}`
1175
+ ($, x) => x === 0 ? `${t2 ? `${styleText2("yellow", S_BAR_END)} ` : ""}${styleText2("yellow", $)}` : ` ${$}`
1185
1176
  ).join(`
1186
1177
  `), s = r2.split(`
1187
- `).length, v = u3.split(`
1178
+ `).length, g = u3.split(`
1188
1179
  `).length + 1;
1189
1180
  return `${r2}${i2}${limitOptions({
1190
1181
  output: n2.output,
@@ -1192,8 +1183,8 @@ ${styleText2("gray", S_BAR)}` : ""}`;
1192
1183
  cursor: this.cursor,
1193
1184
  maxItems: n2.maxItems,
1194
1185
  columnPadding: i2.length,
1195
- rowPadding: s + v,
1196
- style: g
1186
+ rowPadding: s + g,
1187
+ style: p
1197
1188
  }).join(`
1198
1189
  ${i2}`)}
1199
1190
  ${u3}
@@ -1201,53 +1192,72 @@ ${u3}
1201
1192
  }
1202
1193
  default: {
1203
1194
  const i2 = t2 ? `${styleText2("cyan", S_BAR)} ` : "", u3 = r2.split(`
1204
- `).length, s = t2 ? 2 : 1;
1195
+ `).length, s = formatInstructionFooter(MULTISELECT_INSTRUCTIONS, t2), g = s.join(`
1196
+ `), $ = s.length + 1;
1205
1197
  return `${r2}${i2}${limitOptions({
1206
1198
  output: n2.output,
1207
1199
  options: this.options,
1208
1200
  cursor: this.cursor,
1209
1201
  maxItems: n2.maxItems,
1210
1202
  columnPadding: i2.length,
1211
- rowPadding: u3 + s,
1212
- style: g
1203
+ rowPadding: u3 + $,
1204
+ style: p
1213
1205
  }).join(`
1214
1206
  ${i2}`)}
1215
- ${t2 ? styleText2("cyan", S_BAR_END) : ""}
1207
+ ${g}
1216
1208
  `;
1217
1209
  }
1218
1210
  }
1219
1211
  }
1220
1212
  }).prompt();
1221
1213
  };
1222
- var W$1 = (o2) => styleText2("dim", o2);
1214
+ var cancel = (o2 = "", t2) => {
1215
+ const i2 = t2?.output ?? process.stdout, e = t2?.withGuide ?? settings.withGuide ? `${styleText2("gray", S_BAR_END)} ` : "";
1216
+ i2.write(`${e}${styleText2("red", o2)}
1217
+
1218
+ `);
1219
+ };
1220
+ var intro = (o2 = "", t2) => {
1221
+ const i2 = t2?.output ?? process.stdout, e = t2?.withGuide ?? settings.withGuide ? `${styleText2("gray", S_BAR_START)} ` : "";
1222
+ i2.write(`${e}${o2}
1223
+ `);
1224
+ };
1225
+ var outro = (o2 = "", t2) => {
1226
+ const i2 = t2?.output ?? process.stdout, e = t2?.withGuide ?? settings.withGuide ? `${styleText2("gray", S_BAR)}
1227
+ ${styleText2("gray", S_BAR_END)} ` : "";
1228
+ i2.write(`${e}${o2}
1229
+
1230
+ `);
1231
+ };
1232
+ var W$1 = (o2) => o2;
1223
1233
  var C = (o2, e, s) => {
1224
1234
  const a3 = {
1225
1235
  hard: true,
1226
1236
  trim: false
1227
1237
  }, i2 = wrapAnsi(o2, e, a3).split(`
1228
- `), c2 = i2.reduce((n2, r2) => Math.max(dist_default2(r2), n2), 0), u3 = i2.map(s).reduce((n2, r2) => Math.max(dist_default2(r2), n2), 0), g = e - (u3 - c2);
1238
+ `), c2 = i2.reduce((n2, t2) => Math.max(dist_default2(t2), n2), 0), u3 = i2.map(s).reduce((n2, t2) => Math.max(dist_default2(t2), n2), 0), g = e - (u3 - c2);
1229
1239
  return wrapAnsi(o2, g, a3);
1230
1240
  };
1231
1241
  var note = (o2 = "", e = "", s) => {
1232
1242
  const a3 = s?.output ?? process$1.stdout, i2 = s?.withGuide ?? settings.withGuide, c2 = s?.format ?? W$1, g = ["", ...C(o2, getColumns(a3) - 6, c2).split(`
1233
- `).map(c2), ""], n2 = dist_default2(e), r2 = Math.max(
1234
- g.reduce((m, F) => {
1243
+ `).map(c2), ""], n2 = dist_default2(e), t2 = Math.max(
1244
+ g.reduce((m2, F) => {
1235
1245
  const O = dist_default2(F);
1236
- return O > m ? O : m;
1246
+ return O > m2 ? O : m2;
1237
1247
  }, 0),
1238
1248
  n2
1239
1249
  ) + 2, h2 = g.map(
1240
- (m) => `${styleText2("gray", S_BAR)} ${m}${" ".repeat(r2 - dist_default2(m))}${styleText2("gray", S_BAR)}`
1250
+ (m2) => `${styleText2("gray", S_BAR)} ${m2}${" ".repeat(t2 - dist_default2(m2))}${styleText2("gray", S_BAR)}`
1241
1251
  ).join(`
1242
1252
  `), T = i2 ? `${styleText2("gray", S_BAR)}
1243
1253
  ` : "", l$1 = i2 ? S_CONNECT_LEFT : S_CORNER_BOTTOM_LEFT;
1244
1254
  a3.write(
1245
1255
  `${T}${styleText2("green", S_STEP_SUBMIT)} ${styleText2("reset", e)} ${styleText2(
1246
1256
  "gray",
1247
- S_BAR_H.repeat(Math.max(r2 - n2 - 1, 1)) + S_CORNER_TOP_RIGHT
1257
+ S_BAR_H.repeat(Math.max(t2 - n2 - 1, 1)) + S_CORNER_TOP_RIGHT
1248
1258
  )}
1249
1259
  ${h2}
1250
- ${styleText2("gray", l$1 + S_BAR_H.repeat(r2 + 2) + S_CORNER_BOTTOM_RIGHT)}
1260
+ ${styleText2("gray", l$1 + S_BAR_H.repeat(t2 + 2) + S_CORNER_BOTTOM_RIGHT)}
1251
1261
  `
1252
1262
  );
1253
1263
  };
@@ -1260,18 +1270,18 @@ var spinner = ({
1260
1270
  errorMessage: O,
1261
1271
  frames: E = unicode ? ["\u25D2", "\u25D0", "\u25D3", "\u25D1"] : ["\u2022", "o", "O", "0"],
1262
1272
  delay: F = unicode ? 80 : 120,
1263
- signal: m,
1273
+ signal: m2,
1264
1274
  ...I
1265
1275
  } = {}) => {
1266
1276
  const u3 = isCI();
1267
- let M, T, d2 = false, S = false, s = "", p, w = performance.now();
1277
+ let M, T, d = false, S = false, s = "", p, w = performance.now();
1268
1278
  const x = getColumns(n2), k = I?.styleFrame ?? W, g = (e) => {
1269
1279
  const r2 = e > 1 ? O ?? settings.messages.error : G ?? settings.messages.cancel;
1270
- S = e === 1, d2 && (a3(r2, e), S && typeof h2 == "function" && h2());
1280
+ S = e === 1, d && (a3(r2, e), S && typeof h2 == "function" && h2());
1271
1281
  }, f = () => g(2), i2 = () => g(1), A = () => {
1272
- process.on("uncaughtExceptionMonitor", f), process.on("unhandledRejection", f), process.on("SIGINT", i2), process.on("SIGTERM", i2), process.on("exit", g), m && m.addEventListener("abort", i2);
1282
+ process.on("uncaughtExceptionMonitor", f), process.on("unhandledRejection", f), process.on("SIGINT", i2), process.on("SIGTERM", i2), process.on("exit", g), m2 && m2.addEventListener("abort", i2);
1273
1283
  }, H = () => {
1274
- process.removeListener("uncaughtExceptionMonitor", f), process.removeListener("unhandledRejection", f), process.removeListener("SIGINT", i2), process.removeListener("SIGTERM", i2), process.removeListener("exit", g), m && m.removeEventListener("abort", i2);
1284
+ process.removeListener("uncaughtExceptionMonitor", f), process.removeListener("unhandledRejection", f), process.removeListener("SIGINT", i2), process.removeListener("SIGTERM", i2), process.removeListener("exit", g), m2 && m2.removeEventListener("abort", i2);
1275
1285
  }, y = () => {
1276
1286
  if (p === void 0) return;
1277
1287
  u3 && n2.write(`
@@ -1286,7 +1296,7 @@ var spinner = ({
1286
1296
  const r2 = (performance.now() - e) / 1e3, t2 = Math.floor(r2 / 60), o2 = Math.floor(r2 % 60);
1287
1297
  return t2 > 0 ? `[${t2}m ${o2}s]` : `[${o2}s]`;
1288
1298
  }, N = I.withGuide ?? settings.withGuide, P = (e = "") => {
1289
- d2 = true, M = block({ output: n2 }), s = C2(e), w = performance.now(), N && n2.write(`${styleText2("gray", S_BAR)}
1299
+ d = true, M = block({ output: n2 }), s = C2(e), w = performance.now(), N && n2.write(`${styleText2("gray", S_BAR)}
1290
1300
  `);
1291
1301
  let r2 = 0, t2 = 0;
1292
1302
  A(), T = setInterval(() => {
@@ -1310,8 +1320,8 @@ var spinner = ({
1310
1320
  n2.write(j), r2 = r2 + 1 < E.length ? r2 + 1 : 0, t2 = t2 < 4 ? t2 + 0.125 : 0;
1311
1321
  }, F);
1312
1322
  }, a3 = (e = "", r2 = 0, t2 = false) => {
1313
- if (!d2) return;
1314
- d2 = false, clearInterval(T), y();
1323
+ if (!d) return;
1324
+ d = false, clearInterval(T), y();
1315
1325
  const o2 = r2 === 0 ? styleText2("green", S_STEP_SUBMIT) : r2 === 1 ? styleText2("red", S_STEP_CANCEL) : styleText2("red", S_STEP_ERROR);
1316
1326
  s = e ?? s, t2 || (l2 === "timer" ? n2.write(`${o2} ${s} ${_(w)}
1317
1327
  `) : n2.write(`${o2} ${s}
@@ -1336,20 +1346,24 @@ var u2 = {
1336
1346
  heavy: unicodeOr("\u2501", "="),
1337
1347
  block: unicodeOr("\u2588", "#")
1338
1348
  };
1339
- var c = (e, a3) => e.includes(`
1340
- `) ? e.split(`
1341
- `).map((t2) => a3(t2)).join(`
1342
- `) : a3(e);
1343
- var select = (e) => {
1344
- const a3 = (t2, d2) => {
1345
- const s = t2.label ?? String(t2.value);
1346
- switch (d2) {
1349
+ var SELECT_INSTRUCTIONS = [
1350
+ `${styleText2("dim", "\u2191/\u2193")} to navigate`,
1351
+ `${styleText2("dim", "Enter:")} confirm`
1352
+ ];
1353
+ var c = (t2, a3) => t2.includes(`
1354
+ `) ? t2.split(`
1355
+ `).map((i2) => a3(i2)).join(`
1356
+ `) : a3(t2);
1357
+ var select = (t2) => {
1358
+ const a3 = (i2, m2) => {
1359
+ const s = i2.label ?? String(i2.value);
1360
+ switch (m2) {
1347
1361
  case "disabled":
1348
- return `${styleText2("gray", S_RADIO_INACTIVE)} ${c(s, (n2) => styleText2("gray", n2))}${t2.hint ? ` ${styleText2("dim", `(${t2.hint ?? "disabled"})`)}` : ""}`;
1362
+ return `${styleText2("gray", S_RADIO_INACTIVE)} ${c(s, (n2) => styleText2("gray", n2))}${i2.hint ? ` ${styleText2("dim", `(${i2.hint ?? "disabled"})`)}` : ""}`;
1349
1363
  case "selected":
1350
1364
  return `${c(s, (n2) => styleText2("dim", n2))}`;
1351
1365
  case "active":
1352
- return `${styleText2("green", S_RADIO_ACTIVE)} ${s}${t2.hint ? ` ${styleText2("dim", `(${t2.hint})`)}` : ""}`;
1366
+ return `${styleText2("green", S_RADIO_ACTIVE)} ${s}${i2.hint ? ` ${styleText2("dim", `(${i2.hint})`)}` : ""}`;
1353
1367
  case "cancelled":
1354
1368
  return `${c(s, (n2) => styleText2(["strikethrough", "dim"], n2))}`;
1355
1369
  default:
@@ -1357,52 +1371,53 @@ var select = (e) => {
1357
1371
  }
1358
1372
  };
1359
1373
  return new a2({
1360
- options: e.options,
1361
- signal: e.signal,
1362
- input: e.input,
1363
- output: e.output,
1364
- initialValue: e.initialValue,
1374
+ options: t2.options,
1375
+ signal: t2.signal,
1376
+ input: t2.input,
1377
+ output: t2.output,
1378
+ initialValue: t2.initialValue,
1365
1379
  render() {
1366
- const t2 = e.withGuide ?? settings.withGuide, d2 = `${symbol(this.state)} `, s = `${symbolBar(this.state)} `, n2 = wrapTextWithPrefix(
1367
- e.output,
1368
- e.message,
1380
+ const i2 = t2.withGuide ?? settings.withGuide, m2 = `${symbol(this.state)} `, s = `${symbolBar(this.state)} `, n2 = wrapTextWithPrefix(
1381
+ t2.output,
1382
+ t2.message,
1369
1383
  s,
1370
- d2
1371
- ), u3 = `${t2 ? `${styleText2("gray", S_BAR)}
1384
+ m2
1385
+ ), u3 = `${i2 ? `${styleText2("gray", S_BAR)}
1372
1386
  ` : ""}${n2}
1373
1387
  `;
1374
1388
  switch (this.state) {
1375
1389
  case "submit": {
1376
- const r2 = t2 ? `${styleText2("gray", S_BAR)} ` : "", l2 = wrapTextWithPrefix(
1377
- e.output,
1390
+ const r2 = i2 ? `${styleText2("gray", S_BAR)} ` : "", o2 = wrapTextWithPrefix(
1391
+ t2.output,
1378
1392
  a3(this.options[this.cursor], "selected"),
1379
1393
  r2
1380
1394
  );
1381
- return `${u3}${l2}`;
1395
+ return `${u3}${o2}`;
1382
1396
  }
1383
1397
  case "cancel": {
1384
- const r2 = t2 ? `${styleText2("gray", S_BAR)} ` : "", l2 = wrapTextWithPrefix(
1385
- e.output,
1398
+ const r2 = i2 ? `${styleText2("gray", S_BAR)} ` : "", o2 = wrapTextWithPrefix(
1399
+ t2.output,
1386
1400
  a3(this.options[this.cursor], "cancelled"),
1387
1401
  r2
1388
1402
  );
1389
- return `${u3}${l2}${t2 ? `
1403
+ return `${u3}${o2}${i2 ? `
1390
1404
  ${styleText2("gray", S_BAR)}` : ""}`;
1391
1405
  }
1392
1406
  default: {
1393
- const r2 = t2 ? `${styleText2("cyan", S_BAR)} ` : "", l2 = t2 ? styleText2("cyan", S_BAR_END) : "", g = u3.split(`
1394
- `).length, h2 = t2 ? 2 : 1;
1407
+ const r2 = i2 ? `${styleText2("cyan", S_BAR)} ` : "", o2 = u3.split(`
1408
+ `).length, $ = formatInstructionFooter(SELECT_INSTRUCTIONS, i2), h2 = $.join(`
1409
+ `), b = $.length + 1;
1395
1410
  return `${u3}${r2}${limitOptions({
1396
- output: e.output,
1411
+ output: t2.output,
1397
1412
  cursor: this.cursor,
1398
1413
  options: this.options,
1399
- maxItems: e.maxItems,
1414
+ maxItems: t2.maxItems,
1400
1415
  columnPadding: r2.length,
1401
- rowPadding: g + h2,
1402
- style: (p, b) => a3(p, p.disabled ? "disabled" : b ? "active" : "inactive")
1416
+ rowPadding: o2 + b,
1417
+ style: (p, x) => a3(p, p.disabled ? "disabled" : x ? "active" : "inactive")
1403
1418
  }).join(`
1404
1419
  ${r2}`)}
1405
- ${l2}
1420
+ ${h2}
1406
1421
  `;
1407
1422
  }
1408
1423
  }
@@ -1424,10 +1439,10 @@ var text = (t2) => new n({
1424
1439
  `, c2 = t2.placeholder ? styleText2("inverse", t2.placeholder[0]) + styleText2("dim", t2.placeholder.slice(1)) : styleText2(["inverse", "hidden"], "_"), o2 = this.userInput ? this.userInputWithCursor : c2, a3 = this.value ?? "";
1425
1440
  switch (this.state) {
1426
1441
  case "error": {
1427
- const n2 = this.error ? ` ${styleText2("yellow", this.error)}` : "", r2 = i2 ? `${styleText2("yellow", S_BAR)} ` : "", d2 = i2 ? styleText2("yellow", S_BAR_END) : "";
1442
+ const n2 = this.error ? ` ${styleText2("yellow", this.error)}` : "", r2 = i2 ? `${styleText2("yellow", S_BAR)} ` : "", d = i2 ? styleText2("yellow", S_BAR_END) : "";
1428
1443
  return `${s.trim()}
1429
1444
  ${r2}${o2}
1430
- ${d2}${n2}
1445
+ ${d}${n2}
1431
1446
  `;
1432
1447
  }
1433
1448
  case "submit": {
@@ -1588,11 +1603,11 @@ function capture(cmd, args, cwd) {
1588
1603
  const child = onWindows ? spawn([cmd, ...args].join(" "), { cwd, shell: true }) : spawn(cmd, args, { cwd });
1589
1604
  let stdout2 = "";
1590
1605
  let stderr = "";
1591
- child.stdout?.on("data", (d2) => {
1592
- stdout2 += d2.toString();
1606
+ child.stdout?.on("data", (d) => {
1607
+ stdout2 += d.toString();
1593
1608
  });
1594
- child.stderr?.on("data", (d2) => {
1595
- stderr += d2.toString();
1609
+ child.stderr?.on("data", (d) => {
1610
+ stderr += d.toString();
1596
1611
  });
1597
1612
  child.on("error", reject);
1598
1613
  child.on("close", (code) => {
@@ -1680,9 +1695,7 @@ function visibleWidth(s) {
1680
1695
  function box(lines, paint = (s) => s) {
1681
1696
  const width = lines.reduce((w, l2) => Math.max(w, visibleWidth(l2)), 0);
1682
1697
  const side = paint("\u2502");
1683
- const body = lines.map(
1684
- (l2) => ` ${side} ${l2}${" ".repeat(width - visibleWidth(l2))} ${side}`
1685
- );
1698
+ const body = lines.map((l2) => ` ${side} ${l2}${" ".repeat(width - visibleWidth(l2))} ${side}`);
1686
1699
  return [
1687
1700
  " " + paint(`\u256D${"\u2500".repeat(width + 4)}\u256E`),
1688
1701
  ...body,
@@ -2025,7 +2038,7 @@ function scaffold(name, template, features, aiTools, images) {
2025
2038
  "@types/react-dom": "^19.2.3",
2026
2039
  eslint: "^10.2.0",
2027
2040
  prettier: "^3.8.1",
2028
- toilscript: "^0.1.27",
2041
+ toilscript: "^0.1.35",
2029
2042
  typescript: "^6.0.3"
2030
2043
  };
2031
2044
  for (const dep of requiredPackages(features).sort()) {
@@ -2067,9 +2080,18 @@ export default defineConfig({
2067
2080
  // Generated files don't need formatting. (toilscript server decorators like @main /
2068
2081
  // @remote-on-functions are handled by the toiljs/prettier-plugin, so server/ is not ignored.)
2069
2082
  ".prettierignore": "node_modules\nbuild\n.toil\nshared/server.ts\ntoil-env.d.ts\ntoil-routes.d.ts\nserver/_emails.ts\nserver/toil-server-env.d.ts\n",
2070
- ".gitignore": "node_modules\nbuild\n.toil\nshared/server.ts\ntoil-env.d.ts\ntoil-routes.d.ts\n# Local dev env vars/secrets (never commit)\n.env\n.env.secrets\n",
2071
- // Use the project's pinned TypeScript (node_modules) instead of VS Code's bundled version.
2072
- ".vscode/settings.json": JSON.stringify({ "typescript.tsdk": "node_modules/typescript/lib" }, null, 4) + "\n",
2083
+ ".gitignore": "node_modules\nbuild\n.toil\nshared/server.ts\ntoil-env.d.ts\ntoil-routes.d.ts\nhosts/*/_tmpl/\n# Local dev env vars/secrets (never commit)\n.env\n.env.secrets\n",
2084
+ // Use the project's pinned TypeScript (node_modules) instead of VS Code's bundled
2085
+ // version, and prompt to switch, so the editor loads the toilscript LS plugin wired
2086
+ // in server/tsconfig.json (which clears the @database / @data editor false positives).
2087
+ ".vscode/settings.json": JSON.stringify(
2088
+ {
2089
+ "typescript.tsdk": "node_modules/typescript/lib",
2090
+ "typescript.enablePromptUseWorkspaceTsdk": true
2091
+ },
2092
+ null,
2093
+ 4
2094
+ ) + "\n",
2073
2095
  "toil-env.d.ts": TOIL_ENV_DTS,
2074
2096
  // Stub typed-routes augmentation (RoutePath = string until the first dev/build regenerates it).
2075
2097
  "toil-routes.d.ts": "// AUTO-GENERATED by toil, do not edit.\nexport {};\n",
@@ -2120,9 +2142,15 @@ export default defineConfig({
2120
2142
  null,
2121
2143
  4
2122
2144
  ) + "\n",
2145
+ // The toilscript LS plugin teaches the editor about compiler-injected members
2146
+ // (`@database` static collections like `Db.users`, the `@data` codec, `@user`), so
2147
+ // stock TypeScript stops false-flagging them as TS2339. Editor-only; ignored by tsc.
2123
2148
  "server/tsconfig.json": JSON.stringify(
2124
2149
  {
2125
2150
  extends: "toilscript/std/assembly.json",
2151
+ compilerOptions: {
2152
+ plugins: [{ name: "toilscript/std/ts-plugin.cjs" }]
2153
+ },
2126
2154
  include: ["./**/*.ts"]
2127
2155
  },
2128
2156
  null,
@@ -2505,13 +2533,17 @@ import fs4 from "node:fs";
2505
2533
  import { createRequire } from "node:module";
2506
2534
  import path5 from "node:path";
2507
2535
  import { fileURLToPath as fileURLToPath3 } from "node:url";
2508
- import { loadConfig as loadConfig2, scanRoutes, TOIL_SERVER_ENV_DTS as TOIL_SERVER_ENV_DTS2 } from "toiljs/compiler";
2536
+ import {
2537
+ loadConfig as loadConfig2,
2538
+ scanRoutes,
2539
+ TOIL_SERVER_ENV_DTS as TOIL_SERVER_ENV_DTS2
2540
+ } from "toiljs/compiler";
2509
2541
 
2510
2542
  // src/cli/diagnostics.ts
2511
2543
  function parseVersion(v) {
2512
- const m = /(\d+)(?:\.(\d+))?(?:\.(\d+))?/.exec(v);
2513
- if (!m) return [0, 0, 0];
2514
- return [Number(m[1]), Number(m[2] ?? 0), Number(m[3] ?? 0)];
2544
+ const m2 = /(\d+)(?:\.(\d+))?(?:\.(\d+))?/.exec(v);
2545
+ if (!m2) return [0, 0, 0];
2546
+ return [Number(m2[1]), Number(m2[2] ?? 0), Number(m2[3] ?? 0)];
2515
2547
  }
2516
2548
  function satisfiesMin(version2, range) {
2517
2549
  const [a3, b, c2] = parseVersion(version2);
@@ -2682,9 +2714,9 @@ function findRelativeAssets(files) {
2682
2714
  const lines = file.source.split("\n");
2683
2715
  for (let i2 = 0; i2 < lines.length; i2++) {
2684
2716
  attr.lastIndex = 0;
2685
- let m;
2686
- while ((m = attr.exec(lines[i2])) !== null) {
2687
- const value = m[1] ?? m[2] ?? "";
2717
+ let m2;
2718
+ while ((m2 = attr.exec(lines[i2])) !== null) {
2719
+ const value = m2[1] ?? m2[2] ?? "";
2688
2720
  if (isBrokenRelativeAsset(value)) {
2689
2721
  issues.push({ file: file.path, line: i2 + 1, value });
2690
2722
  }
@@ -2838,6 +2870,27 @@ function checkRestDispatch(f) {
2838
2870
  fix: "In your handler add `const hit = Rest.dispatch(req); if (hit != null) return hit;`, or set `Server.handler = () => new RestHandler()`."
2839
2871
  };
2840
2872
  }
2873
+ function checkServerTsPlugin(present) {
2874
+ return present ? { id: "server-ts-plugin", label: "toilscript editor plugin", status: "pass" } : {
2875
+ id: "server-ts-plugin",
2876
+ label: "toilscript editor plugin",
2877
+ status: "warn",
2878
+ detail: "server tsconfig is missing the toilscript LS plugin, so the editor wrongly flags @database static collections (e.g. GuestbookDb.totals) and @data members as TS2339",
2879
+ fix: 'Run `toiljs doctor --fix` to add { "plugins": [{ "name": "toilscript/std/ts-plugin.cjs" }] } to your server tsconfig, then pick the workspace TypeScript version and restart the TS server.'
2880
+ };
2881
+ }
2882
+ function checkAuthSecrets(f) {
2883
+ if (!f.usesAuth || f.sessionSecretSet) {
2884
+ return { id: "auth-secrets", label: "Session secret", status: "pass" };
2885
+ }
2886
+ return {
2887
+ id: "auth-secrets",
2888
+ label: "Session secret",
2889
+ status: "warn",
2890
+ detail: "auth is used but AUTH_SESSION_SECRET is unset: sessions fall back to a PUBLISHED key, so anyone can forge a session cookie and skip login",
2891
+ fix: `Set AUTH_SESSION_SECRET to a long random value in .env.secrets (local) and on your deploy target (also AUTH_OPRF_SEED / AUTH_KEM_SK if you use password login). Generate one: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))".`
2892
+ };
2893
+ }
2841
2894
  function summarize(groups) {
2842
2895
  let pass = 0;
2843
2896
  let warn2 = 0;
@@ -2969,6 +3022,41 @@ function gatherRestFacts(root, toilconfig) {
2969
3022
  }
2970
3023
  return { hasControllers, dispatched };
2971
3024
  }
3025
+ function secretDefined(root, key) {
3026
+ const raw = readFile(path5.join(root, ".env.secrets"));
3027
+ if (raw === null) return false;
3028
+ return new RegExp(`^\\s*(?:export\\s+)?${key}\\s*=\\s*\\S`, "m").test(raw);
3029
+ }
3030
+ function gatherAuthFacts(root, toilconfig) {
3031
+ let usesAuth = false;
3032
+ for (const src of serverSources(root, toilconfig)) {
3033
+ if (/\bAuthService\b/.test(src) || /@user\b/.test(src) || /@auth\b/.test(src)) {
3034
+ usesAuth = true;
3035
+ break;
3036
+ }
3037
+ }
3038
+ return { usesAuth, sessionSecretSet: secretDefined(root, "AUTH_SESSION_SECRET") };
3039
+ }
3040
+ var TS_PLUGIN_NAME = "toilscript/std/ts-plugin.cjs";
3041
+ function serverTsconfigPath(root, toilconfig) {
3042
+ const dirs = /* @__PURE__ */ new Set();
3043
+ const entries = Array.isArray(toilconfig?.entries) ? toilconfig.entries.filter((e) => typeof e === "string") : [];
3044
+ for (const e of entries) dirs.add(path5.dirname(path5.resolve(root, e)));
3045
+ if (dirs.size === 0) dirs.add(path5.join(root, "server"));
3046
+ for (const dir of dirs) {
3047
+ const p = path5.join(dir, "tsconfig.json");
3048
+ if (fs4.existsSync(p)) return p;
3049
+ }
3050
+ return null;
3051
+ }
3052
+ function tsconfigHasToilPlugin(tsconfig) {
3053
+ const plugins = asRecord(tsconfig?.compilerOptions)?.plugins;
3054
+ if (!Array.isArray(plugins)) return false;
3055
+ return plugins.some((p) => {
3056
+ const name = asRecord(p)?.name;
3057
+ return typeof name === "string" && name.includes("ts-plugin");
3058
+ });
3059
+ }
2972
3060
  function applyRpcFix(root) {
2973
3061
  const changed = [];
2974
3062
  const skipped = [];
@@ -3064,7 +3152,9 @@ function applyRpcFix(root) {
3064
3152
  }
3065
3153
  const serverToilconfig = readJsonObject(path5.join(root, "toilconfig.json"));
3066
3154
  if (serverToilconfig !== null) {
3067
- const entries = Array.isArray(serverToilconfig.entries) ? serverToilconfig.entries.filter((e) => typeof e === "string") : [];
3155
+ const entries = Array.isArray(serverToilconfig.entries) ? serverToilconfig.entries.filter(
3156
+ (e) => typeof e === "string"
3157
+ ) : [];
3068
3158
  const dirs = /* @__PURE__ */ new Set();
3069
3159
  for (const e of entries) dirs.add(path5.dirname(path5.resolve(root, e)));
3070
3160
  if (dirs.size === 0) dirs.add(path5.join(root, "server"));
@@ -3154,6 +3244,50 @@ function applyPrettierFix(root, pkg) {
3154
3244
  changed.push(".prettierrc.json");
3155
3245
  return { changed, skipped };
3156
3246
  }
3247
+ function applyServerEditorFix(root, toilconfig) {
3248
+ const changed = [];
3249
+ const skipped = [];
3250
+ const tsPath = serverTsconfigPath(root, toilconfig);
3251
+ if (tsPath === null) {
3252
+ skipped.push("server/tsconfig.json (not found; add the toilscript ts-plugin by hand)");
3253
+ } else {
3254
+ const rel = path5.relative(root, tsPath);
3255
+ const raw = readFile(tsPath);
3256
+ const parsed = raw !== null ? readJsonObject(tsPath) : null;
3257
+ if (parsed === null) {
3258
+ skipped.push(`${rel} (JSON with comments; add the "${TS_PLUGIN_NAME}" plugin by hand)`);
3259
+ } else if (!tsconfigHasToilPlugin(parsed)) {
3260
+ const co = asRecord(parsed.compilerOptions) ?? {};
3261
+ const existingPlugins = Array.isArray(co.plugins) ? co.plugins : [];
3262
+ co.plugins = [...existingPlugins, { name: TS_PLUGIN_NAME }];
3263
+ parsed.compilerOptions = co;
3264
+ writeFile(tsPath, JSON.stringify(parsed, null, 4) + "\n");
3265
+ changed.push(rel);
3266
+ }
3267
+ }
3268
+ const vsPath = path5.join(root, ".vscode", "settings.json");
3269
+ const vsRaw = readFile(vsPath);
3270
+ const vs = vsRaw !== null ? readJsonObject(vsPath) : {};
3271
+ if (vs === null) {
3272
+ skipped.push(".vscode/settings.json (unparseable; set typescript.tsdk by hand)");
3273
+ } else {
3274
+ let touched = false;
3275
+ if (vs["typescript.tsdk"] !== "node_modules/typescript/lib") {
3276
+ vs["typescript.tsdk"] = "node_modules/typescript/lib";
3277
+ touched = true;
3278
+ }
3279
+ if (vs["typescript.enablePromptUseWorkspaceTsdk"] !== true) {
3280
+ vs["typescript.enablePromptUseWorkspaceTsdk"] = true;
3281
+ touched = true;
3282
+ }
3283
+ if (touched) {
3284
+ fs4.mkdirSync(path5.dirname(vsPath), { recursive: true });
3285
+ writeFile(vsPath, JSON.stringify(vs, null, 4) + "\n");
3286
+ changed.push(".vscode/settings.json");
3287
+ }
3288
+ }
3289
+ return { changed, skipped };
3290
+ }
3157
3291
  function frameworkMeta() {
3158
3292
  const pkgPath = path5.resolve(
3159
3293
  path5.dirname(fileURLToPath3(import.meta.url)),
@@ -3288,15 +3422,28 @@ async function runDoctor(opts) {
3288
3422
  const peerChecks = Object.keys(meta.peers).map(peerName);
3289
3423
  const rpcFix = serverPresent && opts.fix ? applyRpcFix(root) : null;
3290
3424
  const prettierFix = serverPresent && opts.fix ? applyPrettierFix(root, projectPkg) : null;
3425
+ const editorFix = serverPresent && opts.fix ? applyServerEditorFix(root, toilconfig) : null;
3291
3426
  const rpcFacts = gatherRpcFacts(root);
3292
3427
  const restFacts = gatherRestFacts(root, toilconfig);
3428
+ const authFacts = gatherAuthFacts(root, toilconfig);
3293
3429
  const prettierPresent = prettierPluginPresent(
3294
3430
  root,
3295
3431
  readJsonObject(path5.join(root, "package.json"))
3296
3432
  );
3297
- const serverFix = rpcFix || prettierFix ? {
3298
- changed: [...rpcFix?.changed ?? [], ...prettierFix?.changed ?? []],
3299
- skipped: [...rpcFix?.skipped ?? [], ...prettierFix?.skipped ?? []]
3433
+ const serverTsPath = serverPresent ? serverTsconfigPath(root, toilconfig) : null;
3434
+ const serverTsParsed = serverTsPath ? readJsonObject(serverTsPath) : null;
3435
+ const serverTsPluginPresent = serverTsPath === null || serverTsParsed === null ? true : tsconfigHasToilPlugin(serverTsParsed);
3436
+ const serverFix = rpcFix || prettierFix || editorFix ? {
3437
+ changed: [
3438
+ ...rpcFix?.changed ?? [],
3439
+ ...prettierFix?.changed ?? [],
3440
+ ...editorFix?.changed ?? []
3441
+ ],
3442
+ skipped: [
3443
+ ...rpcFix?.skipped ?? [],
3444
+ ...prettierFix?.skipped ?? [],
3445
+ ...editorFix?.skipped ?? []
3446
+ ]
3300
3447
  } : null;
3301
3448
  const groups = [
3302
3449
  {
@@ -3354,10 +3501,14 @@ async function runDoctor(opts) {
3354
3501
  checkWasmBuilt(wasmExists),
3355
3502
  checkRpcWiring(rpcFacts),
3356
3503
  checkRestDispatch(restFacts),
3357
- checkPrettierPlugin(prettierPresent)
3504
+ checkPrettierPlugin(prettierPresent),
3505
+ checkServerTsPlugin(serverTsPluginPresent)
3358
3506
  ] : [checkToilconfig(false)]
3359
3507
  }
3360
3508
  ];
3509
+ if (serverPresent) {
3510
+ groups.push({ title: "Security", checks: [checkAuthSecrets(authFacts)] });
3511
+ }
3361
3512
  const summary = summarize(groups);
3362
3513
  if (opts.json) {
3363
3514
  process.stdout.write(JSON.stringify({ groups, summary, fixed: serverFix }, null, 2) + "\n");
@@ -3376,14 +3527,14 @@ async function runDoctor(opts) {
3376
3527
  function renderRpcFix(result) {
3377
3528
  const out = [];
3378
3529
  if (result.changed.length > 0) {
3379
- out.push(" " + success("fixed RPC wiring") + dim(` ${result.changed.join(", ")}`));
3530
+ out.push(" " + success("fixed server wiring") + dim(` ${result.changed.join(", ")}`));
3380
3531
  if (result.changed.includes("package.json")) {
3381
3532
  out.push(
3382
3533
  " " + dim("run your installer (npm/pnpm/yarn) if the toilscript version changed.")
3383
3534
  );
3384
3535
  }
3385
3536
  } else {
3386
- out.push(" " + dim("RPC wiring already in place, nothing to fix."));
3537
+ out.push(" " + dim("server wiring already in place, nothing to fix."));
3387
3538
  }
3388
3539
  for (const item of result.skipped) out.push(" " + warn("skipped") + dim(` ${item}`));
3389
3540
  process.stdout.write(out.join("\n") + "\n\n");
@@ -3401,9 +3552,9 @@ import path6 from "node:path";
3401
3552
 
3402
3553
  // src/cli/updates.ts
3403
3554
  function parseVersion2(v) {
3404
- const m = /(\d+)(?:\.(\d+))?(?:\.(\d+))?/.exec(v);
3405
- if (!m) return [0, 0, 0];
3406
- return [Number(m[1]), Number(m[2] ?? 0), Number(m[3] ?? 0)];
3555
+ const m2 = /(\d+)(?:\.(\d+))?(?:\.(\d+))?/.exec(v);
3556
+ if (!m2) return [0, 0, 0];
3557
+ return [Number(m2[1]), Number(m2[2] ?? 0), Number(m2[3] ?? 0)];
3407
3558
  }
3408
3559
  function classifyBump(from, to) {
3409
3560
  const [fa, fb, fc] = parseVersion2(from);
@@ -3569,9 +3720,9 @@ function isCacheFresh(cache, now, ttlMs = CHECK_TTL_MS) {
3569
3720
  return cache.checkedAt <= now && now - cache.checkedAt < ttlMs;
3570
3721
  }
3571
3722
  function parseSemver(v) {
3572
- const m = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?/.exec(v.trim());
3573
- if (!m) return { nums: [0, 0, 0], pre: null };
3574
- return { nums: [Number(m[1]), Number(m[2]), Number(m[3])], pre: m[4] ?? null };
3723
+ const m2 = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?/.exec(v.trim());
3724
+ if (!m2) return { nums: [0, 0, 0], pre: null };
3725
+ return { nums: [Number(m2[1]), Number(m2[2]), Number(m2[3])], pre: m2[4] ?? null };
3575
3726
  }
3576
3727
  function compareSemver(a3, b) {
3577
3728
  const pa = parseSemver(a3);