rootless-config 1.3.0 → 1.4.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.3.0",
3
+ "version": "1.4.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": {
@@ -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, getAllKnownFiles, NEVER_MIGRATE } from '../../core/scriptPatcher.js'
8
+ import { patchPackageScripts, isRootRequired, isPatchable, isWebAsset, NEVER_MIGRATE } from '../../core/scriptPatcher.js'
9
9
 
10
- // Pattern-based detection for file families not covered by the explicit known-files set
11
- const ENV_PATTERN = /^\.env/ // .env, .env.local, .env.production, etc.
12
- const TSCONFIG_PATTERN = /^tsconfig.*\.json$/ // tsconfig.custom.json etc.
13
- const DOCKERFILE_PATTERN = /^Dockerfile/ // Dockerfile.dev, Dockerfile.prod etc.
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 = ENV_PATTERN.test(name)
78
- const isAsset = /\.(ico|png|jpg|jpeg|svg|txt|xml|webmanifest|json)$/.test(name) &&
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)
@@ -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 (/^\.env/.test(filename)) return true // .env.local, .env.production, etc.
203
- if (/^Dockerfile/.test(filename)) return true // Dockerfile.dev, Dockerfile.prod etc.
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