sporades 0.6.3 → 0.6.5

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/CHANGES.md CHANGED
@@ -1,179 +1,9 @@
1
1
  # Changes
2
2
 
3
- ## Unreleased - 2026-08-03
3
+ ## Unreleased - 2026-08-04
4
4
 
5
- Changes since v0.6.1.
6
-
7
- ### 🚀 Features
8
-
9
- - Add scriptable sealed env key management (742ac99).
10
- - Add Microsoft OpenID Connect sign-in (affeed9).
11
- - Complete SMTP runtime production parity (3237d21).
12
- - Add Sign in with Apple (11b22ff).
13
- - Add Facebook OAuth sign-in (3e166fc).
14
- - Support portable SMTP relay modes (59817d3).
15
- - Add multi-provider OAuth configuration (e9f388f).
16
- - Add Mailgun SMTP extensions (c71314c).
17
- - Add Postmark SMTP extensions (768b078).
18
- - Add generic SMTP mail runtime (420d8b0).
19
- - Add stable provider identities (2d5be36).
5
+ Changes since v0.6.4.
20
6
 
21
7
  ### 🐛 Bug Fixes
22
8
 
23
- - Isolate SMTP transport error classification (279e629).
24
- - Guard SMTP error and close traps (203724b).
25
- - Harden SMTP failure and shutdown boundaries (2a77579).
26
- - Preserve Facebook body timeouts (bbbb5b5).
27
- - Harden Facebook OAuth transport (c8ddcc6).
28
- - Harden portable SMTP configuration (edaf97e).
29
-
30
- ### 🔧 Improvements
31
-
32
- - Complete multi-provider OAuth implementation swarm (83217df).
33
- - Accept OAuth coexistence issue 07 (df102b9).
34
- - Final review for OAuth coexistence issue 07 (fa1c3e0).
35
- - Request Apple JWKS taxonomy fix for OAuth issue 07 (63328b1).
36
- - Re-review OAuth endpoint confinement (3a5f38a).
37
- - Request JWKS confinement rework for OAuth issue 07 (fbcc0e6).
38
- - Re-review OAuth coexistence issue 07 (609aa56).
39
- - Request rework for OAuth coexistence issue 07 (e03ef29).
40
- - Review OAuth coexistence issue 07 (81b8cb2).
41
- - Dispatch multi-provider OAuth issue 07 (2c54c36).
42
- - Complete Microsoft OAuth issue 04 (b18dae7).
43
- - Accept Microsoft OAuth issue 04 integration (6c6b7db).
44
- - Record SMTP swarm final verification (291cb7c).
45
- - Review three-provider OAuth integration (df527e8).
46
- - Accept Microsoft OAuth issue 04 candidate (b63b4b1).
47
- - Complete SMTP mail issue 05 (cedc452).
48
- - Accept SMTP mail issue 05 (39684a1).
49
- - Re-review Microsoft OAuth cache saturation (487686a).
50
- - Re-review third replacement for SMTP mail issue 05 (6f64f23).
51
- - Clean up Apple OAuth worktree (40ceac4).
52
- - Request saturation rework for Microsoft OAuth cache (9fb94d5).
53
- - Request third rework for SMTP mail issue 05 (cd611fd).
54
- - Re-review Microsoft OAuth cache lifecycle (c2d5a95).
55
- - Re-review second replacement for SMTP mail issue 05 (b2f455b).
56
- - Complete Apple OAuth issue 05 (ac9eacf).
57
- - Request second rework for SMTP mail issue 05 (f036856).
58
- - Accept Apple OAuth issue 05 integration (06d6af7).
59
- - Request lifecycle rework for Microsoft OAuth cache (5ec1cff).
60
- - Re-review SMTP mail issue 05 (137d31a).
61
- - Review Apple and Facebook OAuth integration (15408ad).
62
- - Re-review Microsoft OAuth cache handling (a2814b4).
63
- - Request rework for SMTP mail issue 05 (55876a9).
64
- - Complete Facebook OAuth issue 06 (49471f2).
65
- - Accept Facebook OAuth issue 06 (e73b98e).
66
- - Re-review Apple OAuth callback parser (b258d8f).
67
- - Re-review Facebook OAuth timeout handling (fc9d1e5).
68
- - Review SMTP mail issue 05 (ebc29e8).
69
- - Request cache rework for Microsoft OAuth issue 04 (2261ffc).
70
- - Request parser rework for Apple OAuth issue 05 (1a4af01).
71
- - Advance Microsoft and Facebook OAuth rework (9c488f6).
72
- - Re-review Apple OAuth issue 05 (d2cc161).
73
- - Re-review Facebook OAuth issue 06 (4a48c7e).
74
- - Dispatch SMTP mail issue 05 (3675858).
75
- - Complete SMTP mail issue 04 (630b589).
76
- - Accept SMTP mail issue 04 (1381dbd).
77
- - Re-review second replacement for SMTP mail issue 04 (3041f53).
78
- - Request second rework for SMTP mail issue 04 (e94dabd).
79
- - Request rework for Facebook OAuth issue 06 (a84fd43).
80
- - Re-review SMTP mail issue 04 (ea9df9e).
81
- - Request rework for Microsoft OAuth issue 04 (8a7b75e).
82
- - Review Facebook OAuth issue 06 (af9f4e8).
83
- - Request rework for SMTP mail issue 04 (8140e85).
84
- - Review Microsoft OAuth issue 04 (6a02358).
85
- - Request rework for Apple OAuth issue 05 (f60ad7b).
86
- - Review Apple OAuth issue 05 (274f085).
87
- - Review SMTP mail issue 04 (3d67ba2).
88
- - Dispatch provider OAuth implementation frontier (a53d2aa).
89
- - Complete multi-provider OAuth issue 03 (1dc0371).
90
- - Accept multi-provider OAuth issue 03 (32e289c).
91
- - Re-review invariant-safe OAuth issue 03 (4257dbb).
92
- - Dispatch SMTP mail issue 04 (ff61b3e).
93
- - Complete SMTP mail issue 03 (f7e9916).
94
- - Accept SMTP mail issue 03 (54ab22c).
95
- - Re-review third replacement for SMTP mail issue 03 (50495c8).
96
- - Request invariant rework for multi-provider OAuth issue 03 (ad54e22).
97
- - Request third rework for SMTP mail issue 03 (31da8bc).
98
- - Re-review atomic multi-provider OAuth issue 03 (cb94846).
99
- - Re-review second replacement for SMTP mail issue 03 (a4614cd).
100
- - Request second rework for SMTP mail issue 03 (53b0d36).
101
- - Re-review SMTP mail issue 03 (ec21ea5).
102
- - Request atomic rework for multi-provider OAuth issue 03 (15a9336).
103
- - Re-review multi-provider OAuth issue 03 (c58107a).
104
- - Request rework for SMTP mail issue 03 (fbf0fc2).
105
- - Review SMTP mail issue 03 (7f8623a).
106
- - Request rework for multi-provider OAuth issue 03 (8295165).
107
- - Dispatch SMTP mail issue 03 (239f016).
108
- - Complete SMTP mail issue 02 (912a1ac).
109
- - Review multi-provider OAuth issue 03 (fe44a93).
110
- - Accept SMTP mail issue 02 (8e91a6e).
111
- - Re-review SMTP mail issue 02 (9944346).
112
- - Request rework for SMTP mail issue 02 (855ecbf).
113
- - Review SMTP mail issue 02 (2708b4f).
114
- - Dispatch SMTP mail issue 02 (9339ad0).
115
- - Complete SMTP mail issue 01 (6b0d35f).
116
- - Accept SMTP mail issue 01 (86c590c).
117
- - Re-review encoded address fix for SMTP mail issue 01 (fc30f8f).
118
- - Request third rework for SMTP mail issue 01 (8befa61).
119
- - Re-review RFC 2047 fix for SMTP mail issue 01 (99dd6ac).
120
- - Request second rework for SMTP mail issue 01 (f864623).
121
- - Dispatch multi-provider OAuth issue 03 (7d345e9).
122
- - Complete multi-provider OAuth issue 02 (c3da4ab).
123
- - Accept multi-provider OAuth issue 02 (cc6d385).
124
- - Re-review SMTP mail issue 01 (486fed4).
125
- - Re-review multi-provider OAuth issue 02 (2e632ae).
126
- - Request rework for SMTP mail issue 01 (075813f).
127
- - Request rework for multi-provider OAuth issue 02 (dbffa22).
128
- - Dispatch review for SMTP mail issue 01 (34d9960).
129
- - Review SMTP mail issue 01 (4271868).
130
- - Review multi-provider OAuth issue 02 (bb0292c).
131
- - Dispatch SMTP mail issue 01 (a51c647).
132
- - Initialize SMTP mail swarm ledger (ac20cf1).
133
- - Clean up multi-provider OAuth issue 01 worktree (4297774).
134
- - Dispatch multi-provider OAuth issue 02 (d059d44).
135
- - Complete multi-provider OAuth issue 01 (da7602c).
136
- - Accept multi-provider OAuth issue 01 (e925fa0).
137
- - Re-review multi-provider OAuth issue 01 (6595fa4).
138
- - Re-review multi-provider OAuth issue 01 (33decb6).
139
- - Plan SMTP mail support (35e1519).
140
- - Request rework for multi-provider OAuth issue 01 (0f08d69).
141
- - Review multi-provider OAuth issue 01 (b4f5d58).
142
- - Dispatch multi-provider OAuth issue 01 (ab58692).
143
- - Initialize multi-provider OAuth swarm ledger (716075b).
144
- - Plan multi-provider OAuth expansion (1cc5f60).
145
-
146
- ### 📝 Documentation
147
-
148
- - Contract provider-neutral OAuth runtime (df4901f).
149
- - Fail closed on Microsoft OIDC cache saturation (5889257).
150
- - Bound Microsoft OIDC cache lifecycle (9e3aeff).
151
- - Coalesce Microsoft OIDC cache refreshes (7b54d69).
152
- - Harden Microsoft OIDC network and token parsing (13774fe).
153
- - Reject malformed OAuth form callbacks (1817519).
154
- - Harden Sign in with Apple boundaries (301ff15).
155
- - Reject duplicate file transaction targets (3cabaf0).
156
- - Make OAuth configuration updates failure-atomic (b94bb52).
157
- - Harden multi-provider OAuth configuration (56a1f14).
158
- - Make Mailgun header boundaries exact (378ca69).
159
- - Validate Mailgun headers before delivery (66080b0).
160
- - Harden Mailgun SMTP header encoding (9436714).
161
- - Update generated SMTP API docs (0cf6139).
162
- - Harden SMTP MIME and TLS errors (89536c1).
163
- - Harden OAuth state and identity validation (6ef5d6a).
164
- - Deepen runtime OAuth provider seam (210aeb2).
165
- - Harden legacy provider identity claims (39e9e7a).
166
-
167
- ### 🧪 Tests
168
-
169
- - Integrate Microsoft OAuth with sibling providers (11bf24e).
170
- - Prove implicit TLS fails closed (24f10b2).
171
- - Define portable SMTP security contract (81698b3).
172
- - Harden Postmark provider data objects (3940fd5).
173
- - Fold encoded address headers at 76 columns (ab731ed).
174
- - Bound MIME encoded-word lines (b78ca71).
175
-
176
- ### 📦 Packaging
177
-
178
- - Release v0.6.2 (3d21e1a).
179
- - Align esbuild script approval version (613f597).
9
+ - Password reset and logout for email/password accounts (1cbe35f).
@@ -391,7 +391,7 @@ function writeEnvelope(result, failed = false) {
391
391
  }
