otherplane 0.1.0 → 0.1.1

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 (2) hide show
  1. package/bin/otherplane.mjs +63 -25
  2. package/package.json +1 -1
@@ -30,7 +30,7 @@ import {
30
30
  readdirSync, statSync, mkdirSync, copyFileSync, cpSync, rmSync, existsSync, readFileSync, writeFileSync,
31
31
  } from 'node:fs';
32
32
  import { createServer } from 'node:http';
33
- import { join, dirname } from 'node:path';
33
+ import { join, dirname, sep } from 'node:path';
34
34
  import { fileURLToPath } from 'node:url';
35
35
  import { createRequire } from 'node:module';
36
36
 
@@ -57,14 +57,56 @@ function resolveEngine() {
57
57
  }
58
58
  }
59
59
  const ENGINE = resolveEngine();
60
- const PUBLIC = join(ENGINE, 'public');
61
60
  const CONTENT_DIRS = ['rooms', 'worlds', 'music'];
62
61
  const CONFIG_PATH = join(PROJECT, 'otherplane.config.json');
63
- // Next writes its export into the engine dir; the deployable belongs in the
64
- // PROJECT (the engine may be an installed package elsewhere). We copy it there.
65
- const ENGINE_OUT = join(ENGINE, 'out');
66
62
  const OUT = join(PROJECT, 'out');
67
63
 
64
+ // When installed, the engine lives inside node_modules — but Next/Turbopack won't
65
+ // transpile app source under node_modules. So for an installed engine we build a
66
+ // COPY staged outside it (in the project's .otherplane/), with its own real
67
+ // node_modules. In this repo's sibling layout we build in place.
68
+ const INSTALLED = ENGINE.split(sep).includes('node_modules');
69
+ let BUILD_ENGINE = ENGINE; // where Next actually runs
70
+ const publicDir = () => join(BUILD_ENGINE, 'public');
71
+ const engineOut = () => join(BUILD_ENGINE, 'out');
72
+
73
+ // Install the engine's deps into `dir` if missing (real node_modules, not a
74
+ // symlink — Turbopack can't resolve `next` through a symlinked node_modules).
75
+ function ensureDepsIn(dir) {
76
+ if (existsSync(join(dir, 'node_modules', 'next'))) return;
77
+ console.log('installing the engine’s dependencies (one-time per project, ~a minute)…');
78
+ run('npm', ['--prefix', dir, 'install', '--no-audit', '--no-fund']);
79
+ }
80
+
81
+ // Pick where Next runs and make sure its deps are there.
82
+ // • sibling (this repo): build in place.
83
+ // • installed: the engine sits inside node_modules, but Next won't transpile app
84
+ // source under node_modules — so stage a copy in PROJECT/.otherplane/engine and
85
+ // install deps THERE (kept across builds; `otherplane clean` drops it).
86
+ function prepareEngine() {
87
+ if (!INSTALLED) { BUILD_ENGINE = ENGINE; ensureDepsIn(ENGINE); return; }
88
+ const work = join(PROJECT, '.otherplane', 'engine');
89
+ // Refresh source every run (cheap); keep node_modules (expensive) across builds.
90
+ if (existsSync(work)) {
91
+ for (const e of readdirSync(work)) {
92
+ if (e !== 'node_modules') rmSync(join(work, e), { recursive: true, force: true });
93
+ }
94
+ } else {
95
+ mkdirSync(work, { recursive: true });
96
+ }
97
+ cpSync(ENGINE, work, {
98
+ recursive: true,
99
+ filter: (src) => {
100
+ const rel = src.slice(ENGINE.length);
101
+ return !rel.startsWith(`${sep}node_modules`)
102
+ && !rel.startsWith(`${sep}.next`)
103
+ && !rel.startsWith(`${sep}out`);
104
+ },
105
+ });
106
+ BUILD_ENGINE = work;
107
+ ensureDepsIn(work);
108
+ }
109
+
68
110
  // Env passed to every engine (Next) invocation so it reads THIS project's config
69
111
  // and content — not whatever happens to sit beside the engine dir.
70
112
  const engineEnv = (extra = {}) => ({ ...process.env, OTHERPLANE_PROJECT: PROJECT, ...extra });
@@ -100,16 +142,9 @@ function syncDir(src, dest) {
100
142
  }
101
143
 
102
144
  function sync() {
103
- for (const d of CONTENT_DIRS) syncDir(join(PROJECT, d), join(PUBLIC, d));
145
+ for (const d of CONTENT_DIRS) syncDir(join(PROJECT, d), join(publicDir(), d));
104
146
  }
105
147
 
