@moku-labs/game 0.14.12 → 0.14.13

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 (56) hide show
  1. package/README.md +13 -12
  2. package/dist/app/page.d.mts +8 -3
  3. package/dist/app/page.mjs +57 -12
  4. package/dist/app/system.d.mts +5 -3
  5. package/dist/app/system.mjs +2 -0
  6. package/dist/app.d.mts +15 -7
  7. package/dist/app.mjs +131 -27
  8. package/dist/{assets-BrKSg1O6.mjs → assets-CEMfBogh.mjs} +471 -120
  9. package/dist/assets.d.mts +30 -13
  10. package/dist/assets.mjs +1 -1
  11. package/dist/cli.d.mts +105 -8
  12. package/dist/cli.mjs +1036 -128
  13. package/dist/{commands-BaCx2B9j.d.mts → commands-DAETmK86.d.mts} +43 -7
  14. package/dist/{config-CdARrrhU.mjs → config-B87aFTW-.mjs} +25 -4
  15. package/dist/control.d.mts +130 -17
  16. package/dist/control.mjs +4 -4
  17. package/dist/{define-Ce8SVEN-.d.mts → define-BASd9ZUh.d.mts} +32 -4
  18. package/dist/dev-DbHgx-R-.mjs +89 -0
  19. package/dist/{fake-MpzwZhIZ.mjs → fake-6mazmHK3.mjs} +20 -2
  20. package/dist/features-DowEilZm.mjs +266 -0
  21. package/dist/{headless-CMHNsKjW.d.mts → headless-cFveciuY.d.mts} +1 -1
  22. package/dist/hot.d.mts +4 -2
  23. package/dist/hot.mjs +7 -4
  24. package/dist/{index-CLQ8NOTw.d.mts → index-DedbED3T.d.mts} +317 -69
  25. package/dist/index.d.mts +5 -6
  26. package/dist/index.mjs +3 -3
  27. package/dist/inspect.d.mts +12 -7
  28. package/dist/inspect.mjs +2 -2
  29. package/dist/jsx-dev-runtime.d.mts +1 -1
  30. package/dist/jsx-runtime.d.mts +1 -1
  31. package/dist/lint.d.mts +8 -3
  32. package/dist/lint.mjs +432 -20
  33. package/dist/project.d.mts +15 -1
  34. package/dist/project.mjs +1 -1
  35. package/dist/{run-Bf2hKyjZ.mjs → run-BbipFRc6.mjs} +250 -51
  36. package/dist/{runtime-BCnKn0xf.d.mts → runtime-DoSlRx8X.d.mts} +1 -1
  37. package/dist/{session-DgukhR_7.mjs → session-CeKgO2hy.mjs} +30 -2
  38. package/dist/{sources-Duzj6lhV.mjs → sources-Cdvb0rOS.mjs} +33 -6
  39. package/dist/{src-CtC8w3_z.mjs → src-BEbJ_sSx.mjs} +35536 -17789
  40. package/dist/testing.d.mts +23 -13
  41. package/dist/testing.mjs +10 -6
  42. package/dist/{types-DOlpeuGx.d.mts → types-7GXcXxxv.d.mts} +174 -242
  43. package/dist/{types-BjTtGSFb.d.mts → types-BGcda5Le.d.mts} +4459 -732
  44. package/dist/{types-gisvSHoC.d.mts → types-C73hbmxi.d.mts} +129 -17
  45. package/dist/{types-CAPv9l29.d.mts → types-DngFQJro.d.mts} +43 -5
  46. package/dist/{types-Y_rCuXtj.d.mts → types-GHE4J5Uf.d.mts} +1025 -191
  47. package/dist/{visual-dMW8iqAE.mjs → visual-2m4wJL_z.mjs} +111 -7
  48. package/dist/visual-_XhoGeIC.d.mts +345 -0
  49. package/dist/visual.d.mts +1 -332
  50. package/dist/visual.mjs +130 -20
  51. package/dist/{watch-CWI_on4T.mjs → watch-BEdrp80i.mjs} +149 -30
  52. package/llms.txt +99 -48
  53. package/package.json +8 -6
  54. package/dist/dev-Cc50Dk7H.mjs +0 -47
  55. package/dist/memory-pUQ7YZWC.d.mts +0 -50
  56. package/dist/types-BX4Ge_j5.d.mts +0 -656
