rootless-config 1.7.4 → 1.8.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.
- package/package.json +3 -2
- package/src/cli/commands/migrate.js +12 -1
- package/src/core/scriptPatcher.js +69 -0
- package/src/index.js +2 -1
- package/src/serve/index.js +74 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rootless-config",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.1",
|
|
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": {
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
},
|
|
9
9
|
"main": "./src/index.js",
|
|
10
10
|
"exports": {
|
|
11
|
-
".": "./src/index.js"
|
|
11
|
+
".": "./src/index.js",
|
|
12
|
+
"./serve": "./src/serve/index.js"
|
|
12
13
|
},
|
|
13
14
|
"files": [
|
|
14
15
|
"bin",
|
|
@@ -5,7 +5,7 @@ import { readdir, 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,17 @@ 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 (await import('node:fs/promises')).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
|
+
}
|
|
95
106
|
// Binary-safe stream copy — works for .png, .js, .css, etc.
|
|
96
107
|
await copyFile(src, dest)
|
|
97
108
|
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
|
|
package/src/index.js
CHANGED
|
@@ -5,6 +5,7 @@ import { createWatcher } from './watch/watcher.js'
|
|
|
5
5
|
import { createDirtySet } from './watch/dirtySet.js'
|
|
6
6
|
import { runIncrementalRegeneration } from './watch/incrementalRegeneration.js'
|
|
7
7
|
import { resolveContainerPath } from './core/pathResolver.js'
|
|
8
|
+
import { resolveStaticFile, getServeDirs } from './serve/index.js'
|
|
8
9
|
|
|
9
10
|
async function prepare(options = {}) {
|
|
10
11
|
return preparePipeline(options)
|
|
@@ -36,4 +37,4 @@ async function watch(options = {}) {
|
|
|
36
37
|
}
|
|
37
38
|
}
|
|
38
39
|
|
|
39
|
-
export { prepare, watch, clean }
|
|
40
|
+
export { prepare, watch, clean, resolveStaticFile, getServeDirs }
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/*-------- Public serve API — for use in Tandem Sites and other dev servers --------*/
|
|
2
|
+
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
import { stat, readFile } from 'node:fs/promises'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Resolves a URL path to a real file on disk.
|
|
8
|
+
*
|
|
9
|
+
* Search order (first match wins):
|
|
10
|
+
* [1] projectRoot — real files/folders in project root
|
|
11
|
+
* [2] .root/assets/ — files migrated to rootless container
|
|
12
|
+
* [3] .root/env/ — .env files in rootless container
|
|
13
|
+
*
|
|
14
|
+
* Also tries index.html / index.htm for directory requests.
|
|
15
|
+
*
|
|
16
|
+
* @param {string} requestPath — e.g. '/viewer/app/code/app.js' or '/'
|
|
17
|
+
* @param {string} projectRoot — absolute path to the project root
|
|
18
|
+
* @returns {Promise<string|null>} absolute path to the file, or null if not found
|
|
19
|
+
*/
|
|
20
|
+
async function resolveStaticFile(requestPath, projectRoot) {
|
|
21
|
+
const containerPath = path.join(projectRoot, '.root')
|
|
22
|
+
const assetsDir = path.join(containerPath, 'assets')
|
|
23
|
+
const envDir = path.join(containerPath, 'env')
|
|
24
|
+
const searchDirs = [projectRoot, assetsDir, envDir]
|
|
25
|
+
|
|
26
|
+
const rel = decodeURIComponent(requestPath).replace(/^\//, '') || ''
|
|
27
|
+
|
|
28
|
+
for (const dir of searchDirs) {
|
|
29
|
+
// Block access to .root internals when searching from projectRoot
|
|
30
|
+
if (dir === projectRoot && rel.startsWith('.root')) continue
|
|
31
|
+
|
|
32
|
+
const candidates = rel === ''
|
|
33
|
+
? [
|
|
34
|
+
path.join(dir, 'index.html'),
|
|
35
|
+
path.join(dir, 'index.htm'),
|
|
36
|
+
]
|
|
37
|
+
: [
|
|
38
|
+
path.join(dir, rel),
|
|
39
|
+
path.join(dir, rel, 'index.html'),
|
|
40
|
+
path.join(dir, rel, 'index.htm'),
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
for (const candidate of candidates) {
|
|
44
|
+
try {
|
|
45
|
+
if ((await stat(candidate)).isFile()) return candidate
|
|
46
|
+
} catch {
|
|
47
|
+
// not found, try next
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return null
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Returns the ordered list of directories that rootless serves from.
|
|
57
|
+
* Useful for configuring a static server manually.
|
|
58
|
+
*
|
|
59
|
+
* @param {string} projectRoot
|
|
60
|
+
* @returns {{ projectRoot: string, assetsDir: string, envDir: string, searchDirs: string[] }}
|
|
61
|
+
*/
|
|
62
|
+
function getServeDirs(projectRoot) {
|
|
63
|
+
const containerPath = path.join(projectRoot, '.root')
|
|
64
|
+
const assetsDir = path.join(containerPath, 'assets')
|
|
65
|
+
const envDir = path.join(containerPath, 'env')
|
|
66
|
+
return {
|
|
67
|
+
projectRoot,
|
|
68
|
+
assetsDir,
|
|
69
|
+
envDir,
|
|
70
|
+
searchDirs: [projectRoot, assetsDir, envDir],
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export { resolveStaticFile, getServeDirs }
|