@uknows/setup 0.1.0 → 0.2.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 (48) hide show
  1. package/README.md +99 -32
  2. package/lib/automate.mjs +1299 -0
  3. package/lib/check.mjs +149 -33
  4. package/lib/ci.mjs +21 -0
  5. package/lib/cli.mjs +114 -29
  6. package/lib/cloudid.mjs +456 -0
  7. package/lib/code.mjs +147 -0
  8. package/lib/context.mjs +126 -0
  9. package/lib/contract-docs.mjs +1780 -198
  10. package/lib/contract.mjs +93 -10
  11. package/lib/detect.mjs +96 -44
  12. package/lib/dockerfile.mjs +83 -0
  13. package/lib/endpoint.mjs +36 -5
  14. package/lib/engine.mjs +1272 -126
  15. package/lib/existing.mjs +448 -54
  16. package/lib/git.mjs +53 -0
  17. package/lib/hooks/UknowsOpenTelemetry.cs +118 -0
  18. package/lib/hooks/pins.mjs +28 -0
  19. package/lib/hooks/uknows-otel.cjs +82 -0
  20. package/lib/hooks/uknows_otel.py +179 -0
  21. package/lib/hosts.mjs +521 -16
  22. package/lib/locks.mjs +353 -0
  23. package/lib/manifest.mjs +27 -7
  24. package/lib/members.mjs +269 -0
  25. package/lib/plan.mjs +130 -0
  26. package/lib/settings.mjs +216 -28
  27. package/lib/signin.mjs +259 -0
  28. package/lib/stacks/azure.mjs +345 -0
  29. package/lib/stacks/channel.mjs +125 -0
  30. package/lib/stacks/dotnet.mjs +336 -29
  31. package/lib/stacks/functions-hooks.mjs +228 -0
  32. package/lib/stacks/functions.mjs +148 -0
  33. package/lib/stacks/guided.mjs +153 -30
  34. package/lib/stacks/index.mjs +10 -2
  35. package/lib/stacks/java.mjs +14 -13
  36. package/lib/stacks/node.mjs +142 -19
  37. package/lib/stacks/otel.mjs +56 -4
  38. package/lib/stacks/python-bootstrap.mjs +27 -7
  39. package/lib/stacks/python-lock.mjs +105 -0
  40. package/lib/stacks/python-sets.mjs +143 -0
  41. package/lib/stacks/python.mjs +261 -63
  42. package/lib/toml.mjs +64 -0
  43. package/lib/types.mjs +52 -5
  44. package/lib/ui.mjs +139 -20
  45. package/lib/updates.mjs +398 -0
  46. package/lib/versions.mjs +115 -0
  47. package/lib/workspace.mjs +4 -1
  48. package/package.json +3 -8