392
392
 
393
393
  // src/cli/cli-version.ts
394
- var CLI_VERSION = "0.6.3";
394
+ var CLI_VERSION = "0.6.5";
395
395
 
396
396
  // src/cli/schedule-inspection-envelope.ts
397
397
  function sanitizeScheduleInspectionEnvelope(envelope, invalid) {
package/bin/sporades.js CHANGED
@@ -82,6 +82,9 @@ export const auth = {
82
82
  signOut() {
83
83
  return connect().signOut();
84
84
  },
85
+ setPassword(email, newPassword) {
86
+ return connect().setPassword(email, newPassword);
87
+ },
85
88
  };
86
89
 
87
90
  export const files = {
@@ -162,6 +165,9 @@ export function createHooks(primitives) {
162
165
  signOut() {
163
166
  return connect().signOut();
164
167
  },
168
+ setPassword(email, newPassword) {
169
+ return connect().setPassword(email, newPassword);
170
+ },
165
171
  };
166
172
  }
167
173
 
@@ -217,6 +223,7 @@ export function createVueComposables(primitives) {
217
223
  state.signUp = (provider, credentials) => connect().signUp(provider, credentials);
218
224
  state.signIn = (provider, credentials) => connect().signIn(provider, credentials);
219
225
  state.signOut = () => connect().signOut();
226
+ state.setPassword = (email, newPassword) => connect().setPassword(email, newPassword);
220
227
  return state;
221
228
  }
222
229
 
@@ -268,6 +275,7 @@ export function createSolidPrimitives(primitives) {
268
275
  signUp: (provider, credentials) => connect().signUp(provider, credentials),
269
276
  signIn: (provider, credentials) => connect().signIn(provider, credentials),
270
277
  signOut: () => connect().signOut(),
278
+ setPassword: (email, newPassword) => connect().setPassword(email, newPassword),
271
279
  };
272
280
  }
