rootless-config 1.3.0 → 1.4.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 +1 -1
- package/src/cli/commands/migrate.js +8 -18
- package/src/cli/commands/setup.js +54 -0
- package/src/cli/index.js +1 -1
- package/src/core/scriptPatcher.js +156 -5
package/package.json
CHANGED
|
@@ -5,28 +5,20 @@ import { readdir, unlink, readFile } from 'node:fs/promises'
|
|
|
5
5
|
import { createLogger } from '../../utils/logger.js'
|
|
6
6
|
import { fileExists, ensureDir, atomicWrite, readJsonFile } from '../../utils/fsUtils.js'
|
|
7
7
|
import { confirm } from '../../utils/prompt.js'
|
|
8
|
-
import { patchPackageScripts,
|
|
8
|
+
import { patchPackageScripts, isRootRequired, isPatchable, isWebAsset, NEVER_MIGRATE } from '../../core/scriptPatcher.js'
|
|
9
9
|
|
|
10
|
-
//
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const DOCKER_COMPOSE_PATTERN = /^docker-compose/ // docker-compose.override.yml etc.
|
|
10
|
+
// A file is migratable when rootless knows what to do with it:
|
|
11
|
+
// - isRootRequired → must be in root (will be copied there by prepare)
|
|
12
|
+
// - isPatchable → can be redirected via --config flag in package.json scripts
|
|
13
|
+
// Anything that's NEVER_MIGRATE (lock files, package.json) is excluded.
|
|
15
14
|
|
|
16
15
|
async function findMigratableFiles(projectRoot) {
|
|
17
|
-
const knownFiles = getAllKnownFiles()
|
|
18
16
|
const entries = await readdir(projectRoot, { withFileTypes: true })
|
|
19
17
|
return entries
|
|
20
18
|
.filter(e => {
|
|
21
19
|
if (!e.isFile()) return false
|
|
22
20
|
if (NEVER_MIGRATE.has(e.name)) return false
|
|
23
|
-
return (
|
|
24
|
-
knownFiles.has(e.name) ||
|
|
25
|
-
ENV_PATTERN.test(e.name) ||
|
|
26
|
-
TSCONFIG_PATTERN.test(e.name) ||
|
|
27
|
-
DOCKERFILE_PATTERN.test(e.name) ||
|
|
28
|
-
DOCKER_COMPOSE_PATTERN.test(e.name)
|
|
29
|
-
)
|
|
21
|
+
return isRootRequired(e.name) || isPatchable(e.name)
|
|
30
22
|
})
|
|
31
23
|
.map(e => e.name)
|
|
32
24
|
.sort()
|
|
@@ -74,10 +66,8 @@ export default {
|
|
|
74
66
|
|
|
75
67
|
for (const name of candidates) {
|
|
76
68
|
const src = path.join(projectRoot, name)
|
|
77
|
-
const isEnv =
|
|
78
|
-
const
|
|
79
|
-
['favicon.ico', 'robots.txt', 'sitemap.xml', 'manifest.json', 'site.webmanifest'].includes(name)
|
|
80
|
-
const destSubdir = isEnv ? 'env' : (isAsset ? 'assets' : 'configs')
|
|
69
|
+
const isEnv = /^\.env/.test(name)
|
|
70
|
+
const destSubdir = isEnv ? 'env' : (isWebAsset(name) ? 'assets' : 'configs')
|
|
81
71
|
const destDir = path.join(containerPath, destSubdir)
|
|
82
72
|
|
|
83
73
|
await ensureDir(destDir)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/*-------- rootless setup — one-command full root cleanup: init + migrate + prepare --------*/
|
|
2
|
+
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
import { createLogger } from '../../utils/logger.js'
|
|
5
|
+
import initCmd from './init.js'
|
|
6
|
+
import migrateCmd from './migrate.js'
|
|
7
|
+
import prepareCmd from './prepare.js'
|
|
8
|
+
|
|
9
|
+
export default {
|
|
10
|
+
name: 'setup',
|
|
11
|
+
description: 'Full automated root cleanup: init .root, migrate all configs, prepare (copy + patch scripts)',
|
|
12
|
+
|
|
13
|
+
async handler(args) {
|
|
14
|
+
const logger = createLogger({ verbose: args.verbose ?? false })
|
|
15
|
+
const projectRoot = args.cwd ? path.resolve(args.cwd) : process.cwd()
|
|
16
|
+
|
|
17
|
+
logger.info('─────────────────────────────────────────')
|
|
18
|
+
logger.info(' rootless setup — cleaning your project root')
|
|
19
|
+
logger.info('─────────────────────────────────────────')
|
|
20
|
+
logger.info('')
|
|
21
|
+
|
|
22
|
+
// ── Step 1: init ──────────────────────────────────────────────────────────
|
|
23
|
+
logger.info('[1/3] Initializing .root container…')
|
|
24
|
+
await initCmd.handler({ ...args, yes: true, cwd: projectRoot })
|
|
25
|
+
logger.info('')
|
|
26
|
+
|
|
27
|
+
// ── Step 2: migrate ───────────────────────────────────────────────────────
|
|
28
|
+
logger.info('[2/3] Migrating config files from root…')
|
|
29
|
+
await migrateCmd.handler({ ...args, yes: true, cwd: projectRoot })
|
|
30
|
+
logger.info('')
|
|
31
|
+
|
|
32
|
+
// ── Step 3: prepare ───────────────────────────────────────────────────────
|
|
33
|
+
logger.info('[3/3] Preparing root (copying mandatory files + patching scripts)…')
|
|
34
|
+
await prepareCmd.handler({ ...args, yes: true })
|
|
35
|
+
logger.info('')
|
|
36
|
+
|
|
37
|
+
logger.info('─────────────────────────────────────────')
|
|
38
|
+
logger.success('Setup complete! Your project root is clean.')
|
|
39
|
+
logger.info('')
|
|
40
|
+
logger.info('What happened:')
|
|
41
|
+
logger.info(' • .root/ container created')
|
|
42
|
+
logger.info(' • Config files moved to .root/configs/')
|
|
43
|
+
logger.info(' • .env* files moved to .root/env/')
|
|
44
|
+
logger.info(' • Web assets moved to .root/assets/')
|
|
45
|
+
logger.info(' • package.json scripts patched with --config flags')
|
|
46
|
+
logger.info(' • Mandatory files copied back to root (prepare)')
|
|
47
|
+
logger.info('')
|
|
48
|
+
logger.info('Next steps:')
|
|
49
|
+
logger.info(' rootless status — see what\'s managed')
|
|
50
|
+
logger.info(' rootless doctor — verify everything is wired correctly')
|
|
51
|
+
logger.info(' rootless watch — auto-sync on .root/ changes')
|
|
52
|
+
logger.info('─────────────────────────────────────────')
|
|
53
|
+
},
|
|
54
|
+
}
|
package/src/cli/index.js
CHANGED
|
@@ -20,7 +20,7 @@ async function run(argv) {
|
|
|
20
20
|
for (const cmd of registry.list()) {
|
|
21
21
|
const sub = program.command(cmd.name).description(cmd.description)
|
|
22
22
|
|
|
23
|
-
if (['prepare', 'watch', 'migrate'].includes(cmd.name)) {
|
|
23
|
+
if (['prepare', 'watch', 'migrate', 'setup'].includes(cmd.name)) {
|
|
24
24
|
sub.option('--yes', 'Auto-approve all file override prompts')
|
|
25
25
|
sub.option('--no-yes', 'Auto-decline all file override prompts')
|
|
26
26
|
}
|
|
@@ -146,8 +146,149 @@ const ROOT_REQUIRED_FILES = new Set([
|
|
|
146
146
|
'renovate.json', '.renovaterc', '.renovaterc.json',
|
|
147
147
|
// Misc
|
|
148
148
|
'.htaccess', 'Procfile',
|
|
149
|
+
// GitHub Pages
|
|
150
|
+
'.nojekyll', 'CNAME', 'CXNAME',
|
|
151
|
+
// Hosting platform
|
|
152
|
+
'_redirects', '_headers', 'vercel.json', 'netlify.toml', 'wrangler.toml', 'fly.toml',
|
|
153
|
+
// Status / health
|
|
154
|
+
'status.json', 'health.json',
|
|
155
|
+
// NGINX / server
|
|
156
|
+
'nginx.conf', 'nginx.config',
|
|
157
|
+
// Service workers
|
|
158
|
+
'sw.js', 'service-worker.js',
|
|
159
|
+
// Web entry points
|
|
160
|
+
'index.html', 'index.htm', 'index.php',
|
|
149
161
|
])
|
|
150
162
|
|
|
163
|
+
// ─── Pattern-based root-required detection ───────────────────────────────────
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Regex patterns for files that MUST be physically in the project root,
|
|
167
|
+
* matched against the basename only.
|
|
168
|
+
* Used for file families whose exact names can vary
|
|
169
|
+
* (e.g. icon-192.png, 404.htm, logo-dark.svg, server.ps1 …).
|
|
170
|
+
*/
|
|
171
|
+
const ROOT_REQUIRED_PATTERNS = [
|
|
172
|
+
// Environment files
|
|
173
|
+
/^\.env/,
|
|
174
|
+
// Docker variants
|
|
175
|
+
/^Dockerfile(\..+)?$/,
|
|
176
|
+
/^docker-compose.*\.(yml|yaml)$/,
|
|
177
|
+
// tsconfig variants
|
|
178
|
+
/^tsconfig.*\.json$/,
|
|
179
|
+
// GitHub Pages & hosting markers
|
|
180
|
+
/^\.nojekyll$/,
|
|
181
|
+
/^CNAME$/i,
|
|
182
|
+
/^CXNAME$/i,
|
|
183
|
+
// Error pages — 404.html, 404.htm, 404.md, 500.html …
|
|
184
|
+
/^[45]\d{2}\.(html?|php|md|txt)$/,
|
|
185
|
+
// Web entry points
|
|
186
|
+
/^index\.(html?|php)$/,
|
|
187
|
+
// index-style assets — index-ui.css, index.app.js, index.min.js …
|
|
188
|
+
/^index[-.].+\.(css|js|mjs)$/,
|
|
189
|
+
// Service workers
|
|
190
|
+
/^sw\.js$/,
|
|
191
|
+
/^service-worker.*\.js$/,
|
|
192
|
+
/^workbox-.*\.js$/,
|
|
193
|
+
// Web manifests
|
|
194
|
+
/^manifest.*\.json$/,
|
|
195
|
+
/^site\.webmanifest$/,
|
|
196
|
+
// Favicon & icons
|
|
197
|
+
/^favicon(\..+)?$/,
|
|
198
|
+
/^apple-touch-icon.*\.(png|jpg)$/,
|
|
199
|
+
/^icon.*\.(png|ico|svg|jpg|jpeg)$/,
|
|
200
|
+
/^android-chrome-.*\.(png|jpg)$/,
|
|
201
|
+
/^mstile-.*\.png$/,
|
|
202
|
+
/^browserconfig\.xml$/,
|
|
203
|
+
// Social & OG images
|
|
204
|
+
/^og[-_]image.*\.(png|jpg|jpeg|webp)$/,
|
|
205
|
+
/^twitter[-_]image.*\.(png|jpg|jpeg|webp)$/,
|
|
206
|
+
/^social[-_]image.*\.(png|jpg|jpeg|webp)$/,
|
|
207
|
+
/^opengraph.*\.(png|jpg|jpeg|webp)$/,
|
|
208
|
+
// Logo & branding
|
|
209
|
+
/^logo.*\.(png|svg|jpg|jpeg|webp)$/,
|
|
210
|
+
// Screenshots & splash screens
|
|
211
|
+
/^splash.*\.(png|jpg|jpeg)$/,
|
|
212
|
+
/^screenshot.*\.(png|jpg|jpeg)$/,
|
|
213
|
+
// Sitemaps & SEO
|
|
214
|
+
/^sitemap.*\.(xml|txt)$/,
|
|
215
|
+
// NGINX & server configs
|
|
216
|
+
/^nginx.*\.(conf|config)$/,
|
|
217
|
+
/\.nginx$/,
|
|
218
|
+
/^\.htaccess$/,
|
|
219
|
+
/^Procfile(\..+)?$/,
|
|
220
|
+
// Shell / batch scripts in root (server.ps1, server.run.cmd, start.sh …)
|
|
221
|
+
/\.ps1$/,
|
|
222
|
+
/\.sh$/,
|
|
223
|
+
/\.(bat|cmd)$/,
|
|
224
|
+
// Version manager dotfiles
|
|
225
|
+
/^\.nvmrc$/,
|
|
226
|
+
/^\.node-version$/,
|
|
227
|
+
// Git
|
|
228
|
+
/^\.gitignore$/,
|
|
229
|
+
/^\.gitattributes$/,
|
|
230
|
+
/^\.gitmodules$/,
|
|
231
|
+
/^CODEOWNERS$/,
|
|
232
|
+
// Docker misc
|
|
233
|
+
/^\.dockerignore$/,
|
|
234
|
+
// Documentation (any extension, any case)
|
|
235
|
+
/^README(\..+)?$/i,
|
|
236
|
+
/^CHANGELOG(\..+)?$/i,
|
|
237
|
+
/^OVERVIEW(\..+)?$/i,
|
|
238
|
+
/^LICENSE(\..+)?$/i,
|
|
239
|
+
/^CONTRIBUTING(\..+)?$/i,
|
|
240
|
+
/^CODE_OF_CONDUCT(\..+)?$/i,
|
|
241
|
+
/^SECURITY(\..+)?$/i,
|
|
242
|
+
/^SUPPORT(\..+)?$/i,
|
|
243
|
+
/^COPYING(\..+)?$/i,
|
|
244
|
+
/^AUTHORS(\..+)?$/i,
|
|
245
|
+
/^NOTICE(\..+)?$/i,
|
|
246
|
+
// Hosting platform
|
|
247
|
+
/^_redirects$/,
|
|
248
|
+
/^_headers$/,
|
|
249
|
+
/^vercel\.json$/,
|
|
250
|
+
/^netlify\.toml$/,
|
|
251
|
+
/^wrangler\.toml$/,
|
|
252
|
+
/^fly\.toml$/,
|
|
253
|
+
// Status / health check files
|
|
254
|
+
/^status\.json$/,
|
|
255
|
+
/^health\.json$/,
|
|
256
|
+
]
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Patterns for web assets served directly from the root by a web server.
|
|
260
|
+
* Files matching these go into .root/assets/ so they get copied to root on `prepare`.
|
|
261
|
+
*/
|
|
262
|
+
const WEB_ASSET_PATTERNS = [
|
|
263
|
+
/^favicon(\..+)?$/,
|
|
264
|
+
/^apple-touch-icon.*\.(png|jpg)$/,
|
|
265
|
+
/^icon.*\.(png|ico|svg|jpg|jpeg)$/,
|
|
266
|
+
/^android-chrome-.*\.(png|jpg)$/,
|
|
267
|
+
/^mstile-.*\.png$/,
|
|
268
|
+
/^browserconfig\.xml$/,
|
|
269
|
+
/^og[-_]image.*\.(png|jpg|jpeg|webp)$/,
|
|
270
|
+
/^twitter[-_]image.*\.(png|jpg|jpeg|webp)$/,
|
|
271
|
+
/^social[-_]image.*\.(png|jpg|jpeg|webp)$/,
|
|
272
|
+
/^opengraph.*\.(png|jpg|jpeg|webp)$/,
|
|
273
|
+
/^logo.*\.(png|svg|jpg|jpeg|webp)$/,
|
|
274
|
+
/^splash.*\.(png|jpg|jpeg)$/,
|
|
275
|
+
/^screenshot.*\.(png|jpg|jpeg)$/,
|
|
276
|
+
/^[45]\d{2}\.(html?|php|md|txt)$/,
|
|
277
|
+
/^index\.(html?|php)$/,
|
|
278
|
+
/^index[-.].+\.(css|js|mjs)$/,
|
|
279
|
+
/^sw\.js$/,
|
|
280
|
+
/^service-worker.*\.js$/,
|
|
281
|
+
/^workbox-.*\.js$/,
|
|
282
|
+
/^manifest.*\.json$/,
|
|
283
|
+
/^site\.webmanifest$/,
|
|
284
|
+
/^sitemap.*\.(xml|txt)$/,
|
|
285
|
+
/^robots\.txt$/,
|
|
286
|
+
/^status\.json$/,
|
|
287
|
+
/^health\.json$/,
|
|
288
|
+
/^_redirects$/,
|
|
289
|
+
/^_headers$/,
|
|
290
|
+
]
|
|
291
|
+
|
|
151
292
|
/**
|
|
152
293
|
* Lock files and core manifests that should NEVER be migrated.
|
|
153
294
|
* They must always live in the project root and are managed by package managers.
|
|
@@ -195,18 +336,25 @@ function isPatchable(filename) {
|
|
|
195
336
|
|
|
196
337
|
/**
|
|
197
338
|
* Returns true if the file must be physically present in the project root.
|
|
339
|
+
* Checks exact names first, then the pattern registry.
|
|
198
340
|
*/
|
|
199
341
|
function isRootRequired(filename) {
|
|
200
342
|
if (NEVER_MIGRATE.has(filename)) return false
|
|
201
343
|
if (ROOT_REQUIRED_FILES.has(filename)) return true
|
|
202
|
-
if (
|
|
203
|
-
if (
|
|
204
|
-
if (/^tsconfig.*\.json$/.test(filename)) return true // tsconfig.custom.json variants
|
|
205
|
-
if (/^docker-compose/.test(filename)) return true // docker-compose.override.yml etc.
|
|
206
|
-
if (!isPatchable(filename)) return true // unknown file → copy to root to be safe
|
|
344
|
+
if (ROOT_REQUIRED_PATTERNS.some(p => p.test(filename))) return true
|
|
345
|
+
if (!isPatchable(filename)) return true // unknown file → safe default: copy to root
|
|
207
346
|
return false
|
|
208
347
|
}
|
|
209
348
|
|
|
349
|
+
/**
|
|
350
|
+
* Returns true if the file is a web asset that should be served
|
|
351
|
+
* directly from the project root by a web server (images, HTML, SW, manifest …).
|
|
352
|
+
* These land in .root/assets/ and are copied to root on `prepare`.
|
|
353
|
+
*/
|
|
354
|
+
function isWebAsset(filename) {
|
|
355
|
+
return WEB_ASSET_PATTERNS.some(p => p.test(filename))
|
|
356
|
+
}
|
|
357
|
+
|
|
210
358
|
// ─── Config map building ─────────────────────────────────────────────────────
|
|
211
359
|
|
|
212
360
|
/**
|
|
@@ -328,7 +476,10 @@ export {
|
|
|
328
476
|
patchScriptString,
|
|
329
477
|
isPatchable,
|
|
330
478
|
isRootRequired,
|
|
479
|
+
isWebAsset,
|
|
331
480
|
getAllKnownFiles,
|
|
332
481
|
NEVER_MIGRATE,
|
|
482
|
+
ROOT_REQUIRED_PATTERNS,
|
|
483
|
+
WEB_ASSET_PATTERNS,
|
|
333
484
|
}
|
|
334
485
|
|