mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-04-01 21:13:09 +00:00
add documentation for ai agents (#3115)
* add documentation for ai agents * docs: consolidate CLAUDE.md and CONTRIBUTING.md, add style guidelines - Refactor CLAUDE.md to reference CONTRIBUTING.md for build/test/format info - Expand CONTRIBUTING.md with comprehensive build targets, env vars, and testing - Add code style guidelines section with conventional commits - Standardize SDK prettier config to use single quotes (matching web) - Add project-level Claude Code settings to disable co-author attribution * style(sdk): apply prettier with single quotes Run prettier across sdk/base and sdk/package to apply the standardized quote style (single quotes matching web). * docs: add USER.md for per-developer TODO filtering - Add agents/USER.md to .gitignore (contains user identifier) - Document session startup flow in CLAUDE.md: - Create USER.md if missing, prompting for identifier - Filter TODOs by @username tags - Offer relevant TODOs on session start * docs: add i18n documentation task to agent TODOs * docs: document i18n ID patterns in core/ Add agents/i18n-patterns.md covering rust-i18n setup, translation file format, t!() macro usage, key naming conventions, and locale selection. Remove completed TODO item and add reference in CLAUDE.md. * chore: clarify that all builds work on any OS with Docker
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
const dropId: unique symbol = Symbol("id")
|
||||
const dropId: unique symbol = Symbol('id')
|
||||
export type DropRef = { [dropId]: number }
|
||||
|
||||
export abstract class Drop {
|
||||
@@ -19,7 +19,7 @@ export abstract class Drop {
|
||||
|
||||
return new Proxy(this, {
|
||||
set(target: any, prop, value) {
|
||||
if (prop === "dropRef" || prop == "dropId") return false
|
||||
if (prop === 'dropRef' || prop == 'dropId') return false
|
||||
target[prop] = value
|
||||
;(weak as any)[prop] = value
|
||||
return true
|
||||
@@ -60,7 +60,7 @@ export class DropPromise<T> implements Promise<T> {
|
||||
private static idCtr: number = 0
|
||||
private dropId: number
|
||||
private dropRef: DropRef;
|
||||
[Symbol.toStringTag] = "DropPromise"
|
||||
[Symbol.toStringTag] = 'DropPromise'
|
||||
private constructor(
|
||||
private readonly promise: Promise<T>,
|
||||
dropFnOrRef?: (() => void) | DropRef,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Effects } from "../Effects"
|
||||
import * as T from "../types"
|
||||
import { DropGenerator, DropPromise } from "./Drop"
|
||||
import { Effects } from '../Effects'
|
||||
import * as T from '../types'
|
||||
import { DropGenerator, DropPromise } from './Drop'
|
||||
|
||||
export class GetSystemSmtp {
|
||||
constructor(readonly effects: Effects) {}
|
||||
@@ -27,7 +27,7 @@ export class GetSystemSmtp {
|
||||
this.effects.onLeaveContext(() => {
|
||||
resolveCell.resolve()
|
||||
})
|
||||
abort?.addEventListener("abort", () => resolveCell.resolve())
|
||||
abort?.addEventListener('abort', () => resolveCell.resolve())
|
||||
while (this.effects.isInContext && !abort?.aborted) {
|
||||
let callback: () => void = () => {}
|
||||
const waitForNext = new Promise<void>((resolve) => {
|
||||
@@ -39,7 +39,7 @@ export class GetSystemSmtp {
|
||||
})
|
||||
await waitForNext
|
||||
}
|
||||
return new Promise<never>((_, rej) => rej(new Error("aborted")))
|
||||
return new Promise<never>((_, rej) => rej(new Error('aborted')))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -49,7 +49,7 @@ export class GetSystemSmtp {
|
||||
abort?: AbortSignal,
|
||||
): AsyncGenerator<T.SmtpValue | null, never, unknown> {
|
||||
const ctrl = new AbortController()
|
||||
abort?.addEventListener("abort", () => ctrl.abort())
|
||||
abort?.addEventListener('abort', () => ctrl.abort())
|
||||
return DropGenerator.of(this.watchGen(ctrl.signal), () => ctrl.abort())
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ export class GetSystemSmtp {
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(
|
||||
"callback function threw an error @ GetSystemSmtp.onChange",
|
||||
'callback function threw an error @ GetSystemSmtp.onChange',
|
||||
e,
|
||||
)
|
||||
}
|
||||
@@ -82,7 +82,7 @@ export class GetSystemSmtp {
|
||||
.catch((e) => callback(null, e))
|
||||
.catch((e) =>
|
||||
console.error(
|
||||
"callback function threw an error @ GetSystemSmtp.onChange",
|
||||
'callback function threw an error @ GetSystemSmtp.onChange',
|
||||
e,
|
||||
),
|
||||
)
|
||||
|
||||
@@ -2,7 +2,7 @@ export const asError = (e: unknown) => {
|
||||
if (e instanceof Error) {
|
||||
return new Error(e as any)
|
||||
}
|
||||
if (typeof e === "string") {
|
||||
if (typeof e === 'string') {
|
||||
return new Error(`${e}`)
|
||||
}
|
||||
return new Error(`${JSON.stringify(e)}`)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { object } from "ts-matches"
|
||||
import { object } from 'ts-matches'
|
||||
|
||||
export function deepEqual(...args: unknown[]) {
|
||||
const objects = args.filter(object.test)
|
||||
|
||||
@@ -23,7 +23,7 @@ export function partialDiff<T>(
|
||||
} else {
|
||||
return
|
||||
}
|
||||
} else if (typeof prev === "object" && typeof next === "object") {
|
||||
} else if (typeof prev === 'object' && typeof next === 'object') {
|
||||
if (prev === null || next === null) return { diff: next }
|
||||
const res = { diff: {} as Record<keyof T, any> }
|
||||
const keys = Object.keys(next) as (keyof T)[]
|
||||
@@ -48,14 +48,14 @@ export function partialDiff<T>(
|
||||
|
||||
export function deepMerge(...args: unknown[]): unknown {
|
||||
const lastItem = (args as any)[args.length - 1]
|
||||
if (typeof lastItem !== "object" || !lastItem) return lastItem
|
||||
if (typeof lastItem !== 'object' || !lastItem) return lastItem
|
||||
if (Array.isArray(lastItem))
|
||||
return deepMergeList(
|
||||
...(args.filter((x) => Array.isArray(x)) as unknown[][]),
|
||||
)
|
||||
return deepMergeObject(
|
||||
...(args.filter(
|
||||
(x) => typeof x === "object" && x && !Array.isArray(x),
|
||||
(x) => typeof x === 'object' && x && !Array.isArray(x),
|
||||
) as object[]),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { DefaultString } from "../actions/input/inputSpecTypes"
|
||||
import { getRandomString } from "./getRandomString"
|
||||
import { DefaultString } from '../actions/input/inputSpecTypes'
|
||||
import { getRandomString } from './getRandomString'
|
||||
|
||||
export function getDefaultString(defaultSpec: DefaultString): string {
|
||||
if (typeof defaultSpec === "string") {
|
||||
if (typeof defaultSpec === 'string') {
|
||||
return defaultSpec
|
||||
} else {
|
||||
return getRandomString(defaultSpec)
|
||||
|
||||
@@ -11,7 +11,7 @@ export function getRandomCharInSet(charset: string): string {
|
||||
}
|
||||
charIdx -= range.len
|
||||
}
|
||||
throw new Error("unreachable")
|
||||
throw new Error('unreachable')
|
||||
}
|
||||
function stringToCharSet(charset: string): CharSet {
|
||||
let set: CharSet = { ranges: [], len: 0 }
|
||||
@@ -20,10 +20,10 @@ function stringToCharSet(charset: string): CharSet {
|
||||
let in_range = false
|
||||
for (let char of charset) {
|
||||
switch (char) {
|
||||
case ",":
|
||||
case ',':
|
||||
if (start !== null && end !== null) {
|
||||
if (start!.charCodeAt(0) > end!.charCodeAt(0)) {
|
||||
throw new Error("start > end of charset")
|
||||
throw new Error('start > end of charset')
|
||||
}
|
||||
const len = end.charCodeAt(0) - start.charCodeAt(0) + 1
|
||||
set.ranges.push({
|
||||
@@ -40,20 +40,20 @@ function stringToCharSet(charset: string): CharSet {
|
||||
set.ranges.push({ start, end: start, len: 1 })
|
||||
start = null
|
||||
} else if (start !== null && in_range) {
|
||||
end = ","
|
||||
end = ','
|
||||
} else if (start === null && end === null && !in_range) {
|
||||
start = ","
|
||||
start = ','
|
||||
} else {
|
||||
throw new Error('unexpected ","')
|
||||
}
|
||||
break
|
||||
case "-":
|
||||
case '-':
|
||||
if (start === null) {
|
||||
start = "-"
|
||||
start = '-'
|
||||
} else if (!in_range) {
|
||||
in_range = true
|
||||
} else if (in_range && end === null) {
|
||||
end = "-"
|
||||
end = '-'
|
||||
} else {
|
||||
throw new Error('unexpected "-"')
|
||||
}
|
||||
@@ -70,7 +70,7 @@ function stringToCharSet(charset: string): CharSet {
|
||||
}
|
||||
if (start !== null && end !== null) {
|
||||
if (start!.charCodeAt(0) > end!.charCodeAt(0)) {
|
||||
throw new Error("start > end of charset")
|
||||
throw new Error('start > end of charset')
|
||||
}
|
||||
const len = end.charCodeAt(0) - start.charCodeAt(0) + 1
|
||||
set.ranges.push({
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { RandomString } from "../actions/input/inputSpecTypes"
|
||||
import { getRandomCharInSet } from "./getRandomCharInSet"
|
||||
import { RandomString } from '../actions/input/inputSpecTypes'
|
||||
import { getRandomCharInSet } from './getRandomCharInSet'
|
||||
|
||||
export function getRandomString(generator: RandomString): string {
|
||||
let s = ""
|
||||
let s = ''
|
||||
for (let i = 0; i < generator.len; i++) {
|
||||
s = s + getRandomCharInSet(generator.charset)
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { PackageId, ServiceInterfaceId, ServiceInterfaceType } from "../types"
|
||||
import { knownProtocols } from "../interfaces/Host"
|
||||
import { AddressInfo, Host, Hostname, HostnameInfo } from "../types"
|
||||
import { Effects } from "../Effects"
|
||||
import { DropGenerator, DropPromise } from "./Drop"
|
||||
import { IpAddress, IPV6_LINK_LOCAL } from "./ip"
|
||||
import { deepEqual } from "./deepEqual"
|
||||
import { once } from "./once"
|
||||
import { PackageId, ServiceInterfaceId, ServiceInterfaceType } from '../types'
|
||||
import { knownProtocols } from '../interfaces/Host'
|
||||
import { AddressInfo, Host, Hostname, HostnameInfo } from '../types'
|
||||
import { Effects } from '../Effects'
|
||||
import { DropGenerator, DropPromise } from './Drop'
|
||||
import { IpAddress, IPV6_LINK_LOCAL } from './ip'
|
||||
import { deepEqual } from './deepEqual'
|
||||
import { once } from './once'
|
||||
|
||||
export type UrlString = string
|
||||
export type HostId = string
|
||||
@@ -14,68 +14,68 @@ const getHostnameRegex = /^(\w+:\/\/)?([^\/\:]+)(:\d{1,3})?(\/)?/
|
||||
export const getHostname = (url: string): Hostname | null => {
|
||||
const founds = url.match(getHostnameRegex)?.[2]
|
||||
if (!founds) return null
|
||||
const parts = founds.split("@")
|
||||
const parts = founds.split('@')
|
||||
const last = parts[parts.length - 1] as Hostname | null
|
||||
return last
|
||||
}
|
||||
|
||||
type FilterKinds =
|
||||
| "onion"
|
||||
| "mdns"
|
||||
| "domain"
|
||||
| "ip"
|
||||
| "ipv4"
|
||||
| "ipv6"
|
||||
| "localhost"
|
||||
| "link-local"
|
||||
| 'onion'
|
||||
| 'mdns'
|
||||
| 'domain'
|
||||
| 'ip'
|
||||
| 'ipv4'
|
||||
| 'ipv6'
|
||||
| 'localhost'
|
||||
| 'link-local'
|
||||
export type Filter = {
|
||||
visibility?: "public" | "private"
|
||||
visibility?: 'public' | 'private'
|
||||
kind?: FilterKinds | FilterKinds[]
|
||||
predicate?: (h: HostnameInfo) => boolean
|
||||
exclude?: Filter
|
||||
}
|
||||
|
||||
type VisibilityFilter<V extends "public" | "private"> = V extends "public"
|
||||
? (HostnameInfo & { public: true }) | VisibilityFilter<Exclude<V, "public">>
|
||||
: V extends "private"
|
||||
type VisibilityFilter<V extends 'public' | 'private'> = V extends 'public'
|
||||
? (HostnameInfo & { public: true }) | VisibilityFilter<Exclude<V, 'public'>>
|
||||
: V extends 'private'
|
||||
?
|
||||
| (HostnameInfo & { public: false })
|
||||
| VisibilityFilter<Exclude<V, "private">>
|
||||
| VisibilityFilter<Exclude<V, 'private'>>
|
||||
: never
|
||||
type KindFilter<K extends FilterKinds> = K extends "onion"
|
||||
? (HostnameInfo & { kind: "onion" }) | KindFilter<Exclude<K, "onion">>
|
||||
: K extends "mdns"
|
||||
type KindFilter<K extends FilterKinds> = K extends 'onion'
|
||||
? (HostnameInfo & { kind: 'onion' }) | KindFilter<Exclude<K, 'onion'>>
|
||||
: K extends 'mdns'
|
||||
?
|
||||
| (HostnameInfo & { kind: "ip"; hostname: { kind: "local" } })
|
||||
| KindFilter<Exclude<K, "mdns">>
|
||||
: K extends "domain"
|
||||
| (HostnameInfo & { kind: 'ip'; hostname: { kind: 'local' } })
|
||||
| KindFilter<Exclude<K, 'mdns'>>
|
||||
: K extends 'domain'
|
||||
?
|
||||
| (HostnameInfo & { kind: "ip"; hostname: { kind: "domain" } })
|
||||
| KindFilter<Exclude<K, "domain">>
|
||||
: K extends "ipv4"
|
||||
| (HostnameInfo & { kind: 'ip'; hostname: { kind: 'domain' } })
|
||||
| KindFilter<Exclude<K, 'domain'>>
|
||||
: K extends 'ipv4'
|
||||
?
|
||||
| (HostnameInfo & { kind: "ip"; hostname: { kind: "ipv4" } })
|
||||
| KindFilter<Exclude<K, "ipv4">>
|
||||
: K extends "ipv6"
|
||||
| (HostnameInfo & { kind: 'ip'; hostname: { kind: 'ipv4' } })
|
||||
| KindFilter<Exclude<K, 'ipv4'>>
|
||||
: K extends 'ipv6'
|
||||
?
|
||||
| (HostnameInfo & { kind: "ip"; hostname: { kind: "ipv6" } })
|
||||
| KindFilter<Exclude<K, "ipv6">>
|
||||
: K extends "ip"
|
||||
? KindFilter<Exclude<K, "ip"> | "ipv4" | "ipv6">
|
||||
| (HostnameInfo & { kind: 'ip'; hostname: { kind: 'ipv6' } })
|
||||
| KindFilter<Exclude<K, 'ipv6'>>
|
||||
: K extends 'ip'
|
||||
? KindFilter<Exclude<K, 'ip'> | 'ipv4' | 'ipv6'>
|
||||
: never
|
||||
|
||||
type FilterReturnTy<F extends Filter> = F extends {
|
||||
visibility: infer V extends "public" | "private"
|
||||
visibility: infer V extends 'public' | 'private'
|
||||
}
|
||||
? VisibilityFilter<V> & FilterReturnTy<Omit<F, "visibility">>
|
||||
? VisibilityFilter<V> & FilterReturnTy<Omit<F, 'visibility'>>
|
||||
: F extends {
|
||||
kind: (infer K extends FilterKinds) | (infer K extends FilterKinds)[]
|
||||
}
|
||||
? KindFilter<K> & FilterReturnTy<Omit<F, "kind">>
|
||||
? KindFilter<K> & FilterReturnTy<Omit<F, 'kind'>>
|
||||
: F extends {
|
||||
predicate: (h: HostnameInfo) => h is infer H extends HostnameInfo
|
||||
}
|
||||
? H & FilterReturnTy<Omit<F, "predicate">>
|
||||
? H & FilterReturnTy<Omit<F, 'predicate'>>
|
||||
: F extends { exclude: infer E extends Filter } // MUST BE LAST
|
||||
? HostnameInfo extends FilterReturnTy<E>
|
||||
? HostnameInfo
|
||||
@@ -84,26 +84,26 @@ type FilterReturnTy<F extends Filter> = F extends {
|
||||
|
||||
const nonLocalFilter = {
|
||||
exclude: {
|
||||
kind: ["localhost", "link-local"] as ("localhost" | "link-local")[],
|
||||
kind: ['localhost', 'link-local'] as ('localhost' | 'link-local')[],
|
||||
},
|
||||
} as const
|
||||
const publicFilter = {
|
||||
visibility: "public",
|
||||
visibility: 'public',
|
||||
} as const
|
||||
const onionFilter = {
|
||||
kind: "onion",
|
||||
kind: 'onion',
|
||||
} as const
|
||||
|
||||
type Formats = "hostname-info" | "urlstring" | "url"
|
||||
type Formats = 'hostname-info' | 'urlstring' | 'url'
|
||||
type FormatReturnTy<
|
||||
F extends Filter,
|
||||
Format extends Formats,
|
||||
> = Format extends "hostname-info"
|
||||
? FilterReturnTy<F> | FormatReturnTy<F, Exclude<Format, "hostname-info">>
|
||||
: Format extends "url"
|
||||
? URL | FormatReturnTy<F, Exclude<Format, "url">>
|
||||
: Format extends "urlstring"
|
||||
? UrlString | FormatReturnTy<F, Exclude<Format, "urlstring">>
|
||||
> = Format extends 'hostname-info'
|
||||
? FilterReturnTy<F> | FormatReturnTy<F, Exclude<Format, 'hostname-info'>>
|
||||
: Format extends 'url'
|
||||
? URL | FormatReturnTy<F, Exclude<Format, 'url'>>
|
||||
: Format extends 'urlstring'
|
||||
? UrlString | FormatReturnTy<F, Exclude<Format, 'urlstring'>>
|
||||
: never
|
||||
|
||||
export type Filled<F extends Filter = {}> = {
|
||||
@@ -114,7 +114,7 @@ export type Filled<F extends Filter = {}> = {
|
||||
sslUrl: UrlString | null
|
||||
}
|
||||
|
||||
format: <Format extends Formats = "urlstring">(
|
||||
format: <Format extends Formats = 'urlstring'>(
|
||||
format?: Format,
|
||||
) => FormatReturnTy<{}, Format>[]
|
||||
|
||||
@@ -162,12 +162,12 @@ export const addressHostToUrl = (
|
||||
scheme in knownProtocols &&
|
||||
port === knownProtocols[scheme as keyof typeof knownProtocols].defaultPort
|
||||
let hostname
|
||||
if (host.kind === "onion") {
|
||||
if (host.kind === 'onion') {
|
||||
hostname = host.hostname.value
|
||||
} else if (host.kind === "ip") {
|
||||
if (host.hostname.kind === "domain") {
|
||||
} else if (host.kind === 'ip') {
|
||||
if (host.hostname.kind === 'domain') {
|
||||
hostname = host.hostname.value
|
||||
} else if (host.hostname.kind === "ipv6") {
|
||||
} else if (host.hostname.kind === 'ipv6') {
|
||||
hostname = IPV6_LINK_LOCAL.contains(host.hostname.value)
|
||||
? `[${host.hostname.value}%${host.hostname.scopeId}]`
|
||||
: `[${host.hostname.value}]`
|
||||
@@ -175,9 +175,9 @@ export const addressHostToUrl = (
|
||||
hostname = host.hostname.value
|
||||
}
|
||||
}
|
||||
return `${scheme ? `${scheme}://` : ""}${
|
||||
username ? `${username}@` : ""
|
||||
}${hostname}${excludePort ? "" : `:${port}`}${suffix}`
|
||||
return `${scheme ? `${scheme}://` : ''}${
|
||||
username ? `${username}@` : ''
|
||||
}${hostname}${excludePort ? '' : `:${port}`}${suffix}`
|
||||
}
|
||||
let url = null
|
||||
if (hostname.hostname.port !== null) {
|
||||
@@ -200,39 +200,39 @@ function filterRec(
|
||||
const pred = filter.predicate
|
||||
hostnames = hostnames.filter((h) => invert !== pred(h))
|
||||
}
|
||||
if (filter.visibility === "public")
|
||||
if (filter.visibility === 'public')
|
||||
hostnames = hostnames.filter(
|
||||
(h) => invert !== (h.kind === "onion" || h.public),
|
||||
(h) => invert !== (h.kind === 'onion' || h.public),
|
||||
)
|
||||
if (filter.visibility === "private")
|
||||
if (filter.visibility === 'private')
|
||||
hostnames = hostnames.filter(
|
||||
(h) => invert !== (h.kind !== "onion" && !h.public),
|
||||
(h) => invert !== (h.kind !== 'onion' && !h.public),
|
||||
)
|
||||
if (filter.kind) {
|
||||
const kind = new Set(
|
||||
Array.isArray(filter.kind) ? filter.kind : [filter.kind],
|
||||
)
|
||||
if (kind.has("ip")) {
|
||||
kind.add("ipv4")
|
||||
kind.add("ipv6")
|
||||
if (kind.has('ip')) {
|
||||
kind.add('ipv4')
|
||||
kind.add('ipv6')
|
||||
}
|
||||
hostnames = hostnames.filter(
|
||||
(h) =>
|
||||
invert !==
|
||||
((kind.has("onion") && h.kind === "onion") ||
|
||||
(kind.has("mdns") &&
|
||||
h.kind === "ip" &&
|
||||
h.hostname.kind === "local") ||
|
||||
(kind.has("domain") &&
|
||||
h.kind === "ip" &&
|
||||
h.hostname.kind === "domain") ||
|
||||
(kind.has("ipv4") && h.kind === "ip" && h.hostname.kind === "ipv4") ||
|
||||
(kind.has("ipv6") && h.kind === "ip" && h.hostname.kind === "ipv6") ||
|
||||
(kind.has("localhost") &&
|
||||
["localhost", "127.0.0.1", "::1"].includes(h.hostname.value)) ||
|
||||
(kind.has("link-local") &&
|
||||
h.kind === "ip" &&
|
||||
h.hostname.kind === "ipv6" &&
|
||||
((kind.has('onion') && h.kind === 'onion') ||
|
||||
(kind.has('mdns') &&
|
||||
h.kind === 'ip' &&
|
||||
h.hostname.kind === 'local') ||
|
||||
(kind.has('domain') &&
|
||||
h.kind === 'ip' &&
|
||||
h.hostname.kind === 'domain') ||
|
||||
(kind.has('ipv4') && h.kind === 'ip' && h.hostname.kind === 'ipv4') ||
|
||||
(kind.has('ipv6') && h.kind === 'ip' && h.hostname.kind === 'ipv6') ||
|
||||
(kind.has('localhost') &&
|
||||
['localhost', '127.0.0.1', '::1'].includes(h.hostname.value)) ||
|
||||
(kind.has('link-local') &&
|
||||
h.kind === 'ip' &&
|
||||
h.hostname.kind === 'ipv6' &&
|
||||
IPV6_LINK_LOCAL.contains(IpAddress.parse(h.hostname.value)))),
|
||||
)
|
||||
}
|
||||
@@ -275,11 +275,11 @@ export const filledAddress = (
|
||||
...addressInfo,
|
||||
hostnames,
|
||||
toUrls,
|
||||
format: <Format extends Formats = "urlstring">(format?: Format) => {
|
||||
format: <Format extends Formats = 'urlstring'>(format?: Format) => {
|
||||
let res: FormatReturnTy<{}, Format>[] = hostnames as any
|
||||
if (format === "hostname-info") return res
|
||||
if (format === 'hostname-info') return res
|
||||
const urls = hostnames.flatMap(toUrlArray)
|
||||
if (format === "url") res = urls.map((u) => new URL(u)) as any
|
||||
if (format === 'url') res = urls.map((u) => new URL(u)) as any
|
||||
else res = urls as any
|
||||
return res
|
||||
},
|
||||
@@ -386,7 +386,7 @@ export class GetServiceInterface<Mapped = ServiceInterfaceFilled | null> {
|
||||
this.effects.onLeaveContext(() => {
|
||||
resolveCell.resolve()
|
||||
})
|
||||
abort?.addEventListener("abort", () => resolveCell.resolve())
|
||||
abort?.addEventListener('abort', () => resolveCell.resolve())
|
||||
while (this.effects.isInContext && !abort?.aborted) {
|
||||
let callback: () => void = () => {}
|
||||
const waitForNext = new Promise<void>((resolve) => {
|
||||
@@ -406,7 +406,7 @@ export class GetServiceInterface<Mapped = ServiceInterfaceFilled | null> {
|
||||
}
|
||||
await waitForNext
|
||||
}
|
||||
return new Promise<never>((_, rej) => rej(new Error("aborted")))
|
||||
return new Promise<never>((_, rej) => rej(new Error('aborted')))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -414,7 +414,7 @@ export class GetServiceInterface<Mapped = ServiceInterfaceFilled | null> {
|
||||
*/
|
||||
watch(abort?: AbortSignal): AsyncGenerator<Mapped, never, unknown> {
|
||||
const ctrl = new AbortController()
|
||||
abort?.addEventListener("abort", () => ctrl.abort())
|
||||
abort?.addEventListener('abort', () => ctrl.abort())
|
||||
return DropGenerator.of(this.watchGen(ctrl.signal), () => ctrl.abort())
|
||||
}
|
||||
|
||||
@@ -438,7 +438,7 @@ export class GetServiceInterface<Mapped = ServiceInterfaceFilled | null> {
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(
|
||||
"callback function threw an error @ GetServiceInterface.onChange",
|
||||
'callback function threw an error @ GetServiceInterface.onChange',
|
||||
e,
|
||||
)
|
||||
}
|
||||
@@ -447,7 +447,7 @@ export class GetServiceInterface<Mapped = ServiceInterfaceFilled | null> {
|
||||
.catch((e) => callback(null, e))
|
||||
.catch((e) =>
|
||||
console.error(
|
||||
"callback function threw an error @ GetServiceInterface.onChange",
|
||||
'callback function threw an error @ GetServiceInterface.onChange',
|
||||
e,
|
||||
),
|
||||
)
|
||||
@@ -465,7 +465,7 @@ export class GetServiceInterface<Mapped = ServiceInterfaceFilled | null> {
|
||||
return next
|
||||
}
|
||||
}
|
||||
throw new Error("context left before predicate passed")
|
||||
throw new Error('context left before predicate passed')
|
||||
}),
|
||||
() => ctrl.abort(),
|
||||
)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Effects } from "../Effects"
|
||||
import { PackageId } from "../osBindings"
|
||||
import { deepEqual } from "./deepEqual"
|
||||
import { DropGenerator, DropPromise } from "./Drop"
|
||||
import { ServiceInterfaceFilled, filledAddress } from "./getServiceInterface"
|
||||
import { Effects } from '../Effects'
|
||||
import { PackageId } from '../osBindings'
|
||||
import { deepEqual } from './deepEqual'
|
||||
import { DropGenerator, DropPromise } from './Drop'
|
||||
import { ServiceInterfaceFilled, filledAddress } from './getServiceInterface'
|
||||
|
||||
const makeManyInterfaceFilled = async ({
|
||||
effects,
|
||||
@@ -86,7 +86,7 @@ export class GetServiceInterfaces<Mapped = ServiceInterfaceFilled[]> {
|
||||
this.effects.onLeaveContext(() => {
|
||||
resolveCell.resolve()
|
||||
})
|
||||
abort?.addEventListener("abort", () => resolveCell.resolve())
|
||||
abort?.addEventListener('abort', () => resolveCell.resolve())
|
||||
while (this.effects.isInContext && !abort?.aborted) {
|
||||
let callback: () => void = () => {}
|
||||
const waitForNext = new Promise<void>((resolve) => {
|
||||
@@ -105,7 +105,7 @@ export class GetServiceInterfaces<Mapped = ServiceInterfaceFilled[]> {
|
||||
}
|
||||
await waitForNext
|
||||
}
|
||||
return new Promise<never>((_, rej) => rej(new Error("aborted")))
|
||||
return new Promise<never>((_, rej) => rej(new Error('aborted')))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -113,7 +113,7 @@ export class GetServiceInterfaces<Mapped = ServiceInterfaceFilled[]> {
|
||||
*/
|
||||
watch(abort?: AbortSignal): AsyncGenerator<Mapped, never, unknown> {
|
||||
const ctrl = new AbortController()
|
||||
abort?.addEventListener("abort", () => ctrl.abort())
|
||||
abort?.addEventListener('abort', () => ctrl.abort())
|
||||
return DropGenerator.of(this.watchGen(ctrl.signal), () => ctrl.abort())
|
||||
}
|
||||
|
||||
@@ -137,7 +137,7 @@ export class GetServiceInterfaces<Mapped = ServiceInterfaceFilled[]> {
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(
|
||||
"callback function threw an error @ GetServiceInterfaces.onChange",
|
||||
'callback function threw an error @ GetServiceInterfaces.onChange',
|
||||
e,
|
||||
)
|
||||
}
|
||||
@@ -146,7 +146,7 @@ export class GetServiceInterfaces<Mapped = ServiceInterfaceFilled[]> {
|
||||
.catch((e) => callback(null, e))
|
||||
.catch((e) =>
|
||||
console.error(
|
||||
"callback function threw an error @ GetServiceInterfaces.onChange",
|
||||
'callback function threw an error @ GetServiceInterfaces.onChange',
|
||||
e,
|
||||
),
|
||||
)
|
||||
@@ -164,7 +164,7 @@ export class GetServiceInterfaces<Mapped = ServiceInterfaceFilled[]> {
|
||||
return next
|
||||
}
|
||||
}
|
||||
throw new Error("context left before predicate passed")
|
||||
throw new Error('context left before predicate passed')
|
||||
}),
|
||||
() => ctrl.abort(),
|
||||
)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { boolean } from "ts-matches"
|
||||
import { ExtendedVersion } from "../exver"
|
||||
import { boolean } from 'ts-matches'
|
||||
import { ExtendedVersion } from '../exver'
|
||||
|
||||
export type Vertex<VMetadata = null, EMetadata = null> = {
|
||||
metadata: VMetadata
|
||||
@@ -23,9 +23,9 @@ export class Graph<VMetadata = null, EMetadata = null> {
|
||||
return JSON.stringify(
|
||||
this.vertices,
|
||||
(k, v) => {
|
||||
if (k === "metadata") return metadataRepr(v)
|
||||
if (k === "from") return metadataRepr(v.metadata)
|
||||
if (k === "to") return metadataRepr(v.metadata)
|
||||
if (k === 'metadata') return metadataRepr(v)
|
||||
if (k === 'from') return metadataRepr(v.metadata)
|
||||
if (k === 'to') return metadataRepr(v.metadata)
|
||||
return v
|
||||
},
|
||||
2,
|
||||
@@ -33,8 +33,8 @@ export class Graph<VMetadata = null, EMetadata = null> {
|
||||
}
|
||||
addVertex(
|
||||
metadata: VMetadata,
|
||||
fromEdges: Array<Omit<Edge<EMetadata, VMetadata>, "to">>,
|
||||
toEdges: Array<Omit<Edge<EMetadata, VMetadata>, "from">>,
|
||||
fromEdges: Array<Omit<Edge<EMetadata, VMetadata>, 'to'>>,
|
||||
toEdges: Array<Omit<Edge<EMetadata, VMetadata>, 'from'>>,
|
||||
): Vertex<VMetadata, EMetadata> {
|
||||
const vertex: Vertex<VMetadata, EMetadata> = {
|
||||
metadata,
|
||||
|
||||
@@ -1,34 +1,34 @@
|
||||
import { inMs } from "./inMs"
|
||||
import { inMs } from './inMs'
|
||||
|
||||
describe("inMs", () => {
|
||||
test("28.001s", () => {
|
||||
expect(inMs("28.001s")).toBe(28001)
|
||||
describe('inMs', () => {
|
||||
test('28.001s', () => {
|
||||
expect(inMs('28.001s')).toBe(28001)
|
||||
})
|
||||
test("28.123s", () => {
|
||||
expect(inMs("28.123s")).toBe(28123)
|
||||
test('28.123s', () => {
|
||||
expect(inMs('28.123s')).toBe(28123)
|
||||
})
|
||||
test(".123s", () => {
|
||||
expect(inMs(".123s")).toBe(123)
|
||||
test('.123s', () => {
|
||||
expect(inMs('.123s')).toBe(123)
|
||||
})
|
||||
test("123ms", () => {
|
||||
expect(inMs("123ms")).toBe(123)
|
||||
test('123ms', () => {
|
||||
expect(inMs('123ms')).toBe(123)
|
||||
})
|
||||
test("1h", () => {
|
||||
expect(inMs("1h")).toBe(3600000)
|
||||
test('1h', () => {
|
||||
expect(inMs('1h')).toBe(3600000)
|
||||
})
|
||||
test("1m", () => {
|
||||
expect(inMs("1m")).toBe(60000)
|
||||
test('1m', () => {
|
||||
expect(inMs('1m')).toBe(60000)
|
||||
})
|
||||
test("1m", () => {
|
||||
expect(inMs("1d")).toBe(1000 * 60 * 60 * 24)
|
||||
test('1m', () => {
|
||||
expect(inMs('1d')).toBe(1000 * 60 * 60 * 24)
|
||||
})
|
||||
test("123", () => {
|
||||
expect(() => inMs("123")).toThrowError("Invalid time format: 123")
|
||||
test('123', () => {
|
||||
expect(() => inMs('123')).toThrowError('Invalid time format: 123')
|
||||
})
|
||||
test("123 as number", () => {
|
||||
test('123 as number', () => {
|
||||
expect(inMs(123)).toBe(123)
|
||||
})
|
||||
test.only("undefined", () => {
|
||||
test.only('undefined', () => {
|
||||
expect(inMs(undefined)).toBe(undefined)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -2,11 +2,11 @@ const matchTimeRegex = /^\s*(\d+)?(\.\d+)?\s*(ms|s|m|h|d)/
|
||||
|
||||
const unitMultiplier = (unit?: string) => {
|
||||
if (!unit) return 1
|
||||
if (unit === "ms") return 1
|
||||
if (unit === "s") return 1000
|
||||
if (unit === "m") return 1000 * 60
|
||||
if (unit === "h") return 1000 * 60 * 60
|
||||
if (unit === "d") return 1000 * 60 * 60 * 24
|
||||
if (unit === 'ms') return 1
|
||||
if (unit === 's') return 1000
|
||||
if (unit === 'm') return 1000 * 60
|
||||
if (unit === 'h') return 1000 * 60 * 60
|
||||
if (unit === 'd') return 1000 * 60 * 60 * 24
|
||||
throw new Error(`Invalid unit: ${unit}`)
|
||||
}
|
||||
const digitsMs = (digits: string | null, multiplier: number) => {
|
||||
@@ -16,13 +16,13 @@ const digitsMs = (digits: string | null, multiplier: number) => {
|
||||
return Math.round(value * divideBy)
|
||||
}
|
||||
export const inMs = (time?: string | number) => {
|
||||
if (typeof time === "number") return time
|
||||
if (typeof time === 'number') return time
|
||||
if (!time) return undefined
|
||||
const matches = time.match(matchTimeRegex)
|
||||
if (!matches) throw new Error(`Invalid time format: ${time}`)
|
||||
const [_, leftHandSide, digits, unit] = matches
|
||||
const multiplier = unitMultiplier(unit)
|
||||
const firstValue = parseInt(leftHandSide || "0") * multiplier
|
||||
const firstValue = parseInt(leftHandSide || '0') * multiplier
|
||||
const secondValue = digitsMs(digits, multiplier)
|
||||
|
||||
return firstValue + secondValue
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
/// Currently being used
|
||||
export { addressHostToUrl } from "./getServiceInterface"
|
||||
export { getDefaultString } from "./getDefaultString"
|
||||
export * from "./ip"
|
||||
export { addressHostToUrl } from './getServiceInterface'
|
||||
export { getDefaultString } from './getDefaultString'
|
||||
export * from './ip'
|
||||
|
||||
/// Not being used, but known to be browser compatible
|
||||
export {
|
||||
GetServiceInterface,
|
||||
getServiceInterface,
|
||||
filledAddress,
|
||||
} from "./getServiceInterface"
|
||||
export { getServiceInterfaces } from "./getServiceInterfaces"
|
||||
export { once } from "./once"
|
||||
export { asError } from "./asError"
|
||||
export * as Patterns from "./patterns"
|
||||
export * from "./typeHelpers"
|
||||
export { GetSystemSmtp } from "./GetSystemSmtp"
|
||||
export { Graph, Vertex } from "./graph"
|
||||
export { inMs } from "./inMs"
|
||||
export { splitCommand } from "./splitCommand"
|
||||
export { nullIfEmpty } from "./nullIfEmpty"
|
||||
export { deepMerge, partialDiff } from "./deepMerge"
|
||||
export { deepEqual } from "./deepEqual"
|
||||
export * as regexes from "./regexes"
|
||||
export { stringFromStdErrOut } from "./stringFromStdErrOut"
|
||||
} from './getServiceInterface'
|
||||
export { getServiceInterfaces } from './getServiceInterfaces'
|
||||
export { once } from './once'
|
||||
export { asError } from './asError'
|
||||
export * as Patterns from './patterns'
|
||||
export * from './typeHelpers'
|
||||
export { GetSystemSmtp } from './GetSystemSmtp'
|
||||
export { Graph, Vertex } from './graph'
|
||||
export { inMs } from './inMs'
|
||||
export { splitCommand } from './splitCommand'
|
||||
export { nullIfEmpty } from './nullIfEmpty'
|
||||
export { deepMerge, partialDiff } from './deepMerge'
|
||||
export { deepEqual } from './deepEqual'
|
||||
export * as regexes from './regexes'
|
||||
export { stringFromStdErrOut } from './stringFromStdErrOut'
|
||||
|
||||
@@ -8,9 +8,9 @@ export class IpAddress {
|
||||
}
|
||||
static parse(address: string): IpAddress {
|
||||
let octets
|
||||
if (address.includes(":")) {
|
||||
if (address.includes(':')) {
|
||||
octets = new Array(16).fill(0)
|
||||
const segs = address.split(":")
|
||||
const segs = address.split(':')
|
||||
let idx = 0
|
||||
let octIdx = 0
|
||||
while (segs[idx]) {
|
||||
@@ -31,23 +31,23 @@ export class IpAddress {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
octets = address.split(".").map(Number)
|
||||
if (octets.length !== 4) throw new Error("invalid ipv4 address")
|
||||
octets = address.split('.').map(Number)
|
||||
if (octets.length !== 4) throw new Error('invalid ipv4 address')
|
||||
}
|
||||
if (octets.some((o) => isNaN(o) || o > 255)) {
|
||||
throw new Error("invalid ip address")
|
||||
throw new Error('invalid ip address')
|
||||
}
|
||||
return new IpAddress(octets, address)
|
||||
}
|
||||
static fromOctets(octets: number[]) {
|
||||
if (octets.length == 4) {
|
||||
if (octets.some((o) => o > 255)) {
|
||||
throw new Error("invalid ip address")
|
||||
throw new Error('invalid ip address')
|
||||
}
|
||||
return new IpAddress(octets, octets.join("."))
|
||||
return new IpAddress(octets, octets.join('.'))
|
||||
} else if (octets.length == 16) {
|
||||
if (octets.some((o) => o > 255)) {
|
||||
throw new Error("invalid ip address")
|
||||
throw new Error('invalid ip address')
|
||||
}
|
||||
let pre = octets.slice(0, 8)
|
||||
while (pre[pre.length - 1] == 0) {
|
||||
@@ -58,12 +58,12 @@ export class IpAddress {
|
||||
post.unshift()
|
||||
}
|
||||
if (pre.length + post.length == 16) {
|
||||
return new IpAddress(octets, octets.join(":"))
|
||||
return new IpAddress(octets, octets.join(':'))
|
||||
} else {
|
||||
return new IpAddress(octets, pre.join(":") + "::" + post.join(":"))
|
||||
return new IpAddress(octets, pre.join(':') + '::' + post.join(':'))
|
||||
}
|
||||
} else {
|
||||
throw new Error("invalid ip address")
|
||||
throw new Error('invalid ip address')
|
||||
}
|
||||
}
|
||||
isIpv4(): boolean {
|
||||
@@ -88,7 +88,7 @@ export class IpAddress {
|
||||
}
|
||||
}
|
||||
if (octets[0] > 255) {
|
||||
throw new Error("overflow incrementing ip")
|
||||
throw new Error('overflow incrementing ip')
|
||||
}
|
||||
return IpAddress.fromOctets(octets)
|
||||
}
|
||||
@@ -105,12 +105,12 @@ export class IpAddress {
|
||||
}
|
||||
}
|
||||
if (octets[0] < 0) {
|
||||
throw new Error("underflow decrementing ip")
|
||||
throw new Error('underflow decrementing ip')
|
||||
}
|
||||
return IpAddress.fromOctets(octets)
|
||||
}
|
||||
cmp(other: string | IpAddress): -1 | 0 | 1 {
|
||||
if (typeof other === "string") other = IpAddress.parse(other)
|
||||
if (typeof other === 'string') other = IpAddress.parse(other)
|
||||
const len = Math.max(this.octets.length, other.octets.length)
|
||||
for (let i = 0; i < len; i++) {
|
||||
const left = this.octets[i] || 0
|
||||
@@ -130,7 +130,7 @@ export class IpAddress {
|
||||
) {
|
||||
// already rendered
|
||||
} else if (this.octets.length === 4) {
|
||||
this.renderedAddress = this.octets.join(".")
|
||||
this.renderedAddress = this.octets.join('.')
|
||||
this.renderedOctets = [...this.octets]
|
||||
} else if (this.octets.length === 16) {
|
||||
const contigZeros = this.octets.reduce(
|
||||
@@ -149,12 +149,12 @@ export class IpAddress {
|
||||
{ start: 0, end: 0, current: 0 },
|
||||
)
|
||||
if (contigZeros.end - contigZeros.start >= 2) {
|
||||
return `${this.octets.slice(0, contigZeros.start).join(":")}::${this.octets.slice(contigZeros.end).join(":")}`
|
||||
return `${this.octets.slice(0, contigZeros.start).join(':')}::${this.octets.slice(contigZeros.end).join(':')}`
|
||||
}
|
||||
this.renderedAddress = this.octets.join(":")
|
||||
this.renderedAddress = this.octets.join(':')
|
||||
this.renderedOctets = [...this.octets]
|
||||
} else {
|
||||
console.warn("invalid octet length for IpAddress", this.octets)
|
||||
console.warn('invalid octet length for IpAddress', this.octets)
|
||||
}
|
||||
return this.renderedAddress
|
||||
}
|
||||
@@ -170,18 +170,18 @@ export class IpNet extends IpAddress {
|
||||
}
|
||||
static fromIpPrefix(ip: IpAddress, prefix: number): IpNet {
|
||||
if (prefix > ip.octets.length * 8) {
|
||||
throw new Error("invalid prefix")
|
||||
throw new Error('invalid prefix')
|
||||
}
|
||||
return new IpNet(ip.octets, prefix, ip.address)
|
||||
}
|
||||
static parse(ipnet: string): IpNet {
|
||||
const [address, prefixStr] = ipnet.split("/", 2)
|
||||
const [address, prefixStr] = ipnet.split('/', 2)
|
||||
const ip = IpAddress.parse(address)
|
||||
const prefix = Number(prefixStr)
|
||||
return IpNet.fromIpPrefix(ip, prefix)
|
||||
}
|
||||
contains(address: string | IpAddress | IpNet): boolean {
|
||||
if (typeof address === "string") address = IpAddress.parse(address)
|
||||
if (typeof address === 'string') address = IpAddress.parse(address)
|
||||
if (address instanceof IpNet && address.prefix < this.prefix) return false
|
||||
if (this.octets.length !== address.octets.length) return false
|
||||
let prefix = this.prefix
|
||||
@@ -235,14 +235,14 @@ export class IpNet extends IpAddress {
|
||||
}
|
||||
|
||||
export const PRIVATE_IPV4_RANGES = [
|
||||
IpNet.parse("127.0.0.0/8"),
|
||||
IpNet.parse("10.0.0.0/8"),
|
||||
IpNet.parse("172.16.0.0/12"),
|
||||
IpNet.parse("192.168.0.0/16"),
|
||||
IpNet.parse('127.0.0.0/8'),
|
||||
IpNet.parse('10.0.0.0/8'),
|
||||
IpNet.parse('172.16.0.0/12'),
|
||||
IpNet.parse('192.168.0.0/16'),
|
||||
]
|
||||
|
||||
export const IPV4_LOOPBACK = IpNet.parse("127.0.0.0/8")
|
||||
export const IPV6_LOOPBACK = IpNet.parse("::1/128")
|
||||
export const IPV6_LINK_LOCAL = IpNet.parse("fe80::/10")
|
||||
export const IPV4_LOOPBACK = IpNet.parse('127.0.0.0/8')
|
||||
export const IPV6_LOOPBACK = IpNet.parse('::1/128')
|
||||
export const IPV6_LINK_LOCAL = IpNet.parse('fe80::/10')
|
||||
|
||||
export const CGNAT = IpNet.parse("100.64.0.0/10")
|
||||
export const CGNAT = IpNet.parse('100.64.0.0/10')
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import { Pattern } from "../actions/input/inputSpecTypes"
|
||||
import * as regexes from "./regexes"
|
||||
import { Pattern } from '../actions/input/inputSpecTypes'
|
||||
import * as regexes from './regexes'
|
||||
|
||||
export const ipv6: Pattern = {
|
||||
regex: regexes.ipv6.matches(),
|
||||
description: "Must be a valid IPv6 address",
|
||||
description: 'Must be a valid IPv6 address',
|
||||
}
|
||||
|
||||
export const ipv4: Pattern = {
|
||||
regex: regexes.ipv4.matches(),
|
||||
description: "Must be a valid IPv4 address",
|
||||
description: 'Must be a valid IPv4 address',
|
||||
}
|
||||
|
||||
export const hostname: Pattern = {
|
||||
regex: regexes.hostname.matches(),
|
||||
description: "Must be a valid hostname",
|
||||
description: 'Must be a valid hostname',
|
||||
}
|
||||
|
||||
export const localHostname: Pattern = {
|
||||
@@ -28,7 +28,7 @@ export const torHostname: Pattern = {
|
||||
|
||||
export const url: Pattern = {
|
||||
regex: regexes.url.matches(),
|
||||
description: "Must be a valid URL",
|
||||
description: 'Must be a valid URL',
|
||||
}
|
||||
|
||||
export const localUrl: Pattern = {
|
||||
@@ -44,26 +44,26 @@ export const torUrl: Pattern = {
|
||||
export const ascii: Pattern = {
|
||||
regex: regexes.ascii.matches(),
|
||||
description:
|
||||
"May only contain ASCII characters. See https://www.w3schools.com/charsets/ref_html_ascii.asp",
|
||||
'May only contain ASCII characters. See https://www.w3schools.com/charsets/ref_html_ascii.asp',
|
||||
}
|
||||
|
||||
export const domain: Pattern = {
|
||||
regex: regexes.domain.matches(),
|
||||
description: "Must be a valid Fully Qualified Domain Name",
|
||||
description: 'Must be a valid Fully Qualified Domain Name',
|
||||
}
|
||||
|
||||
export const email: Pattern = {
|
||||
regex: regexes.email.matches(),
|
||||
description: "Must be a valid email address",
|
||||
description: 'Must be a valid email address',
|
||||
}
|
||||
|
||||
export const emailWithName: Pattern = {
|
||||
regex: regexes.emailWithName.matches(),
|
||||
description: "Must be a valid email address, optionally with a name",
|
||||
description: 'Must be a valid email address, optionally with a name',
|
||||
}
|
||||
|
||||
export const base64: Pattern = {
|
||||
regex: regexes.base64.matches(),
|
||||
description:
|
||||
"May only contain base64 characters. See https://base64.guru/learn/base64-characters",
|
||||
'May only contain base64 characters. See https://base64.guru/learn/base64-characters',
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ export class ComposableRegex {
|
||||
}
|
||||
|
||||
export const escapeLiteral = (str: string) =>
|
||||
str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
|
||||
str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
||||
|
||||
// https://ihateregex.io/expr/ipv6/
|
||||
export const ipv6 = new ComposableRegex(
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { arrayOf, string } from "ts-matches"
|
||||
import { arrayOf, string } from 'ts-matches'
|
||||
|
||||
export const splitCommand = (
|
||||
command: string | [string, ...string[]],
|
||||
): string[] => {
|
||||
if (arrayOf(string).test(command)) return command
|
||||
return ["sh", "-c", command]
|
||||
return ['sh', '-c', command]
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import * as T from "../types"
|
||||
import * as T from '../types'
|
||||
|
||||
// prettier-ignore
|
||||
export type FlattenIntersection<T> =
|
||||
@@ -9,7 +9,7 @@ T extends object ? {} & {[P in keyof T]: T[P]} :
|
||||
export type _<T> = FlattenIntersection<T>
|
||||
|
||||
export const isKnownError = (e: unknown): e is T.KnownError =>
|
||||
e instanceof Object && ("error" in e || "error-code" in e)
|
||||
e instanceof Object && ('error' in e || 'error-code' in e)
|
||||
|
||||
declare const affine: unique symbol
|
||||
|
||||
@@ -23,41 +23,41 @@ export type NoAny<A> = NeverPossible extends A
|
||||
: A
|
||||
|
||||
type CapitalLetters =
|
||||
| "A"
|
||||
| "B"
|
||||
| "C"
|
||||
| "D"
|
||||
| "E"
|
||||
| "F"
|
||||
| "G"
|
||||
| "H"
|
||||
| "I"
|
||||
| "J"
|
||||
| "K"
|
||||
| "L"
|
||||
| "M"
|
||||
| "N"
|
||||
| "O"
|
||||
| "P"
|
||||
| "Q"
|
||||
| "R"
|
||||
| "S"
|
||||
| "T"
|
||||
| "U"
|
||||
| "V"
|
||||
| "W"
|
||||
| "X"
|
||||
| "Y"
|
||||
| "Z"
|
||||
| 'A'
|
||||
| 'B'
|
||||
| 'C'
|
||||
| 'D'
|
||||
| 'E'
|
||||
| 'F'
|
||||
| 'G'
|
||||
| 'H'
|
||||
| 'I'
|
||||
| 'J'
|
||||
| 'K'
|
||||
| 'L'
|
||||
| 'M'
|
||||
| 'N'
|
||||
| 'O'
|
||||
| 'P'
|
||||
| 'Q'
|
||||
| 'R'
|
||||
| 'S'
|
||||
| 'T'
|
||||
| 'U'
|
||||
| 'V'
|
||||
| 'W'
|
||||
| 'X'
|
||||
| 'Y'
|
||||
| 'Z'
|
||||
|
||||
type Numbers = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
|
||||
type Numbers = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
|
||||
|
||||
type CapitalChars = CapitalLetters | Numbers
|
||||
|
||||
export type ToKebab<S extends string> = S extends string
|
||||
? S extends `${infer Head}${CapitalChars}${infer Tail}` // string has a capital char somewhere
|
||||
? Head extends "" // there is a capital char in the first position
|
||||
? Tail extends ""
|
||||
? Head extends '' // there is a capital char in the first position
|
||||
? Tail extends ''
|
||||
? Lowercase<S> /* 'A' */
|
||||
: S extends `${infer Caps}${Tail}` // tail exists, has capital characters
|
||||
? Caps extends CapitalChars
|
||||
@@ -68,7 +68,7 @@ export type ToKebab<S extends string> = S extends string
|
||||
: `${ToKebab<Caps>}${ToKebab<Tail>}` /* 'AbCD','AbcD', */ /* TODO: if tail is only numbers, append without underscore */
|
||||
: never /* never reached, used for inference of caps */
|
||||
: never
|
||||
: Tail extends "" /* 'aB' 'abCD' 'ABCD' 'AB' */
|
||||
: Tail extends '' /* 'aB' 'abCD' 'ABCD' 'AB' */
|
||||
? S extends `${Head}${infer Caps}`
|
||||
? Caps extends CapitalChars
|
||||
? Head extends Lowercase<Head> /* 'abcD' */
|
||||
@@ -110,7 +110,7 @@ function test() {
|
||||
B extends A ? null : never
|
||||
) : never
|
||||
)) =>{ }
|
||||
t<"foo-bar", ToKebab<"FooBar">>(null)
|
||||
t<'foo-bar', ToKebab<'FooBar'>>(null)
|
||||
// @ts-expect-error
|
||||
t<"foo-3ar", ToKebab<"FooBar">>(null)
|
||||
t<'foo-3ar', ToKebab<'FooBar'>>(null)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user