1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159 | /**
* Restore the historical dist/ file tree as thin ESM re-export shims.
*
* v1.3.0 was compiled by tsc, so dist/ mirrored src/ one file per module, and
* package.json's "./games/*" subpath export made part of that tree public. A
* shadow-cljs :esm build emits one FLAT file per :modules entry instead
* (dist/index.js, dist/node.js, dist/internals.js), so every historical path
* would 404 after the port.
*
* The table below is therefore the API contract: one entry per file the
* TypeScript build published, listing the exact names it exported, and which
* flat module now provides them. Each shim is generated deterministically
* (sorted, fixed formatting) so the committed dist stays byte-stable for
* deploy/check-dist.sh.
*
* The two `*-protocol` files that were TYPES ONLY in TypeScript compile to an
* empty module, and are emitted as `export {};` — same as tsc produced.
*
* Adding a name here does NOT create it: it must already be a named export of
* the target module (see shadow-cljs.edn :exports), or Node throws at import.
* test/subpaths.test.mjs imports every path below and checks every name.
*/
import { mkdirSync, writeFileSync } from "node:fs";
import { dirname, join } from "node:path";
const DIST = new URL("../dist", import.meta.url).pathname;
/** path (under dist/, without .js) -> { from: module, names: [...] } */
const SHIMS = {
// ---- the package root's own neighbours (relative-import compatibility) ----
"apps": { from: "index", names: ["GAMES", "SIGNAL", "DEFAULT_ORIGIN"] },
"connect": { from: "index", names: ["connect", "RoomPeer"] },
// ---- core/ (never a subpath export; kept for relative deep imports) ----
"core/index": { from: "index", names: ["createNet", "newRoomSecret"] },
"core/room-crypto": {
from: "internals",
names: ["RoomCrypto", "randomBytes", "toB64", "fromB64", "toB64url", "fromB64url"],
also: { from: "index", names: ["newRoomSecret"] },
},
"core/bootstrap": { from: "internals", names: ["fetchRelayInfo"] },
"core/turn": { from: "internals", names: ["IceConfig"] },
"core/node": { from: "internals", names: ["NetNode"] },
"core/peers": { from: "internals", names: ["PeerManager"] },
// ---- node/ (the "./node" subpath export resolves to node/index.js) ----
"node/index": {
from: "node",
names: ["installOrigin", "installOriginWebSocket", "installOriginFetch", "loadIdentity"],
},
"node/env": {
from: "internals",
names: ["selfPeerIds", "installOriginWebSocket", "installOriginFetch"],
also: { from: "index", names: ["installOrigin", "installBrowserGlobals"] },
},
"node/identity": { from: "index", names: ["loadIdentity"] },
// ---- social/ ----
"social/agent": { from: "index", names: ["Social", "suiteFriendLink"] },
// ---- games/ (the "./games/*" subpath export) ----
"games/room": { from: "index", names: ["GameRoom"] },
"games/seance": { from: "index", names: ["SeanceClient"] },
"games/neon-grid": { from: "index", names: ["NeonGridClient"] },
"games/tessera": { from: "index", names: ["TesseraClient"] },
"games/lampion": { from: "index", names: ["LampionClient"] },
"games/odeon": { from: "index", names: ["OdeonClient"] },
"games/valley-blocks": { from: "index", names: ["ValleyBlocksClient"] },
"games/valley-blocks-brain": {
from: "index",
names: [
"enumeratePlacements", "executePlacement", "placementStep", "planOf",
"boardMetrics", "dellacherieScore", "stockPick", "snakeStep",
],
},
// ---- games/host/ ----
"games/host/rng": { from: "index", names: ["mulberry32"] },
"games/host/sim": { from: "internals", names: ["NO_EMIT"] },
"games/host/driver": { from: "index", names: ["HostDriver"] },
"games/host/seance-protocol": { typesOnly: true },
"games/host/neon-grid-protocol": { typesOnly: true },
"games/host/seance-sim": {
from: "internals",
names: ["CANDLES", "REVEAL_MS", "walkMs", "START_BELL_MS", "OVER_LOBBY_MS", "PICK_GRACE_MS"],
also: { from: "index", names: ["SeanceSim"] },
},
"games/host/seance-host": { from: "index", names: ["SeanceHost"] },
"games/host/neon-grid-sim": {
from: "internals",
names: [
"GW", "GH", "WIN_SCORE", "DX", "DY",
"COUNTDOWN_MS", "OVER_ROUND_MS", "OVER_MATCH_MS", "MATCH_LOBBY_MS",
],
also: { from: "index", names: ["NeonGridSim"] },
},
"games/host/neon-grid-host": { from: "index", names: ["NeonGridHost"] },
"games/host/valley-blocks-core": {
from: "internals",
names: [
"PIECE_TYPES", "cellsFor", "kicksFor", "Board", "Bag",
"COLS", "ROWS", "HIDDEN_ROWS", "TOTAL_ROWS",
"LINES_PER_LEVEL", "pointsForLines", "SOFT_DROP_POINTS", "HARD_DROP_POINTS",
"gravitySeconds", "levelForLines", "snakeInterval", "snakeDuration",
],
},
"games/host/valley-blocks-engine": { from: "index", names: ["ValleyBlocksEngine"] },
"games/host/valley-blocks-protocol": {
from: "internals",
names: ["MAX_PLAYERS", "STATE_INTERVAL_MS", "PING_INTERVAL_MS", "serializeGrid", "boardStateOf"],
},
"games/host/valley-blocks-sim": {
from: "internals",
names: ["ROUND_LOBBY_MS", "DEFAULT_MODE", "DEFAULT_TARGET"],
also: { from: "index", names: ["ValleyBlocksSim"] },
},
"games/host/valley-blocks-host": { from: "index", names: ["ValleyBlocksHost"] },
// ---- gym/ ----
"gym/seance": { from: "index", names: ["SeanceGym"] },
"gym/neon-grid": { from: "index", names: ["NeonGridGym"] },
"gym/valley-blocks": { from: "index", names: ["ValleyBlocksGym"] },
};
const HEADER =
"// Generated by scripts/gen-subpaths.mjs — re-export shim for the file layout\n" +
"// the TypeScript build (v1.3.0) published. Do not edit; edit the table there.\n";
/**
* The flat modules live at dist/<mod>.js, so a shim at dist/a/b/c.js reaches
* them by climbing back to the root. `relative()` alone is wrong here: for
* dist/node/index.js and the module "node" it yields "" (it compares the shim's
* DIRECTORY with a name that is actually a FILE at the root), which produced a
* "./.js" specifier. Count the depth instead.
*/
const specifier = (path, mod) => {
const depth = path.split("/").length - 1;
return depth === 0 ? `./${mod}` : `${"../".repeat(depth)}${mod}`;
};
let files = 0;
let names = 0;
for (const [path, spec] of Object.entries(SHIMS)) {
const file = join(DIST, `${path}.js`);
mkdirSync(dirname(file), { recursive: true });
let body = HEADER;
if (spec.typesOnly) {
body += "// (types only in the TypeScript original — no runtime exports)\nexport {};\n";
} else {
for (const part of [spec, spec.also].filter(Boolean)) {
const sorted = [...part.names].sort();
body += `export { ${sorted.join(", ")} } from "${specifier(path, part.from)}.js";\n`;
names += sorted.length;
}
}
writeFileSync(file, body);
files++;
}
console.log(`gen-subpaths: ${files} shims, ${names} re-exported names`);
|