106
- // Install the engine's deps on first use (so `npx otherplane` / a global install
107
- // works with no separate setup step). No-op once node_modules exists.
108
- function ensureEngineDeps() {
109
- if (existsSync(join(ENGINE, 'node_modules'))) return;
110
- console.log('installing the engine’s dependencies (one-time, ~a minute)…');
111
- run('npm', ['--prefix', ENGINE, 'install']);
112
- }
113
148
 
114
149
  // How many rooms the project actually has (a folder with a room.json).
115
150
  function roomCount() {
@@ -124,7 +159,7 @@ function roomCount() {
124
159
  function syncRoom(slug) {
125
160
  const src = join(PROJECT, 'rooms', slug, 'room.json');
126
161
  if (!existsSync(src)) return;
127
- const destDir = join(PUBLIC, 'rooms', slug);
162
+ const destDir = join(publicDir(), 'rooms', slug);
128
163
  mkdirSync(destDir, { recursive: true });
129
164
  copyFileSync(src, join(destDir, 'room.json'));
130
165
  }
@@ -353,7 +388,7 @@ function startEditServer() {
353
388
  // (not spawnSync) so the writer's event loop keeps serving while Next runs.
354
389
  function runEdit() {
355
390
  const server = startEditServer();
356
- const child = spawn('npm', ['--prefix', ENGINE, 'run', 'edit'], {
391
+ const child = spawn('npm', ['--prefix', BUILD_ENGINE, 'run', 'edit'], {
357
392
  stdio: 'inherit',
358
393
  cwd: PROJECT,
359
394
  env: engineEnv({ NEXT_PUBLIC_EDIT_API: `http://localhost:${EDIT_PORT}` }),
@@ -423,15 +458,15 @@ function buildEngine(base) {
423
458
  console.error('no rooms yet — add one with `otherplane new` or create rooms/<slug>/room.json, then build.');
424
459
  process.exit(1);
425
460
  }
426
- ensureEngineDeps();
461
+ prepareEngine();
427
462
  check();
428
463
  sync();
429
- run('npm', ['--prefix', ENGINE, 'run', 'build'], {
464
+ run('npm', ['--prefix', BUILD_ENGINE, 'run', 'build'], {
430
465
  env: engineEnv(base != null ? { OTHERPLANE_BASE_PATH: base } : {}),
431
466
  });
432
- if (ENGINE_OUT !== OUT) {
467
+ if (engineOut() !== OUT) {
433
468
  rmSync(OUT, { recursive: true, force: true });
434
- cpSync(ENGINE_OUT, OUT, { recursive: true });
469
+ cpSync(engineOut(), OUT, { recursive: true });
435
470
  console.log(`✓ exported to ${OUT}`);
436
471
  }
437
472
  }
@@ -446,12 +481,12 @@ switch (cmd) {
446
481
  sync();
447
482
  break;
448
483
  case 'dev':
449
- ensureEngineDeps();
484
+ prepareEngine();
450
485
  sync();
451
- run('npm', ['--prefix', ENGINE, 'run', 'dev'], { env: engineEnv() });
486
+ run('npm', ['--prefix', BUILD_ENGINE, 'run', 'dev'], { env: engineEnv() });
452
487
  break;
453
488
  case 'edit':
454
- ensureEngineDeps();
489
+ prepareEngine();
455
490
  sync();
456
491
  runEdit();
457
492
  break;
@@ -469,11 +504,14 @@ switch (cmd) {
469
504
  run('python3', ['-m', 'http.server', '8000'], { cwd: OUT });
470
505
  break;
471
506
  case 'clean':
507
+ // Remove build output in both layouts: the in-place engine (dev sibling), the
508
+ // staged copy (installed), and the project's out/.
472
509
  rmSync(join(ENGINE, '.next'), { recursive: true, force: true });
473
- rmSync(ENGINE_OUT, { recursive: true, force: true });
510
+ rmSync(join(ENGINE, 'out'), { recursive: true, force: true });
511
+ for (const d of CONTENT_DIRS) rmSync(join(ENGINE, 'public', d), { recursive: true, force: true });
512
+ rmSync(join(PROJECT, '.otherplane'), { recursive: true, force: true });
474
513
  rmSync(OUT, { recursive: true, force: true });
475
- for (const d of CONTENT_DIRS) rmSync(join(PUBLIC, d), { recursive: true, force: true });
476
- console.log('cleaned build output (.next, out) and the synced mirror');
514
+ console.log('cleaned build output (.next, out, .otherplane) and the synced mirror');
477
515
  break;
478
516
  case 'setup':
479
517
  run('npm', ['--prefix', ENGINE, 'install']);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "otherplane",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "A static site generator for walkable Gaussian-splat museums. Generate a 3D room per section, link rooms with doorways, publish a static walkable site.",
5
5
  "keywords": [
6
6
  "gaussian-splatting",