@@ -1,7 +1,8 @@
1
+ import { n as findFeatureUnits } from "./features-DowEilZm.mjs";
1
2
  import { watch } from "node:fs";
2
3
  import path from "node:path";
3
- import { readFile, readdir, realpath, stat } from "node:fs/promises";
4
4
  import { createHash } from "node:crypto";
5
+ import { readFile, readdir, realpath, stat } from "node:fs/promises";
5
6
  //#region src/project/paths.ts
6
7
  /**
7
8
  * @file project — which files the index reads, and the guard on every path it reads. Paths in and
@@ -1556,17 +1557,22 @@ function sha1(bytes) {
1556
1557
  return createHash("sha1").update(bytes).digest("hex");
1557
1558
  }
1558
1559
  /**
1559
- * The revision of an index: the sha1 of the lines `${path}\0${hash}\n`, sorted by path.
1560
+ * The revision of an index: the sha1 of the lines `${path}\0${hash}\n`, one per file, and
1561
+ * `${dir}/\0${parent}\n`, one per feature unit, all sorted together. Without units the value is
1562
+ * the one of the files alone.
1560
1563
  *
1561
1564
  * @param files - Every indexed file with its hash.
1565
+ * @param owners - The feature units of the game: owners and code-only sub-features.
1562
1566
  * @returns The hex digest.
1563
1567
  * @example
1564
1568
  * ```ts
1565
1569
  * revisionOf({}); // "da39a3ee5e6b4b0d3255bfef95601890afd80709"
1566
1570
  * ```
1567
1571
  */
