rootless-config 1.8.0 → 1.8.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rootless-config",
3
- "version": "1.8.0",
3
+ "version": "1.8.2",
4
4
  "description": "Store project config files outside the project root, auto-deploy them where tools expect them.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,11 +1,11 @@
1
1
  /*-------- rootless migrate — moves existing root configs into .root container --------*/
2
2
 
3
3
  import path from 'node:path'
4
- import { readdir, unlink } from 'node:fs/promises'
4
+ import { readdir, readFile, unlink } from 'node:fs/promises'
5
5
  import { createLogger } from '../../utils/logger.js'
6
6
  import { fileExists, ensureDir, atomicWrite, copyFile, readJsonFile } from '../../utils/fsUtils.js'
7
7
  import { confirm } from '../../utils/prompt.js'
8
- import { patchPackageScripts, isPatchable, NEVER_MIGRATE } from '../../core/scriptPatcher.js'
8
+ import { patchPackageScripts, isPatchable, NEVER_MIGRATE, patchServerScript } from '../../core/scriptPatcher.js'
9
9
 
10
10
  // Files that are OS/editor/VCS noise — never migrate, never touch
11
11
  const SYSTEM_FILES = new Set([
@@ -92,6 +92,33 @@ export default {
92
92
  const dest = path.join(destDir, name)
93
93
 
94
94
  if (isCleanMode) {
95
+ // For PS1 server scripts: patch $root before writing to .root/assets/
96
+ if (name.endsWith('.ps1')) {
97
+ const raw = await readFile(src, 'utf8')
98
+ const patched = patchServerScript(raw)
99
+ if (patched) {
100
+ await atomicWrite(dest, patched)
101
+ await unlink(src)
102
+ logger.success(`Moved+patched .root/${destSubdir}/${name} (server root → project root)`)
103
+ continue
104
+ }
105
+ }
106
+ // For .cmd/.bat launcher scripts: fix relative path to .ps1 → use %~dp0
107
+ // so the ps1 is found relative to the cmd file's own location
108
+ if (name.endsWith('.cmd') || name.endsWith('.bat')) {
109
+ const raw = await readFile(src, 'utf8')
110
+ // Replace: -File .server.ps1 → -File "%~dp0.server.ps1"
111
+ const patched = raw.replace(
112
+ /(-File\s+)(?!")([^\s"]+\.ps1)/gi,
113
+ (_, flag, ps1) => `${flag}"%~dp0${ps1}"`
114
+ )
115
+ if (patched !== raw) {
116
+ await atomicWrite(dest, patched)
117
+ await unlink(src)
118
+ logger.success(`Moved+patched .root/${destSubdir}/${name} (ps1 path → %~dp0-relative)`)
119
+ continue
120
+ }
121
+ }
95
122
  // Binary-safe stream copy — works for .png, .js, .css, etc.
96
123
  await copyFile(src, dest)
97
124
  await unlink(src)
@@ -459,6 +459,74 @@ async function patchPackageScripts(projectRoot, configsDir, logger) {
459
459
  return changed
460
460
  }
461
461
 
462
+ /**
463
+ * Patches a PowerShell HTTP server script (.server.ps1 style) so it works
464
+ * correctly after being moved to .root/assets/.
465
+ *
466
+ * The script originally uses $PSScriptRoot as web root. After migration,
467
+ * $PSScriptRoot = .root/assets/ — so subdirectories left in the project root
468
+ * (viewer/, agreement/, etc.) are invisible to it.
469
+ *
470
+ * This patch:
471
+ * 1. Sets $root to the project root (2 dirs up from $PSScriptRoot)
472
+ * 2. Keeps $assetsRoot pointing to .root/assets/ for fallback
473
+ * 3. After path resolution, falls back to $assetsRoot when file not found in $root
474
+ * 4. Fixes the 404 page lookup to also check $assetsRoot
475
+ *
476
+ * Returns patched content, or null if the file doesn't look like a server script.
477
+ */
478
+ function patchServerScript(content) {
479
+ // Only patch scripts that use $PSScriptRoot as web root
480
+ if (!content.includes('$PSScriptRoot') || !content.includes('$root')) return null
481
+
482
+ let out = content
483
+
484
+ // 1. Redefine $root → project root (2 levels up), keep $assetsRoot for fallback
485
+ out = out.replace(
486
+ /\$root(\s*)=(\s*)\$PSScriptRoot\.TrimEnd\('\\\\?'\)\s*\+\s*'\\\\'?/,
487
+ `$assetsRoot$1=$2$PSScriptRoot.TrimEnd('\\') + '\\'
488
+ $root$1=$2(Get-Item "$PSScriptRoot\\..\\..").FullName.TrimEnd('\\') + '\\'`
489
+ )
490
+
491
+ // If the above didn't match (different whitespace/quoting), try simpler form
492
+ if (out === content) {
493
+ const marker = `$root = $PSScriptRoot.TrimEnd('\\') + '\\'`
494
+ if (content.includes(marker)) {
495
+ out = out.replace(
496
+ marker,
497
+ `$assetsRoot = $PSScriptRoot.TrimEnd('\\') + '\\'
498
+ $root = (Get-Item "$PSScriptRoot\\..\\..").FullName.TrimEnd('\\') + '\\'`
499
+ )
500
+ }
501
+ }
502
+
503
+ // 2. Add rootless fallback after $filePath = $resolved
504
+ const filePathLine = ' $filePath = $resolved'
505
+ if (out.includes(filePathLine)) {
506
+ out = out.replace(
507
+ filePathLine,
508
+ filePathLine + `
509
+ # rootless: fallback to .root/assets/ when file not found in project root
510
+ if ($relPath -ne '' -and -not (Test-Path $filePath)) {
511
+ $assetsCandidate = [System.IO.Path]::GetFullPath((Join-Path $assetsRoot $relPath))
512
+ if (Test-Path $assetsCandidate) { $filePath = $assetsCandidate }
513
+ }`
514
+ )
515
+ }
516
+
517
+ // 3. Fix 404 page lookup to also check $assetsRoot
518
+ const notFoundLine = ' $candidate = Join-Path $root $filename'
519
+ if (out.includes(notFoundLine)) {
520
+ out = out.replace(
521
+ notFoundLine,
522
+ notFoundLine + `
523
+ if (-not (Test-Path $candidate -PathType Leaf)) { $candidate = Join-Path $assetsRoot $filename }`
524
+ )
525
+ }
526
+
527
+ return out === content ? null : out
528
+ }
529
+
462
530
  /**
463
531
  * Add "prepare": "rootless prepare --yes" to project's package.json.
464
532
  */
@@ -492,5 +560,6 @@ export {
492
560
  NEVER_MIGRATE,
493
561
  ROOT_REQUIRED_PATTERNS,
494
562
  WEB_ASSET_PATTERNS,
563
+ patchServerScript,
495
564
  }
496
565