rootless-config 1.7.3 → 1.8.0

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.7.3",
3
+ "version": "1.8.0",
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",
@@ -142,10 +142,15 @@ export default {
142
142
  return
143
143
  }
144
144
 
145
- // Если stdout пайпится (Invoke-Expression) — выводим HOOK_BODY
146
- // Если терминал показываем подсказку
145
+ // Если stdout пайпится (Invoke-Expression) — выводим одну строку PS,
146
+ // которая декодирует base64 и исполняет весь блок целиком.
147
+ // Это обходит проблему PS-пайпа (строки идут по одной → незакрытые блоки).
148
+ // Правильный вызов: rootless activate | Invoke-Expression
147
149
  if (!process.stdout.isTTY) {
148
- process.stdout.write(HOOK_BODY + '\n')
150
+ const encoded = Buffer.from(HOOK_BODY, 'utf8').toString('base64')
151
+ process.stdout.write(
152
+ `[System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String('${encoded}')) | Invoke-Expression\n`
153
+ )
149
154
  } else {
150
155
  logger.info('')
151
156
  logger.info('To activate the CURRENT session, run:')
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 }