273
281
 
@@ -365,6 +373,7 @@ export function createLitControllers() {
365
373
  controller.signUp = (provider, credentials) => connect().signUp(provider, credentials);
366
374
  controller.signIn = (provider, credentials) => connect().signIn(provider, credentials);
367
375
  controller.signOut = () => connect().signOut();
376
+ controller.setPassword = (email, newPassword) => connect().setPassword(email, newPassword);
368
377
  return controller;
369
378
  }
370
379
 
@@ -437,6 +446,7 @@ export function createInfernoAdapters() {
437
446
  adapter.signUp = (provider, credentials) => connect().signUp(provider, credentials);
438
447
  adapter.signIn = (provider, credentials) => connect().signIn(provider, credentials);
439
448
  adapter.signOut = () => connect().signOut();
449
+ adapter.setPassword = (email, newPassword) => connect().setPassword(email, newPassword);
440
450
  return adapter;
441
451
  }
442
452
  return { queryAdapter, mutationAdapter, authAdapter };
@@ -486,6 +496,7 @@ export function createSvelteStores() {
486
496
  signUp: (provider, credentials) => connect().signUp(provider, credentials),
487
497
  signIn: (provider, credentials) => connect().signIn(provider, credentials),
488
498
  signOut: () => connect().signOut(),
499
+ setPassword: (email, newPassword) => connect().setPassword(email, newPassword),
489
500
  };