1568
- function revisionOf(files) {
1569
- return sha1(Object.entries(files).map(([file, entry]) => `${file}\0${entry.hash}\n`).toSorted().join(""));
1572
+ function revisionOf(files, owners = []) {
1573
+ const lines = Object.entries(files).map(([file, entry]) => `${file}\0${entry.hash}\n`);
1574
+ const units = owners.map((owner) => `${owner.dir}/\0${owner.parent ?? ""}\n`);
1575
+ return sha1([...lines, ...units].toSorted().join(""));
1570
1576
  }
1571
1577
  //#endregion
1572
1578
  //#region src/project/shapes.ts
@@ -1778,7 +1784,26 @@ function deepFreeze(value) {
1778
1784
  return value;
1779
1785
  }
1780
1786
  /**
1787
+ * The `features` table of the index: one entry per unit, in unit order, the parent only when there
1788
+ * is one.
1789
+ *
1790
+ * @param owners - The feature units.
1791
+ * @returns The table.
1792
+ * @example
1793
+ * ```ts
1794
+ * featuresOf([{ name: "home", dir: "features/home", depth: 0, resources: { assets: true, strings: false } }]); // { home: { dir: "features/home" } }
1795
+ * ```
1796
+ */
1797
+ function featuresOf(owners) {
1798
+ return Object.fromEntries(owners.map((owner) => [owner.name, owner.parent === void 0 ? { dir: owner.dir } : {
1799
+ dir: owner.dir,
1800
+ parent: owner.parent
1801
+ }]));
1802
+ }
1803
+ /**
1781
1804
  * Assembles the index of a catalog. The index names the tsconfig when the assembly has aliases.
1805
+ * With the feature units it carries `features`, a clash adds one unresolved item, and the units
1806
+ * join the revision; without them the index has no `features` key.
1782
1807
  *
1783
1808
  * @param assembly - The records sorted by path, the module facts of the good files, the aliases.
1784
1809
  * @param manifest - The root-relative manifest path when the file exists.
@@ -1808,14 +1833,20 @@ function assembleIndex(assembly, manifest) {
1808
1833
  }
1809
1834
  addStyleUses(assembly, symbols);
1810
1835
  addComponentUses(assembly, symbols);
1836
+ const owners = assembly.owners;
1837
+ if (owners?.error !== void 0) unresolved.push({
1838
+ path: "features",
1839
+ reason: owners.error
1840
+ });
1811
1841
  return deepFreeze({
1812
1842
  schemaVersion: 1,
1813
- revision: revisionOf(files),
1843
+ revision: revisionOf(files, owners?.owners),
1814
1844
  ...manifest === void 0 ? {} : { manifest },
1815
1845
  ...assembly.aliases === void 0 ? {} : { tsconfig: assembly.aliases.file },
1816
1846
  symbols: finishSymbols(symbols),
1817
1847
  files,
1818
- unresolved
1848
+ unresolved,
1849
+ ...owners === void 0 ? {} : { features: featuresOf(owners.owners) }
1819
1850
  });
1820
1851
  }
1821
1852
  //#endregion
@@ -2369,9 +2400,10 @@ function refreshExtractions(catalog, resolution) {
2369
2400
  * @param catalog - The catalog.
2370
2401
  * @param manifest - The root-relative manifest path when the file exists.
2371
2402
  * @param aliases - The tsconfig aliases when the game declares some.
2403
+ * @param owners - The feature units of the game, when the catalog has a root.
2372
2404
  * @returns The index, frozen.
2373
2405
  */
2374
- function buildIndex(catalog, manifest, aliases) {
2406
+ function buildIndex(catalog, manifest, aliases, owners) {
2375
2407
  const files = [...catalog.records].toSorted(([first], [second]) => first < second ? -1 : 1);
2376
2408
  const modules = /* @__PURE__ */ new Map();
2377
2409
  for (const [path, record] of files) if (record.good !== void 0) modules.set(path, record.good.module);
@@ -2382,7 +2414,8 @@ function buildIndex(catalog, manifest, aliases) {
2382
2414
  return assembleIndex({
2383
2415
  files,
2384
2416
  modules,
2385
- aliases
2417
+ aliases,
2418
+ owners
2386
2419
  }, manifest);
2387
2420
  }
2388
2421
  //#endregion
@@ -2398,12 +2431,32 @@ function pathsOf(entry) {
2398
2431
  return [...new Set(entry.def.map((anchor) => anchor.path))];
2399
2432
  }
2400
2433
  /**
2434
+ * The feature names whose entry differs between two `features` tables: added, removed, or with
2435
+ * another folder or parent.
2436
+ *
2437
+ * @param before - The table before the batch, when the index has one.
2438
+ * @param after - The table after the batch, when the index has one.
2439
+ * @returns The names, sorted.
2440
+ * @example
2441
+ * ```ts
2442
+ * changedFeatures({ a: { dir: "features/a" } }, { b: { dir: "features/b" } }); // ["a", "b"]
2443
+ * ```
2444
+ */
2445
+ function changedFeatures(before = {}, after = {}) {
2446
+ return [.../* @__PURE__ */ new Set([...Object.keys(before), ...Object.keys(after)])].filter((name) => {
2447
+ const was = before[name];
2448
+ const is = after[name];
2449
+ return was?.dir !== is?.dir || was?.parent !== is?.parent;
2450
+ }).toSorted();
2451
+ }
2452
+ /**
2401
2453
  * Compares the index before a batch with the index after it.
2402
2454
  *
2403
2455
  * @param before - The index before the batch.
2404
2456
  * @param after - The index after the batch.
2405
2457
  * @param files - The paths whose bytes changed, appeared or vanished.
2406
- * @returns The change: the new revision, the files sorted, the moves and the removed keys.
2458
+ * @returns The change: the new revision, the files sorted, the moves, the removed keys and the
2459
+ * features that moved, when some did.
2407
2460
  */
2408
2461
  function diffIndexes(before, after, files) {
2409
2462
  const moved = [];
@@ -2427,11 +2480,13 @@ function diffIndexes(before, after, files) {
2427
2480
  });
2428
2481
  }
2429
2482
  }
2483
+ const features = changedFeatures(before.features, after.features);
2430
2484
  return {
2431
2485
  revision: after.revision,
2432
2486
  files: files.toSorted(),
2433
2487
  moved,
2434
- removed
2488
+ removed,
2489
+ ...features.length === 0 ? {} : { features }
2435
2490
  };
2436
2491
  }
2437
2492
  //#endregion
