init
This commit is contained in:
10
.u8.json
10
.u8.json
@@ -96,6 +96,16 @@
|
||||
"packageVersion": "1.0.0",
|
||||
"packageName": "bootstrap"
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-10-23T12:52:15.389Z",
|
||||
"template": "pkg",
|
||||
"values": {
|
||||
"monoRepo": true,
|
||||
"packagePrefix": "@morten-olsen/box-",
|
||||
"packageVersion": "1.0.0",
|
||||
"packageName": "resource-cloudflare"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,9 +1,39 @@
|
||||
import { KubeConfig } from '@kubernetes/client-node';
|
||||
import { ApiextensionsV1Api, CustomObjectsApi, KubeConfig, KubernetesObjectApi } from '@kubernetes/client-node';
|
||||
class K8sConfig {
|
||||
#config: KubeConfig;
|
||||
#objectsApi?: KubernetesObjectApi;
|
||||
#customObjectsApi?: CustomObjectsApi;
|
||||
#extensionsApi?: ApiextensionsV1Api;
|
||||
|
||||
class K8sConfig extends KubeConfig {
|
||||
constructor() {
|
||||
super();
|
||||
this.loadFromDefault();
|
||||
this.#config = new KubeConfig();
|
||||
this.#config.loadFromDefault();
|
||||
|
||||
}
|
||||
|
||||
public get kubeConfig() {
|
||||
return this.#config;
|
||||
}
|
||||
|
||||
public get objectsApi() {
|
||||
if (!this.#objectsApi) {
|
||||
this.#objectsApi = this.#config.makeApiClient(KubernetesObjectApi)
|
||||
}
|
||||
return this.#objectsApi;
|
||||
}
|
||||
|
||||
public get customObjectsApi() {
|
||||
if (!this.#customObjectsApi) {
|
||||
this.#customObjectsApi = this.#config.makeApiClient(CustomObjectsApi);
|
||||
}
|
||||
return this.#customObjectsApi;
|
||||
}
|
||||
|
||||
public get extensionsApi() {
|
||||
if (!this.#extensionsApi) {
|
||||
this.#extensionsApi = this.#config.makeApiClient(ApiextensionsV1Api)
|
||||
}
|
||||
return this.#extensionsApi;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Resource } from '../resources/resource/resource.js';
|
||||
import type { V1CustomResourceDefinition } from '@kubernetes/client-node';
|
||||
|
||||
import { Resource } from '../resources/resource/resource.js';
|
||||
|
||||
class CRD extends Resource<V1CustomResourceDefinition> {
|
||||
public static readonly apiVersion = 'apiextensions.k8s.io/v1';
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Resource } from '../resources/resource/resource.js';
|
||||
import type { V1Deployment } from '@kubernetes/client-node';
|
||||
|
||||
import { Resource } from '../resources/resource/resource.js';
|
||||
|
||||
class Deployment extends Resource<V1Deployment> {
|
||||
public static readonly apiVersion = 'apps/v1';
|
||||
public static readonly kind = 'Deployment';
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Resource } from '../resources/resource/resource.js';
|
||||
import type { V1Namespace } from '@kubernetes/client-node';
|
||||
|
||||
import { Resource } from '../resources/resource/resource.js';
|
||||
|
||||
class Namespace extends Resource<V1Namespace> {
|
||||
public static readonly apiVersion = 'v1';
|
||||
public static readonly kind = 'Namespace';
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Resource } from '../resources/resource/resource.js';
|
||||
import type { V1PersistentVolume } from '@kubernetes/client-node';
|
||||
|
||||
import { Resource } from '../resources/resource/resource.js';
|
||||
|
||||
class PersistentVolume extends Resource<V1PersistentVolume> {
|
||||
public static readonly apiVersion = 'v1';
|
||||
public static readonly kind = 'PersistentVolume';
|
||||
|
||||
@@ -1,23 +1,19 @@
|
||||
import { Resource, type ResourceOptions } from '../resources/resource/resource.js';
|
||||
import type { KubernetesObject, V1Secret } from '@kubernetes/client-node';
|
||||
import { decodeSecret, encodeSecret } from '../utils/utils.secrets.js';
|
||||
|
||||
import { decodeSecret, encodeSecret } from '../utils/utils.secrets.js';
|
||||
import { Resource } from '../exports.js';
|
||||
|
||||
type SetOptions<T extends Record<string, string | undefined>> = T | ((current: T | undefined) => T | Promise<T>);
|
||||
|
||||
class Secret<T extends Record<string, string> = Record<string, string>> extends Resource<V1Secret> {
|
||||
class Secret extends Resource<V1Secret> {
|
||||
public static readonly apiVersion = 'v1';
|
||||
public static readonly kind = 'Secret';
|
||||
|
||||
constructor(options: ResourceOptions<V1Secret>) {
|
||||
super(options);
|
||||
}
|
||||
|
||||
public get value() {
|
||||
return decodeSecret(this.data) as T | undefined;
|
||||
return decodeSecret(this.data);
|
||||
}
|
||||
|
||||
public set = async (options: SetOptions<T>, data?: KubernetesObject) => {
|
||||
public set = async (options: SetOptions<Record<string, string>>, data?: KubernetesObject) => {
|
||||
const value = typeof options === 'function' ? await Promise.resolve(options(this.value)) : options;
|
||||
await this.ensure({
|
||||
...data,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Resource } from '../resources/resource/resource.js';
|
||||
import type { V1Service } from '@kubernetes/client-node';
|
||||
|
||||
import { Resource } from '../resources/resource/resource.js';
|
||||
|
||||
class Service extends Resource<V1Service> {
|
||||
public static readonly apiVersion = 'v1';
|
||||
public static readonly kind = 'Service';
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Resource } from '../resources/resource/resource.js';
|
||||
import type { V1StatefulSet } from '@kubernetes/client-node';
|
||||
|
||||
import { Resource } from '../resources/resource/resource.js';
|
||||
|
||||
class StatefulSet extends Resource<V1StatefulSet> {
|
||||
public static readonly apiVersion = 'apps/v1';
|
||||
public static readonly kind = 'StatefulSet';
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Resource } from '../resources/resource/resource.js';
|
||||
import type { V1StorageClass } from '@kubernetes/client-node';
|
||||
|
||||
import { Resource } from '../resources/resource/resource.js';
|
||||
|
||||
class StorageClass extends Resource<V1StorageClass> {
|
||||
public static readonly apiVersion = 'storage.k8s.io/v1';
|
||||
public static readonly kind = 'StorageClass';
|
||||
|
||||
@@ -1,19 +1,10 @@
|
||||
import { CRD } from "./core.crd.js";
|
||||
import { Deployment } from "./core.deployment.js";
|
||||
import { Namespace } from "./core.namespace.js";
|
||||
import { PersistentVolume } from "./core.pv.js";
|
||||
import { Secret } from "./core.secret.js";
|
||||
import { Service } from "./core.service.js";
|
||||
import { StatefulSet } from "./core.stateful-set.js";
|
||||
import { StorageClass } from "./core.storage-class.js";
|
||||
import { CRD } from './core.crd.js';
|
||||
import { Deployment } from './core.deployment.js';
|
||||
import { Namespace } from './core.namespace.js';
|
||||
import { PersistentVolume } from './core.pv.js';
|
||||
import { Secret } from './core.secret.js';
|
||||
import { Service } from './core.service.js';
|
||||
import { StatefulSet } from './core.stateful-set.js';
|
||||
import { StorageClass } from './core.storage-class.js';
|
||||
|
||||
export {
|
||||
CRD,
|
||||
Deployment,
|
||||
Namespace,
|
||||
PersistentVolume,
|
||||
Secret,
|
||||
Service,
|
||||
StatefulSet,
|
||||
StorageClass,
|
||||
}
|
||||
export { CRD, Deployment, Namespace, PersistentVolume, Secret, Service, StatefulSet, StorageClass };
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Services } from '@morten-olsen/box-utils/services';
|
||||
|
||||
import { ResourceService } from './resources/resources.js';
|
||||
|
||||
class K8sOperator {
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { z, type ZodType } from 'zod';
|
||||
import { CustomObjectsApi, PatchStrategy, setHeaderOptions, type KubernetesObject } from '@kubernetes/client-node';
|
||||
import { PatchStrategy, setHeaderOptions, type KubernetesObject } from '@kubernetes/client-node';
|
||||
import { CronJob, CronTime } from 'cron';
|
||||
|
||||
import { K8sConfig } from '../../config/config.js';
|
||||
|
||||
import { CoalescingQueue } from '@morten-olsen/box-utils/coalescing-queue';
|
||||
import { FINALIZER } from '@morten-olsen/box-utils/consts';
|
||||
|
||||
import { NotReadyError } from '../../errors/errors.js';
|
||||
|
||||
import { Resource, type ResourceOptions } from './resource.js';
|
||||
import { NotReadyError } from '../../errors/errors.js'
|
||||
import { K8sConfig } from '../../config/config.js';
|
||||
import type { ResourceClass } from '../resources.service.js';
|
||||
|
||||
const customResourceStatusSchema = z.object({
|
||||
observedGeneration: z.number().optional(),
|
||||
@@ -35,7 +37,7 @@ class CustomResource<TSpec extends ZodType> extends Resource<
|
||||
public static readonly apiVersion: string;
|
||||
public static readonly status = customResourceStatusSchema;
|
||||
public static readonly labels: Record<string, string> = {};
|
||||
public static readonly dependsOn?: Resource<KubernetesObject>[];
|
||||
public static readonly dependsOn?: ResourceClass<ExplicitAny>[];
|
||||
|
||||
#reconcileQueue: CoalescingQueue<void>;
|
||||
#cron: CronJob;
|
||||
@@ -45,9 +47,31 @@ class CustomResource<TSpec extends ZodType> extends Resource<
|
||||
this.#reconcileQueue = new CoalescingQueue({
|
||||
action: async () => {
|
||||
try {
|
||||
if (!this.exists || this.manifest?.metadata?.deletionTimestamp) {
|
||||
if (!this.exists) {
|
||||
return;
|
||||
}
|
||||
// TODO: Read FINALIZER
|
||||
// const finalizers = this.metadata?.finalizers || [];
|
||||
if (this.manifest?.metadata?.deletionTimestamp) {
|
||||
await this.destroy?.();
|
||||
// if (this.metadata?.finalizers?.includes(FINALIZER)) {
|
||||
// await this.patch({
|
||||
// metadata: {
|
||||
// finalizers: finalizers.filter((f) => f !== FINALIZER),
|
||||
// deletionTimestamp: this.metadata?.deletionTimestamp,
|
||||
// },
|
||||
// } as any)
|
||||
// }
|
||||
return;
|
||||
}
|
||||
// if (this.destroy && !finalizers.includes(FINALIZER)) {
|
||||
// return await this.patch({
|
||||
// metadata: {
|
||||
// finalizers: [...finalizers, FINALIZER]
|
||||
// },
|
||||
// spec: this.spec!,
|
||||
// });
|
||||
// }
|
||||
await this.markSeen();
|
||||
await this.reconcile?.();
|
||||
await this.markReady();
|
||||
@@ -111,6 +135,8 @@ class CustomResource<TSpec extends ZodType> extends Resource<
|
||||
};
|
||||
|
||||
public reconcile?: () => Promise<void>;
|
||||
public destroy?: () => Promise<void>;
|
||||
|
||||
public queueReconcile = () => {
|
||||
return this.#reconcileQueue.run();
|
||||
};
|
||||
@@ -151,7 +177,7 @@ class CustomResource<TSpec extends ZodType> extends Resource<
|
||||
public patchStatus = (status: Partial<z.infer<typeof customResourceStatusSchema>>) =>
|
||||
this.queue.add(async () => {
|
||||
const config = this.services.get(K8sConfig);
|
||||
const customObjectsApi = config.makeApiClient(CustomObjectsApi);
|
||||
const customObjectsApi = config.customObjectsApi;
|
||||
if (this.scope === 'Cluster') {
|
||||
await customObjectsApi.patchClusterCustomObjectStatus(
|
||||
{
|
||||
@@ -180,4 +206,3 @@ class CustomResource<TSpec extends ZodType> extends Resource<
|
||||
}
|
||||
|
||||
export { CustomResource, type CustomResourceOptions };
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { EventEmitter } from '@morten-olsen/box-utils/event-emitter';
|
||||
|
||||
import type { ResourceClass } from '../resources.js';
|
||||
|
||||
import type { ResourceEvents } from './resource.js';
|
||||
@@ -19,6 +20,9 @@ class ResourceReference<T extends ResourceClass<ExplicitAny>> extends EventEmitt
|
||||
}
|
||||
|
||||
public set current(value: InstanceType<T> | undefined) {
|
||||
if (value === this.#current?.instance) {
|
||||
return;
|
||||
}
|
||||
const previous = this.#current;
|
||||
this.#current?.unsubscribe();
|
||||
if (value) {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { ApiException, KubernetesObjectApi, PatchStrategy, type KubernetesObject } from '@kubernetes/client-node';
|
||||
import deepEqual from 'deep-equal';
|
||||
|
||||
import { ResourceService } from '../resources.service.js';
|
||||
import type { Services } from '@morten-olsen/box-utils/services';
|
||||
import { EventEmitter } from '@morten-olsen/box-utils/event-emitter';
|
||||
import { Queue } from '@morten-olsen/box-utils/queue';
|
||||
import { isDeepSubset } from '@morten-olsen/box-utils/objects';
|
||||
|
||||
import { ResourceService } from '../resources.service.js';
|
||||
import { K8sConfig } from '../../config/config.js';
|
||||
|
||||
type ResourceSelector = {
|
||||
@@ -41,7 +41,7 @@ class Resource<T extends KubernetesObject> extends EventEmitter<ResourceEvents<T
|
||||
return this.#queue;
|
||||
}
|
||||
|
||||
public get services() {
|
||||
public get services(): Services {
|
||||
return this.#options.services;
|
||||
}
|
||||
|
||||
@@ -144,7 +144,7 @@ class Resource<T extends KubernetesObject> extends EventEmitter<ResourceEvents<T
|
||||
this.#queue.add(async () => {
|
||||
const { services } = this.#options;
|
||||
const config = services.get(K8sConfig);
|
||||
const objectsApi = config.makeApiClient(KubernetesObjectApi);
|
||||
const { objectsApi } = config;
|
||||
const body = {
|
||||
...patch,
|
||||
apiVersion: this.selector.apiVersion,
|
||||
@@ -190,4 +190,3 @@ class Resource<T extends KubernetesObject> extends EventEmitter<ResourceEvents<T
|
||||
}
|
||||
|
||||
export { Resource, type ResourceOptions, type ResourceEvents };
|
||||
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
|
||||
import { ApiException, ApiextensionsV1Api, type KubernetesObject } from '@kubernetes/client-node';
|
||||
import type { ZodType } from 'zod';
|
||||
import { EventEmitter } from '@morten-olsen/box-utils/event-emitter';
|
||||
import type { Services } from '@morten-olsen/box-utils/services';
|
||||
|
||||
import { WatcherService } from '../watchers/watchers.js';
|
||||
import { K8sConfig } from '../config/config.js';
|
||||
|
||||
import { createManifest } from './resources.utils.js';
|
||||
import { Resource, type ResourceOptions } from './resource/resource.js';
|
||||
import { EventEmitter } from '@morten-olsen/box-utils/event-emitter';
|
||||
import type { Services } from '@morten-olsen/box-utils/services';
|
||||
|
||||
type ResourceClass<T extends KubernetesObject> = (new (options: ResourceOptions<T>) => InstanceType<typeof Resource<T>>) & {
|
||||
type ResourceClass<T extends KubernetesObject> = (new (
|
||||
options: ResourceOptions<T>,
|
||||
) => InstanceType<typeof Resource<T>>) & {
|
||||
apiVersion: string;
|
||||
kind: string;
|
||||
plural?: string;
|
||||
@@ -47,13 +48,17 @@ class ResourceService extends EventEmitter<ResourceServiceEvents> {
|
||||
|
||||
public register = async (...resources: ResourceClass<ExplicitAny>[]) => {
|
||||
for (const resource of resources) {
|
||||
if (!this.#registry.has(resource)) {
|
||||
this.#registry.set(resource, {
|
||||
apiVersion: resource.apiVersion,
|
||||
kind: resource.kind,
|
||||
plural: resource.plural,
|
||||
resources: [],
|
||||
});
|
||||
if (this.#registry.has(resource)) {
|
||||
return;
|
||||
}
|
||||
this.#registry.set(resource, {
|
||||
apiVersion: resource.apiVersion,
|
||||
kind: resource.kind,
|
||||
plural: resource.plural,
|
||||
resources: [],
|
||||
});
|
||||
if ('dependsOn' in resource && Array.isArray(resource.dependsOn)) {
|
||||
await this.register(...resource.dependsOn as ResourceClass<ExplicitAny>[]);
|
||||
}
|
||||
const watcherService = this.#services.get(WatcherService);
|
||||
const watcher = watcherService.create({
|
||||
@@ -65,7 +70,7 @@ class ResourceService extends EventEmitter<ResourceServiceEvents> {
|
||||
if (!name) {
|
||||
return;
|
||||
}
|
||||
const current = this.get(resource, name, namespace);
|
||||
const current = this.#get(resource, name, namespace, manifest);
|
||||
current.manifest = manifest;
|
||||
});
|
||||
await watcher.start();
|
||||
@@ -76,7 +81,7 @@ class ResourceService extends EventEmitter<ResourceServiceEvents> {
|
||||
return (this.#registry.get(type)?.resources?.filter((r) => r.exists) as InstanceType<T>[]) || [];
|
||||
};
|
||||
|
||||
public get = <T extends ResourceClass<ExplicitAny>>(type: T, name: string, namespace?: string) => {
|
||||
#get = <T extends ResourceClass<ExplicitAny>>(type: T, name: string, namespace?: string, manifest?: unknown) => {
|
||||
let resourceRegistry = this.#registry.get(type);
|
||||
if (!resourceRegistry) {
|
||||
resourceRegistry = {
|
||||
@@ -98,6 +103,7 @@ class ResourceService extends EventEmitter<ResourceServiceEvents> {
|
||||
namespace,
|
||||
},
|
||||
services: this.#services,
|
||||
manifest,
|
||||
});
|
||||
current.on('changed', this.emit.bind(this, 'changed', current));
|
||||
resources.push(current);
|
||||
@@ -105,9 +111,13 @@ class ResourceService extends EventEmitter<ResourceServiceEvents> {
|
||||
return current as InstanceType<T>;
|
||||
};
|
||||
|
||||
public get = <T extends ResourceClass<ExplicitAny>>(type: T, name: string, namespace?: string) => {
|
||||
return this.#get(type, name, namespace);
|
||||
};
|
||||
|
||||
public install = async (...resources: InstallableResourceClass<ExplicitAny>[]) => {
|
||||
const config = this.#services.get(K8sConfig);
|
||||
const extensionsApi = config.makeApiClient(ApiextensionsV1Api);
|
||||
const { extensionsApi } = config;
|
||||
for (const resource of resources) {
|
||||
try {
|
||||
const manifest = createManifest(resource);
|
||||
@@ -136,4 +146,3 @@ class ResourceService extends EventEmitter<ResourceServiceEvents> {
|
||||
}
|
||||
|
||||
export { ResourceService, Resource, type ResourceOptions, type ResourceClass, type InstallableResourceClass };
|
||||
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
export { CustomResource, type CustomResourceOptions } from './resource/resource.custom.js';
|
||||
export { ResourceReference } from './resource/resource.reference.js';
|
||||
export { ResourceService, Resource, type ResourceOptions, type ResourceClass, type InstallableResourceClass } from './resources.service.js';
|
||||
|
||||
export {
|
||||
ResourceService,
|
||||
Resource,
|
||||
type ResourceOptions,
|
||||
type ResourceClass,
|
||||
type InstallableResourceClass,
|
||||
} from './resources.service.js';
|
||||
|
||||
@@ -52,4 +52,3 @@ const createManifest = (defintion: InstallableResourceClass<ExplicitAny>) => {
|
||||
};
|
||||
|
||||
export { createManifest };
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { KubernetesObjectApi, makeInformer, type Informer, type KubernetesObject } from '@kubernetes/client-node';
|
||||
import { EventEmitter } from '@morten-olsen/box-utils/event-emitter';
|
||||
import type { Services } from '@morten-olsen/box-utils/services';
|
||||
|
||||
import { K8sConfig } from '../../config/config.js';
|
||||
|
||||
type ResourceChangedAction = 'add' | 'update' | 'delete';
|
||||
@@ -32,11 +33,10 @@ class Watcher<T extends KubernetesObject> extends EventEmitter<WatcherEvents<T>>
|
||||
const { services, apiVersion, kind, selector } = this.#options;
|
||||
const plural = this.#options.plural ?? kind.toLowerCase() + 's';
|
||||
const [version, group] = apiVersion.split('/').toReversed();
|
||||
const config = services.get(K8sConfig);
|
||||
const { kubeConfig, objectsApi } = services.get(K8sConfig);
|
||||
const path = group ? `/apis/${group}/${version}/${plural}` : `/api/${version}/${plural}`;
|
||||
const objectsApi = config.makeApiClient(KubernetesObjectApi);
|
||||
const informer = makeInformer<T>(
|
||||
config,
|
||||
kubeConfig,
|
||||
path,
|
||||
async () => {
|
||||
return objectsApi.list(apiVersion, kind);
|
||||
@@ -67,4 +67,3 @@ class Watcher<T extends KubernetesObject> extends EventEmitter<WatcherEvents<T>>
|
||||
}
|
||||
|
||||
export { Watcher, type WatcherOptions, type ResourceChangedAction };
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Services, destroy } from "@morten-olsen/box-utils/services";
|
||||
import { Watcher, type WatcherOptions } from "./watcher/watcher.js";
|
||||
import { Services, destroy } from '@morten-olsen/box-utils/services';
|
||||
|
||||
import { Watcher, type WatcherOptions } from './watcher/watcher.js';
|
||||
|
||||
class WatcherService {
|
||||
#services: Services;
|
||||
|
||||
1
packages/k8s/tsconfig.tsbuildinfo
Normal file
1
packages/k8s/tsconfig.tsbuildinfo
Normal file
@@ -0,0 +1 @@
|
||||
{"root":["./src/exports.ts","./src/global.d.ts","./src/operator.ts","./src/config/config.ts","./src/core/core.crd.ts","./src/core/core.deployment.ts","./src/core/core.namespace.ts","./src/core/core.pv.ts","./src/core/core.secret.ts","./src/core/core.service.ts","./src/core/core.stateful-set.ts","./src/core/core.storage-class.ts","./src/core/core.ts","./src/errors/errors.ts","./src/resources/resources.service.ts","./src/resources/resources.ts","./src/resources/resources.utils.ts","./src/resources/resource/resource.custom.ts","./src/resources/resource/resource.reference.ts","./src/resources/resource/resource.ts","./src/utils/utils.secrets.ts","./src/watchers/watchers.ts","./src/watchers/watcher/watcher.ts"],"version":"5.9.3"}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { z } from "@morten-olsen/box-k8s";
|
||||
import { z } from '@morten-olsen/box-k8s';
|
||||
|
||||
const valueOrSecret = z.object({
|
||||
value: z.string().optional(),
|
||||
@@ -16,9 +16,11 @@ const serverSpec = z.object({
|
||||
database: z.object({
|
||||
url: valueOrSecret,
|
||||
}),
|
||||
clients: z.object({
|
||||
substitutions: z.record(z.string(), z.string()).optional()
|
||||
}).optional(),
|
||||
clients: z
|
||||
.object({
|
||||
substitutions: z.record(z.string(), z.string()).optional(),
|
||||
})
|
||||
.optional(),
|
||||
});
|
||||
|
||||
export { serverSpec };
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import { CustomResource, Secret, type CustomResourceOptions } from "@morten-olsen/box-k8s";
|
||||
import { serverSpec } from "./server.schemas.js";
|
||||
import { CustomResource, NotReadyError, Secret, type CustomResourceOptions } from '@morten-olsen/box-k8s';
|
||||
import { API_VERSION } from '@morten-olsen/box-utils/consts';
|
||||
|
||||
type SecretData = {
|
||||
secret: string;
|
||||
};
|
||||
import { serverSpec } from './server.schemas.js';
|
||||
|
||||
class AuthentikServer extends CustomResource<typeof serverSpec> {
|
||||
public static readonly apiVersion = API_VERSION;
|
||||
@@ -12,16 +9,30 @@ class AuthentikServer extends CustomResource<typeof serverSpec> {
|
||||
public static readonly spec = serverSpec;
|
||||
public static readonly scope = 'Namespaced';
|
||||
|
||||
#secret: Secret<SecretData>;
|
||||
#secret: Secret;
|
||||
|
||||
constructor(options: CustomResourceOptions<typeof serverSpec>) {
|
||||
super(options);
|
||||
this.#secret = this.resources.get(Secret<SecretData>, `${this.name}-secret`, this.namespace);
|
||||
this.#secret = this.resources.get(Secret, `${this.name}-secret`, this.namespace);
|
||||
this.#secret.on('changed', this.queueReconcile);
|
||||
}
|
||||
|
||||
public reconcile = async () => {
|
||||
if (!this.#secret.value?.secret) {
|
||||
await this.#secret.set(
|
||||
{
|
||||
secret: crypto.randomUUID(),
|
||||
},
|
||||
{
|
||||
metadata: {
|
||||
ownerReferences: [this.ref],
|
||||
},
|
||||
},
|
||||
);
|
||||
throw new NotReadyError();
|
||||
}
|
||||
const { secret } = this.#secret.value;
|
||||
};
|
||||
}
|
||||
|
||||
export { AuthentikServer }
|
||||
export { AuthentikServer };
|
||||
|
||||
1
packages/resource-authentik/tsconfig.tsbuildinfo
Normal file
1
packages/resource-authentik/tsconfig.tsbuildinfo
Normal file
@@ -0,0 +1 @@
|
||||
{"root":["./src/exports.ts","./src/global.d.ts","./src/resources/oidc-client/oidc-client.ts","./src/resources/server/server.schemas.ts","./src/resources/server/server.ts"],"version":"5.9.3"}
|
||||
4
packages/resource-cloudflare/.gitignore
vendored
Normal file
4
packages/resource-cloudflare/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
/node_modules/
|
||||
/dist/
|
||||
/coverage/
|
||||
/.env
|
||||
@@ -0,0 +1,9 @@
|
||||
apiVersion: playground.homelab.olsen.cloud/v1
|
||||
kind: CloudflareAccount
|
||||
metadata:
|
||||
name: main
|
||||
spec:
|
||||
token:
|
||||
secret: cloudflare
|
||||
namespace: homelab
|
||||
key: token
|
||||
@@ -0,0 +1,10 @@
|
||||
apiVersion: playground.homelab.olsen.cloud/v1
|
||||
kind: CloudflareDnsRecord
|
||||
metadata:
|
||||
name: test
|
||||
spec:
|
||||
account: main
|
||||
domain: olsen.cloud
|
||||
subdomain: testing1
|
||||
type: CNAME
|
||||
value: hello
|
||||
33
packages/resource-cloudflare/package.json
Normal file
33
packages/resource-cloudflare/package.json
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"type": "module",
|
||||
"main": "dist/exports.js",
|
||||
"scripts": {
|
||||
"dev": "tsx --watch src/start.ts",
|
||||
"build": "tsc --build",
|
||||
"test:unit": "vitest --run --passWithNoTests",
|
||||
"test": "pnpm run \"/^test:/\""
|
||||
},
|
||||
"packageManager": "pnpm@10.6.0",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"exports": {
|
||||
".": "./dist/exports.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@morten-olsen/box-configs": "workspace:*",
|
||||
"@morten-olsen/box-tests": "workspace:*",
|
||||
"@types/node": "24.9.1",
|
||||
"@vitest/coverage-v8": "4.0.1",
|
||||
"tsx": "^4.20.6",
|
||||
"typescript": "5.9.3",
|
||||
"vitest": "4.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@morten-olsen/box-k8s": "workspace:*",
|
||||
"@morten-olsen/box-utils": "workspace:*",
|
||||
"cloudflare": "^5.2.0"
|
||||
},
|
||||
"name": "@morten-olsen/box-resource-cloudflare",
|
||||
"version": "1.0.0"
|
||||
}
|
||||
0
packages/resource-cloudflare/src/exports.ts
Normal file
0
packages/resource-cloudflare/src/exports.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { z } from '@morten-olsen/box-k8s';
|
||||
|
||||
const cloudflareAccountSchema = z.object({
|
||||
token: z.object({
|
||||
secret: z.string(),
|
||||
namespace: z.string(),
|
||||
key: z.string(),
|
||||
}),
|
||||
allowedNamespaces: z.array(z.string()).optional(),
|
||||
});
|
||||
|
||||
export { cloudflareAccountSchema };
|
||||
@@ -0,0 +1,48 @@
|
||||
import { CustomResource, NotReadyError, ResourceReference, Secret, type CustomResourceOptions } from "@morten-olsen/box-k8s";
|
||||
import { cloudflareAccountSchema } from "./account.schemas.js";
|
||||
import { API_VERSION } from "@morten-olsen/box-utils/consts";
|
||||
|
||||
class CloudflareAccountResource extends CustomResource<typeof cloudflareAccountSchema> {
|
||||
public static readonly apiVersion = API_VERSION;
|
||||
public static readonly kind = "CloudflareAccount";
|
||||
public static readonly spec = cloudflareAccountSchema;
|
||||
public static readonly scope = "Cluster";
|
||||
public static readonly dependsOn = [Secret];
|
||||
|
||||
#secret: ResourceReference<typeof Secret>;
|
||||
|
||||
constructor(options: CustomResourceOptions<typeof cloudflareAccountSchema>) {
|
||||
super(options);
|
||||
this.#secret = new ResourceReference(this.#getSecret());
|
||||
this.#secret.on('changed', this.queueReconcile);
|
||||
}
|
||||
|
||||
public get token() {
|
||||
const spec = this.spec;
|
||||
if (!spec) {
|
||||
return;
|
||||
}
|
||||
return this.#secret.current?.value?.[spec.token.key];
|
||||
}
|
||||
|
||||
#getSecret = () => {
|
||||
const spec = this.spec;
|
||||
if (!spec) {
|
||||
return;
|
||||
}
|
||||
return this.resources.get(
|
||||
Secret,
|
||||
spec.token.secret,
|
||||
spec.token.namespace,
|
||||
)
|
||||
}
|
||||
|
||||
public reconcile = async () => {
|
||||
this.#secret.current = this.#getSecret();
|
||||
if (!this.token) {
|
||||
throw new NotReadyError('Token not found');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export { CloudflareAccountResource };
|
||||
@@ -0,0 +1,13 @@
|
||||
import { z } from '@morten-olsen/box-k8s';
|
||||
|
||||
const cloudflareDnsRecordSchema = z.object({
|
||||
account: z.string(),
|
||||
domain: z.string(),
|
||||
subdomain: z.string().optional(),
|
||||
type: z.enum(['A', 'CNAME', 'MX']),
|
||||
proxy: z.boolean().optional(),
|
||||
value: z.string(),
|
||||
ttl: z.number().optional(),
|
||||
});
|
||||
|
||||
export { cloudflareDnsRecordSchema };
|
||||
@@ -0,0 +1,40 @@
|
||||
import { CustomResource, NotReadyError } from '@morten-olsen/box-k8s';
|
||||
|
||||
import { CloudflareService } from '../../services/cloudflare/cloudflare.js';
|
||||
|
||||
import { cloudflareDnsRecordSchema } from './dns-record.schemas.js';
|
||||
import { API_VERSION } from '@morten-olsen/box-utils/consts';
|
||||
import { CloudflareAccountResource } from '../account/account.js';
|
||||
|
||||
class CloudflareDnsRecordResource extends CustomResource<typeof cloudflareDnsRecordSchema> {
|
||||
public static readonly apiVersion = API_VERSION;
|
||||
public static readonly kind = "CloudflareDnsRecord";
|
||||
public static readonly spec = cloudflareDnsRecordSchema;
|
||||
public static readonly scope = "Namespaced";
|
||||
public static readonly dependsOn = [CloudflareAccountResource];
|
||||
|
||||
public get dnsId() {
|
||||
return `homelab|${this.namespace}|${this.name}`
|
||||
}
|
||||
|
||||
public reconcile = async () => {
|
||||
if (!this.spec) {
|
||||
throw new NotReadyError('Missing spec');
|
||||
}
|
||||
const service = this.services.get(CloudflareService);
|
||||
const { getDnsRecord, ensrureDnsRecord } = service.getAccount(this.spec.account);
|
||||
await ensrureDnsRecord(this.dnsId, this.spec);
|
||||
};
|
||||
|
||||
public destroy = async () => {
|
||||
|
||||
if (!this.spec) {
|
||||
throw new NotReadyError('Missing spec');
|
||||
}
|
||||
const service = this.services.get(CloudflareService);
|
||||
const { removeDnsRecord } = service.getAccount(this.spec.account);
|
||||
await removeDnsRecord(this.dnsId, this.spec.domain);
|
||||
}
|
||||
}
|
||||
|
||||
export { CloudflareDnsRecordResource, cloudflareDnsRecordSchema };
|
||||
@@ -0,0 +1,121 @@
|
||||
import type { Services } from "@morten-olsen/box-utils/services";
|
||||
import type { CloudflareAccountResource } from "../../resources/account/account.js"
|
||||
import API from 'cloudflare';
|
||||
import type { Zone } from "cloudflare/resources/zones/zones.mjs";
|
||||
import type { z } from "@morten-olsen/box-k8s";
|
||||
import type { cloudflareDnsRecordSchema } from "../../resources/dns-record/dns-record.schemas.js";
|
||||
|
||||
type CloudflareAccountOptions = {
|
||||
services: Services;
|
||||
resource: CloudflareAccountResource;
|
||||
}
|
||||
|
||||
class CloudflareAccount {
|
||||
#options: CloudflareAccountOptions;
|
||||
#api?: API;
|
||||
#zones: Map<string, Promise<Zone | undefined>>
|
||||
|
||||
constructor(options: CloudflareAccountOptions) {
|
||||
this.#zones = new Map();
|
||||
this.#options = options;
|
||||
this.#api = new API({
|
||||
apiToken: this.token,
|
||||
});
|
||||
}
|
||||
|
||||
public get token() {
|
||||
return this.#options.resource.token;
|
||||
}
|
||||
|
||||
public get api() {
|
||||
if (!this.#api) {
|
||||
this.#api = new API({
|
||||
apiToken: this.token,
|
||||
})
|
||||
}
|
||||
return this.#api;
|
||||
}
|
||||
|
||||
#getZone = async (name: string) => {
|
||||
const zones = await this.api.zones.list({
|
||||
name,
|
||||
})
|
||||
const [zone] = zones.result;
|
||||
return zone;
|
||||
}
|
||||
|
||||
public getZone = async (name: string) => {
|
||||
if (!this.#zones.has(name)) {
|
||||
this.#zones.set(name, this.#getZone(name));
|
||||
}
|
||||
const current = this.#zones.get(name);
|
||||
return await current;
|
||||
}
|
||||
|
||||
public getDnsRecord = async (id: string, domain: string) => {
|
||||
const zone = await this.getZone(domain);
|
||||
|
||||
if (!zone) {
|
||||
return;
|
||||
}
|
||||
|
||||
const dnsRecords = await this.api.dns.records.list({
|
||||
zone_id: zone.id,
|
||||
comment: {
|
||||
exact: id,
|
||||
},
|
||||
})
|
||||
const [dnsRecord] = dnsRecords.result;
|
||||
|
||||
return dnsRecord;
|
||||
}
|
||||
|
||||
public removeDnsRecord = async (id: string, domain: string) => {
|
||||
const zone = await this.getZone(domain);
|
||||
if (!zone) {
|
||||
return;
|
||||
}
|
||||
const record = await this.getDnsRecord(id, domain);
|
||||
if (!record) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.api.dns.records.delete(record.id, {
|
||||
zone_id: zone.id,
|
||||
});
|
||||
}
|
||||
|
||||
public ensrureDnsRecord = async (id: string, options: z.infer<typeof cloudflareDnsRecordSchema>) => {
|
||||
const { domain, subdomain, value, type, ttl = 1, proxy } = options;
|
||||
const zone = await this.getZone(options.domain);
|
||||
if (!zone) {
|
||||
throw new Error('Zone not found');
|
||||
}
|
||||
const current = await this.getDnsRecord(id, domain);
|
||||
|
||||
if (!current) {
|
||||
await this.api.dns.records.create({
|
||||
zone_id: zone.id,
|
||||
type,
|
||||
name: subdomain ? `${subdomain}.${domain}` : domain,
|
||||
content: value,
|
||||
comment: id,
|
||||
ttl,
|
||||
proxied: proxy,
|
||||
})
|
||||
} else {
|
||||
|
||||
await this.api.dns.records.update(current.id, {
|
||||
zone_id: zone.id,
|
||||
type,
|
||||
name: subdomain ? `${subdomain}.${domain}` : domain,
|
||||
content: value,
|
||||
comment: id,
|
||||
ttl,
|
||||
proxied: proxy,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export { CloudflareAccount };
|
||||
@@ -0,0 +1,32 @@
|
||||
import type { Services } from "@morten-olsen/box-utils/services";
|
||||
import { CloudflareAccount } from "./cloudflare.account.js";
|
||||
import { ResourceService } from "@morten-olsen/box-k8s";
|
||||
import { CloudflareAccountResource } from "../../resources/account/account.js";
|
||||
|
||||
class CloudflareService {
|
||||
#services: Services;
|
||||
#instances: Map<string, CloudflareAccount>;
|
||||
|
||||
constructor(services: Services) {
|
||||
this.#services = services;
|
||||
this.#instances = new Map();
|
||||
}
|
||||
|
||||
public getAccount = (name: string) => {
|
||||
if (!this.#instances.has(name)) {
|
||||
const resourceService = this.#services.get(ResourceService);
|
||||
const resource = resourceService.get(CloudflareAccountResource, name);
|
||||
this.#instances.set(name, new CloudflareAccount({
|
||||
resource,
|
||||
services: this.#services,
|
||||
}))
|
||||
}
|
||||
const current = this.#instances.get(name);
|
||||
if (!current) {
|
||||
throw new Error('Could not get cloudflare account');
|
||||
}
|
||||
return current;
|
||||
}
|
||||
}
|
||||
|
||||
export { CloudflareService };
|
||||
13
packages/resource-cloudflare/src/start.ts
Normal file
13
packages/resource-cloudflare/src/start.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { K8sOperator } from '@morten-olsen/box-k8s';
|
||||
import { CloudflareAccountResource } from './resources/account/account.js';
|
||||
import { CloudflareDnsRecordResource } from './resources/dns-record/dns-record.js';
|
||||
|
||||
const operator = new K8sOperator();
|
||||
await operator.resources.install(
|
||||
CloudflareAccountResource,
|
||||
CloudflareDnsRecordResource,
|
||||
)
|
||||
await operator.resources.register(
|
||||
CloudflareAccountResource,
|
||||
CloudflareDnsRecordResource,
|
||||
)
|
||||
9
packages/resource-cloudflare/tsconfig.json
Normal file
9
packages/resource-cloudflare/tsconfig.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist"
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts"
|
||||
],
|
||||
"extends": "@morten-olsen/box-configs/tsconfig.json"
|
||||
}
|
||||
12
packages/resource-cloudflare/vitest.config.ts
Normal file
12
packages/resource-cloudflare/vitest.config.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { defineConfig } from 'vitest/config';
|
||||
import { getAliases } from '@morten-olsen/box-tests/vitest';
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default defineConfig(async () => {
|
||||
const aliases = await getAliases();
|
||||
return {
|
||||
resolve: {
|
||||
alias: aliases,
|
||||
},
|
||||
};
|
||||
});
|
||||
1
packages/resource-redis/tsconfig.tsbuildinfo
Normal file
1
packages/resource-redis/tsconfig.tsbuildinfo
Normal file
@@ -0,0 +1 @@
|
||||
{"root":["./src/exports.ts"],"version":"5.9.3"}
|
||||
@@ -1,4 +1,5 @@
|
||||
const API_GROUP = 'playground.homelab.olsen.cloud';
|
||||
const API_VERSION = `${API_GROUP}/v1`;
|
||||
const FINALIZER = `finalizer.${API_GROUP}`;
|
||||
|
||||
export { API_VERSION, API_GROUP };
|
||||
export { API_VERSION, API_GROUP, FINALIZER };
|
||||
|
||||
@@ -23,7 +23,7 @@ class EventEmitter<T extends Record<string, (...args: ExplicitAny[]) => void | P
|
||||
abortController.signal.addEventListener('abort', () => {
|
||||
this.#listeners.set(event, listeners?.difference(new Set([callbackClone])));
|
||||
});
|
||||
return abortController.abort;
|
||||
return () => abortController.abort();
|
||||
};
|
||||
|
||||
once = <K extends keyof T>(event: K, callback: EventListener<Parameters<T[K]>>, options: OnOptions = {}) => {
|
||||
@@ -62,4 +62,3 @@ class EventEmitter<T extends Record<string, (...args: ExplicitAny[]) => void | P
|
||||
}
|
||||
|
||||
export { EventEmitter };
|
||||
|
||||
|
||||
@@ -28,6 +28,6 @@ function isDeepSubset<T>(actual: ExplicitAny, expected: T): expected is T {
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
export { isDeepSubset };
|
||||
|
||||
@@ -38,4 +38,3 @@ class Queue {
|
||||
}
|
||||
|
||||
export { Queue };
|
||||
|
||||
|
||||
194
pnpm-lock.yaml
generated
194
pnpm-lock.yaml
generated
@@ -34,7 +34,7 @@ importers:
|
||||
version: 6.0.9(@pnpm/logger@5.2.0)
|
||||
'@vitest/coverage-v8':
|
||||
specifier: 'catalog:'
|
||||
version: 4.0.1(vitest@4.0.1(@types/node@24.9.1))
|
||||
version: 4.0.1(vitest@4.0.1(@types/node@24.9.1)(tsx@4.20.6))
|
||||
eslint:
|
||||
specifier: 9.38.0
|
||||
version: 9.38.0
|
||||
@@ -61,7 +61,7 @@ importers:
|
||||
version: 8.46.2(eslint@9.38.0)(typescript@5.9.3)
|
||||
vitest:
|
||||
specifier: 'catalog:'
|
||||
version: 4.0.1(@types/node@24.9.1)
|
||||
version: 4.0.1(@types/node@24.9.1)(tsx@4.20.6)
|
||||
|
||||
packages/bootstrap:
|
||||
devDependencies:
|
||||
@@ -76,13 +76,13 @@ importers:
|
||||
version: 24.9.1
|
||||
'@vitest/coverage-v8':
|
||||
specifier: 'catalog:'
|
||||
version: 4.0.1(vitest@4.0.1(@types/node@24.9.1))
|
||||
version: 4.0.1(vitest@4.0.1(@types/node@24.9.1)(tsx@4.20.6))
|
||||
typescript:
|
||||
specifier: 'catalog:'
|
||||
version: 5.9.3
|
||||
vitest:
|
||||
specifier: 'catalog:'
|
||||
version: 4.0.1(@types/node@24.9.1)
|
||||
version: 4.0.1(@types/node@24.9.1)(tsx@4.20.6)
|
||||
|
||||
packages/configs: {}
|
||||
|
||||
@@ -118,13 +118,13 @@ importers:
|
||||
version: 24.9.1
|
||||
'@vitest/coverage-v8':
|
||||
specifier: 'catalog:'
|
||||
version: 4.0.1(vitest@4.0.1(@types/node@24.9.1))
|
||||
version: 4.0.1(vitest@4.0.1(@types/node@24.9.1)(tsx@4.20.6))
|
||||
typescript:
|
||||
specifier: 'catalog:'
|
||||
version: 5.9.3
|
||||
vitest:
|
||||
specifier: 'catalog:'
|
||||
version: 4.0.1(@types/node@24.9.1)
|
||||
version: 4.0.1(@types/node@24.9.1)(tsx@4.20.6)
|
||||
|
||||
packages/operator:
|
||||
dependencies:
|
||||
@@ -137,13 +137,13 @@ importers:
|
||||
version: 24.9.1
|
||||
'@vitest/coverage-v8':
|
||||
specifier: 'catalog:'
|
||||
version: 4.0.1(vitest@4.0.1(@types/node@24.9.1))
|
||||
version: 4.0.1(vitest@4.0.1(@types/node@24.9.1)(tsx@4.20.6))
|
||||
typescript:
|
||||
specifier: 'catalog:'
|
||||
version: 5.9.3
|
||||
vitest:
|
||||
specifier: 'catalog:'
|
||||
version: 4.0.1(@types/node@24.9.1)
|
||||
version: 4.0.1(@types/node@24.9.1)(tsx@4.20.6)
|
||||
|
||||
packages/resource-authentik:
|
||||
dependencies:
|
||||
@@ -165,13 +165,47 @@ importers:
|
||||
version: 24.9.1
|
||||
'@vitest/coverage-v8':
|
||||
specifier: 'catalog:'
|
||||
version: 4.0.1(vitest@4.0.1(@types/node@24.9.1))
|
||||
version: 4.0.1(vitest@4.0.1(@types/node@24.9.1)(tsx@4.20.6))
|
||||
typescript:
|
||||
specifier: 'catalog:'
|
||||
version: 5.9.3
|
||||
vitest:
|
||||
specifier: 'catalog:'
|
||||
version: 4.0.1(@types/node@24.9.1)
|
||||
version: 4.0.1(@types/node@24.9.1)(tsx@4.20.6)
|
||||
|
||||
packages/resource-cloudflare:
|
||||
dependencies:
|
||||
'@morten-olsen/box-k8s':
|
||||
specifier: workspace:*
|
||||
version: link:../k8s
|
||||
'@morten-olsen/box-utils':
|
||||
specifier: workspace:*
|
||||
version: link:../utils
|
||||
cloudflare:
|
||||
specifier: ^5.2.0
|
||||
version: 5.2.0
|
||||
devDependencies:
|
||||
'@morten-olsen/box-configs':
|
||||
specifier: workspace:*
|
||||
version: link:../configs
|
||||
'@morten-olsen/box-tests':
|
||||
specifier: workspace:*
|
||||
version: link:../tests
|
||||
'@types/node':
|
||||
specifier: 24.9.1
|
||||
version: 24.9.1
|
||||
'@vitest/coverage-v8':
|
||||
specifier: 4.0.1
|
||||
version: 4.0.1(vitest@4.0.1(@types/node@24.9.1)(tsx@4.20.6))
|
||||
tsx:
|
||||
specifier: ^4.20.6
|
||||
version: 4.20.6
|
||||
typescript:
|
||||
specifier: 5.9.3
|
||||
version: 5.9.3
|
||||
vitest:
|
||||
specifier: 4.0.1
|
||||
version: 4.0.1(@types/node@24.9.1)(tsx@4.20.6)
|
||||
|
||||
packages/resource-postgres:
|
||||
dependencies:
|
||||
@@ -193,13 +227,13 @@ importers:
|
||||
version: 24.9.1
|
||||
'@vitest/coverage-v8':
|
||||
specifier: 'catalog:'
|
||||
version: 4.0.1(vitest@4.0.1(@types/node@24.9.1))
|
||||
version: 4.0.1(vitest@4.0.1(@types/node@24.9.1)(tsx@4.20.6))
|
||||
typescript:
|
||||
specifier: 'catalog:'
|
||||
version: 5.9.3
|
||||
vitest:
|
||||
specifier: 'catalog:'
|
||||
version: 4.0.1(@types/node@24.9.1)
|
||||
version: 4.0.1(@types/node@24.9.1)(tsx@4.20.6)
|
||||
|
||||
packages/resource-redis:
|
||||
dependencies:
|
||||
@@ -221,13 +255,13 @@ importers:
|
||||
version: 24.9.1
|
||||
'@vitest/coverage-v8':
|
||||
specifier: 'catalog:'
|
||||
version: 4.0.1(vitest@4.0.1(@types/node@24.9.1))
|
||||
version: 4.0.1(vitest@4.0.1(@types/node@24.9.1)(tsx@4.20.6))
|
||||
typescript:
|
||||
specifier: 'catalog:'
|
||||
version: 5.9.3
|
||||
vitest:
|
||||
specifier: 'catalog:'
|
||||
version: 4.0.1(@types/node@24.9.1)
|
||||
version: 4.0.1(@types/node@24.9.1)(tsx@4.20.6)
|
||||
|
||||
packages/tests:
|
||||
dependencies:
|
||||
@@ -243,13 +277,13 @@ importers:
|
||||
version: 24.9.1
|
||||
'@vitest/coverage-v8':
|
||||
specifier: 'catalog:'
|
||||
version: 4.0.1(vitest@4.0.1(@types/node@24.9.1))
|
||||
version: 4.0.1(vitest@4.0.1(@types/node@24.9.1)(tsx@4.20.6))
|
||||
typescript:
|
||||
specifier: 'catalog:'
|
||||
version: 5.9.3
|
||||
vitest:
|
||||
specifier: 'catalog:'
|
||||
version: 4.0.1(@types/node@24.9.1)
|
||||
version: 4.0.1(@types/node@24.9.1)(tsx@4.20.6)
|
||||
|
||||
packages/utils:
|
||||
dependencies:
|
||||
@@ -271,13 +305,13 @@ importers:
|
||||
version: 24.9.1
|
||||
'@vitest/coverage-v8':
|
||||
specifier: 'catalog:'
|
||||
version: 4.0.1(vitest@4.0.1(@types/node@24.9.1))
|
||||
version: 4.0.1(vitest@4.0.1(@types/node@24.9.1)(tsx@4.20.6))
|
||||
typescript:
|
||||
specifier: 'catalog:'
|
||||
version: 5.9.3
|
||||
vitest:
|
||||
specifier: 'catalog:'
|
||||
version: 4.0.1(@types/node@24.9.1)
|
||||
version: 4.0.1(@types/node@24.9.1)(tsx@4.20.6)
|
||||
|
||||
packages:
|
||||
|
||||
@@ -845,6 +879,9 @@ packages:
|
||||
'@types/node-fetch@2.6.13':
|
||||
resolution: {integrity: sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==}
|
||||
|
||||
'@types/node@18.19.130':
|
||||
resolution: {integrity: sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==}
|
||||
|
||||
'@types/node@24.9.1':
|
||||
resolution: {integrity: sha512-QoiaXANRkSXK6p0Duvt56W208du4P9Uye9hWLWgGMDTEoKPhuenzNcC4vGUmrNkiOKTlIrBoyNQYNpSwfEZXSg==}
|
||||
|
||||
@@ -956,6 +993,10 @@ packages:
|
||||
engines: {node: '>= 8'}
|
||||
hasBin: true
|
||||
|
||||
abort-controller@3.0.0:
|
||||
resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==}
|
||||
engines: {node: '>=6.5'}
|
||||
|
||||
acorn-jsx@5.3.2:
|
||||
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
|
||||
peerDependencies:
|
||||
@@ -970,6 +1011,10 @@ packages:
|
||||
resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==}
|
||||
engines: {node: '>= 14'}
|
||||
|
||||
agentkeepalive@4.6.0:
|
||||
resolution: {integrity: sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==}
|
||||
engines: {node: '>= 8.0.0'}
|
||||
|
||||
ajv@6.12.6:
|
||||
resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
|
||||
|
||||
@@ -1171,6 +1216,9 @@ packages:
|
||||
resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==}
|
||||
engines: {node: '>=0.8'}
|
||||
|
||||
cloudflare@5.2.0:
|
||||
resolution: {integrity: sha512-dVzqDpPFYR9ApEC9e+JJshFJZXcw4HzM8W+3DHzO5oy9+8rLC53G7x6fEf9A7/gSuSCxuvndzui5qJKftfIM9A==}
|
||||
|
||||
color-convert@2.0.1:
|
||||
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
|
||||
engines: {node: '>=7.0.0'}
|
||||
@@ -1417,6 +1465,10 @@ packages:
|
||||
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
event-target-shim@5.0.1:
|
||||
resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
eventemitter3@5.0.1:
|
||||
resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==}
|
||||
|
||||
@@ -1488,10 +1540,17 @@ packages:
|
||||
resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
form-data-encoder@1.7.2:
|
||||
resolution: {integrity: sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==}
|
||||
|
||||
form-data@4.0.4:
|
||||
resolution: {integrity: sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
formdata-node@4.4.1:
|
||||
resolution: {integrity: sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==}
|
||||
engines: {node: '>= 12.20'}
|
||||
|
||||
fsevents@2.3.3:
|
||||
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
|
||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||
@@ -1530,6 +1589,9 @@ packages:
|
||||
resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
get-tsconfig@4.13.0:
|
||||
resolution: {integrity: sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==}
|
||||
|
||||
glob-parent@5.1.2:
|
||||
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
|
||||
engines: {node: '>= 6'}
|
||||
@@ -1597,6 +1659,9 @@ packages:
|
||||
resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==}
|
||||
engines: {node: '>=10.17.0'}
|
||||
|
||||
humanize-ms@1.2.1:
|
||||
resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==}
|
||||
|
||||
ignore@5.3.2:
|
||||
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
|
||||
engines: {node: '>= 4'}
|
||||
@@ -1942,6 +2007,11 @@ packages:
|
||||
engines: {node: '>=10'}
|
||||
hasBin: true
|
||||
|
||||
node-domexception@1.0.0:
|
||||
resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
|
||||
engines: {node: '>=10.5.0'}
|
||||
deprecated: Use your platform's native DOMException instead
|
||||
|
||||
node-fetch@2.7.0:
|
||||
resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==}
|
||||
engines: {node: 4.x || >=6.0.0}
|
||||
@@ -2168,6 +2238,9 @@ packages:
|
||||
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
resolve-pkg-maps@1.0.0:
|
||||
resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
|
||||
|
||||
resolve@1.22.11:
|
||||
resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -2420,6 +2493,11 @@ packages:
|
||||
tslib@2.8.1:
|
||||
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
|
||||
|
||||
tsx@4.20.6:
|
||||
resolution: {integrity: sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
hasBin: true
|
||||
|
||||
turbo-darwin-64@2.5.8:
|
||||
resolution: {integrity: sha512-Dh5bCACiHO8rUXZLpKw+m3FiHtAp2CkanSyJre+SInEvEr5kIxjGvCK/8MFX8SFRjQuhjtvpIvYYZJB4AGCxNQ==}
|
||||
cpu: [x64]
|
||||
@@ -2498,6 +2576,9 @@ packages:
|
||||
resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
undici-types@5.26.5:
|
||||
resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==}
|
||||
|
||||
undici-types@7.16.0:
|
||||
resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==}
|
||||
|
||||
@@ -2588,6 +2669,10 @@ packages:
|
||||
wcwidth@1.0.1:
|
||||
resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==}
|
||||
|
||||
web-streams-polyfill@4.0.0-beta.3:
|
||||
resolution: {integrity: sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==}
|
||||
engines: {node: '>= 14'}
|
||||
|
||||
webidl-conversions@3.0.1:
|
||||
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
|
||||
|
||||
@@ -3213,6 +3298,10 @@ snapshots:
|
||||
'@types/node': 24.9.1
|
||||
form-data: 4.0.4
|
||||
|
||||
'@types/node@18.19.130':
|
||||
dependencies:
|
||||
undici-types: 5.26.5
|
||||
|
||||
'@types/node@24.9.1':
|
||||
dependencies:
|
||||
undici-types: 7.16.0
|
||||
@@ -3318,7 +3407,7 @@ snapshots:
|
||||
'@typescript-eslint/types': 8.46.2
|
||||
eslint-visitor-keys: 4.2.1
|
||||
|
||||
'@vitest/coverage-v8@4.0.1(vitest@4.0.1(@types/node@24.9.1))':
|
||||
'@vitest/coverage-v8@4.0.1(vitest@4.0.1(@types/node@24.9.1)(tsx@4.20.6))':
|
||||
dependencies:
|
||||
'@bcoe/v8-coverage': 1.0.2
|
||||
'@vitest/utils': 4.0.1
|
||||
@@ -3331,7 +3420,7 @@ snapshots:
|
||||
magicast: 0.3.5
|
||||
std-env: 3.10.0
|
||||
tinyrainbow: 3.0.3
|
||||
vitest: 4.0.1(@types/node@24.9.1)
|
||||
vitest: 4.0.1(@types/node@24.9.1)(tsx@4.20.6)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -3344,13 +3433,13 @@ snapshots:
|
||||
chai: 6.2.0
|
||||
tinyrainbow: 3.0.3
|
||||
|
||||
'@vitest/mocker@4.0.1(vite@7.1.12(@types/node@24.9.1))':
|
||||
'@vitest/mocker@4.0.1(vite@7.1.12(@types/node@24.9.1)(tsx@4.20.6))':
|
||||
dependencies:
|
||||
'@vitest/spy': 4.0.1
|
||||
estree-walker: 3.0.3
|
||||
magic-string: 0.30.19
|
||||
optionalDependencies:
|
||||
vite: 7.1.12(@types/node@24.9.1)
|
||||
vite: 7.1.12(@types/node@24.9.1)(tsx@4.20.6)
|
||||
|
||||
'@vitest/pretty-format@4.0.1':
|
||||
dependencies:
|
||||
@@ -3378,6 +3467,10 @@ snapshots:
|
||||
dependencies:
|
||||
isexe: 2.0.0
|
||||
|
||||
abort-controller@3.0.0:
|
||||
dependencies:
|
||||
event-target-shim: 5.0.1
|
||||
|
||||
acorn-jsx@5.3.2(acorn@8.15.0):
|
||||
dependencies:
|
||||
acorn: 8.15.0
|
||||
@@ -3386,6 +3479,10 @@ snapshots:
|
||||
|
||||
agent-base@7.1.4: {}
|
||||
|
||||
agentkeepalive@4.6.0:
|
||||
dependencies:
|
||||
humanize-ms: 1.2.1
|
||||
|
||||
ajv@6.12.6:
|
||||
dependencies:
|
||||
fast-deep-equal: 3.1.3
|
||||
@@ -3613,6 +3710,18 @@ snapshots:
|
||||
|
||||
clone@1.0.4: {}
|
||||
|
||||
cloudflare@5.2.0:
|
||||
dependencies:
|
||||
'@types/node': 18.19.130
|
||||
'@types/node-fetch': 2.6.13
|
||||
abort-controller: 3.0.0
|
||||
agentkeepalive: 4.6.0
|
||||
form-data-encoder: 1.7.2
|
||||
formdata-node: 4.4.1
|
||||
node-fetch: 2.7.0
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
|
||||
color-convert@2.0.1:
|
||||
dependencies:
|
||||
color-name: 1.1.4
|
||||
@@ -3991,6 +4100,8 @@ snapshots:
|
||||
|
||||
esutils@2.0.3: {}
|
||||
|
||||
event-target-shim@5.0.1: {}
|
||||
|
||||
eventemitter3@5.0.1: {}
|
||||
|
||||
events-universal@1.0.1:
|
||||
@@ -4065,6 +4176,8 @@ snapshots:
|
||||
dependencies:
|
||||
is-callable: 1.2.7
|
||||
|
||||
form-data-encoder@1.7.2: {}
|
||||
|
||||
form-data@4.0.4:
|
||||
dependencies:
|
||||
asynckit: 0.4.0
|
||||
@@ -4073,6 +4186,11 @@ snapshots:
|
||||
hasown: 2.0.2
|
||||
mime-types: 2.1.35
|
||||
|
||||
formdata-node@4.4.1:
|
||||
dependencies:
|
||||
node-domexception: 1.0.0
|
||||
web-streams-polyfill: 4.0.0-beta.3
|
||||
|
||||
fsevents@2.3.3:
|
||||
optional: true
|
||||
|
||||
@@ -4122,6 +4240,10 @@ snapshots:
|
||||
es-errors: 1.3.0
|
||||
get-intrinsic: 1.3.0
|
||||
|
||||
get-tsconfig@4.13.0:
|
||||
dependencies:
|
||||
resolve-pkg-maps: 1.0.0
|
||||
|
||||
glob-parent@5.1.2:
|
||||
dependencies:
|
||||
is-glob: 4.0.3
|
||||
@@ -4173,6 +4295,10 @@ snapshots:
|
||||
|
||||
human-signals@2.1.0: {}
|
||||
|
||||
humanize-ms@1.2.1:
|
||||
dependencies:
|
||||
ms: 2.1.3
|
||||
|
||||
ignore@5.3.2: {}
|
||||
|
||||
ignore@7.0.5: {}
|
||||
@@ -4488,6 +4614,8 @@ snapshots:
|
||||
split2: 3.2.2
|
||||
through2: 4.0.2
|
||||
|
||||
node-domexception@1.0.0: {}
|
||||
|
||||
node-fetch@2.7.0:
|
||||
dependencies:
|
||||
whatwg-url: 5.0.0
|
||||
@@ -4707,6 +4835,8 @@ snapshots:
|
||||
|
||||
resolve-from@4.0.0: {}
|
||||
|
||||
resolve-pkg-maps@1.0.0: {}
|
||||
|
||||
resolve@1.22.11:
|
||||
dependencies:
|
||||
is-core-module: 2.16.1
|
||||
@@ -5023,6 +5153,13 @@ snapshots:
|
||||
|
||||
tslib@2.8.1: {}
|
||||
|
||||
tsx@4.20.6:
|
||||
dependencies:
|
||||
esbuild: 0.25.11
|
||||
get-tsconfig: 4.13.0
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.3
|
||||
|
||||
turbo-darwin-64@2.5.8:
|
||||
optional: true
|
||||
|
||||
@@ -5111,6 +5248,8 @@ snapshots:
|
||||
has-symbols: 1.1.0
|
||||
which-boxed-primitive: 1.1.1
|
||||
|
||||
undici-types@5.26.5: {}
|
||||
|
||||
undici-types@7.16.0: {}
|
||||
|
||||
unique-string@2.0.0:
|
||||
@@ -5123,7 +5262,7 @@ snapshots:
|
||||
|
||||
util-deprecate@1.0.2: {}
|
||||
|
||||
vite@7.1.12(@types/node@24.9.1):
|
||||
vite@7.1.12(@types/node@24.9.1)(tsx@4.20.6):
|
||||
dependencies:
|
||||
esbuild: 0.25.11
|
||||
fdir: 6.5.0(picomatch@4.0.3)
|
||||
@@ -5134,11 +5273,12 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@types/node': 24.9.1
|
||||
fsevents: 2.3.3
|
||||
tsx: 4.20.6
|
||||
|
||||
vitest@4.0.1(@types/node@24.9.1):
|
||||
vitest@4.0.1(@types/node@24.9.1)(tsx@4.20.6):
|
||||
dependencies:
|
||||
'@vitest/expect': 4.0.1
|
||||
'@vitest/mocker': 4.0.1(vite@7.1.12(@types/node@24.9.1))
|
||||
'@vitest/mocker': 4.0.1(vite@7.1.12(@types/node@24.9.1)(tsx@4.20.6))
|
||||
'@vitest/pretty-format': 4.0.1
|
||||
'@vitest/runner': 4.0.1
|
||||
'@vitest/snapshot': 4.0.1
|
||||
@@ -5155,7 +5295,7 @@ snapshots:
|
||||
tinyexec: 0.3.2
|
||||
tinyglobby: 0.2.15
|
||||
tinyrainbow: 3.0.3
|
||||
vite: 7.1.12(@types/node@24.9.1)
|
||||
vite: 7.1.12(@types/node@24.9.1)(tsx@4.20.6)
|
||||
why-is-node-running: 2.3.0
|
||||
optionalDependencies:
|
||||
'@types/node': 24.9.1
|
||||
@@ -5177,6 +5317,8 @@ snapshots:
|
||||
dependencies:
|
||||
defaults: 1.0.4
|
||||
|
||||
web-streams-polyfill@4.0.0-beta.3: {}
|
||||
|
||||
webidl-conversions@3.0.1: {}
|
||||
|
||||
whatwg-url@5.0.0:
|
||||
|
||||
Reference in New Issue
Block a user