@uknows/setup 0.0.0-stage → 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 (53) hide show
  1. package/LICENSE +190 -0
  2. package/README.md +209 -2
  3. package/bin/uknows-setup.mjs +14 -0
  4. package/lib/automate.mjs +1299 -0
  5. package/lib/check.mjs +327 -0
  6. package/lib/ci.mjs +21 -0
  7. package/lib/cli.mjs +251 -0
  8. package/lib/cloudid.mjs +456 -0
  9. package/lib/code.mjs +147 -0
  10. package/lib/context.mjs +126 -0
  11. package/lib/contract-docs.mjs +2736 -0
  12. package/lib/contract.mjs +149 -0
  13. package/lib/detect.mjs +627 -0
  14. package/lib/diff.mjs +147 -0
  15. package/lib/doc.mjs +98 -0
  16. package/lib/dockerfile.mjs +455 -0
  17. package/lib/endpoint.mjs +81 -0
  18. package/lib/engine.mjs +1753 -0
  19. package/lib/existing.mjs +562 -0
  20. package/lib/git.mjs +100 -0
  21. package/lib/hooks/UknowsOpenTelemetry.cs +118 -0
  22. package/lib/hooks/pins.mjs +28 -0
  23. package/lib/hooks/uknows-otel.cjs +82 -0
  24. package/lib/hooks/uknows_otel.py +179 -0
  25. package/lib/hosts.mjs +650 -0
  26. package/lib/locks.mjs +353 -0
  27. package/lib/manifest.mjs +132 -0
  28. package/lib/members.mjs +269 -0
  29. package/lib/plan.mjs +130 -0
  30. package/lib/settings.mjs +468 -0
  31. package/lib/signin.mjs +259 -0
  32. package/lib/stacks/azure.mjs +345 -0
  33. package/lib/stacks/channel.mjs +125 -0
  34. package/lib/stacks/dotnet.mjs +472 -0
  35. package/lib/stacks/functions-hooks.mjs +228 -0
  36. package/lib/stacks/functions.mjs +148 -0
  37. package/lib/stacks/guided.mjs +227 -0
  38. package/lib/stacks/index.mjs +18 -0
  39. package/lib/stacks/java.mjs +57 -0
  40. package/lib/stacks/k8s.mjs +31 -0
  41. package/lib/stacks/node.mjs +358 -0
  42. package/lib/stacks/otel.mjs +63 -0
  43. package/lib/stacks/python-bootstrap.mjs +71 -0
  44. package/lib/stacks/python-lock.mjs +105 -0
  45. package/lib/stacks/python-sets.mjs +143 -0
  46. package/lib/stacks/python.mjs +632 -0
  47. package/lib/toml.mjs +64 -0
  48. package/lib/types.mjs +208 -0
  49. package/lib/ui.mjs +225 -0
  50. package/lib/updates.mjs +398 -0
  51. package/lib/versions.mjs +115 -0
  52. package/lib/workspace.mjs +201 -0
  53. package/package.json +37 -4