@@ -2528,8 +2583,9 @@ async function walkRoot(root) {
2528
2583
  * walk, the tsconfig aliases with the stamps of the config files they came from, the current index
2529
2584
  * and a queue that runs one update at a time. `syncWithDisk` is the batch walk of a watch batch:
2530
2585
  * it walks the root, hashes the files whose stamp moved and every new one, drops the vanished
2531
- * ones, reads the aliases again when a config file moved, and rebuilds the index when bytes or
2532
- * aliases changed. `reindexPath` reads one path now for `changed`.
2586
+ * ones, reads the aliases again when a config file moved, reads the feature units again, and
2587
+ * rebuilds the index when bytes, aliases or units changed. `reindexPath` reads one path now for
2588
+ * `changed`.
2533
2589
  */
2534
2590
  /** The dev manifest `moku-game keys` writes. */
2535
2591
  const MANIFEST_FILE = "generated/manifest.json";
@@ -2635,6 +2691,55 @@ async function rereadAliases(session) {
2635
2691
  return !sameAliases(before, session.aliases);
2636
2692
  }
2637
2693
  /**
2694
+ * Reads the feature units of a game: the owners and the code-only sub-features. A name clash keeps
2695
+ * the units of the read before, as a tsconfig that stops parsing keeps its aliases.
2696
+ *
2697
+ * @param root - The real root.
2698
+ * @param before - The read before, if any.
2699
+ * @returns The read.
2700
+ */
2701
+ function readOwners(root, before) {
2702
+ try {
2703
+ return { owners: findFeatureUnits(root) };
2704
+ } catch (error) {
2705
+ const message = error instanceof Error ? error.message : String(error);
2706
+ return {
2707
+ owners: before?.owners ?? [],
2708
+ error: message
2709
+ };
2710
+ }
2711
+ }
2712
+ /**
2713
+ * Whether two reads of the feature units agree: the same names, folders and parents in the same
2714
+ * order, and the same clash message.
2715
+ *
2716
+ * @param before - The read before.
2717
+ * @param after - The read now.
2718
+ * @returns True when the index would not change by them.
2719
+ * @example
2720
+ * ```ts
2721
+ * sameOwners({ owners: [] }, { owners: [], error: "clash" }); // false
2722
+ * ```
2723
+ */
2724
+ function sameOwners(before, after) {
2725
+ return before.error === after.error && before.owners.length === after.owners.length && before.owners.every((owner, at) => {
2726
+ const other = after.owners[at];
2727
+ return owner.name === other?.name && owner.dir === other.dir && owner.parent === other.parent;
2728
+ });
2729
+ }
2730
+ /**
2731
+ * Reads the feature units again and keeps the new read.
2732
+ *
2733
+ * @param session - The project.
2734
+ * @returns True when they differ from the read before.
2735
+ */
2736
+ function rereadOwners(session) {
2737
+ const read = readOwners(session.root, session.owners);
2738
+ const isMoved = !sameOwners(session.owners, read);
2739
+ session.owners = read;
2740
+ return isMoved;
2741
+ }
2742
+ /**
2638
2743
  * Reads one file into the catalog, or drops it when it is gone or cannot be read.
2639
2744
  *
2640
2745
  * @param session - The project.
@@ -2659,7 +2764,7 @@ async function readIntoCatalog(session, file) {
2659
2764
  async function rebuild(session, files) {
2660
2765
  const before = session.index;
2661
2766
  const manifest = await presentManifest(session.root, session.manifest);
2662
- session.index = buildIndex(session.catalog, manifest, session.aliases);
2767
+ session.index = buildIndex(session.catalog, manifest, session.aliases, session.owners);
2663
2768
  return diffIndexes(before, session.index, files);
2664
2769
  }
2665
2770
  /**
@@ -2682,21 +2787,23 @@ async function openSession(options) {
2682
2787
  const aliases = followedAliases(read);
2683
2788
  const walk = await walkRoot(root);
2684
2789
  const catalog = createCatalog(typescript);
2790
+ const owners = readOwners(root);
2685
2791
  const session = {
2686
2792
  root,
2687
2793
  manifest,
2688
2794
  tsconfig,
2689
2795
  aliases,
2690
2796
  configStamps: await stampConfigs(root, configsOf(tsconfig, read)),
2797
+ owners,
2691
2798
  catalog,
2692
2799
  stamps: walk.files,
2693
2800
  folders: walk.folders,
2694
- index: buildIndex(catalog, void 0, aliases),
2801
+ index: buildIndex(catalog, void 0, aliases, owners),
2695
2802
  shapes: /* @__PURE__ */ new WeakMap(),
2696
2803
  queue: Promise.resolve()
2697
2804
  };
2698
2805
  for (const file of [...walk.files.keys()].toSorted()) await readIntoCatalog(session, file);
2699
- session.index = buildIndex(catalog, await presentManifest(root, manifest), aliases);
2806
+ session.index = buildIndex(catalog, await presentManifest(root, manifest), aliases, owners);
2700
2807
  return session;
2701
2808
  }