@@ -0,0 +1,126 @@
1
+ /*
2
+ * The build context of a Dockerfile, read from the repo: which folder `docker build` sends, where the app is in
3
+ * the image (the WORKDIR of the final stage), and whether .dockerignore leaves a file out. Static reading only.
4
+ * A wrong guess makes the build fail at COPY, so every function returns null when it is not sure.
5
+ */
6
+ import { posix } from 'node:path'
7
+ import { copySources } from './detect.mjs'
8
+
9
+ const GLOB = /[*?[]/
10
+ const dirOf = (path) => posix.dirname(path)
11
+ const at = (dir, name) => (dir === '.' ? name : `${dir}/${name}`)
12
+ const unique = (list) => [...new Set(list)]
13
+
14
+ /**
15
+ * The folder that `docker build` sends for this Dockerfile: the Dockerfile folder, the service folder or the repo
16
+ * root. The COPY and ADD sources decide: a folder fits when every source that exists in any of these folders exists
17
+ * in it. Sources that exist nowhere (built before docker build runs) do not count. null unless exactly 1 folder fits.
18
+ * @param {Pick<import('./workspace.mjs').RepoView, 'exists'|'list'>} view
19
+ * @param {string} dockerfile Repo path of the Dockerfile.
20
+ * @param {import('./dockerfile.mjs').DockerfileInfo} info
21
+ * @param {string} serviceDir
22
+ * @returns {string|null}
23
+ */
24
+ export function buildContext(view, dockerfile, info, serviceDir) {
25
+ const candidates = unique([dirOf(dockerfile), serviceDir, '.'])
26
+ const sources = copySources(info).map((src) => src.replace(/^\/+/, '')).filter(Boolean)
27
+ const found = (dir, src) => present(view, dir, src)
28
+ const counted = sources.filter((src) => candidates.some((dir) => found(dir, src)))
29
+ const fits = candidates.filter((dir) => counted.every((src) => found(dir, src)))
30
+ return fits.length === 1 ? fits[0] : null
31
+ }
32
+
33
+ /** True when src exists in dir. A pattern (package*.json) counts when a name in its folder matches it. */
34
+ function present(view, dir, src) {
35
+ const path = posix.normalize(at(dir, src))
36
+ if (path.startsWith('../') || path === '..') return false
37
+ if (!GLOB.test(path)) return view.exists(path)
38
+ const folder = dirOf(path)
39
+ if (GLOB.test(folder)) return false
40
+ const re = globRegex(posix.basename(path))
41
+ return view.list(folder).some((name) => re.test(name))
42
+ }
43
+
44
+ /**
45
+ * The WORKDIR of the final stage, with the stages it builds on (FROM <stage>). null when it is not set in the
46
+ * Dockerfile, uses a variable, or is relative to a WORKDIR of the base image.
47
+ * @param {import('./dockerfile.mjs').DockerfileInfo} info
48
+ * @returns {string|null}
49
+ */
50
+ export function finalWorkdir(info) {
51
+ const chain = []
52
+ for (let i = info.stages.length - 1, hops = 0; i >= 0 && hops < 20; hops++) {
53
+ chain.unshift(info.stages[i])
54
+ const base = info.stages[i].image.toLowerCase()
55
+ i = info.stages.findLastIndex((s, j) => j < i && s.name?.toLowerCase() === base)
56
+ }
57
+ let dir = null
58
+ for (const x of chain.flatMap((stage) => stage.instructions).filter((y) => y.keyword === 'WORKDIR')) {
59
+ const value = x.args.trim().replace(/^(["'])(.*)\1$/, '$2')
60
+ if (!value || /[$\s]/.test(value)) return null
61
+ dir = value.startsWith('/') ? value : dir === null ? null : posix.join(dir, value)
62
+ }
63
+ return dir === null ? null : posix.normalize(dir).replace(/(?<=.)\/+$/, '')
64
+ }
65
+
66
+ /**
67
+ * The ignore file that leaves path (relative to the context) out of the build context, or null when none does.
68
+ * BuildKit reads <Dockerfile>.dockerignore next to the Dockerfile first. The legacy builder reads only
69
+ * .dockerignore in the context. Both count, so the file reaches the image with either builder.
70
+ * @param {Pick<import('./workspace.mjs').RepoView, 'read'>} view
71
+ * @param {string} context
72
+ * @param {string} dockerfile
73
+ * @param {string} path
74
+ * @returns {string|null}
75
+ */
76
+ export function ignoredBy(view, context, dockerfile, path) {
77
+ for (const file of unique([`${dockerfile}.dockerignore`, at(context, '.dockerignore')])) {
78
+ let text = null
79
+ try { text = view.read(file) } catch { /* not readable */ }
80
+ if (text !== null && dockerIgnored(path, text)) return file
81
+ }
82
+ return null
83
+ }
84
+
85
+ /**
86
+ * True when the .dockerignore text leaves path out, as Docker reads it: 1 pattern per line, # comments, ! for an
87
+ * exception, ** for any number of folders. A pattern that matches a folder leaves out all files in it. The last
88
+ * pattern that matches wins.
89
+ * @param {string} path Relative to the context, with '/'.
90
+ * @param {string} text
91
+ */
92
+ export function dockerIgnored(path, text) {
93
+ const parts = path.split('/')
94
+ let ignored = false
95
+ for (const raw of text.split(/\r?\n/)) {
96
+ const line = raw.trim()
97
+ if (!line || line.startsWith('#')) continue
98
+ const exception = line.startsWith('!')
99
+ const pattern = posix.normalize((exception ? line.slice(1) : line).trim().replace(/^\/+/, '')).replace(/\/+$/, '')
100
+ if (!pattern) continue
101
+ const re = pattern === '.' ? /^/ : globRegex(pattern)
102
+ if (parts.some((_, i) => re.test(parts.slice(0, i + 1).join('/')))) ignored = !exception
103
+ }
104
+ return ignored
105
+ }
106
+
107
+ /** A Docker path pattern as a regex: * and ? stay in 1 folder, ** crosses folders, [..] is a class. */
108
+ function globRegex(pattern) {
109
+ let out = ''
110
+ for (let i = 0; i < pattern.length; i++) {
111
+ const ch = pattern[i]
112
+ if (ch === '*' && pattern[i + 1] === '*') {
113
+ const slash = pattern[i + 2] === '/'
114
+ out += slash ? '(?:.*/)?' : '.*'
115
+ i += slash ? 2 : 1
116
+ } else if (ch === '*') out += '[^/]*'
117
+ else if (ch === '?') out += '[^/]'
118
+ else if (ch === '[') {
119
+ const end = pattern.indexOf(']', i + 1)
120
+ if (end < 0) { out += '\\['; continue }
121
+ out += `[${pattern.slice(i + 1, end).replace(/^!/, '^').replace(/\\/g, '\\\\')}]`
122
+ i = end
123
+ } else out += ch.replace(/[.+^${}()|\\]/g, '\\$&')
124
+ }
125
+ return new RegExp(`^${out}$`)
126
+ }