@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.
- package/LICENSE +190 -0
- package/README.md +209 -2
- package/bin/uknows-setup.mjs +14 -0
- package/lib/automate.mjs +1299 -0
- package/lib/check.mjs +327 -0
- package/lib/ci.mjs +21 -0
- package/lib/cli.mjs +251 -0
- package/lib/cloudid.mjs +456 -0
- package/lib/code.mjs +147 -0
- package/lib/context.mjs +126 -0
- package/lib/contract-docs.mjs +2736 -0
- package/lib/contract.mjs +149 -0
- package/lib/detect.mjs +627 -0
- package/lib/diff.mjs +147 -0
- package/lib/doc.mjs +98 -0
- package/lib/dockerfile.mjs +455 -0
- package/lib/endpoint.mjs +81 -0
- package/lib/engine.mjs +1753 -0
- package/lib/existing.mjs +562 -0
- package/lib/git.mjs +100 -0
- package/lib/hooks/UknowsOpenTelemetry.cs +118 -0
- package/lib/hooks/pins.mjs +28 -0
- package/lib/hooks/uknows-otel.cjs +82 -0
- package/lib/hooks/uknows_otel.py +179 -0
- package/lib/hosts.mjs +650 -0
- package/lib/locks.mjs +353 -0
- package/lib/manifest.mjs +132 -0
- package/lib/members.mjs +269 -0
- package/lib/plan.mjs +130 -0
- package/lib/settings.mjs +468 -0
- package/lib/signin.mjs +259 -0
- package/lib/stacks/azure.mjs +345 -0
- package/lib/stacks/channel.mjs +125 -0
- package/lib/stacks/dotnet.mjs +472 -0
- package/lib/stacks/functions-hooks.mjs +228 -0
- package/lib/stacks/functions.mjs +148 -0
- package/lib/stacks/guided.mjs +227 -0
- package/lib/stacks/index.mjs +18 -0
- package/lib/stacks/java.mjs +57 -0
- package/lib/stacks/k8s.mjs +31 -0
- package/lib/stacks/node.mjs +358 -0
- package/lib/stacks/otel.mjs +63 -0
- package/lib/stacks/python-bootstrap.mjs +71 -0
- package/lib/stacks/python-lock.mjs +105 -0
- package/lib/stacks/python-sets.mjs +143 -0
- package/lib/stacks/python.mjs +632 -0
- package/lib/toml.mjs +64 -0
- package/lib/types.mjs +208 -0
- package/lib/ui.mjs +225 -0
- package/lib/updates.mjs +398 -0
- package/lib/versions.mjs +115 -0
- package/lib/workspace.mjs +201 -0
- 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
|
+
}
|