@highstate/common 0.9.16 → 0.9.19
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/dist/{chunk-HZBJ6LLS.js → chunk-WDYIUWYZ.js} +659 -267
- package/dist/chunk-WDYIUWYZ.js.map +1 -0
- package/dist/highstate.manifest.json +12 -8
- package/dist/index.js +1 -1
- package/dist/units/access-point/index.js +16 -0
- package/dist/units/access-point/index.js.map +1 -0
- package/dist/units/databases/existing-mariadb/index.js +17 -0
- package/dist/units/databases/existing-mariadb/index.js.map +1 -0
- package/dist/units/databases/existing-mongodb/index.js +17 -0
- package/dist/units/databases/existing-mongodb/index.js.map +1 -0
- package/dist/units/databases/existing-postgresql/index.js +17 -0
- package/dist/units/databases/existing-postgresql/index.js.map +1 -0
- package/dist/units/dns/record-set/index.js +22 -11
- package/dist/units/dns/record-set/index.js.map +1 -1
- package/dist/units/existing-server/index.js +12 -12
- package/dist/units/existing-server/index.js.map +1 -1
- package/dist/units/network/l3-endpoint/index.js +1 -1
- package/dist/units/network/l3-endpoint/index.js.map +1 -1
- package/dist/units/network/l4-endpoint/index.js +1 -1
- package/dist/units/network/l4-endpoint/index.js.map +1 -1
- package/dist/units/script/index.js +1 -1
- package/dist/units/script/index.js.map +1 -1
- package/dist/units/server-dns/index.js +1 -1
- package/dist/units/server-dns/index.js.map +1 -1
- package/dist/units/server-patch/index.js +1 -1
- package/dist/units/server-patch/index.js.map +1 -1
- package/dist/units/ssh/key-pair/index.js +6 -6
- package/dist/units/ssh/key-pair/index.js.map +1 -1
- package/package.json +61 -8
- package/src/shared/access-point.ts +110 -0
- package/src/shared/command.ts +310 -69
- package/src/shared/dns.ts +150 -90
- package/src/shared/files.ts +34 -34
- package/src/shared/gateway.ts +117 -0
- package/src/shared/impl-ref.ts +123 -0
- package/src/shared/index.ts +4 -0
- package/src/shared/network.ts +41 -27
- package/src/shared/passwords.ts +38 -2
- package/src/shared/ssh.ts +261 -126
- package/src/shared/tls.ts +123 -0
- package/src/units/access-point/index.ts +12 -0
- package/src/units/databases/existing-mariadb/index.ts +14 -0
- package/src/units/databases/existing-mongodb/index.ts +14 -0
- package/src/units/databases/existing-postgresql/index.ts +14 -0
- package/src/units/dns/record-set/index.ts +21 -11
- package/src/units/existing-server/index.ts +12 -17
- package/src/units/ssh/key-pair/index.ts +6 -6
- package/dist/chunk-HZBJ6LLS.js.map +0 -1
@@ -0,0 +1,123 @@
|
|
1
|
+
import type { ImplementationReference } from "@highstate/library"
|
2
|
+
import type { z } from "@highstate/contract"
|
3
|
+
import { type Output, output, toPromise, type Input, type Unwrap } from "@highstate/pulumi"
|
4
|
+
|
5
|
+
/**
|
6
|
+
* The ImplementationMediator is used as a contract between the calling code and the implementation.
|
7
|
+
*
|
8
|
+
* From the calling code perspective, it provides a way to define the input and output schemas for the implementation
|
9
|
+
* and call the implementation with the provided input.
|
10
|
+
*
|
11
|
+
* From the implementation perspective, it provides a way to get zod function with automatic type inference and validation.
|
12
|
+
*/
|
13
|
+
export class ImplementationMediator<
|
14
|
+
TInputSchema extends z.ZodType,
|
15
|
+
TOutputSchema extends z.ZodType,
|
16
|
+
> {
|
17
|
+
constructor(
|
18
|
+
readonly path: string,
|
19
|
+
private readonly inputSchema: TInputSchema,
|
20
|
+
private readonly outputSchema: TOutputSchema,
|
21
|
+
) {}
|
22
|
+
|
23
|
+
implement<TDataSchema extends z.ZodType>(
|
24
|
+
dataSchema: TDataSchema,
|
25
|
+
func: (
|
26
|
+
input: z.infer<TInputSchema>,
|
27
|
+
data: z.infer<TDataSchema>,
|
28
|
+
) => z.infer<TOutputSchema> | Promise<z.infer<TOutputSchema>>,
|
29
|
+
) {
|
30
|
+
return async (
|
31
|
+
input: z.infer<TInputSchema>,
|
32
|
+
data: z.infer<TDataSchema>,
|
33
|
+
): Promise<z.infer<TOutputSchema>> => {
|
34
|
+
const parsedInput = this.inputSchema.safeParse(input)
|
35
|
+
if (!parsedInput.success) {
|
36
|
+
throw new Error(
|
37
|
+
`Invalid input for implementation "${this.path}": ${parsedInput.error.message}`,
|
38
|
+
)
|
39
|
+
}
|
40
|
+
|
41
|
+
const parsedData = dataSchema.safeParse(data)
|
42
|
+
if (!parsedData.success) {
|
43
|
+
throw new Error(
|
44
|
+
`Invalid data for implementation "${this.path}": ${parsedData.error.message}`,
|
45
|
+
)
|
46
|
+
}
|
47
|
+
|
48
|
+
const result = await func(parsedInput.data, parsedData.data)
|
49
|
+
const parsedResult = this.outputSchema.safeParse(result)
|
50
|
+
|
51
|
+
if (!parsedResult.success) {
|
52
|
+
throw new Error(
|
53
|
+
`Invalid output from implementation "${this.path}": ${parsedResult.error.message}`,
|
54
|
+
)
|
55
|
+
}
|
56
|
+
|
57
|
+
return parsedResult.data
|
58
|
+
}
|
59
|
+
}
|
60
|
+
|
61
|
+
async call(
|
62
|
+
implRef: Input<ImplementationReference>,
|
63
|
+
input: Input<z.infer<TInputSchema>>,
|
64
|
+
): Promise<z.infer<TOutputSchema>> {
|
65
|
+
const resolvedImplRef = await toPromise(implRef)
|
66
|
+
const resolvedInput = await toPromise(input)
|
67
|
+
|
68
|
+
const importPath = `${resolvedImplRef.package}/impl/${this.path}`
|
69
|
+
|
70
|
+
let impl: Record<string, unknown>
|
71
|
+
try {
|
72
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
73
|
+
impl = await import(importPath)
|
74
|
+
} catch (error) {
|
75
|
+
throw new Error(`Failed to import module "${importPath}" required by implementation.`, {
|
76
|
+
cause: error,
|
77
|
+
})
|
78
|
+
}
|
79
|
+
|
80
|
+
const funcs = Object.entries(impl).filter(value => typeof value[1] === "function") as [
|
81
|
+
string,
|
82
|
+
Function,
|
83
|
+
][]
|
84
|
+
|
85
|
+
if (funcs.length === 0) {
|
86
|
+
throw new Error(`No implementation functions found in module "${importPath}".`)
|
87
|
+
}
|
88
|
+
|
89
|
+
if (funcs.length > 1) {
|
90
|
+
throw new Error(
|
91
|
+
`Multiple implementation functions found in module "${importPath}": ${funcs.map(func => func[0]).join(", ")}. ` +
|
92
|
+
"Ensure only one function is exported.",
|
93
|
+
)
|
94
|
+
}
|
95
|
+
|
96
|
+
const [funcName, implFunc] = funcs[0]
|
97
|
+
|
98
|
+
let result: unknown
|
99
|
+
try {
|
100
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
101
|
+
result = await implFunc(resolvedInput, resolvedImplRef.data)
|
102
|
+
} catch (error) {
|
103
|
+
console.error(`Error in implementation function "${funcName}":`, error)
|
104
|
+
throw new Error(`Implementation function "${funcName}" failed`)
|
105
|
+
}
|
106
|
+
|
107
|
+
const parsedResult = this.outputSchema.safeParse(result)
|
108
|
+
if (!parsedResult.success) {
|
109
|
+
throw new Error(
|
110
|
+
`Implementation function "${funcName}" returned invalid result: ${parsedResult.error.message}`,
|
111
|
+
)
|
112
|
+
}
|
113
|
+
|
114
|
+
return parsedResult.data
|
115
|
+
}
|
116
|
+
|
117
|
+
callOutput(
|
118
|
+
implRef: Input<ImplementationReference>,
|
119
|
+
input: Input<z.infer<TInputSchema>>,
|
120
|
+
): Output<Unwrap<z.infer<TOutputSchema>>> {
|
121
|
+
return output(this.call(implRef, input))
|
122
|
+
}
|
123
|
+
}
|
package/src/shared/index.ts
CHANGED
package/src/shared/network.ts
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
import type { ArrayPatchMode, network } from "@highstate/library"
|
2
|
-
import {
|
2
|
+
import { type Input, toPromise } from "@highstate/pulumi"
|
3
3
|
import { uniqueBy } from "remeda"
|
4
4
|
|
5
5
|
/**
|
@@ -248,14 +248,14 @@ export async function requireInputL4Endpoint(
|
|
248
248
|
}
|
249
249
|
|
250
250
|
/**
|
251
|
-
*
|
251
|
+
* Converts L3 endpoint to L4 endpoint by adding a port and protocol.
|
252
252
|
*
|
253
253
|
* @param l3Endpoint The L3 endpoint to convert.
|
254
254
|
* @param port The port to add to the L3 endpoint.
|
255
255
|
* @param protocol The protocol to add to the L3 endpoint. Defaults to "tcp".
|
256
256
|
* @returns The L4 endpoint with the port and protocol added.
|
257
257
|
*/
|
258
|
-
export function
|
258
|
+
export function l3EndpointToL4(
|
259
259
|
l3Endpoint: InputL3Endpoint,
|
260
260
|
port: number,
|
261
261
|
protocol: network.L4Protocol = "tcp",
|
@@ -276,11 +276,14 @@ export function l3ToL4Endpoint(
|
|
276
276
|
*
|
277
277
|
* @returns The filtered list of endpoints.
|
278
278
|
*/
|
279
|
-
export function filterEndpoints<
|
279
|
+
export function filterEndpoints<
|
280
|
+
TEndpoint extends network.L34Endpoint,
|
281
|
+
TType extends network.L34Endpoint["type"],
|
282
|
+
>(
|
280
283
|
endpoints: TEndpoint[],
|
281
284
|
filter?: network.EndpointFilter,
|
282
|
-
types?:
|
283
|
-
): TEndpoint[] {
|
285
|
+
types?: TType[],
|
286
|
+
): (TEndpoint & { type: TType })[] {
|
284
287
|
if (filter?.length) {
|
285
288
|
endpoints = endpoints.filter(endpoint => filter.includes(endpoint.visibility))
|
286
289
|
} else if (endpoints.some(endpoint => endpoint.visibility === "public")) {
|
@@ -289,11 +292,11 @@ export function filterEndpoints<TEndpoint extends network.L34Endpoint>(
|
|
289
292
|
endpoints = endpoints.filter(endpoint => endpoint.visibility === "external")
|
290
293
|
}
|
291
294
|
|
292
|
-
if (types
|
293
|
-
endpoints = endpoints.filter(endpoint => types.includes(endpoint.type))
|
295
|
+
if (types?.length) {
|
296
|
+
endpoints = endpoints.filter(endpoint => types.includes(endpoint.type as TType))
|
294
297
|
}
|
295
298
|
|
296
|
-
return endpoints
|
299
|
+
return endpoints as (TEndpoint & { type: TType })[]
|
297
300
|
}
|
298
301
|
|
299
302
|
/**
|
@@ -369,31 +372,42 @@ export function parseL7Endpoint(l7Endpoint: InputL7Endpoint): network.L7Endpoint
|
|
369
372
|
* @param endpoints The new endpoints to add in string format.
|
370
373
|
* @param inputEndpoints The input endpoints to add in object format.
|
371
374
|
* @param mode The mode to use when updating the endpoints. Can be "replace" or "prepend". Defaults to "prepend".
|
372
|
-
*
|
373
|
-
* @returns The updated list of endpoints.
|
375
|
+
* @returns The updated list of endpoints with duplicates removed.
|
374
376
|
*/
|
375
|
-
export async function updateEndpoints<
|
376
|
-
currentEndpoints: Input<
|
377
|
-
endpoints: string[],
|
378
|
-
inputEndpoints: Input<
|
377
|
+
export async function updateEndpoints<TEndpoints extends network.L34Endpoint>(
|
378
|
+
currentEndpoints: Input<TEndpoints[]>,
|
379
|
+
endpoints: string[] | undefined,
|
380
|
+
inputEndpoints: Input<TEndpoints[]> | undefined,
|
379
381
|
mode: ArrayPatchMode = "prepend",
|
380
|
-
): Promise<
|
382
|
+
): Promise<TEndpoints[]> {
|
381
383
|
const resolvedCurrentEndpoints = await toPromise(currentEndpoints)
|
382
|
-
const
|
383
|
-
|
384
|
-
const newEndpoints = uniqueBy(
|
385
|
-
//
|
386
|
-
[...endpoints.map(parseL34Endpoint), ...resolvedInputEndpoints],
|
387
|
-
endpoint => l34EndpointToString(endpoint),
|
388
|
-
)
|
384
|
+
const newEndpoints = await parseEndpoints(endpoints, inputEndpoints)
|
389
385
|
|
390
386
|
if (mode === "replace") {
|
391
|
-
return newEndpoints as
|
387
|
+
return newEndpoints as TEndpoints[]
|
392
388
|
}
|
393
389
|
|
394
390
|
return uniqueBy(
|
395
|
-
//
|
396
391
|
[...newEndpoints, ...resolvedCurrentEndpoints],
|
397
|
-
|
398
|
-
) as
|
392
|
+
l34EndpointToString,
|
393
|
+
) as TEndpoints[]
|
394
|
+
}
|
395
|
+
|
396
|
+
/**
|
397
|
+
* Parses a list of endpoints from strings and input objects.
|
398
|
+
*
|
399
|
+
* @param endpoints The list of endpoint strings to parse.
|
400
|
+
* @param inputEndpoints The list of input endpoint objects to use.
|
401
|
+
* @returns The parsed list of endpoint objects with duplicates removed.
|
402
|
+
*/
|
403
|
+
export async function parseEndpoints<TEndpoints extends network.L34Endpoint>(
|
404
|
+
endpoints: string[] | undefined,
|
405
|
+
inputEndpoints: Input<TEndpoints[]> | undefined,
|
406
|
+
): Promise<TEndpoints[]> {
|
407
|
+
const resolvedInputEndpoints = await toPromise(inputEndpoints)
|
408
|
+
|
409
|
+
return uniqueBy(
|
410
|
+
[...(endpoints?.map(parseL34Endpoint) ?? []), ...(resolvedInputEndpoints ?? [])],
|
411
|
+
l34EndpointToString,
|
412
|
+
) as TEndpoints[]
|
399
413
|
}
|
package/src/shared/passwords.ts
CHANGED
@@ -1,6 +1,42 @@
|
|
1
|
-
import { randomBytes } from "@noble/hashes/utils"
|
1
|
+
import { bytesToHex, randomBytes } from "@noble/hashes/utils"
|
2
2
|
import { secureMask } from "micro-key-producer/password.js"
|
3
3
|
|
4
|
-
|
4
|
+
/**
|
5
|
+
* Generates a secure random password strong enough for online use.
|
6
|
+
*
|
7
|
+
* It uses "Safari Keychain Secure Password" format.
|
8
|
+
*
|
9
|
+
* The approximate entropy is [71 bits](https://support.apple.com/guide/security/automatic-strong-passwords-secc84c811c4/web).
|
10
|
+
*/
|
11
|
+
export function generatePassword(): string {
|
5
12
|
return secureMask.apply(randomBytes(32)).password
|
6
13
|
}
|
14
|
+
|
15
|
+
type KeyFormatMap = {
|
16
|
+
raw: Uint8Array
|
17
|
+
hex: string
|
18
|
+
base64: string
|
19
|
+
}
|
20
|
+
|
21
|
+
/**
|
22
|
+
* Generates a secure random key strong enough for offline use such as encryption.
|
23
|
+
*
|
24
|
+
* The strong entropy is 256 bits.
|
25
|
+
*
|
26
|
+
* @param format The format of the generated key. By default, it is "hex".
|
27
|
+
*/
|
28
|
+
export function generateKey<TFormat extends keyof KeyFormatMap = "hex">(
|
29
|
+
format: TFormat = "hex" as TFormat,
|
30
|
+
): KeyFormatMap[TFormat] {
|
31
|
+
const bytes = randomBytes(32)
|
32
|
+
|
33
|
+
if (format === "raw") {
|
34
|
+
return bytes as KeyFormatMap[TFormat]
|
35
|
+
}
|
36
|
+
|
37
|
+
if (format === "base64") {
|
38
|
+
return Buffer.from(bytes).toString("base64") as KeyFormatMap[TFormat]
|
39
|
+
}
|
40
|
+
|
41
|
+
return bytesToHex(bytes) as KeyFormatMap[TFormat]
|
42
|
+
}
|