package/lib/diff.mjs ADDED
@@ -0,0 +1,147 @@
1
+ /*
2
+ * Line diffs. Lines keep their endings, so a diff of CRLF, mixed or
3
+ * unterminated text is byte exact. The common prefix and suffix are cut off
4
+ * first, then a Myers O(ND) diff runs on the middle. When the middle is too
5
+ * different, it becomes 1 change, which is still correct, just less tidy.
6
+ */
7
+
8
+ const MAX_D = 1000
9
+
10
+ /** Lines with their endings. '' has no lines. */
11
+ export const splitLines = (text) => (text ? text.split(/(?<=\n)/) : [])
12
+
13
+ /** Changed regions between 2 line arrays: [{a, aEnd, b, bEnd}], 0-based, ends exclusive. */
14
+ export function regions(a, b) {
15
+ let pre = 0
16
+ while (pre < a.length && pre < b.length && a[pre] === b[pre]) pre++
17
+ let suf = 0
18
+ while (suf < a.length - pre && suf < b.length - pre && a[a.length - 1 - suf] === b[b.length - 1 - suf]) suf++
19
+ const A = a.slice(pre, a.length - suf), B = b.slice(pre, b.length - suf)
20
+ const out = []
21
+ let i = 0, j = 0
22
+ for (const [mi, mj] of [...(matches(A, B) ?? []), [A.length, B.length]]) {
23
+ if (mi > i || mj > j) out.push({ a: pre + i, aEnd: pre + mi, b: pre + j, bEnd: pre + mj })
24
+ i = mi + 1
25
+ j = mj + 1
26
+ }
27
+ return out
28
+ }
29
+
30
+ /** Matched line pairs [i, j] in order (Myers), or null when more than MAX_D edits apart. */
31
+ function matches(A, B) {
32
+ const n = A.length, m = B.length, max = n + m, off = max + 1
33
+ if (!n || !m) return []
34
+ const v = new Int32Array(2 * max + 3)
35
+ const trace = []
36
+ for (let d = 0; d <= Math.min(max, MAX_D); d++) {
37
+ trace.push(v.slice(off - d - 1, off + d + 2))
38
+ for (let k = -d; k <= d; k += 2) {
39
+ let x = k === -d || (k !== d && v[off + k - 1] < v[off + k + 1]) ? v[off + k + 1] : v[off + k - 1] + 1
40
+ let y = x - k
41
+ while (x < n && y < m && A[x] === B[y]) { x++; y++ }
42
+ v[off + k] = x
43
+ if (x >= n && y >= m) return backtrack(trace, n, m)
44
+ }
45
+ }
46
+ return null
47
+ }
48
+
49
+ function backtrack(trace, n, m) {
50
+ const pairs = []
51
+ let x = n, y = m
52
+ for (let d = trace.length - 1; d >= 0; d--) {
53
+ const snap = trace[d], at = (k) => snap[k + d + 1]
54
+ const k = x - y
55
+ const prevK = k === -d || (k !== d && at(k - 1) < at(k + 1)) ? k + 1 : k - 1
56
+ const prevX = at(prevK), prevY = prevX - prevK
57
+ while (x > prevX && y > prevY) pairs.push([--x, --y])
58
+ x = prevX
59
+ y = prevY
60
+ }
61
+ return pairs.reverse()
62
+ }
63
+
64
+ /**
65
+ * Zero-context hunks for the manifest. `at` is the 0-based line in the
66
+ * after-text. before null means a new file.
67
+ */
68
+ export function hunks(before, after) {
69
+ const a = splitLines(before ?? ''), b = splitLines(after)
70
+ return regions(a, b).map((r) => ({ at: r.b, before: a.slice(r.a, r.aEnd), after: b.slice(r.b, r.bEnd) }))
71
+ }
72
+
73
+ /** Undo hunks by position. Returns the lines, or null when a hunk does not sit where it should. */
74
+ export function reverseAt(lines, list) {
75
+ const out = [...lines]
76
+ for (const h of [...list].sort((p, q) => q.at - p.at)) {
77
+ if (!sameAt(out, h.at, h.after)) return null
78
+ out.splice(h.at, h.after.length, ...h.before)
79
+ }
80
+ return out
81
+ }
82
+
83
+ /**
84
+ * Undo hunks by content: each hunk's added lines must appear exactly once.
85
+ * Returns the lines, or null when a hunk is missing, ambiguous or overlaps another.
86
+ */
87
+ export function reverseByContent(lines, list) {
88
+ const found = []
89
+ for (const h of list) {
90
+ if (!h.after.length) return null
91
+ const spots = []
92
+ for (let i = 0; i + h.after.length <= lines.length; i++) if (sameAt(lines, i, h.after)) spots.push(i)
93
+ if (spots.length !== 1) return null
94
+ found.push({ at: spots[0], h })
95
+ }
96
+ found.sort((p, q) => q.at - p.at)
97
+ for (let i = 1; i < found.length; i++) if (found[i].at + found[i].h.after.length > found[i - 1].at) return null
98
+ const out = [...lines]
99
+ for (const { at, h } of found) out.splice(at, h.after.length, ...h.before)
100
+ return out
101
+ }
102
+
103
+ function sameAt(lines, at, want) {
104
+ if (at < 0 || at + want.length > lines.length) return false
105
+ return want.every((line, i) => lines[at + i] === line)
106
+ }
107
+
108
+ /** Lines added and removed. */
109
+ export function countLines(before, after) {
110
+ let added = 0, removed = 0
111
+ for (const r of regions(splitLines(before ?? ''), splitLines(after ?? ''))) {
112
+ added += r.bEnd - r.b
113
+ removed += r.aEnd - r.a
114
+ }
115
+ return { added, removed }
116
+ }
117
+
118
+ /** A unified diff for display. before null = new file. '' when nothing changed. */
119
+ export function unified(file, before, after, context = 3) {
120
+ const a = splitLines(before ?? ''), b = splitLines(after ?? '')
121
+ const rs = regions(a, b)
122
+ if (!rs.length) return ''
123
+ const out = [`--- ${before === null ? '/dev/null' : `a/${file}`}`, `+++ b/${file}`]
124
+ const line = (sign, text) => {
125
+ out.push(sign + text.replace(/\r?\n$/, ''))
126
+ if (!text.endsWith('\n')) out.push('\')
127
+ }
128
+ for (let g = 0; g < rs.length;) {
129
+ let h = g
130
+ while (h + 1 < rs.length && rs[h + 1].a - rs[h].aEnd <= 2 * context) h++
131
+ const aS = Math.max(0, rs[g].a - context), aE = Math.min(a.length, rs[h].aEnd + context)
132
+ const bS = rs[g].b - (rs[g].a - aS), bE = rs[h].bEnd + (aE - rs[h].aEnd)
133
+ out.push(`@@ -${span(aS, aE - aS)} +${span(bS, bE - bS)} @@`)
134
+ let i = aS
135
+ for (let r = g; r <= h; r++) {
136
+ for (; i < rs[r].a; i++) line(' ', a[i])
137
+ for (let x = rs[r].a; x < rs[r].aEnd; x++) line('-', a[x])
138
+ for (let y = rs[r].b; y < rs[r].bEnd; y++) line('+', b[y])
139
+ i = rs[r].aEnd
140
+ }
141
+ for (; i < aE; i++) line(' ', a[i])
142
+ g = h + 1
143
+ }
144
+ return out.join('\n')
145
+ }
146
+
147
+ const span = (start, length) => (length === 1 ? `${start + 1}` : `${length ? start + 1 : start},${length}`)
package/lib/doc.mjs ADDED
@@ -0,0 +1,98 @@
1
+ /*
2
+ * A text file as lines. Each line keeps its own line ending, so an edit
3
+ * changes only the lines it touches: CRLF, LF, mixed endings and a missing
4
+ * final newline all survive byte for byte. New lines use the dominant ending.
5
+ */
6
+
7
+ export class Doc {
8
+ /** @param {string} text */
9
+ constructor(text = '') {
10
+ const parts = text === '' ? [] : text.split(/(?<=\n)/)
11
+ /** @type {string[]} line content without the ending */
12
+ this.lines = []
13
+ /** @type {string[]} '\n', '\r\n' or '' (last line without a newline) */
14
+ this.ends = []
15
+ for (const part of parts) {
16
+ const end = part.endsWith('\r\n') ? '\r\n' : part.endsWith('\n') ? '\n' : ''
17
+ this.lines.push(part.slice(0, part.length - end.length))
18
+ this.ends.push(end)
19
+ }
20
+ const crlf = this.ends.filter((e) => e === '\r\n').length
21
+ const lf = this.ends.filter((e) => e === '\n').length
22
+ /** The dominant line ending. */
23
+ this.eol = crlf > lf ? '\r\n' : '\n'
24
+ }
25
+
26
+ get length() { return this.lines.length }
27
+
28
+ /** @param {number} i */
29
+ get(i) { return this.lines[i] }
30
+
31
+ /** Replace the content of line i. Keeps its line ending. */
32
+ set(i, content) {
33
+ if (i < 0 || i >= this.lines.length) throw new RangeError(`no line ${i}`)
34
+ this.lines[i] = content
35
+ }
36
+
37
+ /** Insert lines before index i. i === length appends. */
38
+ insert(i, ...contents) {
39
+ if (i < 0 || i > this.lines.length) throw new RangeError(`no line ${i}`)
40
+ if (!contents.length) return
41
+ const last = this.lines.length - 1
42
+ if (i === this.lines.length && last >= 0 && this.ends[last] === '') {
43
+ // Appending after a final line without a newline: keep the file without a final newline.
44
+ this.ends[last] = this.eol
45
+ this.lines.push(...contents)
46
+ this.ends.push(...contents.map((_, k) => (k === contents.length - 1 ? '' : this.eol)))
47
+ return
48
+ }
49
+ this.lines.splice(i, 0, ...contents)
50
+ this.ends.splice(i, 0, ...contents.map(() => this.eol))
51
+ }
52
+
53
+ /** Remove count lines from index i. */
54
+ remove(i, count = 1) {
55
+ this.lines.splice(i, count)
56
+ this.ends.splice(i, count)
57
+ }
58
+
59
+ /** First index >= from whose line matches. test is a RegExp or a function. */
60
+ find(test, from = 0) {
61
+ for (let i = from; i < this.lines.length; i++) {
62
+ if (typeof test === 'function' ? test(this.lines[i], i) : test.test(this.lines[i])) return i
63
+ }
64
+ return -1
65
+ }
66
+
67
+ toString() {
68
+ let out = ''
69
+ for (let i = 0; i < this.lines.length; i++) out += this.lines[i] + this.ends[i]
70
+ return out
71
+ }
72
+ }
73
+
74
+ /** Indentation (leading spaces or tabs) of a line. */
75
+ export const indentOf = (line) => /^[ \t]*/.exec(line)[0]
76
+
77
+ /**
78
+ * Split a command into words when it is a plain literal command. Returns null
79
+ * when it has shell syntax this tool must not guess at: variables,
80
+ * substitution, globs, redirects, pipes, lists, a leading NAME=value, an
81
+ * unclosed quote or a newline. Quotes are removed.
82
+ * @param {string} text
83
+ * @returns {string[]|null}
84
+ */
85
+ export function shellWords(text) {
86
+ if (typeof text !== 'string' || !text.trim() || /[\r\n\0$`;&|<>*?{}\\]/.test(text)) return null
87
+ const words = []
88
+ let word = '', quote = '', started = false
89
+ for (const c of text.trim()) {
90
+ if (quote) { if (c === quote) quote = ''; else word += c; started = true }
91
+ else if (c === '"' || c === "'") { quote = c; started = true }
92
+ else if (/\s/.test(c)) { if (started) { words.push(word); word = ''; started = false } }
93
+ else { word += c; started = true }
94
+ }
95
+ if (quote) return null
96
+ if (started) words.push(word)
97
+ return words.length && !/^[A-Za-z_][A-Za-z0-9_]*=/.test(words[0]) ? words : null
98
+ }