@tiddlywiki/mws 0.0.19 → 0.0.21

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/README.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # MultiWikiServer
2
2
 
3
+ **This is not ready for primetime. Do not use it to protect feelings or intellectual property.**
4
+
5
+ *You really shouldn't be using it to store anything important yet. It is still in the VERY early stages of development and we don't even have proper tests verifying the security mechanisms yet. So please try it out, kick the tires, import your multi-gigabyte wikis and play around with it, but don't actually store anything you want to keep. We'll hopefully get there soon, but even the database schema itself is still in flux as we figure out what the best layout is for all the stuff we have planned.*
6
+
3
7
  Multiple users, multiple wikis for TiddlyWiki.
4
8
 
5
9
  - Bag & Recipe system for storing tiddlers.
package/dist/server.d.ts CHANGED
@@ -228,6 +228,10 @@ declare class StartupCommander {
228
228
  $tw: TW;
229
229
  PasswordService: PasswordService;
230
230
  fieldModules: Record<string, TiddlerFieldModule>;
231
+ versions: {
232
+ tiddlywiki: any;
233
+ mws: string;
234
+ };
231
235
  constructor(config: MWSConfig, $tw: TW, PasswordService: PasswordService);
232
236
  init(): Promise<void>;
233
237
  wikiPath: string;
@@ -333,6 +337,7 @@ declare class StateObject<B extends BodyFormat = BodyFormat, M extends AllowedMe
333
337
  private engine;
334
338
  config: SiteConfig;
335
339
  PasswordService: PasswordService;
340
+ get pathPrefix(): string;
336
341
  constructor(streamer: Streamer,
337
342
  /** The array of Route tree nodes the request matched. */
338
343
  routePath: RouteMatch[],
@@ -554,6 +559,9 @@ declare const Z2: Z2<FieldTypeGroups>;
554
559
  declare global {
555
560
  type PrismaTxnClient = Omit<Commander["engine"], "$connect" | "$disconnect" | "$on" | "$transaction" | "$use" | "$extends">;
556
561
  type ART<T extends (...args: any) => any> = Awaited<ReturnType<T>>;
562
+ type Complete<T> = {
563
+ [K in keyof T]-?: T[K];
564
+ };
557
565
  /**
558
566
  * This primarily makes sure that positional arguments are used correctly
559
567
  * (so you can't switch a title and bag_name around).
@@ -774,10 +782,8 @@ declare class StreamerState {
774
782
  }
775
783
 
776
784
  declare function startupCache(rootRoute: rootRoute, commander: Commander): Promise<{
777
- tiddlerFileCache: Map<string, string>;
778
- tiddlerMemoryCache: Map<string, string>;
779
- tiddlerFiles: Map<string, string>;
780
- tiddlerHashes: Map<string, string>;
785
+ tiddlerFiles: Map<any, any>;
786
+ tiddlerHashes: Map<any, any>;
781
787
  }>;
782
788
  type CacheState = ART<typeof startupCache>;
783
789
 
@@ -797,6 +803,10 @@ declare class Router {
797
803
  engine: Commander["engine"];
798
804
  private SessionManager;
799
805
  PasswordService: Commander["PasswordService"];
806
+ versions: {
807
+ tiddlywiki: any;
808
+ mws: string;
809
+ };
800
810
  fieldModules: Commander["$tw"]["Tiddler"]["fieldModules"];
801
811
  AttachmentService: Commander["AttachmentService"];
802
812
  constructor(rootRoute: rootRoute$1, commander: Commander, tiddlerCache: CacheState);
@@ -888,7 +898,7 @@ interface MWSConfigConfig {
888
898
  readonly pathPrefix?: string;
889
899
  readonly enablePluginCache?: boolean;
890
900
  }
891
- interface SiteConfig extends MWSConfigConfig {
901
+ interface SiteConfig extends Complete<MWSConfigConfig> {
892
902
  wikiPath: string;
893
903
  attachmentSizeLimit: number;
894
904
  attachmentsEnabled: boolean;
package/dist/server.js CHANGED
@@ -152,8 +152,8 @@ var init_global = __esm({
152
152
  });
153
153
  };
154
154
  __Result = class __Result {
155
- constructor(ok8, error, value) {
156
- this.ok = ok8;
155
+ constructor(ok7, error, value) {
156
+ this.ok = ok7;
157
157
  this.error = error;
158
158
  this.value = value;
159
159
  }
@@ -1621,7 +1621,7 @@ var require_destroy = __commonJS({
1621
1621
  "node_modules/destroy/index.js"(exports, module2) {
1622
1622
  "use strict";
1623
1623
  init_esm_shims();
1624
- var EventEmitter = __require("events").EventEmitter;
1624
+ var EventEmitter2 = __require("events").EventEmitter;
1625
1625
  var ReadStream = __require("fs").ReadStream;
1626
1626
  var Stream = __require("stream");
1627
1627
  var Zlib = __require("zlib");
@@ -1688,7 +1688,7 @@ var require_destroy = __commonJS({
1688
1688
  }
1689
1689
  __name(hasDestroy, "hasDestroy");
1690
1690
  function isEventEmitter(val) {
1691
- return val instanceof EventEmitter;
1691
+ return val instanceof EventEmitter2;
1692
1692
  }
1693
1693
  __name(isEventEmitter, "isEventEmitter");
1694
1694
  function isFsReadStream(stream) {
@@ -11569,7 +11569,8 @@ var init_data_checks = __esm({
11569
11569
  init_esm_shims();
11570
11570
  init_utils();
11571
11571
  _DataChecks = class _DataChecks {
11572
- constructor(options) {
11572
+ constructor(config) {
11573
+ this.config = config;
11573
11574
  this.isPermissionName = /* @__PURE__ */ __name((e) => {
11574
11575
  const test2 = e;
11575
11576
  switch (test2) {
@@ -11586,8 +11587,6 @@ var init_data_checks = __esm({
11586
11587
  this.permissions = { READ: "READ", WRITE: "WRITE", ADMIN: "ADMIN" };
11587
11588
  this.isEntityType = /* @__PURE__ */ __name((x) => ["recipe", "bag"].includes(x), "isEntityType");
11588
11589
  this.entityTypes = { recipe: "recipe", bag: "bag" };
11589
- this.allowAnonReads = options.allowAnonReads;
11590
- this.allowAnonWrites = options.allowAnonWrites;
11591
11590
  }
11592
11591
  okTiddlerFields(tiddlerFields) {
11593
11592
  ok2(tiddlerFields.title, "title must not be empty");
@@ -11648,17 +11647,11 @@ var init_data_checks = __esm({
11648
11647
  ].filter(truthy);
11649
11648
  }
11650
11649
  getWhereACL({ permission, user_id, role_ids }) {
11651
- const { allowAnonReads, allowAnonWrites } = this;
11652
- const anonRead = allowAnonReads && permission === "READ";
11653
- const anonWrite = allowAnonWrites && permission === "WRITE";
11654
- const allowAnon = anonRead || anonWrite;
11655
11650
  const allperms = ["READ", "WRITE", "ADMIN"];
11656
11651
  const index = allperms.indexOf(permission);
11657
11652
  if (index === -1) throw new Error("Invalid permission");
11658
11653
  const checkPerms = allperms.slice(index);
11659
11654
  return [
11660
- // allow unowned for any user (conditional for anon reads)
11661
- (user_id || allowAnon) && { acl: { none: {} }, owner_id: null },
11662
11655
  // allow owner for user
11663
11656
  user_id && { owner_id: { equals: user_id, not: null } },
11664
11657
  // allow acl for user
@@ -11669,7 +11662,9 @@ var init_data_checks = __esm({
11669
11662
  role_id: { in: role_ids }
11670
11663
  }
11671
11664
  }
11672
- }
11665
+ },
11666
+ { owner_id: { equals: null, not: null } }
11667
+ // dud to make sure that at least one condition exists
11673
11668
  ].filter(truthy);
11674
11669
  }
11675
11670
  };
@@ -11814,8 +11809,9 @@ var init_streamer = __esm({
11814
11809
  if (error === STREAM_ENDED) return;
11815
11810
  const tag = this.urlInfo.href;
11816
11811
  console.error(tag, error);
11817
- if (!this.headersSent)
11812
+ if (!this.headersSent) {
11818
11813
  this.sendEmpty(500, { "x-reason": "Internal Server Error (catcher)" });
11814
+ }
11819
11815
  }, "catcher");
11820
11816
  this.readBody = /* @__PURE__ */ __name(() => new Promise((resolve13) => {
11821
11817
  const chunks = [];
@@ -12178,6 +12174,9 @@ var init_StateObject = __esm({
12178
12174
  get method() {
12179
12175
  return super.method;
12180
12176
  }
12177
+ get pathPrefix() {
12178
+ return this.config.pathPrefix;
12179
+ }
12181
12180
  okUser() {
12182
12181
  if (!this.user.isLoggedIn) throw "User not authenticated";
12183
12182
  }
@@ -12406,8 +12405,8 @@ var init_manager_recipes = __esm({
12406
12405
  where: isAdmin ? void 0 : {
12407
12406
  OR: [
12408
12407
  { recipe_bags: { every: { bag: { OR } } } },
12409
- { owner_id: { equals: user_id, not: null } }
12410
- ]
12408
+ user_id && { owner_id: { equals: user_id, not: null } }
12409
+ ].filter((e) => e)
12411
12410
  }
12412
12411
  });
12413
12412
  const userListUser = !isAdmin && await prisma.users.findMany({
@@ -12428,7 +12427,8 @@ var init_manager_recipes = __esm({
12428
12427
  username,
12429
12428
  isLoggedIn: state.user.isLoggedIn,
12430
12429
  allowAnonReads: state.config.allowAnonReads,
12431
- allowAnonWrites: state.config.allowAnonWrites
12430
+ allowAnonWrites: state.config.allowAnonWrites,
12431
+ versions: state.router.versions
12432
12432
  };
12433
12433
  });
12434
12434
  this.recipe_create = zodManage((z5) => z5.object({
@@ -12585,8 +12585,7 @@ var init_manager_recipes = __esm({
12585
12585
  });
12586
12586
  return null;
12587
12587
  });
12588
- const { allowAnonReads, allowAnonWrites } = config;
12589
- this.checks = new DataChecks({ allowAnonReads, allowAnonWrites });
12588
+ this.checks = new DataChecks(config);
12590
12589
  }
12591
12590
  static defineRoutes(root, config) {
12592
12591
  registerZodRoutes(root, new _RecipeManager(config), Object.keys(RecipeKeyMap));
@@ -13190,13 +13189,15 @@ var init_TiddlerStore = __esm({
13190
13189
  recipe_bags: {
13191
13190
  // we're saving to the top most bag
13192
13191
  where: { position: 0 },
13193
- select: { bag: { select: { bag_id: true, bag_name: true } } }
13192
+ select: { bag: { select: { bag_id: true, bag_name: true, is_plugin: true } } }
13194
13193
  }
13195
13194
  }
13196
13195
  });
13197
13196
  if (!recipe) throw new UserError("Recipe not found");
13198
13197
  const bag_name = recipe.recipe_bags[0]?.bag.bag_name;
13199
13198
  if (!bag_name) throw new UserError("Recipe has no bag at position 0");
13199
+ if (recipe.recipe_bags[0]?.bag.is_plugin)
13200
+ throw new UserError("Saving to plugin bags is not currently supported. Please use a normal bag. This error occurs if a plugin bag is at the top of the recipe.\n");
13200
13201
  var { tiddler_id } = await this.saveBagTiddlerFields(tiddlerFields, bag_name, attachment_hash);
13201
13202
  return { tiddler_id, bag_name };
13202
13203
  }
@@ -14366,8 +14367,7 @@ var init_bag_file_server = __esm({
14366
14367
  fields: true
14367
14368
  },
14368
14369
  where: {
14369
- is_deleted: false,
14370
- bag: { is_plugin: false }
14370
+ is_deleted: false
14371
14371
  }
14372
14372
  }
14373
14373
  }
@@ -14677,58 +14677,62 @@ var init_router_tiddlers = __esm({
14677
14677
 
14678
14678
  // src/routes/tw-routes.ts
14679
14679
  import { TiddlyWiki } from "tiddlywiki";
14680
- function DocsRoute(parent, config) {
14681
- const wikiRouter = new TWRouter(config);
14682
- console.log("The mws-docs tiddlywiki is being served at " + config.pathPrefix + "/mws-docs");
14683
- parent.defineRoute({
14680
+ import { basename } from "path";
14681
+ import EventEmitter from "events";
14682
+ function DocsRoute(rootRoute2, mountPath, singleFile) {
14683
+ const $tw2 = TiddlyWiki();
14684
+ const last = basename(mountPath);
14685
+ $tw2.preloadTiddler({
14686
+ title: "$:/config/tiddlyweb/host",
14687
+ // if the page url doesn't end with a slash, we have to include the basename here
14688
+ text: singleFile ? last + "/" : ""
14689
+ });
14690
+ $tw2.boot.argv = [
14691
+ "+plugins/tiddlywiki/tiddlyweb",
14692
+ "+plugins/tiddlywiki/filesystem",
14693
+ // relative to the cwd
14694
+ "./editions/mws-docs"
14695
+ ];
14696
+ const hooks = new EventEmitter();
14697
+ $tw2.boot.boot(() => {
14698
+ const Server2 = $tw2.modules.execute("$:/core/modules/server/server.js", "tw-test.ts").Server;
14699
+ const twserver = new Server2({
14700
+ wiki: $tw2.wiki,
14701
+ variables: {
14702
+ // path prefix gets set per-request
14703
+ "path-prefix": "",
14704
+ "root-tiddler": "$:/core/save/all"
14705
+ },
14706
+ routes: []
14707
+ });
14708
+ $tw2.hooks.invokeHook("th-server-command-post-start", twserver, null, "mws");
14709
+ hooks.on("req", (req, res, pathPrefix) => {
14710
+ twserver.requestHandler(req, res, { pathPrefix });
14711
+ });
14712
+ });
14713
+ rootRoute2.defineRoute({
14684
14714
  method: ["OPTIONS", "GET", "HEAD", "POST", "PUT", "DELETE"],
14685
14715
  bodyFormat: "stream",
14686
- path: /^\/mwsp-docs(\/|$)/,
14716
+ path: new RegExp("^" + mountPath + "(/|$)"),
14687
14717
  pathParams: []
14688
14718
  }, async (state) => {
14689
- if (state.urlInfo.pathname === "/mws-docs") throw state.redirect("/mws-docs/");
14719
+ if (singleFile && state.urlInfo.pathname === mountPath + "/")
14720
+ throw state.redirect(mountPath);
14721
+ if (!singleFile && state.urlInfo.pathname === mountPath)
14722
+ throw state.redirect(mountPath + "/");
14690
14723
  const { req, res } = state.streamer;
14691
- if (req.url === "/mws-docs") req.url = "/mws-docs/";
14692
- wikiRouter.twserver.requestHandler(req, res, {});
14693
- return new Promise((r, c) => res.on("finish", r).on("error", c));
14724
+ if (req.url === state.pathPrefix + mountPath)
14725
+ req.url = state.pathPrefix + mountPath + "/";
14726
+ const prom = new Promise((r, c) => res.on("finish", r).on("error", c));
14727
+ hooks.emit("req", req, res, state.pathPrefix + mountPath);
14728
+ return prom;
14694
14729
  });
14695
14730
  }
14696
- var _TWRouter, TWRouter;
14697
14731
  var init_tw_routes = __esm({
14698
14732
  "src/routes/tw-routes.ts"() {
14699
14733
  "use strict";
14700
14734
  init_esm_shims();
14701
14735
  __name(DocsRoute, "DocsRoute");
14702
- _TWRouter = class _TWRouter {
14703
- constructor(config) {
14704
- this.config = config;
14705
- this.$tw = TiddlyWiki();
14706
- this.$tw.boot.argv = [
14707
- "+plugins/tiddlywiki/tiddlyweb",
14708
- "+plugins/tiddlywiki/filesystem",
14709
- "./editions/mws-docs"
14710
- ];
14711
- this.$tw.preloadTiddler({
14712
- text: "$protocol$//$host$" + this.config.pathPrefix + "/mws/",
14713
- title: "$:/config/tiddlyweb/host"
14714
- });
14715
- this.$tw.boot.boot(() => {
14716
- const Server2 = this.$tw.modules.execute("$:/core/modules/server/server.js", "tw-test.ts").Server;
14717
- this.twserver = new Server2({
14718
- wiki: this.$tw.wiki,
14719
- variables: {
14720
- // do not use a trailing slash
14721
- "path-prefix": this.config.pathPrefix + "/mws",
14722
- "root-tiddler": "$:/core/save/all"
14723
- },
14724
- routes: []
14725
- });
14726
- this.$tw.hooks.invokeHook("th-server-command-post-start", this.twserver, null, "mws");
14727
- });
14728
- }
14729
- };
14730
- __name(_TWRouter, "TWRouter");
14731
- TWRouter = _TWRouter;
14732
14736
  }
14733
14737
  });
14734
14738
 
@@ -14748,7 +14752,7 @@ var init_ = __esm({
14748
14752
  // src/routes/index.ts
14749
14753
  import { Prisma } from "@prisma/client";
14750
14754
  async function RootRoute(root, config) {
14751
- if (process.env.ENABLE_DOCS_ROUTE) DocsRoute(root, config);
14755
+ if (process.env.ENABLE_DOCS_ROUTE) DocsRoute(root, "/mws-docs", false);
14752
14756
  TiddlerRouter.defineRoutes(root);
14753
14757
  ManagerRoutes(root, config);
14754
14758
  if (process.env.ENABLE_UNSAFE_PRISMA_ROUTE)
@@ -14991,21 +14995,12 @@ var init_setupDevServer = __esm({
14991
14995
  });
14992
14996
 
14993
14997
  // src/routes/cache.ts
14994
- import { ok as ok4 } from "assert";
14995
14998
  import * as fs2 from "fs";
14996
14999
  import * as path3 from "path";
14997
15000
  import * as crypto from "crypto";
14998
15001
  async function startupCache(rootRoute2, commander2) {
14999
15002
  const $tw2 = commander2.$tw;
15000
- const bootTiddlers = $tw2.loadTiddlersFromPath($tw2.boot.bootPath).flatMap((file) => file.tiddlers);
15001
- const coreTiddler = $tw2.loadPluginFolder($tw2.boot.corePath);
15002
- ok4(coreTiddler);
15003
- const tiddlerMemoryCache = new Map([
15004
- ...bootTiddlers.map((e) => e.title && [e.title, JSON.stringify(e)]).filter(truthy),
15005
- [coreTiddler.title, JSON.stringify(coreTiddler)]
15006
- ]);
15007
- const tiddlerFileCache = /* @__PURE__ */ new Map();
15008
- const { tiddlerFiles, tiddlerHashes } = await importTW5(path3.join($tw2.boot.corePath, ".."), commander2.cachePath, "client", $tw2);
15003
+ const { tiddlerFiles, tiddlerHashes } = commander2.siteConfig.enablePluginCache ? await importTW5(path3.join($tw2.boot.corePath, ".."), commander2.cachePath, "client", $tw2) : { tiddlerFiles: /* @__PURE__ */ new Map(), tiddlerHashes: /* @__PURE__ */ new Map() };
15009
15004
  rootRoute2.defineRoute({
15010
15005
  method: ["GET", "HEAD"],
15011
15006
  path: /^\/\$cache\/(.*)\/plugin\.js$/,
@@ -15020,7 +15015,7 @@ async function startupCache(rootRoute2, commander2) {
15020
15015
  reqpath: state.pathParams.plugin + "/plugin.js"
15021
15016
  });
15022
15017
  });
15023
- return { tiddlerFileCache, tiddlerMemoryCache, tiddlerFiles, tiddlerHashes };
15018
+ return { tiddlerFiles, tiddlerHashes };
15024
15019
  }
15025
15020
  async function importTW5(twFolder, cacheFolder, type, $tw2) {
15026
15021
  const readLevel = /* @__PURE__ */ __name((d) => {
@@ -15160,6 +15155,7 @@ var init_router = __esm({
15160
15155
  this.PasswordService = commander2.PasswordService;
15161
15156
  this.fieldModules = commander2.$tw.Tiddler.fieldModules;
15162
15157
  this.AttachmentService = commander2.AttachmentService;
15158
+ this.versions = commander2.versions;
15163
15159
  }
15164
15160
  static async makeRouter(commander2, enableDevServer) {
15165
15161
  const sendDevServer = await setupDevServer(enableDevServer, commander2.siteConfig.pathPrefix);
@@ -15180,14 +15176,14 @@ var init_router = __esm({
15180
15176
  return this.siteConfig.pathPrefix;
15181
15177
  }
15182
15178
  handleIncomingRequest(req, res) {
15183
- const [ok8, err, streamer] = function() {
15179
+ const [ok7, err, streamer] = function() {
15184
15180
  try {
15185
15181
  return [true, void 0, new Streamer(req, res, this)];
15186
15182
  } catch (e) {
15187
15183
  return [false, e, void 0];
15188
15184
  }
15189
15185
  }.call(this);
15190
- if (!ok8) {
15186
+ if (!ok7) {
15191
15187
  if (err === STREAM_ENDED) return;
15192
15188
  res.writeHead(500, { "x-reason": "handle incoming request" }).end();
15193
15189
  throw err;
@@ -15341,8 +15337,10 @@ var init_router = __esm({
15341
15337
  if (error === STREAM_ENDED) {
15342
15338
  return error;
15343
15339
  } else if (typeof error === "string") {
15340
+ console.log(error);
15344
15341
  return state.sendString(400, { "x-reason": "zod-handler" }, error, "utf8");
15345
15342
  } else if (error instanceof Error && error.name === "UserError") {
15343
+ console.log(error.stack);
15346
15344
  return state.sendString(400, { "x-reason": "user-error" }, error.message, "utf8");
15347
15345
  } else {
15348
15346
  throw error;
@@ -15358,7 +15356,7 @@ var init_router = __esm({
15358
15356
 
15359
15357
  // src/tiddlywiki.ts
15360
15358
  import { TiddlyWiki as TiddlyWiki2 } from "tiddlywiki";
15361
- import { ok as ok5 } from "node:assert";
15359
+ import { ok as ok4 } from "node:assert";
15362
15360
  async function bootTiddlyWiki(wikiPath) {
15363
15361
  const $tw2 = TiddlyWiki2();
15364
15362
  $tw2.utils.eachAsync = eachAsync;
@@ -15402,7 +15400,7 @@ async function bootTiddlyWiki(wikiPath) {
15402
15400
  // this should be inert
15403
15401
  ];
15404
15402
  await new Promise((resolve13) => $tw2.boot.boot(resolve13));
15405
- ok5(!$tw2.boot.tasks.readBrowserTiddlers);
15403
+ ok4(!$tw2.boot.tasks.readBrowserTiddlers);
15406
15404
  return $tw2;
15407
15405
  }
15408
15406
  var init_tiddlywiki = __esm({
@@ -16887,7 +16885,7 @@ var require_command = __commonJS({
16887
16885
  "node_modules/commander/lib/command.js"(exports) {
16888
16886
  "use strict";
16889
16887
  init_esm_shims();
16890
- var EventEmitter = __require("node:events").EventEmitter;
16888
+ var EventEmitter2 = __require("node:events").EventEmitter;
16891
16889
  var childProcess = __require("node:child_process");
16892
16890
  var path6 = __require("node:path");
16893
16891
  var fs4 = __require("node:fs");
@@ -16897,7 +16895,7 @@ var require_command = __commonJS({
16897
16895
  var { Help: Help2, stripColor } = require_help();
16898
16896
  var { Option: Option2, DualOptions } = require_option();
16899
16897
  var { suggestSimilar } = require_suggestSimilar();
16900
- var _Command12 = class _Command12 extends EventEmitter {
16898
+ var _Command12 = class _Command12 extends EventEmitter2 {
16901
16899
  /**
16902
16900
  * Initialize a new `Command`.
16903
16901
  *
@@ -19817,7 +19815,7 @@ __export(listen_exports, {
19817
19815
  ListenerHTTPS: () => ListenerHTTPS,
19818
19816
  info: () => info7
19819
19817
  });
19820
- import { ok as ok6 } from "assert";
19818
+ import { ok as ok5 } from "assert";
19821
19819
  import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
19822
19820
  import { createServer } from "http";
19823
19821
  import * as http2 from "node:http2";
@@ -19874,8 +19872,8 @@ var init_listen = __esm({
19874
19872
  const { port, host } = config;
19875
19873
  if (port && typeof port !== "number") throw new Error("If specified, port must be a number");
19876
19874
  const bindInfo = host ? `HTTPS ${host} ${port}` : `HTTPS ${port}`;
19877
- ok6(config.key && existsSync4(config.key), "Key file not found");
19878
- ok6(config.cert && existsSync4(config.cert), "Cert file not found");
19875
+ ok5(config.key && existsSync4(config.key), "Key file not found");
19876
+ ok5(config.cert && existsSync4(config.cert), "Cert file not found");
19879
19877
  const key = readFileSync3(config.key);
19880
19878
  const cert = readFileSync3(config.cert);
19881
19879
  super(http2.createSecureServer({ key, cert, allowHTTP1: true }), router, bindInfo);
@@ -20143,6 +20141,23 @@ var init_sqlite_adapter = __esm({
20143
20141
  argTypes: []
20144
20142
  }).then((e) => e.rows.map((e2) => e2[0]))
20145
20143
  );
20144
+ if (applied_migrations.size && !applied_migrations.has("20250406213424_init")) {
20145
+ console.log("Unknown migrations have been found in the database.");
20146
+ console.log("There is no way to guarentee the integrity of the database under these conditions.");
20147
+ console.log("The only way I know of that you could be seeing this message is if you installed this verion of MWS in a project either from the PR branch or from a newer version of MWS.");
20148
+ console.log("Please revert back to the version you had installed before.");
20149
+ console.log("The sqlite database can be opened manually with any third-party SQLite tool to retrieve your data. If you do this, make sure you keep all the files in the store folder together, as they are all an integral part of your sqlite database and deleting any of them manually is very likely to cause data loss.");
20150
+ process.exit();
20151
+ } else {
20152
+ console.log([
20153
+ "=======================================================================================",
20154
+ "This version of MWS is no longer supported. It is an alpha version",
20155
+ "and you shouldn't be using it in production anyway. Please export any",
20156
+ "wikis you want to keep by opening them and downloading them as single-file",
20157
+ "wikis by clicking on the cloud status icon and then 'save snapshot for offline use'.",
20158
+ "======================================================================================="
20159
+ ].join("\n"));
20160
+ }
20146
20161
  const migrations = await readdir(dist_resolve("../prisma/migrations"));
20147
20162
  migrations.sort();
20148
20163
  const new_migrations = migrations.filter((m) => !applied_migrations.has(m) && m !== "migration_lock.toml");
@@ -20177,11 +20192,83 @@ var init_sqlite_adapter = __esm({
20177
20192
  }
20178
20193
  });
20179
20194
 
20195
+ // package.json
20196
+ var package_default;
20197
+ var init_package = __esm({
20198
+ "package.json"() {
20199
+ package_default = {
20200
+ name: "@tiddlywiki/mws",
20201
+ description: "TiddlyWiki Multiple Wiki Server",
20202
+ private: false,
20203
+ version: "0.0.21",
20204
+ type: "module",
20205
+ exports: {
20206
+ types: "./src/server.ts",
20207
+ require: "./dist/server.cjs",
20208
+ import: "./dist/server.js"
20209
+ },
20210
+ files: [
20211
+ "dist",
20212
+ "prisma",
20213
+ "react-user-mgmt",
20214
+ "tiddlywiki-types",
20215
+ "editions/mws-docs",
20216
+ "plugins/client",
20217
+ "scripts.mjs"
20218
+ ],
20219
+ engines: {
20220
+ node: ">=18"
20221
+ },
20222
+ scripts: {
20223
+ docs: "node scripts.mjs docs",
20224
+ start: "node scripts.mjs start",
20225
+ certs: "(cd runtime-config && bash localhost_certs.sh)",
20226
+ tsc: "tsc -p tsconfig.json --skipLibCheck",
20227
+ build: "npm run tsc && tsup && node mws.dev.mjs --client-build",
20228
+ "install-android": `GYP_DEFINES="android_ndk_path=''" npm install`,
20229
+ postinstall: "node scripts.mjs prisma:generate",
20230
+ env: "env",
20231
+ "test:pack": "(git clean -dfx tests && npm pack --pack-destination tests && cd tests && npm install && npm install ./tiddlywiki-mws-$npm_package_version.tgz --no-save && npm test)",
20232
+ test: "(git clean -dfx tests && cd tests && npm install .. --no-save && npm test)",
20233
+ fulltest: "mv node_modules node_modules_old; npm run test:pack; mv node_modules_old node_modules",
20234
+ prepublishOnly: "npm run build"
20235
+ },
20236
+ dependencies: {
20237
+ "@prisma/adapter-better-sqlite3": "6.7.0-dev.48",
20238
+ "@prisma/client": "6.6.0",
20239
+ "@serenity-kit/opaque": "^0.8.4",
20240
+ "@types/mime-types": "^2.1.4",
20241
+ "@types/node": "^22.13.1",
20242
+ "@types/send": "^0.17.4",
20243
+ "@types/sjcl": "^1.0.34",
20244
+ "@types/tiddlywiki": "file:tiddlywiki-types",
20245
+ prisma: "6.6.0",
20246
+ "source-map-support": "^0.5.21",
20247
+ zod: "^3.24.2"
20248
+ },
20249
+ devDependencies: {
20250
+ commander: "^13.1.0",
20251
+ esbuild: "0.25.2",
20252
+ "mime-types": "^2.1.35",
20253
+ send: "^1.1.0",
20254
+ sjcl: "^1.0.8",
20255
+ tiddlywiki: "^5.1.22",
20256
+ tsup: "^8.4.0",
20257
+ typescript: "^5.7.3",
20258
+ uuid: "^11.1.0"
20259
+ },
20260
+ peerDependencies: {
20261
+ tiddlywiki: "^5.1.22"
20262
+ }
20263
+ };
20264
+ }
20265
+ });
20266
+
20180
20267
  // src/commander.ts
20181
20268
  import * as path5 from "node:path";
20182
20269
  import { PrismaClient as PrismaClient2 } from "@prisma/client";
20183
20270
  import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync4 } from "node:fs";
20184
- import { ok as ok7 } from "node:assert";
20271
+ import { ok as ok6 } from "node:assert";
20185
20272
  var _StartupCommander, StartupCommander, _Commander, Commander;
20186
20273
  var init_commander = __esm({
20187
20274
  "src/commander.ts"() {
@@ -20193,6 +20280,7 @@ var init_commander = __esm({
20193
20280
  init_esm();
20194
20281
  init_commands();
20195
20282
  init_sqlite_adapter();
20283
+ init_package();
20196
20284
  _StartupCommander = class _StartupCommander {
20197
20285
  constructor(config, $tw2, PasswordService) {
20198
20286
  this.config = config;
@@ -20202,8 +20290,8 @@ var init_commander = __esm({
20202
20290
  this.setupRequired = true;
20203
20291
  this.fieldModules = $tw2.Tiddler.fieldModules;
20204
20292
  if (config.config?.pathPrefix) {
20205
- ok7(config.config.pathPrefix.startsWith("/"), "pathPrefix must start with a slash");
20206
- ok7(!config.config.pathPrefix.endsWith("/"), "pathPrefix must not end with a slash");
20293
+ ok6(config.config.pathPrefix.startsWith("/"), "pathPrefix must start with a slash");
20294
+ ok6(!config.config.pathPrefix.endsWith("/"), "pathPrefix must not end with a slash");
20207
20295
  }
20208
20296
  this.wikiPath = config.wikiPath;
20209
20297
  this.storePath = path5.resolve(this.wikiPath, "store");
@@ -20226,12 +20314,17 @@ var init_commander = __esm({
20226
20314
  contentTypeInfo: $tw2.config.contentTypeInfo,
20227
20315
  storePath: this.storePath,
20228
20316
  pathPrefix: config.config?.pathPrefix ?? "",
20229
- saveLargeTextToFileSystem: void 0
20317
+ saveLargeTextToFileSystem: void 0,
20318
+ enablePluginCache: config.config?.enablePluginCache ?? false
20230
20319
  };
20231
20320
  this.SessionManager = config.SessionManager || SessionManager;
20232
20321
  this.AttachmentService = config.AttachmentService || AttachmentService;
20233
20322
  this.adapter = new SqliteAdapter(this.databasePath);
20234
20323
  this.engine = new PrismaClient2({ log: ["info", "warn"], adapter: this.adapter.adapter });
20324
+ this.versions = {
20325
+ tiddlywiki: $tw2.packageInfo.version,
20326
+ mws: package_default.version
20327
+ };
20235
20328
  }
20236
20329
  async init() {
20237
20330
  await this.adapter.init();
@@ -20239,6 +20332,19 @@ var init_commander = __esm({
20239
20332
  const users = await this.engine.users.count();
20240
20333
  if (!users) {
20241
20334
  this.setupRequired = true;
20335
+ } else {
20336
+ const checkBags = await this.engine.bags.findMany({
20337
+ where: { bag_name: { startsWith: "$:/" } },
20338
+ select: { bag_name: true, tiddlers: { select: { title: true } } }
20339
+ });
20340
+ checkBags.forEach((e) => {
20341
+ e.tiddlers.forEach((f) => {
20342
+ if (f.title !== e.bag_name) {
20343
+ if (e.bag_name === "$:/plugins/tiddlywiki/codemirror-fullscreen-editing" && f.title === "$:/plugins/tiddlywiki/codemirror-fullscreen") return;
20344
+ console.log(`The bag ${e.bag_name} has a tiddler ${f.title}, in the future this will be unsupported. This can occur if the top bag in a recipe is one of the system bags (starting with $:/)`);
20345
+ }
20346
+ });
20347
+ });
20242
20348
  }
20243
20349
  }
20244
20350
  $transaction(fn, options) {