490
501
  }
491
502
 
@@ -990,6 +1001,9 @@ function createConnection() {
990
1001
  return result;
991
1002
  });
992
1003
  },
1004
+ setPassword(email, newPassword) {
1005
+ return request("auth.setPassword", { email, newPassword });
1006
+ },
993
1007
  subscribeQuery(name, listener) {
994
1008
  if (typeof name !== "string" || !name) throw new TypeError("queries.subscribe requires a query name.");
995
1009
  if (typeof listener !== "function") throw new TypeError("queries.subscribe requires a listener function.");
@@ -1311,6 +1325,8 @@ async function buildVite(options) {
1311
1325
  let projectRoot = path.resolve(options.projectDir);
1312
1326
  try {
1313
1327
  projectRoot = await realpath(options.projectDir);
1328
+ const canonicalIndexHtmlPath = path.join(projectRoot, path.basename(options.indexHtmlPath));
1329
+ const projectConfigFile = await findProjectViteConfig(projectRoot);
1314
1330
  if (options.frameworkConfig.framework === "vue") {
1315
1331
  const { plugin, compiler } = await loadProjectVueToolchain(projectRoot);
1316
1332
  frameworkPlugins.push(plugin({ compiler }));
@@ -1327,9 +1343,10 @@ async function buildVite(options) {
1327
1343
  root: projectRoot,
1328
1344
  base: "/",
1329
1345
  publicDir: false,
1330
- configFile: false,
1346
+ configFile: projectConfigFile,
1331
1347
  envFile: false,
1332
1348
  envPrefix: "\0",
1349
+ mode: "production",
1333
1350
  define: {
1334
1351
  "import.meta.env": JSON.stringify({ BASE_URL: "/", MODE: "production", DEV: false, PROD: true, SSR: false })
1335
1352
  },
@@ -1338,14 +1355,25 @@ async function buildVite(options) {
1338
1355
  logLevel: "silent",
1339
1356
  esbuild: options.frameworkConfig.framework === "inferno" ? { jsx: "transform", jsxFactory: "createElement" } : { jsx: "automatic", jsxImportSource: options.frameworkConfig.jsxImportSource ?? void 0 },
1340
1357
  css: { postcss: { plugins: [] } },
1341
- plugins: [...frameworkPlugins, sporadesViteClientPlugin(options.devRefresh === true)],
1358
+ plugins: [
1359
+ ...frameworkPlugins,
1360
+ sporadesViteClientPlugin(options.devRefresh === true),
1361
+ sporadesViteBuildInvariants(canonicalIndexHtmlPath, options.frameworkConfig)
1362
+ ],
1342
1363
  build: {
1343
1364
  write: false,
1344
1365
  emptyOutDir: false,
1345
1366
  sourcemap: true,
1346
1367
  cssCodeSplit: true,
1347
1368
  assetsInlineLimit: 0,
1369
+ watch: null,
1370
+ lib: false,
1371
+ ssr: false,
1372
+ manifest: false,
1373
+ ssrManifest: false,
1348
1374
  rollupOptions: {
1375
+ input: canonicalIndexHtmlPath,
1376
+ external: [],
1349
1377
  output: {
1350
1378
  entryFileNames: "assets/[name]-[hash].js",
1351
1379
  chunkFileNames: "assets/[name]-[hash].js",
@@ -1378,6 +1406,40 @@ async function buildVite(options) {
1378
1406
  throw viteBuildError(error, [options.projectDir, projectRoot], options.frameworkConfig.framework);
1379
1407
  }
1380
1408
  }
1409
+ var VITE_CONFIG_NAMES = [
1410
+ "vite.config.js",
1411
+ "vite.config.mjs",
1412
+ "vite.config.ts",
1413
+ "vite.config.cjs",
1414
+ "vite.config.mts",
1415
+ "vite.config.cts"
1416
+ ];
1417
+ async function findProjectViteConfig(projectRoot) {
1418
+ for (const name of VITE_CONFIG_NAMES) {
1419
+ const candidate = path.join(projectRoot, name);
1420
+ try {
1421
+ const metadata = await lstat(candidate);
1422
+ if (!metadata.isFile() || metadata.isSymbolicLink()) {
1423
+ throw clientToolchainError(
1424
+ `Vite configuration must be a regular file inside the Capsule: ${name}.`,
1425
+ `Replace ${name} with a regular project-owned file, then retry.`
1426
+ );
1427
+ }
1428
+ const canonical = await realpath(candidate);
1429
+ if (!isCanonicalDescendant(projectRoot, canonical)) {
1430
+ throw clientToolchainError(
1431
+ `Vite configuration escaped the Capsule project: ${name}.`,
1432
+ `Move ${name} inside the Capsule project, then retry.`
1433
+ );
1434
+ }
1435
+ return canonical;
1436
+ } catch (error) {
1437
+ if (errorDetails(error).code === "ENOENT") continue;
1438
+ throw error;
1439
+ }
1440
+ }
1441
+ return false;
1442
+ }
1381
1443
  async function loadProjectVueToolchain(projectRoot) {
1382
1444
  const loaded = await loadProjectCompilerToolchain(projectRoot, {
1383
1445
  framework: "Vue",
@@ -1547,6 +1609,66 @@ function sporadesViteClientPlugin(devRefresh = false) {
1547
1609
  }
1548
1610
  };
1549
1611
  }
1612
+ function sporadesViteBuildInvariants(indexHtmlPath, frameworkConfig) {
1613
+ return {
1614
+ name: "sporades-build-invariants",
1615
+ enforce: "post",
1616
+ config() {
1617
+ return {
1618
+ root: path.dirname(indexHtmlPath),
1619
+ base: "/",
1620
+ publicDir: false,
1621
+ envFile: false,
1622
+ envPrefix: "\0",
1623
+ mode: "production",
1624
+ define: {
1625
+ "import.meta.env": JSON.stringify({ BASE_URL: "/", MODE: "production", DEV: false, PROD: true, SSR: false })
1626
+ },
1627
+ appType: "mpa",
1628
+ clearScreen: false,
1629
+ logLevel: "silent",
1630
+ esbuild: frameworkConfig.framework === "inferno" ? { jsx: "transform", jsxFactory: "createElement" } : { jsx: "automatic", jsxImportSource: frameworkConfig.jsxImportSource ?? void 0 },
1631
+ css: { postcss: { plugins: [] } },
1632
+ build: {
1633
+ write: false,
1634
+ emptyOutDir: false,
1635
+ sourcemap: true,
1636
+ cssCodeSplit: true,
1637
+ assetsInlineLimit: 0,
1638
+ watch: null,
1639
+ lib: false,
1640
+ ssr: false,
1641
+ manifest: false,
1642
+ ssrManifest: false,
1643
+ rollupOptions: {
1644
+ input: indexHtmlPath,
1645
+ external: [],
1646
+ output: {
1647
+ entryFileNames: "assets/[name]-[hash].js",
1648
+ chunkFileNames: "assets/[name]-[hash].js",
1649
+ assetFileNames: "assets/[name]-[hash][extname]"
1650
+ }
1651
+ }
1652
+ }
1653
+ };
1654
+ },
1655
+ configResolved(config) {
1656
+ const pluginNames = new Set(config.plugins.map((plugin) => plugin.name));
1657
+ if (!pluginNames.has("sporades-client-runtime") || !pluginNames.has("sporades-build-invariants")) {
1658
+ throw new Error("Sporades mandatory Vite plugins were not preserved.");
1659
+ }
1660
+ const requiredFrameworkPlugin = {
1661
+ vue: "vite:vue",
1662
+ svelte: "vite-plugin-svelte",
1663
+ solid: "solid",
1664
+ inferno: "sporades-inferno-project-jsx"
1665
+ }[frameworkConfig.framework];
1666
+ if (requiredFrameworkPlugin && !pluginNames.has(requiredFrameworkPlugin)) {
1667
+ throw new Error(`Sporades mandatory ${frameworkConfig.framework} Vite plugin was not preserved.`);
1668
+ }
1669
+ }
1670
+ };
1671
+ }
1550
1672
  function referencesLegacyClientShell(html) {
1551
1673
  return /<script\b[^>]*\bsrc\s*=\s*["']\/?client\.js(?:\?[^"']*)?["'][^>]*>/i.test(html);
1552
1674
  }
@@ -5172,6 +5294,11 @@ async function createSqliteDatabaseAdapter(databasePath, options = {}) {
5172
5294
  "INSERT INTO sporades_auth_email_credentials (email, userId, passwordHash, passwordSalt, createdAt) VALUES (?, ?, ?, ?, ?)"
5173
5295
  ).run(row.email, row.userId, row.passwordHash, row.passwordSalt, row.createdAt);
5174
5296
  },
5297
+ updateEmailCredentialPassword(email, passwordHash, passwordSalt) {
5298
+ return this.prepare(
5299
+ "UPDATE sporades_auth_email_credentials SET passwordHash = ?, passwordSalt = ? WHERE email = ?"
5300
+ ).run(passwordHash, passwordSalt, email);
5301
+ },
5175
5302
  findEmailCredentialWithUser(email) {
5176
5303
  const row = this.prepare(
5177
5304
  "SELECT c.email, c.userId, c.passwordHash, c.passwordSalt, u.displayName, u.picture, u.isAuthenticated, u.isGuest FROM sporades_auth_email_credentials c JOIN sporades_auth_users u ON u.id = c.userId WHERE c.email = ?"
@@ -8764,8 +8891,9 @@ async function readEndpointRequest(database, requestUrl, request) {
8764
8891
  };
8765
8892
  }
8766
8893
  function createEndpointContext(database, endpointRequest, session) {
8894
+ const auth = session.auth;
8767
8895
  const context = {
8768
- auth: session.auth,
8896
+ auth,
8769
8897
  env: database.serverEnv,
8770
8898
  log: createEndpointLogger(database, {
8771
8899
  request: {
@@ -8786,6 +8914,12 @@ function createEndpointContext(database, endpointRequest, session) {
8786
8914
  context.privileged = createContextPrivilegedApi(database, () => holder.current);
8787
8915
  context.jobs = createCurrentUserJobApi(database, () => holder.current);
8788
8916
  context.mail = database.mail;
8917
+ context.serverAuth = {
8918
+ async setEmailPassword(email, newPassword) {
8919
+ const result = await setEmailPassword(database, { auth }, email, newPassword);
8920
+ if (!result.ok) throw new Error(result.error?.message ?? "Could not set password.");
8921
+ }
8922
+ };
8789
8923
  return context;
8790
8924
  }
8791
8925
  function createContextHolder(context) {
@@ -10355,6 +10489,16 @@ function createWebSocketHub(getDatabase, trustedRefresh = null) {
10355
10489
  });
10356
10490
  return;
10357
10491
  }
10492
+ if (message.type === "auth.setPassword") {
10493
+ const result = await setEmailPassword(database, client.session, message.email ?? "", message.newPassword ?? "");
10494
+ sendJson(client, {
10495
+ id: message.id ?? null,
10496
+ type: result.ok ? "auth.setPassword.result" : "error",
10497
+ data: result.ok ? { ok: true } : null,
10498
+ error: result.error ?? null
10499
+ });
10500
+ return;
10501
+ }
10358
10502
  if (message.type === "auth.signUp") {
10359
10503
  const result = await signUpWithEmail(database, client.session, message.provider, message.credentials ?? {});
10360
10504
  if (result.ok) {
@@ -12474,6 +12618,25 @@ function writeRedirect(response, location) {
12474
12618
  response.writeHead(302, { location });
12475
12619
  response.end();
12476
12620
  }
12621
+ async function setEmailPassword(database, _session, email, newPassword) {
12622
+ if (!database.authConfig.providers.email.enabled) {
12623
+ return { ok: false, error: emailAuthDisabledError() };
12624
+ }
12625
+ const cleanEmail = typeof email === "string" ? email.trim().toLowerCase() : "";
12626
+ if (!cleanEmail) {
12627
+ return { ok: false, error: { message: "Email is required.", hint: "Provide the email address for the account whose password is being changed." } };
12628
+ }
12629
+ if (typeof newPassword !== "string" || newPassword.length < 8) {
12630
+ return { ok: false, error: { message: "Password is too short.", hint: "Use a password with at least 8 characters." } };
12631
+ }
12632
+ const existing = await database.sqlite.findEmailCredentialWithUser(cleanEmail);
12633
+ if (!existing) {
12634
+ return { ok: false, error: { message: "No email account found for that address.", hint: "Check the email address or register a new account." } };
12635
+ }
12636
+ const password = hashEmailPassword(newPassword);
12637
+ await database.sqlite.updateEmailCredentialPassword(cleanEmail, password.hash, password.salt);
12638
+ return { ok: true };
12639
+ }
12477
12640
  async function signUpWithEmail(database, session, provider, credentials) {
12478
12641
  if (provider !== "email") {
12479
12642
  return {
@@ -13411,6 +13574,12 @@ function createMutationContext(database, auth) {
13411
13574
  context.privileged = createContextPrivilegedApi(database, () => holder.current);
13412
13575
  context.jobs = createCurrentUserJobApi(database, () => holder.current);
13413
13576
  context.mail = database.mail;
13577
+ context.serverAuth = {
13578
+ async setEmailPassword(email, newPassword) {
13579
+ const result = await setEmailPassword(database, { auth }, email, newPassword);
13580
+ if (!result.ok) throw new Error(result.error?.message ?? "Could not set password.");
13581
+ }
13582
+ };
13414
13583
  return context;
13415
13584
  }
13416
13585
  function createCurrentUserJobApi(database, contextGetter) {
@@ -21737,7 +21906,7 @@ jobs:
21737
21906
  }
21738
21907
 
21739
21908
  // src/cli/cli-version.ts
21740
- var CLI_VERSION = "0.6.3";
21909
+ var CLI_VERSION = "0.6.5";
21741
21910
 
21742
21911
  // src/cli/sporades.ts
21743
21912
  var SUPPORTED_TEMPLATES = new Set(CLIENT_TEMPLATES);
@@ -1,2 +1,2 @@
1
- export declare const CLI_VERSION = "0.6.3";
1
+ export declare const CLI_VERSION = "0.6.5";
2
2
  //# sourceMappingURL=cli-version.d.ts.map
@@ -1,3 +1,3 @@
1
1
  // Generated by `npm run build` from package.json. Do not edit by hand.
2
- export const CLI_VERSION = "0.6.3";
2
+ export const CLI_VERSION = "0.6.5";
3
3
  //# sourceMappingURL=cli-version.js.map
@@ -96,6 +96,8 @@ async function buildVite(options) {
96
96
  let projectRoot = path.resolve(options.projectDir);
97
97
  try {
98
98
  projectRoot = await realpath(options.projectDir);
99
+ const canonicalIndexHtmlPath = path.join(projectRoot, path.basename(options.indexHtmlPath));
100
+ const projectConfigFile = await findProjectViteConfig(projectRoot);
99
101
  if (options.frameworkConfig.framework === "vue") {
100
102
  const { plugin, compiler } = await loadProjectVueToolchain(projectRoot);
101
103
  frameworkPlugins.push(plugin({ compiler }));
@@ -115,9 +117,10 @@ async function buildVite(options) {
115
117
  root: projectRoot,
116
118
  base: "/",
117
119
  publicDir: false,
118
- configFile: false,
120
+ configFile: projectConfigFile,
119
121
  envFile: false,
120
122
  envPrefix: "\0",
123
+ mode: "production",
121
124
  define: {
122
125
  "import.meta.env": JSON.stringify({ BASE_URL: "/", MODE: "production", DEV: false, PROD: true, SSR: false }),
123
126
  },
@@ -128,14 +131,25 @@ async function buildVite(options) {
128
131
  ? { jsx: "transform", jsxFactory: "createElement" }
129
132
  : { jsx: "automatic", jsxImportSource: options.frameworkConfig.jsxImportSource ?? undefined },
130
133
  css: { postcss: { plugins: [] } },
131
- plugins: [...frameworkPlugins, sporadesViteClientPlugin(options.devRefresh === true)],
134
+ plugins: [
135
+ ...frameworkPlugins,
136
+ sporadesViteClientPlugin(options.devRefresh === true),
137
+ sporadesViteBuildInvariants(canonicalIndexHtmlPath, options.frameworkConfig),
138
+ ],
132
139
  build: {
133
140
  write: false,
134
141
  emptyOutDir: false,
135
142
  sourcemap: true,
136
143
  cssCodeSplit: true,
137
144
  assetsInlineLimit: 0,
145
+ watch: null,
146
+ lib: false,
147
+ ssr: false,
148
+ manifest: false,
149
+ ssrManifest: false,
138
150
  rollupOptions: {
151
+ input: canonicalIndexHtmlPath,
152
+ external: [],
139
153
  output: {
140
154
  entryFileNames: "assets/[name]-[hash].js",
141
155
  chunkFileNames: "assets/[name]-[hash].js",
@@ -173,6 +187,31 @@ async function buildVite(options) {
173
187
  throw viteBuildError(error, [options.projectDir, projectRoot], options.frameworkConfig.framework);
174
188
  }
175
189
  }
190
+ const VITE_CONFIG_NAMES = [
191
+ "vite.config.js", "vite.config.mjs", "vite.config.ts", "vite.config.cjs", "vite.config.mts", "vite.config.cts",
192
+ ];
193
+ async function findProjectViteConfig(projectRoot) {
194
+ for (const name of VITE_CONFIG_NAMES) {
195
+ const candidate = path.join(projectRoot, name);
196
+ try {
197
+ const metadata = await lstat(candidate);
198
+ if (!metadata.isFile() || metadata.isSymbolicLink()) {
199
+ throw clientToolchainError(`Vite configuration must be a regular file inside the Capsule: ${name}.`, `Replace ${name} with a regular project-owned file, then retry.`);
200
+ }
201
+ const canonical = await realpath(candidate);
202
+ if (!isCanonicalDescendant(projectRoot, canonical)) {
203
+ throw clientToolchainError(`Vite configuration escaped the Capsule project: ${name}.`, `Move ${name} inside the Capsule project, then retry.`);
204
+ }
205
+ return canonical;
206
+ }
207
+ catch (error) {
208
+ if (errorDetails(error).code === "ENOENT")
209
+ continue;
210
+ throw error;
211
+ }
212
+ }
213
+ return false;
214
+ }
176
215
  async function loadProjectVueToolchain(projectRoot) {
177
216
  const loaded = await loadProjectCompilerToolchain(projectRoot, {
178
217
  framework: "Vue",
@@ -340,6 +379,68 @@ function sporadesViteClientPlugin(devRefresh = false) {
340
379
  load(id) { return id === runtimeId ? createClientRuntimeSource({ devRefresh }) : null; },
341
380
  };
342
381
  }
382
+ function sporadesViteBuildInvariants(indexHtmlPath, frameworkConfig) {
383
+ return {
384
+ name: "sporades-build-invariants",
385
+ enforce: "post",
386
+ config() {
387
+ return {
388
+ root: path.dirname(indexHtmlPath),
389
+ base: "/",
390
+ publicDir: false,
391
+ envFile: false,
392
+ envPrefix: "\0",
393
+ mode: "production",
394
+ define: {
395
+ "import.meta.env": JSON.stringify({ BASE_URL: "/", MODE: "production", DEV: false, PROD: true, SSR: false }),
396
+ },
397
+ appType: "mpa",
398
+ clearScreen: false,
399
+ logLevel: "silent",
400
+ esbuild: frameworkConfig.framework === "inferno"
401
+ ? { jsx: "transform", jsxFactory: "createElement" }
402
+ : { jsx: "automatic", jsxImportSource: frameworkConfig.jsxImportSource ?? undefined },
403
+ css: { postcss: { plugins: [] } },
404
+ build: {
405
+ write: false,
406
+ emptyOutDir: false,
407
+ sourcemap: true,
408
+ cssCodeSplit: true,
409
+ assetsInlineLimit: 0,
410
+ watch: null,
411
+ lib: false,
412
+ ssr: false,
413
+ manifest: false,
414
+ ssrManifest: false,
415
+ rollupOptions: {
416
+ input: indexHtmlPath,
417
+ external: [],
418
+ output: {
419
+ entryFileNames: "assets/[name]-[hash].js",
420
+ chunkFileNames: "assets/[name]-[hash].js",
421
+ assetFileNames: "assets/[name]-[hash][extname]",
422
+ },
423
+ },
424
+ },
425
+ };
426
+ },
427
+ configResolved(config) {
428
+ const pluginNames = new Set(config.plugins.map((plugin) => plugin.name));
429
+ if (!pluginNames.has("sporades-client-runtime") || !pluginNames.has("sporades-build-invariants")) {
430
+ throw new Error("Sporades mandatory Vite plugins were not preserved.");
431
+ }
432
+ const requiredFrameworkPlugin = {
433
+ vue: "vite:vue",
434
+ svelte: "vite-plugin-svelte",
435
+ solid: "solid",
436
+ inferno: "sporades-inferno-project-jsx",
437
+ }[frameworkConfig.framework];
438
+ if (requiredFrameworkPlugin && !pluginNames.has(requiredFrameworkPlugin)) {
439
+ throw new Error(`Sporades mandatory ${frameworkConfig.framework} Vite plugin was not preserved.`);
440
+ }
441
+ },
442
+ };
443
+ }
343
444
  function referencesLegacyClientShell(html) {
344
445
  return /<script\b[^>]*\bsrc\s*=\s*["']\/?client\.js(?:\?[^"']*)?["'][^>]*>/i.test(html);
345
446
  }