2702
2809
  /**
@@ -2713,10 +2820,10 @@ function serialize(session, task) {
2713
2820
  }
2714
2821
  /**
2715
2822
  * The batch walk: walks the root, hashes each file whose stamp moved and each new one, drops the
2716
- * vanished ones, reads the aliases again when a config file moved, appeared or vanished, and
2717
- * rebuilds the index when bytes or aliases changed. A save with the same bytes, or a tsconfig
2718
- * edit that leaves the aliases as they were, changes nothing. New aliases list the tsconfig
2719
- * among the changed files.
2823
+ * vanished ones, reads the aliases again when a config file moved, appeared or vanished, reads
2824
+ * the feature units again, and rebuilds the index when bytes, aliases or units changed. A save
2825
+ * with the same bytes, or a tsconfig edit that leaves the aliases as they were, changes nothing.
2826
+ * New aliases list the tsconfig among the changed files; moved units list no file.
2720
2827
  *
2721
2828
  * @param session - The project.
2722
2829
  * @returns The change, or `undefined` when the index stayed the same.
@@ -2734,14 +2841,32 @@ async function syncWithDisk(session) {
2734
2841
  if (await readIntoCatalog(session, file)) files.push(file);
2735
2842
  }
2736
2843
  if (await configsMoved(session) && await rereadAliases(session)) files.push(session.tsconfig);
2844
+ const ownersMoved = rereadOwners(session);
2737
2845
  const manifest = await presentManifest(session.root, session.manifest);
2738
- if (files.length === 0 && manifest === session.index.manifest) return void 0;
2846
+ if (files.length === 0 && manifest === session.index.manifest && !ownersMoved) return void 0;
2739
2847
  return rebuild(session, files);
2740
2848
  }
2741
2849
  /**
2850
+ * Reads one root-relative path into the project: a config file of the aliases reads them again,
2851
+ * an indexed file is parsed or dropped, anything else is skipped.
2852
+ *
2853
+ * @param session - The project.
2854
+ * @param relative - The root-relative POSIX path.
2855
+ * @param real - Its real path, `undefined` when nothing is there.
2856
+ * @returns The paths to report as changed: the tsconfig for new aliases, the file for new bytes.
2857
+ */
2858
+ async function readPath(session, relative, real) {
2859
+ if (session.configStamps.has(relative)) return await rereadAliases(session) ? [session.tsconfig] : [];
2860
+ if (real !== void 0 && real !== path.join(session.root, relative) || !isIndexedFile(relative)) return [];
2861
+ const stamp = real === void 0 ? void 0 : await stampOf(real);
2862
+ if (stamp !== void 0) session.stamps.set(relative, stamp);
2863
+ return await readIntoCatalog(session, relative) ? [relative] : [];
2864
+ }
2865
+ /**
2742
2866
  * Reads one root-relative path now: a changed or new source file is parsed, a deleted one is
2743
2867
  * dropped, the tsconfig or a file it extends reads the aliases again, and anything else changes
2744
- * nothing.
2868
+ * no file. The feature units are read again for every path, so a new door or a new `strings/`
2869
+ * folder shows a sub-feature at once.
2745
2870
  *
2746
2871
  * @param session - The project.
2747
2872
  * @param file - The root-relative path.
@@ -2752,15 +2877,9 @@ async function reindexPath(session, file) {
2752
2877
  const relative = toPosix(file);
2753
2878
  const real = await resolveInside(session.root, relative);
2754
2879
  if (path.isAbsolute(relative)) return session.index;
2755
- if (session.configStamps.has(relative)) {
2756
- if (await rereadAliases(session)) await rebuild(session, [session.tsconfig]);
2757
- return session.index;
2758
- }
2759
- if (real !== void 0 && real !== path.join(session.root, relative) || !isIndexedFile(relative)) return session.index;
2760
- const stamp = real === void 0 ? void 0 : await stampOf(real);
2761
- if (stamp !== void 0) session.stamps.set(relative, stamp);
2762
- if (!await readIntoCatalog(session, relative)) return session.index;
2763
- await rebuild(session, [relative]);
2880
+ const files = await readPath(session, relative, real);
2881
+ const ownersMoved = rereadOwners(session);
2882
+ if (files.length > 0 || ownersMoved) await rebuild(session, files);
2764
2883
  return session.index;
2765
2884
  }
2766
2885
  //#endregion