@looop-games/cli 0.1.33 → 0.1.34
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/CHANGELOG.md +7 -0
- package/lib/room-server.mjs +13 -1
- package/lib/test-cmd.mjs +22 -7
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -14,6 +14,13 @@ Versions: [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
14
14
|
|
|
15
15
|
## [Unreleased]
|
|
16
16
|
|
|
17
|
+
## [0.1.34] - 2026-09-02
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- `looop test` on Windows no longer discovers and runs the engine's own scaffold smokes from `node_modules` (or anything under `overrides/` and dot-folders). The gate could never go green there because those smokes always failed; it now runs only your game's tests, as on macOS and Linux. A test filter typed as `sub/depth` also matches on Windows now.
|
|
22
|
+
- `looop dev`, `looop test` and `looop publish` on Windows no longer fail with `Could not resolve "./entitiessaucer.js"` when a game has entity kinds: the generated room module now writes import paths with forward slashes whichever engine version produced them.
|
|
23
|
+
|
|
17
24
|
## [0.1.33] - 2026-08-27
|
|
18
25
|
|
|
19
26
|
### Added
|
package/lib/room-server.mjs
CHANGED
|
@@ -155,7 +155,19 @@ export function renderEntityComponents(components) {
|
|
|
155
155
|
// generated entry to wrap. Kept as one function so the kind-aliasing, the
|
|
156
156
|
// `./world.js` root convention, and the assemble→lower call can never drift
|
|
157
157
|
// between the dev and publish builds.
|
|
158
|
-
|
|
158
|
+
// A relative path with forward slashes whichever host produced it.
|
|
159
|
+
const posixPath = (p) => p.split('\\').join('/');
|
|
160
|
+
|
|
161
|
+
function renderV2Preamble(raw) {
|
|
162
|
+
// Kind modules are game-relative paths the engine relativized on the
|
|
163
|
+
// creator's machine. An engine whose framework build predates the
|
|
164
|
+
// forward-slash fix emits them with the host separator, so on Windows they
|
|
165
|
+
// arrive as `entities\saucer.js`. Dropped raw into a string literal, `\s` is
|
|
166
|
+
// an escape sequence and esbuild looks for `./entitiessaucer.js`. The CLI and
|
|
167
|
+
// the engine ship separately, so a fixed CLI on an unfixed engine is a real
|
|
168
|
+
// pairing; import specifiers and the URLs the client's kind loader builds
|
|
169
|
+
// from the embedded skeleton are always forward-slash, so normalize both.
|
|
170
|
+
const skeleton = { ...raw, kinds: raw.kinds.map((k) => ({ ...k, module: posixPath(k.module) })) };
|
|
159
171
|
const imports = skeleton.kinds.map(
|
|
160
172
|
(k, i) => `import { ${k.name} as K${i} } from './${k.module}';`,
|
|
161
173
|
);
|
package/lib/test-cmd.mjs
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
import { spawn } from 'node:child_process';
|
|
11
11
|
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
12
12
|
import { createRequire } from 'node:module';
|
|
13
|
-
import { join, relative, dirname } from 'node:path';
|
|
13
|
+
import nodePath, { join, relative, dirname } from 'node:path';
|
|
14
14
|
import { pathToFileURL } from 'node:url';
|
|
15
15
|
import { findProject, resolveEngine } from './project.mjs';
|
|
16
16
|
import { dev } from './dev.mjs';
|
|
@@ -19,16 +19,31 @@ import { portsFor, portInUse } from './ports.mjs';
|
|
|
19
19
|
|
|
20
20
|
const SKIP_DIRS = new Set(['node_modules', 'overrides']);
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
// Whether a game-relative path lies under a directory discovery must ignore:
|
|
23
|
+
// dependencies (the engine ships its own scaffold smokes under node_modules),
|
|
24
|
+
// engine-override copies, and dot-directories. `path.relative()` returns
|
|
25
|
+
// backslash-separated paths on Windows, so the split accepts both separators —
|
|
26
|
+
// splitting on '/' alone yields one giant segment there and skips nothing.
|
|
27
|
+
export function isSkippedPath(rel) {
|
|
28
|
+
return rel.split(/[\\/]/).some((p) => SKIP_DIRS.has(p) || p.startsWith('.'));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// The game-relative path with forward slashes on every platform, so a filter
|
|
32
|
+
// the creator types as `sub/depth` matches on Windows too. The path module is
|
|
33
|
+
// injectable so the Windows form can be produced on any host.
|
|
34
|
+
export const posixRelative = (dir, file, p = nodePath) => p.relative(dir, file).split('\\').join('/');
|
|
35
|
+
|
|
36
|
+
// The path module is injectable for the same reason: with `path.win32` the
|
|
37
|
+
// real readdir → join → relative → skip chain runs with Windows semantics
|
|
38
|
+
// against real files, on any host.
|
|
39
|
+
export function discoverTestFiles(dir, { path: p = nodePath } = {}) {
|
|
23
40
|
const unit = [];
|
|
24
41
|
const smokes = [];
|
|
25
42
|
const entries = readdirSync(dir, { withFileTypes: true, recursive: true });
|
|
26
43
|
for (const entry of entries) {
|
|
27
44
|
if (!entry.isFile()) continue;
|
|
28
|
-
const path = join(entry.parentPath, entry.name);
|
|
29
|
-
|
|
30
|
-
const parts = rel.split('/');
|
|
31
|
-
if (parts.some((p) => SKIP_DIRS.has(p) || p.startsWith('.'))) continue;
|
|
45
|
+
const path = p.join(entry.parentPath, entry.name);
|
|
46
|
+
if (isSkippedPath(p.relative(dir, path))) continue;
|
|
32
47
|
if (entry.name.endsWith('.test.mjs')) unit.push(path);
|
|
33
48
|
else if (entry.name.endsWith('.smoke.mjs')) smokes.push(path);
|
|
34
49
|
}
|
|
@@ -98,7 +113,7 @@ export async function testCmd({ cwd = process.cwd(), log = console.log, devFn =
|
|
|
98
113
|
// milestone close and before publish (qa.md T3). With no pattern, everything
|
|
99
114
|
// runs, exactly as before.
|
|
100
115
|
const scoped = patterns.length > 0;
|
|
101
|
-
const matches = (f) => patterns.some((p) =>
|
|
116
|
+
const matches = (f) => patterns.some((p) => posixRelative(project.dir, f).includes(p));
|
|
102
117
|
const unit = scoped ? all.unit.filter(matches) : all.unit;
|
|
103
118
|
const smokes = scoped ? all.smokes.filter(matches) : all.smokes;
|
|
104
119
|
|