rootless-config 1.0.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.
Files changed (53) hide show
  1. package/README.md +173 -0
  2. package/bin/rootless.js +15 -0
  3. package/package.json +46 -0
  4. package/src/cli/commandRegistry.js +42 -0
  5. package/src/cli/commands/benchmark.js +43 -0
  6. package/src/cli/commands/clean.js +16 -0
  7. package/src/cli/commands/completion.js +59 -0
  8. package/src/cli/commands/debug.js +50 -0
  9. package/src/cli/commands/doctor.js +63 -0
  10. package/src/cli/commands/graph.js +34 -0
  11. package/src/cli/commands/init.js +53 -0
  12. package/src/cli/commands/inspect.js +48 -0
  13. package/src/cli/commands/migrate.js +63 -0
  14. package/src/cli/commands/prepare.js +18 -0
  15. package/src/cli/commands/stats.js +40 -0
  16. package/src/cli/commands/status.js +47 -0
  17. package/src/cli/commands/validate.js +34 -0
  18. package/src/cli/commands/watch.js +40 -0
  19. package/src/cli/index.js +53 -0
  20. package/src/core/cliWrapper.js +106 -0
  21. package/src/core/configGraph.js +50 -0
  22. package/src/core/configSchema.js +45 -0
  23. package/src/core/containerLoader.js +67 -0
  24. package/src/core/executionContext.js +27 -0
  25. package/src/core/fileHashCache.js +39 -0
  26. package/src/core/generatedManifest.js +35 -0
  27. package/src/core/generator.js +59 -0
  28. package/src/core/overrideStrategy.js +15 -0
  29. package/src/core/pathResolver.js +91 -0
  30. package/src/core/remoteLoader.js +64 -0
  31. package/src/core/rootSearch.js +36 -0
  32. package/src/core/versionCheck.js +42 -0
  33. package/src/experimental/capsule.js +11 -0
  34. package/src/experimental/index.js +20 -0
  35. package/src/experimental/virtualFS.js +13 -0
  36. package/src/index.js +39 -0
  37. package/src/plugins/builtins/assetPlugin.js +34 -0
  38. package/src/plugins/builtins/configPlugin.js +38 -0
  39. package/src/plugins/builtins/envPlugin.js +33 -0
  40. package/src/plugins/builtins/index.js +9 -0
  41. package/src/plugins/pluginLoader.js +35 -0
  42. package/src/plugins/pluginRegistry.js +36 -0
  43. package/src/types/configTypes.js +23 -0
  44. package/src/types/errors.js +43 -0
  45. package/src/types/pluginInterface.js +25 -0
  46. package/src/utils/debounce.js +22 -0
  47. package/src/utils/fsUtils.js +48 -0
  48. package/src/utils/hashUtils.js +24 -0
  49. package/src/utils/logger.js +34 -0
  50. package/src/utils/prompt.js +15 -0
  51. package/src/watch/dirtySet.js +30 -0
  52. package/src/watch/incrementalRegeneration.js +32 -0
  53. package/src/watch/watcher.js +38 -0
