@nitra/cursor 1.8.139 → 1.8.140
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
package/scripts/check-docker.mjs
CHANGED
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
* - backend: `mirror.gcr.io/library/alpine:*`, `scratch`, `mirror.gcr.io/library/debian:` з тегом, що
|
|
10
10
|
* містить `slim` (не повний `debian:bookworm`), за винятком PHP/Python — `mirror.gcr.io/library/php:*` або
|
|
11
11
|
* `mirror.gcr.io/library/python:*`
|
|
12
|
-
* - frontend: `mirror.gcr.io/
|
|
12
|
+
* - frontend: `mirror.gcr.io/nginxinc/nginx-unprivileged:*` (preferred) або `mirror.gcr.io/library/nginx:*`,
|
|
13
|
+
* `mirror.gcr.io/openresty/openresty:*`
|
|
13
14
|
*
|
|
14
15
|
* Якщо в Dockerfile є крок `bun install` і це не frontend-образ (фінальний stage — alpine),
|
|
15
16
|
* то очікується компіляція в один бінарник через `bun build --compile` у build stage, а у
|
|
@@ -18,8 +19,10 @@
|
|
|
18
19
|
* Мета — щоб у фінальному образі не було build tooling (Bun/Node та залежностей), а лише
|
|
19
20
|
* дозволений runtime (alpine, scratch, debian slim, за потреби php/python, nginx або openresty).
|
|
20
21
|
*
|
|
21
|
-
* Для `mirror.gcr.io/library/nginx` у
|
|
22
|
-
* мінімальні образи), не `latest` /
|
|
22
|
+
* Для nginx-образів (`mirror.gcr.io/nginxinc/nginx-unprivileged`, `mirror.gcr.io/library/nginx`) у
|
|
23
|
+
* будь-якому `FROM` очікується тег `alpine-slim` (docker.mdc: мінімальні образи), не `latest` /
|
|
24
|
+
* `alpine` / інші. `nginx-unprivileged` запускається від не-root користувача (uid=101) без явного
|
|
25
|
+
* `USER` у Dockerfile — перевірка non-root для нього пропускається.
|
|
23
26
|
*
|
|
24
27
|
* Знаходить Dockerfile, Dockerfile.*, Containerfile, Containerfile.*; пропускає node_modules, .git
|
|
25
28
|
* тощо. Спочатку hadolint з PATH, інакше docker run з образом hadolint/hadolint.
|
|
@@ -40,6 +43,7 @@ const BUN_WORD_RE = /\bbun\b/iu
|
|
|
40
43
|
const USER_LINE_RE = /^\s*USER\s+([^\s#]+)/iu
|
|
41
44
|
|
|
42
45
|
const NGINX_MIRROR_PREFIX = 'mirror.gcr.io/library/nginx'
|
|
46
|
+
const NGINX_UNPRIVILEGED_MIRROR_PREFIX = 'mirror.gcr.io/nginxinc/nginx-unprivileged'
|
|
43
47
|
|
|
44
48
|
/**
|
|
45
49
|
* @typedef {{
|
|
@@ -94,6 +98,7 @@ const RUNTIME_IMAGES = /** @type {const} */ ([
|
|
|
94
98
|
'mirror.gcr.io/library/php',
|
|
95
99
|
'mirror.gcr.io/library/python',
|
|
96
100
|
'mirror.gcr.io/library/nginx',
|
|
101
|
+
'mirror.gcr.io/nginxinc/nginx-unprivileged',
|
|
97
102
|
'mirror.gcr.io/openresty/openresty'
|
|
98
103
|
])
|
|
99
104
|
|
|
@@ -103,7 +108,7 @@ const DEBIAN_VIA_MIRROR_RE = /^mirror\.gcr\.io\/library\/debian:(.+)$/i
|
|
|
103
108
|
/**
|
|
104
109
|
* Чи ref фінального `FROM` відповідає дозволеним у docker.mdc (multistage / runtime).
|
|
105
110
|
* @param {string} lastLower ref без digest, lower case
|
|
106
|
-
* @returns {boolean}
|
|
111
|
+
* @returns {boolean} true, якщо образ дозволений як фінальний runtime
|
|
107
112
|
*/
|
|
108
113
|
function isAllowedFinalRuntimeImage(lastLower) {
|
|
109
114
|
if (lastLower === 'scratch' || lastLower.startsWith('scratch:')) {
|
|
@@ -189,7 +194,9 @@ export function getBunCompileHint(fileContent) {
|
|
|
189
194
|
const hasBunInstall = BUN_INSTALL_RE.test(fileContent)
|
|
190
195
|
const isFinalAlpine = lastLower.startsWith('mirror.gcr.io/library/alpine:')
|
|
191
196
|
const isFinalFrontend =
|
|
192
|
-
lastLower.startsWith('mirror.gcr.io/library/nginx:') ||
|
|
197
|
+
lastLower.startsWith('mirror.gcr.io/library/nginx:') ||
|
|
198
|
+
lastLower.startsWith(`${NGINX_UNPRIVILEGED_MIRROR_PREFIX}:`) ||
|
|
199
|
+
lastLower.startsWith('mirror.gcr.io/openresty/openresty:')
|
|
193
200
|
|
|
194
201
|
if (!hasBunInstall) return null
|
|
195
202
|
if (!isFinalAlpine) return null
|
|
@@ -209,24 +216,26 @@ export function getBunCompileHint(fileContent) {
|
|
|
209
216
|
}
|
|
210
217
|
|
|
211
218
|
/**
|
|
212
|
-
* Перевіряє, що для `mirror.gcr.io/
|
|
219
|
+
* Перевіряє, що для nginx-образів (`mirror.gcr.io/nginxinc/nginx-unprivileged` або
|
|
220
|
+
* `mirror.gcr.io/library/nginx`) у `FROM` вказано тег `alpine-slim` (docker.mdc).
|
|
213
221
|
*
|
|
214
222
|
* @param {string} fileContent вміст Dockerfile/Containerfile
|
|
215
223
|
* @returns {string | null} повідомлення помилки або null
|
|
216
224
|
*/
|
|
217
225
|
export function getNginxAlpineSlimTagHint(fileContent) {
|
|
226
|
+
const prefixes = [NGINX_UNPRIVILEGED_MIRROR_PREFIX, NGINX_MIRROR_PREFIX]
|
|
218
227
|
for (const { line, image } of parseFromStages(fileContent)) {
|
|
219
228
|
const noDigest = (image.split('@')[0] || '').trim()
|
|
220
229
|
const d = noDigest.toLowerCase()
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
+
const prefix = prefixes.find(p => d.startsWith(`${p}:`) || d === p)
|
|
231
|
+
if (prefix) {
|
|
232
|
+
if (d === prefix) {
|
|
233
|
+
return `рядок ${line}: \`FROM ${prefix}\` має явний тег \`alpine-slim\` (docker.mdc: мінімальні образи), зараз без тега (типово latest)`
|
|
234
|
+
}
|
|
235
|
+
const tag = noDigest.slice(prefix.length + 1)
|
|
236
|
+
if (tag.toLowerCase() !== 'alpine-slim') {
|
|
237
|
+
return `рядок ${line}: для nginx потрібен тег \`alpine-slim\` (docker.mdc: мінімальні образи), зараз: \`${tag}\``
|
|
238
|
+
}
|
|
230
239
|
}
|
|
231
240
|
}
|
|
232
241
|
return null
|
|
@@ -247,6 +256,8 @@ export function getNonRootRuntimeHint(fileContent) {
|
|
|
247
256
|
if (stages.length === 0) return null
|
|
248
257
|
|
|
249
258
|
const last = stages.at(-1)
|
|
259
|
+
const lastImage = (last?.from.image || '').split('@')[0] || ''
|
|
260
|
+
const lastLower = lastImage.toLowerCase()
|
|
250
261
|
const lastStageContent = last?.stageContent || ''
|
|
251
262
|
|
|
252
263
|
/** @type {string | null} */
|
|
@@ -259,6 +270,8 @@ export function getNonRootRuntimeHint(fileContent) {
|
|
|
259
270
|
}
|
|
260
271
|
|
|
261
272
|
if (!lastUserToken) {
|
|
273
|
+
// nginx-unprivileged вже запускається від uid=101 (nginx) без явного USER у Dockerfile
|
|
274
|
+
if (lastLower.startsWith(`${NGINX_UNPRIVILEGED_MIRROR_PREFIX}:`)) return null
|
|
262
275
|
return 'у фінальному stage має бути `USER <non-root>` (наприклад `USER app`) — принцип non-root (docker.mdc: не превілейований образ)'
|
|
263
276
|
}
|
|
264
277
|
|
|
@@ -110,14 +110,15 @@ export function normalizeHubRepoPath(imageToken) {
|
|
|
110
110
|
}
|
|
111
111
|
|
|
112
112
|
const HUB_REPOS_REQUIRING_MIRROR = /** @type {const} */ (
|
|
113
|
-
new Set(['oven/bun', 'library/alpine', 'library/nginx', 'library/node'])
|
|
113
|
+
new Set(['oven/bun', 'library/alpine', 'library/nginx', 'library/node', 'nginxinc/nginx-unprivileged'])
|
|
114
114
|
)
|
|
115
115
|
|
|
116
116
|
const EXPECTED_MIRROR = /** @type {const} */ ({
|
|
117
117
|
'oven/bun': 'mirror.gcr.io/oven/bun',
|
|
118
118
|
'library/alpine': 'mirror.gcr.io/library/alpine',
|
|
119
119
|
'library/nginx': 'mirror.gcr.io/library/nginx',
|
|
120
|
-
'library/node': 'mirror.gcr.io/library/node'
|
|
120
|
+
'library/node': 'mirror.gcr.io/library/node',
|
|
121
|
+
'nginxinc/nginx-unprivileged': 'mirror.gcr.io/nginxinc/nginx-unprivileged'
|
|
121
122
|
})
|
|
122
123
|
|
|
123
124
|
/**
|