main
1// Copyright 2022 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15import path from 'node:path'
16import { type Buffer } from 'node:buffer'
17import process from 'node:process'
18import { type TSpawnStore } from './vendor-core.ts'
19
20export { isStringLiteral } from './vendor-core.ts'
21
22export function noop() {}
23
24export function identity<T>(v: T): T {
25 return v
26}
27
28export function randomId() {
29 return Math.random().toString(36).slice(2)
30}
31
32export function isString(obj: any) {
33 return typeof obj === 'string'
34}
35
36const utf8Decoder = new TextDecoder('utf-8')
37export const bufToString = (buf: Buffer | string): string =>
38 isString(buf) ? buf : utf8Decoder.decode(buf)
39
40export const bufArrJoin = (arr: TSpawnStore[keyof TSpawnStore]): string =>
41 arr.reduce((acc, buf) => acc + bufToString(buf), '')
42
43export const getLast = <T>(arr: { length: number; [i: number]: any }): T =>
44 arr[arr.length - 1]
45
46export function preferLocalBin(
47 env: NodeJS.ProcessEnv,
48 ...dirs: (string | undefined)[]
49) {
50 const pathKey =
51 process.platform === 'win32'
52 ? Object.keys(env)
53 .reverse()
54 .find((key) => key.toUpperCase() === 'PATH') || 'Path'
55 : 'PATH'
56 const pathValue = dirs
57 .map(
58 (c) =>
59 c && [
60 path.resolve(c as string, 'node_modules', '.bin'),
61 path.resolve(c as string),
62 ]
63 )
64 .flat()
65 .concat(env[pathKey])
66 .filter(Boolean)
67 .join(path.delimiter)
68
69 return {
70 ...env,
71 [pathKey]: pathValue,
72 }
73}
74
75export function quote(arg: string): string {
76 if (arg === '') return `$''`
77 if (/^[\w/.\-@:=]+$/.test(arg)) return arg
78
79 return (
80 `$'` +
81 arg
82 .replace(/\\/g, '\\\\')
83 .replace(/'/g, "\\'")
84 .replace(/\f/g, '\\f')
85 .replace(/\n/g, '\\n')
86 .replace(/\r/g, '\\r')
87 .replace(/\t/g, '\\t')
88 .replace(/\v/g, '\\v')
89 .replace(/\0/g, '\\0') +
90 `'`
91 )
92}
93
94export function quotePowerShell(arg: string): string {
95 if (arg === '') return `''`
96 if (/^[\w/.\-]+$/.test(arg)) return arg
97
98 return `'` + arg.replace(/'/g, "''") + `'`
99}
100
101export type Duration =
102 | number
103 | `${number}`
104 | `${number}m`
105 | `${number}s`
106 | `${number}ms`
107
108export function parseDuration(d: Duration) {
109 if (typeof d === 'number') {
110 if (isNaN(d) || d < 0) throw new Error(`Invalid duration: "${d}".`)
111 return d
112 }
113 const [m, v, u] = d.match(/^(\d+)(m?s?)$/) || []
114 if (!m) throw new Error(`Unknown duration: "${d}".`)
115
116 return +v * ({ s: 1000, ms: 1, m: 60_000 }[u] || 1)
117}
118
119export const once = <T extends (...args: any[]) => any>(fn: T) => {
120 let called = false
121 let result: ReturnType<T>
122
123 return (...args: Parameters<T>): ReturnType<T> =>
124 called ? result : ((called = true), (result = fn(...args)))
125}
126
127export const proxyOverride = <T extends object>(
128 origin: T,
129 ...fallbacks: any
130): T =>
131 new Proxy(origin, {
132 get(target: any, key) {
133 return (
134 fallbacks.find((f: any) => key in f)?.[key] ?? Reflect.get(target, key)
135 )
136 },
137 }) as T
138
139export const toCamelCase = (str: string) =>
140 str
141 .toLowerCase()
142 .replace(/([a-z])[_-]+([a-z])/g, (_, p1, p2) => p1 + p2.toUpperCase())
143
144export const parseBool = (v: string): boolean | string =>
145 v === 'true' || (v !== 'false' && v)
146
147export const getLines = (
148 chunk: Buffer | string,
149 next: (string | undefined)[],
150 delimiter: string | RegExp
151) => {
152 const lines = ((next.pop() || '') + bufToString(chunk)).split(delimiter)
153 next.push(lines.pop())
154 return lines
155}
156
157export const iteratorToArray = <T>(it: Iterator<T>): T[] => {
158 const arr = []
159 let entry
160 while (!(entry = it.next()).done) arr.push(entry.value)
161 return arr
162}