@@ -0,0 +1,22 @@
1
+ /*-------- Generic debounce utility --------*/
2
+
3
+ function debounce(fn, delay) {
4
+ let timer = null
5
+
6
+ function debounced(...args) {
7
+ clearTimeout(timer)
8
+ timer = setTimeout(() => {
9
+ timer = null
10
+ fn(...args)
11
+ }, delay)
12
+ }
13
+
14
+ debounced.cancel = () => {
15
+ clearTimeout(timer)
16
+ timer = null
17
+ }
18
+
19
+ return debounced
20
+ }
21
+
22
+ export { debounce }
@@ -0,0 +1,48 @@
1
+ /*-------- Atomic filesystem helpers — all operations are async --------*/
2
+
3
+ import { createWriteStream, createReadStream } from 'node:fs'
4
+ import { rename, mkdir, access, constants, writeFile, readFile, rm } from 'node:fs/promises'
5
+ import path from 'node:path'
6
+ import { pipeline } from 'node:stream/promises'
7
+
8
+ async function fileExists(filePath) {
9
+ try {
10
+ await access(filePath, constants.F_OK)
11
+ return true
12
+ } catch {
13
+ return false
14
+ }
15
+ }
16
+
17
+ async function ensureDir(dirPath) {
18
+ await mkdir(dirPath, { recursive: true })
19
+ }
20
+
21
+ async function atomicWrite(targetPath, content) {
22
+ const tmp = `${targetPath}.tmp`
23
+ await ensureDir(path.dirname(targetPath))
24
+ await writeFile(tmp, content, 'utf8')
25
+ await rename(tmp, targetPath)
26
+ }
27
+
28
+ async function readJsonFile(filePath) {
29
+ const raw = await readFile(filePath, 'utf8')
30
+ return JSON.parse(raw)
31
+ }
32
+
33
+ async function writeJsonFile(filePath, data) {
34
+ await atomicWrite(filePath, JSON.stringify(data, null, 2))
35
+ }
36
+
37
+ async function removeFile(filePath) {
38
+ await rm(filePath, { force: true })
39
+ }
40
+
41
+ async function copyFile(src, dest) {
42
+ await ensureDir(path.dirname(dest))
43
+ const readable = createReadStream(src)
44
+ const writable = createWriteStream(dest)
45
+ await pipeline(readable, writable)
46
+ }
47
+
48
+ export { fileExists, ensureDir, atomicWrite, readJsonFile, writeJsonFile, removeFile, copyFile }
@@ -0,0 +1,24 @@
1
+ /*-------- SHA-256 hashing utilities using native node:crypto --------*/
2
+
3
+ import { createHash } from 'node:crypto'
4
+ import { createReadStream } from 'node:fs'
5
+
6
+ function hashString(content) {
7
+ return createHash('sha256').update(content, 'utf8').digest('hex')
8
+ }
9
+
10
+ function hashObject(obj) {
11
+ return hashString(JSON.stringify(obj))
12
+ }
13
+
14
+ async function hashFile(filePath) {
15
+ return new Promise((resolve, reject) => {
16
+ const hash = createHash('sha256')
17
+ const stream = createReadStream(filePath)
18
+ stream.on('data', chunk => hash.update(chunk))
19
+ stream.on('end', () => resolve(hash.digest('hex')))
20
+ stream.on('error', reject)
21
+ })
22
+ }
23
+
24
+ export { hashString, hashObject, hashFile }
@@ -0,0 +1,34 @@
1
+ /*-------- Leveled logger — injected via execution context, never imported globally --------*/
2
+
3
+ const LEVELS = { debug: 0, info: 1, warn: 2, error: 3, silent: 4 }
4
+ const PREFIX = '[ROOTLESS]'
5
+
6
+ const COLORS = {
7
+ debug: '\x1b[36m',
8
+ info: '\x1b[32m',
9
+ warn: '\x1b[33m',
10
+ error: '\x1b[31m',
11
+ reset: '\x1b[0m',
12
+ }
13
+
14
+ function createLogger({ verbose = false, silent = false } = {}) {
15
+ const minLevel = silent ? LEVELS.silent : verbose ? LEVELS.debug : LEVELS.info
16
+
17
+ function log(level, message) {
18
+ if (LEVELS[level] < minLevel) return
19
+ const color = COLORS[level] ?? ''
20
+ const line = `${color}${PREFIX} ${message}${COLORS.reset}`
21
+ level === 'error' ? process.stderr.write(line + '\n') : process.stdout.write(line + '\n')
22
+ }
23
+
24
+ return {
25
+ debug: msg => log('debug', msg),
26
+ info: msg => log('info', msg),
27
+ warn: msg => log('warn', msg),
28
+ error: msg => log('error', msg),
29
+ success: msg => process.stdout.write(`\x1b[32m✔ ${msg}${COLORS.reset}\n`),
30
+ fail: msg => process.stderr.write(`\x1b[31m✖ ${msg}${COLORS.reset}\n`),
31
+ }
32
+ }
33
+
34
+ export { createLogger }
@@ -0,0 +1,15 @@
1
+ /*-------- Interactive terminal prompt via readline/promises --------*/
2
+
3
+ import { createInterface } from 'node:readline/promises'
4
+
5
+ async function confirm(message) {
6
+ const rl = createInterface({ input: process.stdin, output: process.stdout })
7
+ try {
8
+ const answer = await rl.question(`${message} (y/n): `)
9
+ return answer.trim().toLowerCase() === 'y'
10
+ } finally {
11
+ rl.close()
12
+ }
13
+ }
14
+
15
+ export { confirm }
@@ -0,0 +1,30 @@
1
+ /*-------- Dirty file Set with debounced flush — deduplicates watch events --------*/
2
+
3
+ import { debounce } from '../utils/debounce.js'
4
+
5
+ const DEBOUNCE_MS = 100
6
+
7
+ function createDirtySet(onFlush) {
8
+ const dirty = new Set()
9
+
10
+ const flush = debounce(async () => {
11
+ if (dirty.size === 0) return
12
+ const snapshot = new Set(dirty)
13
+ dirty.clear()
14
+ await onFlush(snapshot)
15
+ }, DEBOUNCE_MS)
16
+
17
+ function add(filePath) {
18
+ dirty.add(filePath)
19
+ flush()
20
+ }
21
+
22
+ function clear() {
23
+ dirty.clear()
24
+ flush.cancel()
25
+ }
26
+
27
+ return { add, clear, get size() { return dirty.size } }
28
+ }
29
+
30
+ export { createDirtySet }
@@ -0,0 +1,32 @@
1
+ /*-------- Incremental regeneration — only affected files are rebuilt --------*/
2
+
3
+ import path from 'node:path'
4
+ import { resolveProjectRoot } from '../core/pathResolver.js'
5
+ import { generateFile } from '../core/generator.js'
6
+
7
+ async function runIncrementalRegeneration(changedPaths, ctx) {
8
+ const projectRoot = await resolveProjectRoot()
9
+ const results = []
10
+
11
+ for (const sourcePath of changedPaths) {
12
+ const plugin = ctx.pluginRegistry.resolve(sourcePath)[0] ?? null
13
+ const target = path.join(projectRoot, path.basename(sourcePath))
14
+
15
+ ctx.logger.info(`Changed: ${path.basename(sourcePath)}`)
16
+
17
+ const result = await generateFile({
18
+ source: sourcePath,
19
+ target,
20
+ mode: 'proxy',
21
+ plugin,
22
+ options: ctx.options,
23
+ logger: ctx.logger,
24
+ })
25
+
26
+ results.push(result)
27
+ }
28
+
29
+ return results
30
+ }
31
+
32
+ export { runIncrementalRegeneration }
@@ -0,0 +1,38 @@
1
+ /*-------- Chokidar wrapper — normalizes FS events and manages lifecycle --------*/
2
+
3
+ import chokidar from 'chokidar'
4
+ import path from 'node:path'
5
+ import { EventEmitter } from 'node:events'
6
+
7
+ function createWatcher(watchPaths, options = {}) {
8
+ const emitter = new EventEmitter()
9
+ let watcher = null
10
+
11
+ function start() {
12
+ watcher = chokidar.watch(watchPaths, {
13
+ ignoreInitial: options.ignoreInitial ?? true,
14
+ persistent: true,
15
+ ignored: [/(^|[/\\])\..cache/, /\.tmp$/],
16
+ awaitWriteFinish: { stabilityThreshold: 50, pollInterval: 10 },
17
+ })
18
+
19
+ watcher.on('change', filePath => emitter.emit('change', path.normalize(filePath)))
20
+ watcher.on('add', filePath => emitter.emit('add', path.normalize(filePath)))
21
+ watcher.on('unlink', filePath => emitter.emit('unlink', path.normalize(filePath)))
22
+ watcher.on('error', err => emitter.emit('error', err))
23
+ }
24
+
25
+ async function stop() {
26
+ if (watcher) await watcher.close()
27
+ watcher = null
28
+ }
29
+
30
+ function on(event, cb) {
31
+ emitter.on(event, cb)
32
+ return { start, stop, on }
33
+ }
34
+
35
+ return { start, stop, on }
36
+ }
37
+
38
+ export { createWatcher }