@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 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
@@ -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
- function renderV2Preamble(skeleton) {
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
- export function discoverTestFiles(dir) {
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
- const rel = relative(dir, path);
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) => relative(project.dir, f).includes(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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@looop-games/cli",
3
- "version": "0.1.33",
3
+ "version": "0.1.34",
4
4
  "description": "Looop game development CLI — dev server, login, and publishing for standalone Looop games.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",