diff --git a/.changeset/all-goats-call.md b/.changeset/all-goats-call.md new file mode 100644 index 00000000..373c84e4 --- /dev/null +++ b/.changeset/all-goats-call.md @@ -0,0 +1,5 @@ +--- +"@cartesi/cli": patch +--- + +migrate compose from YAML to TS diff --git a/apps/cli/package.json b/apps/cli/package.json index b1044391..5fbd8c07 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -39,7 +39,8 @@ "semver": "^7.7.2", "smol-toml": "^1.4.2", "tmp": "^0.2.5", - "viem": "^2.37.6" + "viem": "^2.37.6", + "yaml": "^2.8.2" }, "devDependencies": { "@biomejs/biome": "catalog:", @@ -56,7 +57,6 @@ "@types/tmp": "^0.2.6", "@vitest/coverage-istanbul": "^3.2.4", "@wagmi/cli": "^2.5.1", - "copyfiles": "^2.4.1", "npm-run-all": "^4.1.5", "rimraf": "^6.0.1", "ts-node": "^10.9.2", @@ -66,13 +66,12 @@ "vitest": "^3.2.4" }, "scripts": { - "build": "run-s clean codegen compile copy-files", + "build": "run-s clean codegen compile", "clean": "rimraf dist", "codegen": "run-p codegen:wagmi", "codegen:wagmi": "wagmi generate", "compile": "tsc -p tsconfig.build.json", "postcompile": "chmod +x dist/index.js", - "copy-files": "copyfiles -u 1 \"src/**/*.yaml\" \"src/**/*.env\" \"src/**/*.txt\" dist", "lint": "biome lint", "posttest": "pnpm lint", "test": "vitest" diff --git a/apps/cli/src/commands/logs.ts b/apps/cli/src/commands/logs.ts index c698a956..b103da1c 100644 --- a/apps/cli/src/commands/logs.ts +++ b/apps/cli/src/commands/logs.ts @@ -35,10 +35,10 @@ export const createLogsCommand = () => { const serviceInfo = await getServiceInfo({ projectName, - service: "rollups-node", + service: "rollups_node", }); if (!serviceInfo) { - throw new Error(`service rollups-node not found`); + throw new Error(`service rollups_node not found`); } await execa( diff --git a/apps/cli/src/commands/status.ts b/apps/cli/src/commands/status.ts index 345f61bc..1c0739e6 100644 --- a/apps/cli/src/commands/status.ts +++ b/apps/cli/src/commands/status.ts @@ -20,7 +20,7 @@ export const createStatusCommand = () => { const status = await getServiceState({ projectName, - service: "rollups-node", + service: "rollups_node", }); const deployments = await getDeployments({ projectName, diff --git a/apps/cli/src/compose/anvil.ts b/apps/cli/src/compose/anvil.ts new file mode 100644 index 00000000..32d6e27d --- /dev/null +++ b/apps/cli/src/compose/anvil.ts @@ -0,0 +1,63 @@ +import type { ComposeFile, Config, Service } from "../types/compose.js"; +import { DEFAULT_HEALTHCHECK } from "./common.js"; + +type ServiceOptions = { + imageTag?: string; + blockTime?: number; +}; + +// Anvil service +const service = (options?: ServiceOptions): Service => { + const blockTime = options?.blockTime ?? 2; + const imageTag = options?.imageTag ?? "latest"; + + return { + image: `cartesi/sdk:${imageTag}`, + command: ["devnet", "--block-time", blockTime.toString()], + healthcheck: { + ...DEFAULT_HEALTHCHECK, + test: ["CMD", "eth_isready"], + }, + environment: { + ANVIL_IP_ADDR: "0.0.0.0", + }, + }; +}; + +const proxy = (): Config => ({ + name: "anvil-proxy", + content: `http: + routers: + anvil: + rule: "PathPrefix(\`/anvil\`)" + middlewares: + - "remove-anvil-prefix" + service: anvil + middlewares: + remove-anvil-prefix: + replacePathRegex: + regex: "^/anvil(.*)" + replacement: "$1" + services: + anvil: + loadBalancer: + servers: + - url: "http://anvil:8545"`, +}); + +export default (options?: ServiceOptions): ComposeFile => ({ + configs: { + anvil_proxy: proxy(), + }, + services: { + anvil: service(options), + proxy: { + configs: [ + { + source: "anvil_proxy", + target: "/etc/traefik/conf.d/anvil.yaml", + }, + ], + }, + }, +}); diff --git a/apps/cli/src/compose/builder.ts b/apps/cli/src/compose/builder.ts new file mode 100644 index 00000000..cbea35ae --- /dev/null +++ b/apps/cli/src/compose/builder.ts @@ -0,0 +1,654 @@ +import type { + ComposeFile, + Config, + ConfigReference, + Network, + PortMapping, + Secret, + SecretReference, + Service, + Volume, + VolumeMount, +} from "../../src/types/compose.js"; + +/** + * Merges multiple Compose files according to Docker Compose merge rules. + * See: https://docs.docker.com/reference/compose-file/merge/ + * + * Rules: + * - Mappings: merged by adding missing entries and merging conflicting ones + * - Sequences: merged by appending values from overriding file + * - Exceptions: + * - Shell commands (command, entrypoint, healthcheck.test): overridden, not appended + * - Unique resources (ports, volumes, secrets, configs): merged by unique key + */ +export const concat = (files: ComposeFile[]): ComposeFile => { + if (files.length === 0) { + return {}; + } + + return files.reduce((composed, file) => { + return mergeComposeFiles(composed, file); + }, {}); +}; + +/** + * Merges two Compose files according to Docker Compose merge rules + */ +function mergeComposeFiles( + base: ComposeFile, + override: ComposeFile, +): ComposeFile { + const result: ComposeFile = { ...base }; + + // Merge name (override takes precedence) + if (override.name !== undefined) { + result.name = override.name; + } + + // Merge include (append sequences) + if (override.include) { + result.include = mergeSequences(base.include, override.include); + } + + // Merge services (mapping merge) + if (override.services) { + result.services = mergeMappings( + base.services || {}, + override.services, + (baseService, overrideService) => + mergeService(baseService, overrideService), + ); + } + + // Merge networks (mapping merge) + if (override.networks) { + result.networks = mergeMappings( + base.networks || {}, + override.networks, + (baseNetwork, overrideNetwork) => + mergeNetwork(baseNetwork, overrideNetwork), + ); + } + + // Merge volumes (mapping merge) + if (override.volumes) { + result.volumes = mergeMappings( + base.volumes || {}, + override.volumes, + (baseVolume, overrideVolume) => + mergeVolume(baseVolume, overrideVolume), + ); + } + + // Merge secrets (mapping merge) + if (override.secrets) { + result.secrets = mergeMappings( + base.secrets || {}, + override.secrets, + (baseSecret, overrideSecret) => + mergeSecret(baseSecret, overrideSecret), + ); + } + + // Merge configs (mapping merge) + if (override.configs) { + result.configs = mergeMappings( + base.configs || {}, + override.configs, + (baseConfig, overrideConfig) => + mergeConfig(baseConfig, overrideConfig), + ); + } + + // Merge models (mapping merge) + if (override.models) { + result.models = mergeMappings( + base.models || {}, + override.models, + (baseModel, overrideModel) => ({ ...baseModel, ...overrideModel }), + ); + } + + return result; +} + +/** + * Merges two mappings (objects), recursively merging values for matching keys + */ +function mergeMappings( + base: Record, + override: Record, + mergeValue: (baseValue: T, overrideValue: T) => T, +): Record { + const result: Record = { ...base }; + + for (const [key, overrideValue] of Object.entries(override)) { + const baseValue = result[key]; + if (baseValue !== undefined) { + result[key] = mergeValue(baseValue, overrideValue); + } else { + result[key] = overrideValue; + } + } + + return result; +} + +/** + * Merges two services according to Docker Compose merge rules + */ +function mergeService(base: Service, override: Service): Service { + const result: Service = { ...base }; + + // Shell commands: override (not merge) + if (override.command !== undefined) { + result.command = override.command; + } + if (override.entrypoint !== undefined) { + result.entrypoint = override.entrypoint; + } + if (override.healthcheck?.test !== undefined) { + result.healthcheck = { + ...base.healthcheck, + test: override.healthcheck.test, + }; + } + + // Merge other healthcheck fields normally + if (override.healthcheck) { + result.healthcheck = { + ...base.healthcheck, + ...override.healthcheck, + // test was already handled above + test: override.healthcheck.test ?? base.healthcheck?.test, + }; + } + + // Unique resources: merge by unique key + if (override.ports) { + result.ports = mergeUniquePorts(base.ports || [], override.ports); + } + if (override.volumes) { + result.volumes = mergeUniqueVolumes( + base.volumes || [], + override.volumes, + ); + } + if (override.secrets) { + result.secrets = mergeUniqueSecrets( + base.secrets || [], + override.secrets, + ); + } + if (override.configs) { + result.configs = mergeUniqueConfigs( + base.configs || [], + override.configs, + ); + } + + // Sequences: append + if (override.expose) { + result.expose = mergeSequences(base.expose, override.expose); + } + if (override.depends_on) { + // depends_on can be array or object, handle both + if (Array.isArray(override.depends_on)) { + result.depends_on = mergeSequences( + Array.isArray(base.depends_on) ? base.depends_on : [], + override.depends_on, + ); + } else { + // For object format, merge the objects + result.depends_on = { + ...(typeof base.depends_on === "object" && + !Array.isArray(base.depends_on) + ? base.depends_on + : {}), + ...override.depends_on, + }; + } + } + if (override.dns) { + result.dns = mergeSequences( + Array.isArray(base.dns) ? base.dns : base.dns ? [base.dns] : [], + Array.isArray(override.dns) ? override.dns : [override.dns], + ); + } + if (override.dns_search) { + result.dns_search = mergeSequences( + Array.isArray(base.dns_search) + ? base.dns_search + : base.dns_search + ? [base.dns_search] + : [], + Array.isArray(override.dns_search) + ? override.dns_search + : [override.dns_search], + ); + } + if (override.dns_opt) { + result.dns_opt = mergeSequences(base.dns_opt || [], override.dns_opt); + } + if (override.env_file) { + // env_file can be string, string[], or EnvFile[] + const baseArray = Array.isArray(base.env_file) + ? base.env_file + : base.env_file + ? [base.env_file] + : []; + const overrideArray = Array.isArray(override.env_file) + ? override.env_file + : [override.env_file]; + // Type assertion needed because env_file can be string[] or EnvFile[] + result.env_file = [ + ...baseArray, + ...overrideArray, + ] as typeof override.env_file; + } + if (override.labels) { + // labels can be array or object + if (Array.isArray(override.labels)) { + result.labels = mergeSequences( + Array.isArray(base.labels) ? base.labels : [], + override.labels, + ); + } else { + result.labels = { + ...(typeof base.labels === "object" && + !Array.isArray(base.labels) + ? base.labels + : {}), + ...override.labels, + }; + } + } + if (override.extra_hosts) { + // extra_hosts can be array or object + if (Array.isArray(override.extra_hosts)) { + result.extra_hosts = mergeSequences( + Array.isArray(base.extra_hosts) ? base.extra_hosts : [], + override.extra_hosts, + ); + } else { + result.extra_hosts = { + ...(typeof base.extra_hosts === "object" && + !Array.isArray(base.extra_hosts) + ? base.extra_hosts + : {}), + ...override.extra_hosts, + }; + } + } + if (override.sysctls) { + // sysctls can be array or object + if (Array.isArray(override.sysctls)) { + result.sysctls = mergeSequences( + Array.isArray(base.sysctls) ? base.sysctls : [], + override.sysctls, + ); + } else { + result.sysctls = { + ...(typeof base.sysctls === "object" && + !Array.isArray(base.sysctls) + ? base.sysctls + : {}), + ...override.sysctls, + }; + } + } + + // Mappings: merge recursively + if (override.environment) { + if (Array.isArray(override.environment)) { + // Array format: append + result.environment = mergeSequences( + Array.isArray(base.environment) ? base.environment : [], + override.environment, + ); + } else { + // Object format: merge mappings + result.environment = { + ...(typeof base.environment === "object" && + !Array.isArray(base.environment) + ? base.environment + : {}), + ...override.environment, + }; + } + } + if (override.networks) { + if (Array.isArray(override.networks)) { + result.networks = mergeSequences( + Array.isArray(base.networks) ? base.networks : [], + override.networks, + ); + } else { + result.networks = { + ...(typeof base.networks === "object" && + !Array.isArray(base.networks) + ? base.networks + : {}), + ...override.networks, + }; + } + } + + // Other fields: override takes precedence + const overrideFields: Array = [ + "image", + "build", + "restart", + "container_name", + "hostname", + "network_mode", + "pid", + "ipc", + "deploy", + "develop", + "working_dir", + "user", + "cap_add", + "cap_drop", + "devices", + "external_links", + "gpus", + "group_add", + "mem_limit", + "mem_reservation", + "memswap_limit", + "oom_kill_disable", + "oom_score_adj", + "privileged", + "read_only", + "shm_size", + "stdin_open", + "tty", + "ulimits", + "volumes_from", + "cpus", + "cpuset", + "cpu_shares", + "cpu_quota", + "cpu_period", + "cpu_count", + "cpu_percent", + "annotations", + "cgroup", + "cgroup_parent", + "domainname", + "init", + "isolation", + "links", + "mac_address", + "platform", + "security_opt", + "stop_grace_period", + "stop_signal", + "tmpfs", + "attach", + "extends", + "provider", + "blkio_config", + "credential_spec", + "device_cgroup_rules", + "logging", + ]; + + for (const field of overrideFields) { + if (override[field] !== undefined) { + result[field] = override[field] as never; + } + } + + return result; +} + +/** + * Merges two sequences by appending override to base + */ +function mergeSequences(base: T[] | undefined, override: T[]): T[] { + return [...(base || []), ...override]; +} + +/** + * Merges ports arrays, ensuring uniqueness by {ip, target, published, protocol} + */ +function mergeUniquePorts( + base: Array, + override: Array, +): Array { + const result: Array = [...base]; + const seen = new Set(); + + // Add existing ports to seen set + for (const port of base) { + seen.add(getPortKey(port)); + } + + // Add override ports that don't violate uniqueness + for (const port of override) { + const key = getPortKey(port); + if (!seen.has(key)) { + result.push(port); + seen.add(key); + } else { + // Replace existing port with same key + const index = result.findIndex((p) => getPortKey(p) === key); + if (index !== -1) { + result[index] = port; + } + } + } + + return result; +} + +/** + * Gets a unique key for a port mapping + */ +function getPortKey(port: string | PortMapping): string { + if (typeof port === "string") { + // Parse string format: "host:container", "host:container/protocol", etc. + const parts = port.split(":"); + const containerPart = parts[1] || ""; + const [target, protocol] = containerPart.split("/"); + return `${parts[0] || ""}:${target || ""}:${protocol || "tcp"}`; + } + // Object format + const ip = port.host_ip || ""; + const target = port.target.toString(); + const published = port.published?.toString() || ""; + const protocol = port.protocol || "tcp"; + return `${ip}:${target}:${published}:${protocol}`; +} + +/** + * Merges volumes arrays, ensuring uniqueness by target + */ +function mergeUniqueVolumes( + base: Array, + override: Array, +): Array { + const result: Array = [...base]; + const seen = new Set(); + + // Add existing volumes to seen set + for (const volume of base) { + seen.add(getVolumeKey(volume)); + } + + // Add override volumes that don't violate uniqueness + for (const volume of override) { + const key = getVolumeKey(volume); + if (!seen.has(key)) { + result.push(volume); + seen.add(key); + } else { + // Replace existing volume with same key + const index = result.findIndex((v) => getVolumeKey(v) === key); + if (index !== -1) { + result[index] = volume; + } + } + } + + return result; +} + +/** + * Gets a unique key for a volume mount + */ +function getVolumeKey(volume: string | VolumeMount): string { + if (typeof volume === "string") { + // Parse string format: "source:target", "target", etc. + const parts = volume.split(":"); + return parts[parts.length - 1] || volume; + } + return volume.target; +} + +/** + * Merges secrets arrays, ensuring uniqueness by target + */ +function mergeUniqueSecrets( + base: Array, + override: Array, +): Array { + const result: Array = [...base]; + const seen = new Set(); + + // Add existing secrets to seen set + for (const secret of base) { + seen.add(getSecretKey(secret)); + } + + // Add override secrets that don't violate uniqueness + for (const secret of override) { + const key = getSecretKey(secret); + if (!seen.has(key)) { + result.push(secret); + seen.add(key); + } else { + // Replace existing secret with same key + const index = result.findIndex((s) => getSecretKey(s) === key); + if (index !== -1) { + result[index] = secret; + } + } + } + + return result; +} + +/** + * Gets a unique key for a secret reference + */ +function getSecretKey(secret: string | SecretReference): string { + if (typeof secret === "string") { + return secret; + } + return secret.target || secret.source; +} + +/** + * Merges configs arrays, ensuring uniqueness by target + */ +function mergeUniqueConfigs( + base: Array, + override: Array, +): Array { + const result: Array = [...base]; + const seen = new Set(); + + // Add existing configs to seen set + for (const config of base) { + seen.add(getConfigKey(config)); + } + + // Add override configs that don't violate uniqueness + for (const config of override) { + const key = getConfigKey(config); + if (!seen.has(key)) { + result.push(config); + seen.add(key); + } else { + // Replace existing config with same key + const index = result.findIndex((c) => getConfigKey(c) === key); + if (index !== -1) { + result[index] = config; + } + } + } + + return result; +} + +/** + * Gets a unique key for a config reference + */ +function getConfigKey(config: string | ConfigReference): string { + if (typeof config === "string") { + return config; + } + return config.target || config.source; +} + +/** + * Merges two networks + */ +function mergeNetwork(base: Network, override: Network): Network { + return { + ...base, + ...override, + // Merge nested objects + ipam: override.ipam + ? { + ...base.ipam, + ...override.ipam, + config: override.ipam.config + ? mergeSequences(base.ipam?.config, override.ipam.config) + : base.ipam?.config, + } + : base.ipam, + external: + override.external !== undefined ? override.external : base.external, + }; +} + +/** + * Merges two volumes + */ +function mergeVolume(base: Volume, override: Volume): Volume { + return { + ...base, + ...override, + external: + override.external !== undefined ? override.external : base.external, + }; +} + +/** + * Merges two secrets + */ +function mergeSecret(base: Secret, override: Secret): Secret { + return { + ...base, + ...override, + external: + override.external !== undefined ? override.external : base.external, + }; +} + +/** + * Merges two configs + */ +function mergeConfig(base: Config, override: Config): Config { + return { + ...base, + ...override, + external: + override.external !== undefined ? override.external : base.external, + }; +} diff --git a/apps/cli/src/compose/bundler.ts b/apps/cli/src/compose/bundler.ts new file mode 100644 index 00000000..7bc3a448 --- /dev/null +++ b/apps/cli/src/compose/bundler.ts @@ -0,0 +1,94 @@ +import type { ComposeFile, Config, Service } from "../types/compose.js"; +import { DEFAULT_HEALTHCHECK } from "./common.js"; + +type ServiceOptions = { + imageTag?: string; +}; + +const service = (options: ServiceOptions): Service => { + const imageTag = options.imageTag ?? "latest"; + + return { + image: `cartesi/sdk:${imageTag}`, + command: [ + "alto", + "--entrypoints", + "0x5ff137d4b0fdcd49dca30c7cf57e578a026d2789,0x0000000071727De22E5E9d8BAf0edAc6f37da032", + "--log-level", + "info", + "--rpc-url", + "http://anvil:8545", + "--min-executor-balance", + "0", + "--utility-private-key", + "0xdbda1821b80551c9d65939329250298aa3472ba22feea921c0cf5d620ea67b97", + "--executor-private-keys", + "0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6,0x4bbbf85ce3377467afe5d46f804f221813b2bb87f24d81f60f1fcdbf7cbf4356,0x92db14e403b83dfe3df233f83dfa3a0d7096f21ca9b0d6d6b8d88b2b4ec1564e,0x8b3a350cf5c34c9194ca85829a2df0ec3153be0318b5e2d3348e872092edffba,0x47e179ec197488593b187f80a00eb0da91f1b9d0b13f8733639f19c30a34926a", + "--safe-mode", + "false", + "--port", + "4337", + "--public-client-log-level", + "error", + "--wallet-client-log-level", + "error", + "--enable-debug-endpoints", + ], + healthcheck: { + ...DEFAULT_HEALTHCHECK, + test: ["CMD", "curl", "-fsS", "http://127.0.0.1:4337/health"], + }, + }; +}; + +const proxy = (): Config => { + return { + name: "bundler-proxy", + content: `http: + routers: + bundler: + rule: "PathPrefix(\`/bundler\`)" + middlewares: + - "cors" + - "remove-bundler-prefix" + service: bundler + middlewares: + cors: + headers: + accessControlAllowMethods: + - GET + - OPTIONS + - PUT + accessControlAllowHeaders: "*" + accessControlAllowOriginList: + - "*" + accessControlMaxAge: 100 + addVaryHeader: true + remove-bundler-prefix: + replacePathRegex: + regex: "^/bundler/(.*)" + replacement: "/$1" + services: + bundler: + loadBalancer: + servers: + - url: "http://bundler:4337"`, + }; +}; + +export default (options: ServiceOptions): ComposeFile => ({ + configs: { + bundler_proxy: proxy(), + }, + services: { + bundler: service(options), + proxy: { + configs: [ + { + source: "bundler_proxy", + target: "/etc/traefik/conf.d/bundler.yaml", + }, + ], + }, + }, +}); diff --git a/apps/cli/src/compose/common.ts b/apps/cli/src/compose/common.ts new file mode 100644 index 00000000..fe8780c2 --- /dev/null +++ b/apps/cli/src/compose/common.ts @@ -0,0 +1,9 @@ +import type { Healthcheck } from "../types/compose.js"; + +export const DEFAULT_HEALTHCHECK: Healthcheck = { + start_period: "10s", + start_interval: "200ms", + interval: "10s", + timeout: "1s", + retries: 5, +}; diff --git a/apps/cli/src/compose/database.ts b/apps/cli/src/compose/database.ts new file mode 100644 index 00000000..05b640d9 --- /dev/null +++ b/apps/cli/src/compose/database.ts @@ -0,0 +1,28 @@ +import type { ComposeFile, Service } from "../types/compose.js"; +import { DEFAULT_HEALTHCHECK } from "./common.js"; + +type ServiceOptions = { + imageTag?: string; + password: string; +}; + +// Database service +export const service = (options: ServiceOptions): Service => { + const imageTag = options?.imageTag ?? "latest"; + return { + image: `cartesi/rollups-database:${imageTag}`, + healthcheck: { + ...DEFAULT_HEALTHCHECK, + test: ["CMD-SHELL", "pg_isready -U postgres || exit 1"], + }, + environment: { + POSTGRES_PASSWORD: options?.password, + }, + }; +}; + +export default (options: ServiceOptions): ComposeFile => ({ + services: { + database: service(options), + }, +}); diff --git a/apps/cli/src/compose/default.env b/apps/cli/src/compose/default.env deleted file mode 100644 index b972b061..00000000 --- a/apps/cli/src/compose/default.env +++ /dev/null @@ -1,37 +0,0 @@ -# cartesi/rollups-node - -#logs -CARTESI_LOG_LEVEL="${CARTESI_LOG_LEVEL:-info}" - -# features -CARTESI_FEATURE_INPUT_READER_ENABLED="${CARTESI_FEATURE_INPUT_READER_ENABLED:-true}" -CARTESI_FEATURE_CLAIM_SUBMISSION_ENABLED="${CARTESI_FEATURE_CLAIM_SUBMISSION_ENABLED:-true}" -CARTESI_FEATURE_MACHINE_HASH_CHECK_ENABLED="${CARTESI_FEATURE_MACHINE_HASH_CHECK_ENABLED:-true}" -CARTESI_SNAPSHOTS_DIR="/var/lib/cartesi-rollups-node/snapshots" - -# rollups -CARTESI_EVM_READER_RETRY_POLICY_MAX_RETRIES="${CARTESI_EVM_READER_RETRY_POLICY_MAX_RETRIES:-3}" -CARTESI_EVM_READER_RETRY_POLICY_MAX_DELAY="${CARTESI_EVM_READER_RETRY_POLICY_MAX_DELAY:-3}" -CARTESI_ADVANCER_POLLING_INTERVAL="${CARTESI_ADVANCER_POLLING_INTERVAL:-3}" -CARTESI_VALIDATOR_POLLING_INTERVAL="${CARTESI_VALIDATOR_POLLING_INTERVAL:-3}" -CARTESI_CLAIMER_POLLING_INTERVAL="${CARTESI_CLAIMER_POLLING_INTERVAL:-3}" -CARTESI_MAX_STARTUP_TIME="${CARTESI_MAX_STARTUP_TIME:-15}" - -# blockchain -CARTESI_BLOCKCHAIN_ID="${CARTESI_BLOCKCHAIN_ID:-13370}" -CARTESI_BLOCKCHAIN_HTTP_ENDPOINT="${CARTESI_BLOCKCHAIN_HTTP_ENDPOINT:-http://anvil:8545}" -CARTESI_BLOCKCHAIN_WS_ENDPOINT="${CARTESI_BLOCKCHAIN_WS_ENDPOINT:-ws://anvil:8545}" -CARTESI_BLOCKCHAIN_DEFAULT_BLOCK="${CARTESI_BLOCKCHAIN_DEFAULT_BLOCK:-latest}" - -# contracts -CARTESI_CONTRACTS_APPLICATION_FACTORY_ADDRESS="${CARTESI_CONTRACTS_APPLICATION_FACTORY_ADDRESS:-0x26E758238CB6eC5aB70ce0dd52aF2d7b82e1972E}" -CARTESI_CONTRACTS_AUTHORITY_FACTORY_ADDRESS="${CARTESI_CONTRACTS_AUTHORITY_FACTORY_ADDRESS:-0x5a3368b30174d389aFd205a46bAd35BBE6709b8a}" -CARTESI_CONTRACTS_INPUT_BOX_ADDRESS="${CARTESI_CONTRACTS_INPUT_BOX_ADDRESS:-0x1b51e2992A2755Ba4D6F7094032DF91991a0Cfac}" -CARTESI_CONTRACTS_SELF_HOSTED_APPLICATION_FACTORY_ADDRESS="${CARTESI_CONTRACTS_SELF_HOSTED_APPLICATION_FACTORY_ADDRESS:-0x870240e83b1181b419f18303D4ccC56574De2931}" -CARTESI_CONTRACTS_DAVE_APP_FACTORY_ADDRESS="${CARTESI_CONTRACTS_DAVE_APP_FACTORY_ADDRESS:-0x81dfc08498B86be7659eFD66702DB539DD534c9f}" - -# auth -CARTESI_AUTH_MNEMONIC="${CARTESI_AUTH_MNEMONIC:-test test test test test test test test test test test junk}" - -# postgres -CARTESI_DATABASE_CONNECTION="postgres://postgres:password@database:5432/rollupsdb?sslmode=disable" diff --git a/apps/cli/src/compose/docker-compose-anvil.yaml b/apps/cli/src/compose/docker-compose-anvil.yaml deleted file mode 100644 index 4e024fbd..00000000 --- a/apps/cli/src/compose/docker-compose-anvil.yaml +++ /dev/null @@ -1,39 +0,0 @@ -configs: - anvil_proxy: - content: | - http: - routers: - anvil: - rule: "PathPrefix(`/anvil`)" - middlewares: - - "remove-anvil-prefix" - service: anvil - middlewares: - remove-anvil-prefix: - replacePathRegex: - regex: "^/anvil(.*)" - replacement: "$1" - services: - anvil: - loadBalancer: - servers: - - url: "http://anvil:8545" - -services: - anvil: - image: ${CARTESI_SDK_IMAGE} - command: ["devnet", "--block-time", "${CARTESI_BLOCK_TIME:-2}"] - healthcheck: - test: ["CMD", "eth_isready"] - start_period: 10s - start_interval: 200ms - interval: 10s - timeout: 1s - retries: 5 - environment: - ANVIL_IP_ADDR: 0.0.0.0 - - proxy: - configs: - - source: anvil_proxy - target: /etc/traefik/conf.d/anvil.yaml diff --git a/apps/cli/src/compose/docker-compose-bundler.yaml b/apps/cli/src/compose/docker-compose-bundler.yaml deleted file mode 100644 index 9ef9f392..00000000 --- a/apps/cli/src/compose/docker-compose-bundler.yaml +++ /dev/null @@ -1,71 +0,0 @@ -configs: - bundler_proxy: - content: | - http: - routers: - bundler: - rule: "PathPrefix(`/bundler`)" - middlewares: - - "cors" - - "remove-bundler-prefix" - service: bundler - middlewares: - cors: - headers: - accessControlAllowMethods: - - GET - - OPTIONS - - PUT - accessControlAllowHeaders: "*" - accessControlAllowOriginList: - - "*" - accessControlMaxAge: 100 - addVaryHeader: true - remove-bundler-prefix: - replacePathRegex: - regex: "^/bundler/(.*)" - replacement: "/$1" - services: - bundler: - loadBalancer: - servers: - - url: "http://bundler:4337" - -services: - bundler: - image: ${CARTESI_SDK_IMAGE} - command: - - "alto" - - "--entrypoints" - - "0x5ff137d4b0fdcd49dca30c7cf57e578a026d2789,0x0000000071727De22E5E9d8BAf0edAc6f37da032" - - "--log-level" - - "info" - - "--rpc-url" - - "http://anvil:8545" - - "--min-executor-balance" - - "0" - - "--utility-private-key" - - "0xdbda1821b80551c9d65939329250298aa3472ba22feea921c0cf5d620ea67b97" - - "--executor-private-keys" - - "0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6,0x4bbbf85ce3377467afe5d46f804f221813b2bb87f24d81f60f1fcdbf7cbf4356,0x92db14e403b83dfe3df233f83dfa3a0d7096f21ca9b0d6d6b8d88b2b4ec1564e,0x8b3a350cf5c34c9194ca85829a2df0ec3153be0318b5e2d3348e872092edffba,0x47e179ec197488593b187f80a00eb0da91f1b9d0b13f8733639f19c30a34926a" - - "--safe-mode" - - "false" - - "--port" - - "4337" - - "--public-client-log-level" - - "error" - - "--wallet-client-log-level" - - "error" - - "--enable-debug-endpoints" - healthcheck: - test: ["CMD", "curl", "-fsS", "http://127.0.0.1:4337/health"] - start_period: 10s - start_interval: 200ms - interval: 10s - timeout: 1s - retries: 5 - - proxy: - configs: - - source: bundler_proxy - target: /etc/traefik/conf.d/bundler.yaml diff --git a/apps/cli/src/compose/docker-compose-database.yaml b/apps/cli/src/compose/docker-compose-database.yaml deleted file mode 100644 index 55470b24..00000000 --- a/apps/cli/src/compose/docker-compose-database.yaml +++ /dev/null @@ -1,12 +0,0 @@ -services: - database: - image: cartesi/rollups-database:${CARTESI_SDK_VERSION} - healthcheck: - test: ["CMD-SHELL", "pg_isready -U postgres || exit 1"] - start_period: 10s - start_interval: 200ms - interval: 10s - timeout: 1s - retries: 5 - environment: - - POSTGRES_PASSWORD=password diff --git a/apps/cli/src/compose/docker-compose-explorer.yaml b/apps/cli/src/compose/docker-compose-explorer.yaml deleted file mode 100644 index a79884ea..00000000 --- a/apps/cli/src/compose/docker-compose-explorer.yaml +++ /dev/null @@ -1,96 +0,0 @@ -x-explorer_db_env: &explorer_db_env - DB_NAME: explorer - DB_PORT: 5432 - DB_HOST: database - DB_PASS: password - -configs: - explorer_api_proxy: - content: | - http: - routers: - explorer-api: - rule: "PathPrefix(`/explorer-api`)" - middlewares: - - "remove-explorer-api-prefix" - service: explorer-api - middlewares: - remove-explorer-api-prefix: - replacePathRegex: - regex: "^/explorer-api/(.*)" - replacement: "/$1" - services: - explorer-api: - loadBalancer: - servers: - - url: "http://explorer_api:4350" - explorer_proxy: - content: | - http: - routers: - explorer: - rule: "PathPrefix(`/explorer`)" - service: explorer - services: - explorer: - loadBalancer: - servers: - - url: "http://explorer:3000" - -services: - explorer_api: - image: cartesi/rollups-explorer-api:1.1.0 - environment: - <<: *explorer_db_env - GQL_PORT: 4350 - expose: - - 4350 - command: ["sqd", "serve:prod"] - healthcheck: - test: - [ - "CMD", - "wget", - "--spider", - "-q", - "http://127.0.0.1:4350/graphql?query=%7B__typename%7D", - ] - start_period: 10s - start_interval: 200ms - interval: 10s - timeout: 1s - retries: 5 - depends_on: - database: - condition: service_healthy - - squid_processor: - image: cartesi/rollups-explorer-api:1.1.0 - environment: - <<: *explorer_db_env - CHAIN_IDS: ${CARTESI_BLOCKCHAIN_ID:-13370} - RPC_URL_13370: ${RPC_URL:-http://anvil:8545} - BLOCK_CONFIRMATIONS_13370: 0 - GENESIS_BLOCK_13370: 1 - command: ["sqd", "process:prod"] - depends_on: - database: - condition: service_healthy - - explorer: - image: cartesi/rollups-explorer:1.4.0 - environment: - NODE_RPC_URL: "http://127.0.0.1:${CARTESI_LISTEN_PORT:-6751}/anvil" - EXPLORER_API_URL: "http://127.0.0.1:${CARTESI_LISTEN_PORT:-6751}/explorer-api/graphql" - expose: - - 3000 - depends_on: - database: - condition: service_healthy - - proxy: - configs: - - source: explorer_proxy - target: /etc/traefik/conf.d/explorer.yaml - - source: explorer_api_proxy - target: /etc/traefik/conf.d/explorer-api.yaml diff --git a/apps/cli/src/compose/docker-compose-node-cpus.yaml b/apps/cli/src/compose/docker-compose-node-cpus.yaml deleted file mode 100644 index 46955e08..00000000 --- a/apps/cli/src/compose/docker-compose-node-cpus.yaml +++ /dev/null @@ -1,6 +0,0 @@ -services: - rollups-node: - deploy: - resources: - limits: - cpus: "${CARTESI_ROLLUPS_NODE_CPUS}" diff --git a/apps/cli/src/compose/docker-compose-node-memory.yaml b/apps/cli/src/compose/docker-compose-node-memory.yaml deleted file mode 100644 index cd5c8421..00000000 --- a/apps/cli/src/compose/docker-compose-node-memory.yaml +++ /dev/null @@ -1,6 +0,0 @@ -services: - rollups-node: - deploy: - resources: - limits: - memory: "${CARTESI_ROLLUPS_NODE_MEMORY}M" diff --git a/apps/cli/src/compose/docker-compose-node.yaml b/apps/cli/src/compose/docker-compose-node.yaml deleted file mode 100644 index b4a8d059..00000000 --- a/apps/cli/src/compose/docker-compose-node.yaml +++ /dev/null @@ -1,50 +0,0 @@ -configs: - rollups_node_proxy: - content: | - http: - routers: - inspect_server: - rule: "PathPrefix(`/inspect`)" - service: inspect_server - rpc_server: - rule: "PathPrefix(`/rpc`)" - service: rpc_server - services: - inspect_server: - loadBalancer: - servers: - - url: "http://rollups-node:10012" - rpc_server: - loadBalancer: - servers: - - url: "http://rollups-node:10011" - -services: - rollups-node: - image: cartesi/rollups-runtime:${CARTESI_SDK_VERSION} - depends_on: - database: - condition: service_healthy - anvil: - condition: service_healthy - expose: - - "10000" - - "10011" - - "10012" - healthcheck: - test: ["CMD", "curl", "-fsS", "http://127.0.0.1:10000/livez"] - start_period: 10s - start_interval: 200ms - interval: 10s - timeout: 1s - retries: 5 - command: ["cartesi-rollups-node"] - env_file: - - ${CARTESI_BIN_PATH}/compose/default.env - volumes: - - ./.cartesi:/var/lib/cartesi-rollups-node/snapshots:ro - - proxy: - configs: - - source: rollups_node_proxy - target: /etc/traefik/conf.d/rollups-node.yaml diff --git a/apps/cli/src/compose/docker-compose-passkey-server.yaml b/apps/cli/src/compose/docker-compose-passkey-server.yaml deleted file mode 100644 index 86ef45c9..00000000 --- a/apps/cli/src/compose/docker-compose-passkey-server.yaml +++ /dev/null @@ -1,37 +0,0 @@ -configs: - passkey_server_proxy: - content: | - http: - routers: - passkey-server: - rule: "PathPrefix(`/passkey`)" - middlewares: - - "remove-passkey-server-prefix" - service: passkey-server - middlewares: - remove-passkey-server-prefix: - replacePathRegex: - regex: "^/passkey/(.*)" - replacement: "/$1" - services: - passkey-server: - loadBalancer: - servers: - - url: "http://passkey-server:3000" - -services: - passkey-server: - image: ${CARTESI_SDK_IMAGE} - command: ["passkey-server"] - healthcheck: - test: ["CMD", "curl", "-fsS", "http://127.0.0.1:3000/health"] - start_period: 10s - start_interval: 200ms - interval: 10s - timeout: 1s - retries: 5 - - proxy: - configs: - - source: passkey_server_proxy - target: /etc/traefik/conf.d/passkey-server.yaml diff --git a/apps/cli/src/compose/docker-compose-paymaster.yaml b/apps/cli/src/compose/docker-compose-paymaster.yaml deleted file mode 100644 index 3f5d0bcf..00000000 --- a/apps/cli/src/compose/docker-compose-paymaster.yaml +++ /dev/null @@ -1,40 +0,0 @@ -configs: - paymaster_proxy: - content: | - http: - routers: - paymaster: - rule: "PathPrefix(`/paymaster`)" - middlewares: - - "remove-paymaster-prefix" - service: paymaster - middlewares: - remove-paymaster-prefix: - replacePathRegex: - regex: "^/paymaster/(.*)" - replacement: "/$1" - services: - paymaster: - loadBalancer: - servers: - - url: "http://paymaster:3000" - -services: - paymaster: - image: ${CARTESI_SDK_IMAGE} - command: "mock-verifying-paymaster" - environment: - - ALTO_RPC=http://bundler:4337 - - ANVIL_RPC=http://anvil:8545 - healthcheck: - test: ["CMD", "curl", "-fsS", "http://127.0.0.1:3000/ping"] - start_period: 10s - start_interval: 200ms - interval: 10s - timeout: 1s - retries: 5 - - proxy: - configs: - - source: paymaster_proxy - target: /etc/traefik/conf.d/paymaster.yaml diff --git a/apps/cli/src/compose/docker-compose-proxy.yaml b/apps/cli/src/compose/docker-compose-proxy.yaml deleted file mode 100644 index 4d1012aa..00000000 --- a/apps/cli/src/compose/docker-compose-proxy.yaml +++ /dev/null @@ -1,24 +0,0 @@ -services: - proxy: - image: traefik:v3.3.4 - healthcheck: - test: ["CMD", "traefik", "healthcheck", "--ping"] - start_period: 10s - start_interval: 200ms - interval: 10s - timeout: 1s - retries: 5 - command: - [ - "--ping=true", - "--entryPoints.web.address=:8088", - "--entryPoints.traefik.address=:8080", - "--metrics.prometheus=true", - "--metrics.prometheus.addServicesLabels=true", - "--providers.file.directory=/etc/traefik/conf.d", - "--providers.file.watch=true", - "--log", - "--log.level=INFO", - ] - ports: - - ${CARTESI_LISTEN_PORT:-6751}:8088 diff --git a/apps/cli/src/compose/explorer.ts b/apps/cli/src/compose/explorer.ts new file mode 100644 index 00000000..8723d331 --- /dev/null +++ b/apps/cli/src/compose/explorer.ts @@ -0,0 +1,158 @@ +import type { ComposeFile, Config, Service } from "../types/compose.js"; +import { DEFAULT_HEALTHCHECK } from "./common.js"; + +type ServiceOptions = { + databaseHost?: string; + databasePassword: string; + databasePort?: number; + apiTag?: string; + imageTag?: string; + port?: number; +}; + +// Explorer API service +export const apiService = (options: ServiceOptions): Service => { + const imageTag = options.apiTag ?? "latest"; + const databasePassword = options.databasePassword; + const databaseHost = options.databaseHost ?? "database"; + const databasePort = options.databasePort ?? 5432; + + return { + image: `cartesi/rollups-explorer-api:${imageTag}`, + environment: { + DB_PORT: databasePort.toString(), + DB_HOST: databaseHost, + DB_PASS: databasePassword, + DB_NAME: "explorer", + GQL_PORT: 4350, + }, + expose: ["4350"], + command: ["sqd", "serve:prod"], + healthcheck: { + ...DEFAULT_HEALTHCHECK, + test: [ + "CMD", + "wget", + "--spider", + "-q", + "http://127.0.0.1:4350/graphql?query=%7B__typename%7D", + ], + }, + depends_on: { + database: { condition: "service_healthy" }, + }, + }; +}; + +// Explorer API Proxy configuration +export const explorerApiProxyConfig = (): Config => { + return { + name: "explorer-api-proxy", + content: `http: + routers: + explorer-api: + rule: "PathPrefix(\`/explorer-api\`)" + middlewares: + - "remove-explorer-api-prefix" + service: explorer_api + middlewares: + remove-explorer-api-prefix: + replacePathRegex: + regex: "^/explorer-api/(.*)" + replacement: "/$1" + services: + explorer_api: + loadBalancer: + servers: + - url: "http://explorer_api:4350" +`, + }; +}; + +// Squid Processor service +export const squidProcessorService = (options: ServiceOptions): Service => { + const imageTag = options.apiTag ?? "latest"; + const databasePassword = options.databasePassword; + const databaseHost = options.databaseHost ?? "database"; + const databasePort = options.databasePort ?? 5432; + + return { + image: `cartesi/rollups-explorer-api:${imageTag}`, + environment: { + DB_HOST: databaseHost, + DB_NAME: "explorer", + DB_PASS: databasePassword, + DB_PORT: databasePort.toString(), + CHAIN_IDS: "13370", + RPC_URL_13370: "http://anvil:8545", + BLOCK_CONFIRMATIONS_13370: 0, + GENESIS_BLOCK_13370: 1, + }, + command: ["sqd", "process:prod"], + depends_on: { + database: { condition: "service_healthy" }, + }, + }; +}; + +// Explorer service +export const explorerService = (options: ServiceOptions): Service => { + const imageTag = options.imageTag ?? "latest"; + const port = options.port ?? 6571; + + const nodeRpcUrl = `http://127.0.0.1:${port}/anvil`; + const explorerApiUrl = `http://127.0.0.1:${port}/explorer-api/graphql`; + + return { + image: `cartesi/rollups-explorer:${imageTag}`, + environment: { + NODE_RPC_URL: nodeRpcUrl, + EXPLORER_API_URL: explorerApiUrl, + }, + expose: ["3000"], + depends_on: { + database: { condition: "service_healthy" }, + }, + }; +}; + +// Explorer Proxy configuration +export const explorerProxyConfig = (): Config => { + return { + name: "explorer-proxy", + content: `http: + routers: + explorer: + rule: "PathPrefix(\`/explorer\`)" + service: explorer + services: + explorer: + loadBalancer: + servers: + - url: "http://explorer:3000"`, + }; +}; + +export default (options: ServiceOptions): ComposeFile => ({ + configs: { + explorer_api_proxy: explorerApiProxyConfig(), + explorer_proxy: explorerProxyConfig(), + }, + services: { + explorer_api: apiService(options), + explorer: explorerService(options), + squid_processor: squidProcessorService(options), + proxy: { + configs: [ + { + source: "explorer_proxy", + target: "/etc/traefik/conf.d/explorer.yaml", + }, + { + source: "explorer_api_proxy", + target: "/etc/traefik/conf.d/explorer-api.yaml", + }, + ], + }, + }, +}); diff --git a/apps/cli/src/compose/node.ts b/apps/cli/src/compose/node.ts new file mode 100644 index 00000000..b655296a --- /dev/null +++ b/apps/cli/src/compose/node.ts @@ -0,0 +1,118 @@ +import { + daveAppFactoryAddress, + inputBoxAddress, + selfHostedApplicationFactoryAddress, +} from "../contracts.js"; +import type { ComposeFile, Config, Service } from "../types/compose.js"; +import { DEFAULT_HEALTHCHECK } from "./common.js"; + +type ServiceOptions = { + databaseHost?: string; + databasePort?: number; + databasePassword: string; + defaultBlock?: "latest" | "safe" | "pending" | "finalized"; + cpus?: number; + logLevel?: "info" | "debug" | "warn" | "error" | "fatal"; + memory?: number; + mnemonic?: string; + imageTag?: string; +}; + +// Rollups Node service +const service = (options: ServiceOptions): Service => { + const databasePassword = options.databasePassword; + const databaseHost = options.databaseHost ?? "database"; + const databasePort = options.databasePort ?? 5432; + const defaultBlock = options.defaultBlock ?? "latest"; + const imageTag = options.imageTag ?? "latest"; + const logLevel = options.logLevel ?? "info"; + const mnemonic = + options.mnemonic ?? + "test test test test test test test test test test test junk"; + + return { + image: `cartesi/rollups-runtime:${imageTag}`, + depends_on: { + database: { condition: "service_healthy" }, + anvil: { condition: "service_healthy" }, + }, + deploy: { + resources: { + limits: { + cpus: + options.cpus !== undefined + ? options.cpus.toString() + : undefined, + memory: + options.memory !== undefined + ? `${options.memory.toString()}M` + : undefined, + }, + }, + }, + expose: ["10000", "10011", "10012"], + healthcheck: { + ...DEFAULT_HEALTHCHECK, + test: ["CMD", "curl", "-fsS", "http://127.0.0.1:10000/livez"], + }, + command: ["cartesi-rollups-node"], + environment: { + CARTESI_AUTH_MNEMONIC: mnemonic, + CARTESI_BLOCKCHAIN_DEFAULT_BLOCK: defaultBlock, + CARTESI_BLOCKCHAIN_HTTP_ENDPOINT: "http://anvil:8545", + CARTESI_BLOCKCHAIN_ID: "13370", + CARTESI_BLOCKCHAIN_WS_ENDPOINT: "ws://anvil:8545", + CARTESI_CONTRACTS_DAVE_APP_FACTORY_ADDRESS: daveAppFactoryAddress, + CARTESI_CONTRACTS_INPUT_BOX_ADDRESS: inputBoxAddress, + CARTESI_CONTRACTS_SELF_HOSTED_APPLICATION_FACTORY_ADDRESS: + selfHostedApplicationFactoryAddress, + CARTESI_DATABASE_CONNECTION: `postgres://postgres:${databasePassword}@${databaseHost}:${databasePort}/rollupsdb?sslmode=disable`, + CARTESI_LOG_LEVEL: logLevel, + CARTESI_SNAPSHOTS_DIR: "/var/lib/cartesi-rollups-node/snapshots", + }, + volumes: ["./.cartesi:/var/lib/cartesi-rollups-node/snapshots:ro"], + }; +}; + +// Rollups Node proxy configuration +const proxy = (): Config => { + return { + name: "rollups-node-proxy", + content: ` +http: + routers: + inspect_server: + rule: "PathPrefix(\`/inspect\`)" + service: inspect_server + rpc_server: + rule: "PathPrefix(\`/rpc\`)" + service: rpc_server + services: + inspect_server: + loadBalancer: + servers: + - url: "http://rollups_node:10012" + rpc_server: + loadBalancer: + servers: + - url: "http://rollups_node:10011" +`, + }; +}; + +export default (options: ServiceOptions): ComposeFile => ({ + services: { + proxy: { + configs: [ + { + source: "rollups_node_proxy", + target: "/etc/traefik/conf.d/rollups-node.yaml", + }, + ], + }, + rollups_node: service(options), + }, + configs: { + rollups_node_proxy: proxy(), + }, +}); diff --git a/apps/cli/src/compose/passkey.ts b/apps/cli/src/compose/passkey.ts new file mode 100644 index 00000000..df6b8226 --- /dev/null +++ b/apps/cli/src/compose/passkey.ts @@ -0,0 +1,61 @@ +import type { ComposeFile, Config, Service } from "../types/compose.js"; +import { DEFAULT_HEALTHCHECK } from "./common.js"; + +type ServiceOptions = { + imageTag?: string; +}; + +// Passkey Server service +export const service = (options: ServiceOptions): Service => { + const imageTag = options.imageTag ?? "latest"; + + return { + image: `cartesi/sdk:${imageTag}`, + command: ["passkey-server"], + healthcheck: { + ...DEFAULT_HEALTHCHECK, + test: ["CMD", "curl", "-fsS", "http://127.0.0.1:3000/health"], + }, + }; +}; + +// Passkey Proxy configuration +const proxy = (): Config => { + return { + name: "passkey-proxy", + content: `http: + routers: + passkey_server: + rule: "PathPrefix(\`/passkey\`)" + middlewares: + - "remove-passkey-server-prefix" + service: passkey_server + middlewares: + remove-passkey-server-prefix: + replacePathRegex: + regex: "^/passkey/(.*)" + replacement: "/$1" + services: + passkey_server: + loadBalancer: + servers: + - url: "http://passkey_server:3000"`, + }; +}; + +export default (options: ServiceOptions): ComposeFile => ({ + configs: { + passkey_server_proxy: proxy(), + }, + services: { + passkey_server: service(options), + proxy: { + configs: [ + { + source: "passkey_server_proxy", + target: "/etc/traefik/conf.d/passkey_server.yaml", + }, + ], + }, + }, +}); diff --git a/apps/cli/src/compose/paymaster.ts b/apps/cli/src/compose/paymaster.ts new file mode 100644 index 00000000..054bf6fb --- /dev/null +++ b/apps/cli/src/compose/paymaster.ts @@ -0,0 +1,63 @@ +import type { ComposeFile, Config, Service } from "../types/compose.js"; +import { DEFAULT_HEALTHCHECK } from "./common.js"; + +type ServiceOptions = { + imageTag?: string; +}; + +const service = (options: ServiceOptions): Service => { + const imageTag = options.imageTag ?? "latest"; + + return { + image: `cartesi/sdk:${imageTag}`, + command: "mock-verifying-paymaster", + environment: { + ALTO_RPC: "http://bundler:4337", + ANVIL_RPC: "http://anvil:8545", + }, + healthcheck: { + ...DEFAULT_HEALTHCHECK, + test: ["CMD", "curl", "-fsS", "http://127.0.0.1:3000/ping"], + }, + }; +}; + +const proxy = (): Config => { + return { + name: "paymaster-proxy", + content: `http: + routers: + paymaster: + rule: "PathPrefix(\`/paymaster\`)" + middlewares: + - "remove-paymaster-prefix" + service: paymaster + middlewares: + remove-paymaster-prefix: + replacePathRegex: + regex: "^/paymaster/(.*)" + replacement: "/$1" + services: + paymaster: + loadBalancer: + servers: + - url: "http://paymaster:3000"`, + }; +}; + +export default (options: ServiceOptions): ComposeFile => ({ + configs: { + paymaster_proxy: proxy(), + }, + services: { + paymaster: service(options), + proxy: { + configs: [ + { + source: "paymaster_proxy", + target: "/etc/traefik/conf.d/paymaster.yaml", + }, + ], + }, + }, +}); diff --git a/apps/cli/src/compose/proxy.ts b/apps/cli/src/compose/proxy.ts new file mode 100644 index 00000000..43cd87e1 --- /dev/null +++ b/apps/cli/src/compose/proxy.ts @@ -0,0 +1,38 @@ +import type { ComposeFile, Service } from "../types/compose.js"; +import { DEFAULT_HEALTHCHECK } from "./common.js"; + +type ServiceOptions = { + imageTag?: string; + port?: number; +}; + +const service = (options: ServiceOptions): Service => { + const imageTag = options.imageTag ?? "latest"; + const port = options.port ?? 6751; + + return { + image: `traefik:${imageTag}`, + healthcheck: { + ...DEFAULT_HEALTHCHECK, + test: ["CMD", "traefik", "healthcheck", "--ping"], + }, + command: [ + "--ping=true", + "--entryPoints.web.address=:8088", + "--entryPoints.traefik.address=:8080", + "--metrics.prometheus=true", + "--metrics.prometheus.addServicesLabels=true", + "--providers.file.directory=/etc/traefik/conf.d", + "--providers.file.watch=true", + "--log", + "--log.level=INFO", + ], + ports: [`${port}:8088`], + }; +}; + +export default (options: ServiceOptions): ComposeFile => ({ + services: { + proxy: service(options), + }, +}); diff --git a/apps/cli/src/exec/rollups.ts b/apps/cli/src/exec/rollups.ts index ebab9a5c..fa43776b 100644 --- a/apps/cli/src/exec/rollups.ts +++ b/apps/cli/src/exec/rollups.ts @@ -1,7 +1,6 @@ import chalk from "chalk"; import { execa } from "execa"; import { Listr, type ListrTask } from "listr2"; -import path from "node:path"; import pRetry from "p-retry"; import { type Address, @@ -10,13 +9,22 @@ import { getAddress, hexToNumber, } from "viem"; +import { stringify } from "yaml"; import { getContextPath, getMachineHash, getProjectName, getServiceHealth, } from "../base.js"; -import { DEFAULT_SDK_IMAGE } from "../config.js"; +import anvil from "../compose/anvil.js"; +import { concat } from "../compose/builder.js"; +import bundler from "../compose/bundler.js"; +import database from "../compose/database.js"; +import explorer from "../compose/explorer.js"; +import node from "../compose/node.js"; +import passkey from "../compose/passkey.js"; +import paymaster from "../compose/paymaster.js"; +import proxy from "../compose/proxy.js"; export type RollupsDeployment = { name: string; @@ -60,7 +68,7 @@ export const getDeployments = async ( "--project-name", options.projectName, "exec", - "rollups-node", + "rollups_node", "cartesi-rollups-cli", "app", "list", @@ -94,7 +102,6 @@ export const getApplicationAddress = async (options: { type Service = { name: string; // name of the service - file: string; // docker compose file name healthySemaphore?: string; // service to check if the service is healthy healthyTitle?: string | ((port: number, name?: string) => string); // title of the service when it is healthy waitTitle?: string; // title of the service when it is starting @@ -107,7 +114,6 @@ const host = "http://127.0.0.1"; const baseServices: Service[] = [ { name: "anvil", - file: "docker-compose-anvil.yaml", healthySemaphore: "anvil", healthyTitle: (port) => `${chalk.cyan("anvil")} service ready at ${chalk.cyan(`${host}:${port}/anvil`)}`, @@ -116,16 +122,13 @@ const baseServices: Service[] = [ }, { name: "proxy", - file: "docker-compose-proxy.yaml", }, { name: "database", - file: "docker-compose-database.yaml", }, { name: "rpc", - file: "docker-compose-node.yaml", - healthySemaphore: "rollups-node", + healthySemaphore: "rollups_node", healthyTitle: (port) => `${chalk.cyan("rpc")} service ready at ${chalk.cyan(`${host}:${port}/rpc`)}`, waitTitle: `${chalk.cyan("rpc")} service starting...`, @@ -133,8 +136,7 @@ const baseServices: Service[] = [ }, { name: "inspect", - file: "docker-compose-node.yaml", - healthySemaphore: "rollups-node", + healthySemaphore: "rollups_node", healthyTitle: (port, name) => `${chalk.cyan("inspect")} service ready at ${chalk.cyan(`${host}:${port}/inspect/${name ?? ""}`)}`, waitTitle: `${chalk.cyan("inspect")} service starting...`, @@ -145,7 +147,6 @@ const baseServices: Service[] = [ const availableServices: Service[] = [ { name: "bundler", - file: "docker-compose-bundler.yaml", healthySemaphore: "bundler", healthyTitle: (port) => `${chalk.cyan("bundler")} service ready at ${chalk.cyan(`${host}:${port}/bundler/rpc`)}`, @@ -154,7 +155,6 @@ const availableServices: Service[] = [ }, { name: "explorer", - file: "docker-compose-explorer.yaml", healthySemaphore: "explorer_api", healthyTitle: (port) => `${chalk.cyan("explorer")} service ready at ${chalk.cyan(`${host}:${port}/explorer`)}`, @@ -163,7 +163,6 @@ const availableServices: Service[] = [ }, { name: "paymaster", - file: "docker-compose-paymaster.yaml", healthySemaphore: "paymaster", healthyTitle: (port) => `${chalk.cyan("paymaster")} service ready at ${chalk.cyan(`${host}:${port}/paymaster`)}`, @@ -172,8 +171,7 @@ const availableServices: Service[] = [ }, { name: "passkey", - file: "docker-compose-passkey-server.yaml", - healthySemaphore: "passkey-server", + healthySemaphore: "passkey_server", healthyTitle: (port) => `${chalk.cyan("passkey")} service ready at ${chalk.cyan(`${host}:${port}/passkey`)}`, waitTitle: `${chalk.cyan("passkey")} service starting...`, @@ -216,7 +214,7 @@ const serviceMonitorTask = (options: { export const startEnvironment = async (options: { blockTime: number; cpus?: number; - defaultBlock: string; + defaultBlock: "latest" | "safe" | "pending" | "finalized"; dryRun: boolean; memory?: number; port: number; @@ -240,85 +238,80 @@ export const startEnvironment = async (options: { const address = `${host}:${port}`; - // path of the tool instalation - const binPath = path.join( - path.dirname(new URL(import.meta.url).pathname), - "..", - ); - // setup the environment variable used in docker compose const env: NodeJS.ProcessEnv = { - CARTESI_BIN_PATH: binPath, - CARTESI_BLOCK_TIME: blockTime.toString(), CARTESI_BLOCKCHAIN_DEFAULT_BLOCK: defaultBlock, CARTESI_LISTEN_PORT: port.toString(), CARTESI_LOG_LEVEL: verbose ? "debug" : "info", - CARTESI_ROLLUPS_NODE_CPUS: cpus?.toString(), - CARTESI_ROLLUPS_NODE_MEMORY: memory?.toString(), - CARTESI_SDK_IMAGE: `${DEFAULT_SDK_IMAGE}:${runtimeVersion}`, - CARTESI_SDK_VERSION: runtimeVersion, }; - // build a list of unique compose files - const composeFiles = [...new Set(baseServices.map(({ file }) => file))]; + const files = [ + anvil({ blockTime, imageTag: runtimeVersion }), + database({ imageTag: runtimeVersion, password: "password" }), + node({ + cpus, + databasePassword: "password", + defaultBlock, + imageTag: runtimeVersion, + logLevel: verbose ? "debug" : "info", + memory, + }), + proxy({ imageTag: "v3.3.4", port }), + ]; - // cpu and memory limits, mostly for testing and debuggingpurposes - if (cpus) { - composeFiles.push("docker-compose-node-cpus.yaml"); + if (services.includes("explorer")) { + files.push( + explorer({ + imageTag: "1.4.0", + apiTag: "1.1.0", + databasePassword: "password", + port, + }), + ); } - if (memory) { - composeFiles.push("docker-compose-node-memory.yaml"); + if (services.includes("bundler")) { + files.push(bundler({ imageTag: runtimeVersion })); + } + if (services.includes("paymaster")) { + files.push(paymaster({ imageTag: runtimeVersion })); + } + if (services.includes("passkey")) { + files.push(passkey({ imageTag: runtimeVersion })); } - // select subset of optional services - const optionalServices = - services.length === 1 && services[0] === "all" - ? availableServices - : availableServices.filter(({ name }) => services.includes(name)); - - // add to compose files list - composeFiles.push(...optionalServices.map(({ file }) => file)); - - // create the "--file " list - const files = composeFiles.flatMap((f) => [ - "--file", - path.join(binPath, "compose", f), - ]); - - const composeArgs = [ - "compose", - ...files, - "--project-directory", - ".", - "--project-name", - projectName, - ]; + const composeArgs = ["compose", "-f", "-", "--project-directory", "."]; // run in detached mode (background) const upArgs = ["--detach"]; + // merge files, following Docker Compose merge rules + const composeFile = concat([{ name: projectName }, ...files]); + // if only dry run, just return the config if (dryRun) { // parse, resolve and render compose file in canonical format const { stdout: config } = await execa( "docker", [...composeArgs, "config", "--format", "yaml"], - { env }, + { env, input: stringify(composeFile, { lineWidth: 0, indent: 2 }) }, ); return { address, config }; } // pull images first - const pullArgs = ["--policy", "missing"]; - await execa("docker", [...composeArgs, "pull", ...pullArgs], { - env, - stdio: "inherit", - }); + // const pullArgs = ["--policy", "missing"]; + // await execa("docker", [...composeArgs, "pull", ...pullArgs], { + // env, + ////FIXME: stdio and input won't work together + // stdio: "inherit", + // input: composeFile.build() + // }); // run compose await execa("docker", [...composeArgs, "up", ...upArgs], { env, + input: stringify(composeFile, { lineWidth: 0, indent: 2 }), }); return { address }; @@ -381,7 +374,7 @@ export const publishMachine = async (options: { projectName, "cp", snapshotPath, - `rollups-node:${containerSnapshotPath}`, + `rollups_node:${containerSnapshotPath}`, ]); return containerSnapshotPath; }; @@ -419,7 +412,7 @@ export const deployAuthority = async (options: { "--project-name", projectName, "exec", - "rollups-node", + "rollups_node", "cartesi-rollups-cli", "deploy", "authority", @@ -477,7 +470,7 @@ export const deployApplication = async (options: { "--project-name", projectName, "exec", - "rollups-node", + "rollups_node", "cartesi-rollups-cli", "deploy", "application", @@ -509,7 +502,7 @@ export const removeApplication = async (options: { "--project-name", projectName, "exec", - "rollups-node", + "rollups_node", "cartesi-rollups-cli", "app", "status", @@ -528,7 +521,7 @@ export const removeApplication = async (options: { "--project-name", projectName, "exec", - "rollups-node", + "rollups_node", "cartesi-rollups-cli", "app", "remove", diff --git a/apps/cli/src/types/compose.ts b/apps/cli/src/types/compose.ts new file mode 100644 index 00000000..5dbfbb03 --- /dev/null +++ b/apps/cli/src/types/compose.ts @@ -0,0 +1,800 @@ +/** + * Docker Compose File specification + * Based on the official Compose Specification + * https://github.com/compose-spec/compose-go/blob/v2.10.0/schema/compose-spec.json + * https://transform.tools/json-schema-to-typescript + */ +export interface ComposeFile { + /** Define the Compose project name */ + name?: string; + + /** Compose sub-projects to be included */ + include?: Array; + + /** Services that will be used by your application */ + services?: Record; + + /** Networks that are shared among multiple services */ + networks?: Record; + + /** Named volumes that are shared among multiple services */ + volumes?: Record; + + /** Secrets that are shared among multiple services */ + secrets?: Record; + + /** Configurations that are shared among multiple services */ + configs?: Record; + + /** Language models that will be used by your application */ + models?: Record; +} + +export interface Include { + /** Path to the Compose application or sub-project files to include */ + path: string | string[]; + + /** Path to environment files */ + env_file?: string | string[]; + + /** Path to resolve relative paths */ + project_directory?: string; +} + +export interface Service { + /** Specify the image to start the container from */ + image?: string; + + /** Configuration options for building the service's image */ + build?: string | BuildConfig; + + /** Override the default command */ + command?: string | string[] | null; + + /** Override the default entrypoint */ + entrypoint?: string | string[] | null; + + /** Add environment variables */ + environment?: Record | string[]; + + /** Add environment variables from files */ + env_file?: string | string[] | EnvFile[]; + + /** Mount volumes */ + volumes?: Array; + + /** Expose ports without publishing them to the host */ + expose?: Array; + + /** Map ports from container to host */ + ports?: Array; + + /** Express dependency between services */ + depends_on?: string[] | Record; + + /** Restart policy for the container */ + restart?: "no" | "always" | "on-failure" | "unless-stopped" | string; + + /** Grant access to configs */ + configs?: Array; + + /** Grant access to secrets */ + secrets?: Array; + + /** Configure a health check */ + healthcheck?: Healthcheck; + + /** Specify a custom container name */ + container_name?: string; + + /** Custom hostname for the service container */ + hostname?: string; + + /** Add metadata using Docker labels */ + labels?: Record | string[]; + + /** Logging configuration */ + logging?: LoggingConfig; + + /** Network mode */ + network_mode?: string; + + /** Networks to join */ + networks?: string[] | Record; + + /** PID namespace mode */ + pid?: string; + + /** IPC sharing mode */ + ipc?: string; + + /** Deployment configuration */ + deploy?: DeployConfig; + + /** Development configuration */ + develop?: DevelopConfig; + + /** Working directory */ + working_dir?: string; + + /** User to run commands as */ + user?: string; + + /** Add Linux capabilities */ + cap_add?: string[]; + + /** Drop Linux capabilities */ + cap_drop?: string[]; + + /** Device mappings */ + devices?: Array; + + /** DNS servers */ + dns?: string | string[]; + + /** DNS search domains */ + dns_search?: string | string[]; + + /** DNS options */ + dns_opt?: string[]; + + /** Extra hosts */ + extra_hosts?: string[] | Record; + + /** External links */ + external_links?: string[]; + + /** GPU configuration */ + gpus?: string | GpuConfig[]; + + /** Additional groups */ + group_add?: Array; + + /** Memory limit */ + mem_limit?: string | number; + + /** Memory reservation */ + mem_reservation?: string | number; + + /** Memory swap limit */ + memswap_limit?: string | number; + + /** OOM killer disable */ + oom_kill_disable?: boolean | string; + + /** OOM score adjustment */ + oom_score_adj?: number | string; + + /** Privileged mode */ + privileged?: boolean | string; + + /** Read-only root filesystem */ + read_only?: boolean | string; + + /** Shared memory size */ + shm_size?: string | number; + + /** stdin_open */ + stdin_open?: boolean | string; + + /** tty */ + tty?: boolean | string; + + /** Ulimits */ + ulimits?: Record; + + /** Mount volumes from another service */ + volumes_from?: string[]; + + /** CPU configuration */ + cpus?: string | number; + cpuset?: string; + cpu_shares?: string | number; + cpu_quota?: string | number; + cpu_period?: string | number; + cpu_count?: string | number; + cpu_percent?: string | number; + + /** Annotations */ + annotations?: Record | string[]; + + /** Cgroup configuration */ + cgroup?: "host" | "private"; + cgroup_parent?: string; + + /** Domain name */ + domainname?: string; + + /** Init process */ + init?: boolean | string; + + /** Isolation technology */ + isolation?: string; + + /** Links */ + links?: string[]; + + /** Mac address */ + mac_address?: string; + + /** Platform */ + platform?: string; + + /** Security options */ + security_opt?: string[]; + + /** Stop grace period */ + stop_grace_period?: string; + + /** Stop signal */ + stop_signal?: string; + + /** Sysctls */ + sysctls?: Record | string[]; + + /** Tmpfs */ + tmpfs?: string | string[]; + + /** Attach */ + attach?: boolean | string; + + /** Extends another service */ + extends?: string | ExtendsConfig; + + /** External provider */ + provider?: ProviderConfig; + + /** Blkio configuration */ + blkio_config?: BlkioConfig; + + /** Credential spec */ + credential_spec?: CredentialSpec; + + /** Device cgroup rules */ + device_cgroup_rules?: string[]; +} + +export interface BuildConfig { + /** Path to the build context */ + context: string; + + /** Dockerfile name */ + dockerfile?: string; + + /** Inline Dockerfile content */ + dockerfile_inline?: string; + + /** Build arguments */ + args?: Record | string[]; + + /** SSH agent configuration */ + ssh?: Record | string[]; + + /** Labels to apply to the built image */ + labels?: Record | string[]; + + /** Cache sources */ + cache_from?: string[]; + + /** Cache destinations */ + cache_to?: string[]; + + /** Disable build cache */ + no_cache?: boolean | string; + + /** Build stages to not use cache for */ + no_cache_filter?: string | string[]; + + /** Additional build contexts */ + additional_contexts?: Record | string[]; + + /** Network mode for build */ + network?: string; + + /** Pull policy */ + pull?: boolean | string; + + /** Target build stage */ + target?: string; + + /** Shared memory size */ + shm_size?: string | number; + + /** Extra hosts */ + extra_hosts?: string[] | Record; + + /** Isolation technology */ + isolation?: string; + + /** Privileged mode */ + privileged?: boolean | string; + + /** Build secrets */ + secrets?: Array; + + /** Image tags */ + tags?: string[]; + + /** Ulimits */ + ulimits?: Record; + + /** Target platforms */ + platforms?: string[]; + + /** Provenance attestation */ + provenance?: string | boolean; + + /** SBOM attestation */ + sbom?: string | boolean; + + /** Entitlements */ + entitlements?: string[]; +} + +export interface Healthcheck { + /** Disable healthcheck */ + disable?: boolean | string; + + /** Health check test command */ + test?: string | string[]; + + /** Time between checks */ + interval?: string; + + /** Timeout for a single check */ + timeout?: string; + + /** Number of consecutive failures */ + retries?: number | string; + + /** Start period before checks count */ + start_period?: string; + + /** Interval during start period */ + start_interval?: string; +} + +export interface DependsOnConfig { + /** Restart dependent services */ + restart?: boolean | string; + + /** Whether dependency is required */ + required?: boolean; + + /** Condition to wait for */ + condition: + | "service_started" + | "service_healthy" + | "service_completed_successfully"; +} + +export interface ConfigReference { + /** Config name */ + source: string; + + /** Target path in container */ + target?: string; + + /** File UID */ + uid?: string; + + /** File GID */ + gid?: string; + + /** File mode */ + mode?: number | string; +} + +export interface SecretReference { + /** Secret name */ + source: string; + + /** Target path in container */ + target?: string; + + /** File UID */ + uid?: string; + + /** File GID */ + gid?: string; + + /** File mode */ + mode?: number | string; +} + +export interface VolumeMount { + /** Mount type */ + type: "bind" | "volume" | "tmpfs" | "cluster" | "npipe" | "image"; + + /** Source path or volume name */ + source?: string; + + /** Target path in container */ + target: string; + + /** Read-only flag */ + read_only?: boolean | string; + + /** Consistency requirements */ + consistency?: string; + + /** Bind-specific options */ + bind?: { + propagation?: string; + create_host_path?: boolean | string; + recursive?: "enabled" | "disabled" | "writable" | "readonly"; + selinux?: "z" | "Z"; + }; + + /** Volume-specific options */ + volume?: { + labels?: Record | string[]; + nocopy?: boolean | string; + subpath?: string; + }; + + /** Tmpfs-specific options */ + tmpfs?: { + size?: number | string; + mode?: number | string; + }; + + /** Image-specific options */ + image?: { + subpath?: string; + }; +} + +export interface PortMapping { + /** Target port in container */ + target: number; + + /** Published port on host */ + published?: string | number; + + /** Protocol */ + protocol?: "tcp" | "udp"; + + /** Host IP */ + host_ip?: string; + + /** Port mode */ + mode?: "host" | "ingress"; +} + +export interface EnvFile { + /** Path to env file */ + path: string; + + /** Required flag */ + required?: boolean; + + /** Format of env file */ + format?: "env" | "dotenv"; +} + +export interface LoggingConfig { + /** Logging driver */ + driver?: string; + + /** Driver-specific options */ + options?: Record; +} + +export interface NetworkConfig { + /** Aliases for the service on the network */ + aliases?: string[]; + + /** IPv4 address */ + ipv4_address?: string; + + /** IPv6 address */ + ipv6_address?: string; + + /** Link-local IPs */ + link_local_ips?: string[]; + + /** MAC address */ + mac_address?: string; + + /** Driver options */ + driver_opts?: Record; + + /** Priority */ + priority?: number | string; +} + +export interface DeployConfig { + /** Number of replicas */ + replicas?: number | string; + + /** Update configuration */ + update_config?: UpdateConfig; + + /** Rollback configuration */ + rollback_config?: RollbackConfig; + + /** Resource limits and reservations */ + resources?: ResourcesConfig; + + /** Restart policy */ + restart_policy?: RestartPolicyConfig; + + /** Placement constraints */ + placement?: PlacementConfig; + + /** Endpoint mode */ + endpoint_mode?: string; + + /** Labels */ + labels?: Record | string[]; +} + +export interface UpdateConfig { + parallelism?: number | string; + delay?: string; + failure_action?: "continue" | "pause" | "rollback"; + monitor?: string; + max_failure_ratio?: number | string; + order?: "start-first" | "stop-first"; +} + +export interface RollbackConfig { + parallelism?: number | string; + delay?: string; + failure_action?: "continue" | "pause"; + monitor?: string; + max_failure_ratio?: number | string; + order?: "start-first" | "stop-first"; +} + +export interface ResourcesConfig { + limits?: { + cpus?: string | number; + memory?: string; + pids?: number | string; + }; + reservations?: { + cpus?: string | number; + memory?: string; + generic_resources?: GenericResource[]; + devices?: DeviceRequest[]; + }; +} + +export interface GenericResource { + discrete_resource_spec?: { + kind?: string; + value?: number | string; + }; +} + +export interface DeviceRequest { + capabilities: string[]; + count?: string | number; + device_ids?: string[]; + driver?: string; + options?: Record | string[]; +} + +export interface RestartPolicyConfig { + condition?: "none" | "on-failure" | "any"; + delay?: string; + max_attempts?: number | string; + window?: string; +} + +export interface PlacementConfig { + constraints?: string[]; + preferences?: Array<{ spread?: string }>; + max_replicas_per_node?: number | string; +} + +export interface DevelopConfig { + watch?: WatchConfig[]; +} + +export interface WatchConfig { + /** Path to watch */ + path: string; + + /** Action to take */ + action: "rebuild" | "sync" | "restart" | "sync+restart" | "sync+exec"; + + /** Target path for sync */ + target?: string; + + /** Patterns to ignore */ + ignore?: string | string[]; + + /** Patterns to include */ + include?: string | string[]; +} + +export interface DeviceMapping { + source: string; + target?: string; + permissions?: string; +} + +export interface GpuConfig { + capabilities?: string[]; + count?: string | number; + device_ids?: string[]; + driver?: string; + options?: Record | string[]; +} + +export interface UlimitConfig { + soft: number; + hard: number; +} + +export interface ExtendsConfig { + /** Service to extend */ + service: string; + + /** File containing the service */ + file?: string; +} + +export interface ProviderConfig { + /** Provider type */ + type: string; + + /** Provider options */ + options?: Record< + string, + string | number | boolean | Array + >; +} + +export interface BlkioConfig { + device_read_bps?: BlkioLimit[]; + device_read_iops?: BlkioLimit[]; + device_write_bps?: BlkioLimit[]; + device_write_iops?: BlkioLimit[]; + weight?: number | string; + weight_device?: BlkioWeight[]; +} + +export interface BlkioLimit { + path: string; + rate: number | string; +} + +export interface BlkioWeight { + path: string; + weight: number | string; +} + +export interface CredentialSpec { + config?: string; + file?: string; + registry?: string; +} + +export interface Network { + /** Custom name for the network */ + name?: string; + + /** Network driver */ + driver?: string; + + /** Driver-specific options */ + driver_opts?: Record; + + /** IPAM configuration */ + ipam?: IpamConfig; + + /** External network */ + external?: boolean | string | { name?: string }; + + /** Internal network */ + internal?: boolean | string; + + /** Enable IPv4 */ + enable_ipv4?: boolean | string; + + /** Enable IPv6 */ + enable_ipv6?: boolean | string; + + /** Attachable */ + attachable?: boolean | string; + + /** Labels */ + labels?: Record | string[]; +} + +export interface IpamConfig { + driver?: string; + config?: IpamPoolConfig[]; + options?: Record; +} + +export interface IpamPoolConfig { + subnet?: string; + ip_range?: string; + gateway?: string; + aux_addresses?: Record; +} + +export interface Volume { + /** Custom name for the volume */ + name?: string; + + /** Volume driver */ + driver?: string; + + /** Driver-specific options */ + driver_opts?: Record; + + /** External volume */ + external?: boolean | string | { name?: string }; + + /** Labels */ + labels?: Record | string[]; +} + +export interface Secret { + /** Custom name for the secret */ + name?: string; + + /** Environment variable name */ + environment?: string; + + /** File path */ + file?: string; + + /** External secret */ + external?: boolean | string | { name?: string }; + + /** Labels */ + labels?: Record | string[]; + + /** Driver */ + driver?: string; + + /** Driver options */ + driver_opts?: Record; + + /** Template driver */ + template_driver?: string; +} + +export interface Config { + /** Custom name for the config */ + name: string; + + /** Inline content */ + content?: string; + + /** Environment variable name */ + environment?: string; + + /** File path */ + file?: string; + + /** External config */ + external?: boolean | string | { name?: string }; + + /** Labels */ + labels?: Record | string[]; + + /** Template driver */ + template_driver?: string; +} + +export interface Model { + /** Custom name for the model */ + name?: string; + + /** Language model to run */ + model: string; + + /** Context size */ + context_size?: number; + + /** Runtime flags */ + runtime_flags?: string[]; +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9f674efb..a6b01386 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -98,6 +98,9 @@ importers: viem: specifier: ^2.37.6 version: 2.37.6(typescript@5.9.2)(zod@3.25.42) + yaml: + specifier: ^2.8.2 + version: 2.8.2 devDependencies: '@biomejs/biome': specifier: 'catalog:' @@ -141,9 +144,6 @@ importers: '@wagmi/cli': specifier: ^2.5.1 version: 2.5.1(typescript@5.9.2) - copyfiles: - specifier: ^2.4.1 - version: 2.4.1 npm-run-all: specifier: ^4.1.5 version: 4.1.5 @@ -210,7 +210,7 @@ importers: version: link:../tsconfig tsup: specifier: ^8.2.3 - version: 8.3.0(postcss@8.4.47)(typescript@5.5.4) + version: 8.3.0(postcss@8.4.47)(typescript@5.5.4)(yaml@2.8.2) typescript: specifier: 5.5.4 version: 5.5.4 @@ -1651,9 +1651,6 @@ packages: resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} engines: {node: '>= 12'} - cliui@7.0.4: - resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} - color-convert@1.9.3: resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} @@ -1707,10 +1704,6 @@ packages: cookiejar@2.1.4: resolution: {integrity: sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==} - copyfiles@2.4.1: - resolution: {integrity: sha512-fereAvAvxDrQDOXybk3Qu3dPbOoKoysFMWtkY3mv5BsL8//OSZVL5DCLYqgRfY5cWirgRzlC+WSrxp6Bo3eNZg==} - hasBin: true - core-util-is@1.0.3: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} @@ -2049,9 +2042,6 @@ packages: resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} engines: {node: '>=6 <7 || >=8'} - fs.realpath@1.0.0: - resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} - fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -2075,10 +2065,6 @@ packages: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} - get-caller-file@2.0.5: - resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} - engines: {node: 6.* || 8.* || >= 10.*} - get-east-asian-width@1.3.0: resolution: {integrity: sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==} engines: {node: '>=18'} @@ -2129,10 +2115,6 @@ packages: engines: {node: 20 || >=22} hasBin: true - glob@7.2.3: - resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Glob versions prior to v9 are no longer supported - globals@11.12.0: resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} engines: {node: '>=4'} @@ -2243,10 +2225,6 @@ packages: resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} engines: {node: '>=8'} - inflight@1.0.6: - resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} - deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. - inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} @@ -2408,9 +2386,6 @@ packages: resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} engines: {node: '>=0.10.0'} - isarray@0.0.1: - resolution: {integrity: sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==} - isarray@1.0.0: resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} @@ -2693,11 +2668,6 @@ packages: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} - mkdirp@1.0.4: - resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} - engines: {node: '>=10'} - hasBin: true - mnemonist@0.39.6: resolution: {integrity: sha512-A/0v5Z59y63US00cRSLiloEIw3t5G+MiKz4BhX21FI+YBJXBOGW0ohFxTxO08dsOYlzxo87T7vGfZKYp2bcAWA==} @@ -2770,9 +2740,6 @@ packages: node-releases@2.0.18: resolution: {integrity: sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==} - noms@0.0.0: - resolution: {integrity: sha512-lNDU9VJaOPxUmXcLb+HQFeUgQQPtMI24Gt6hgfuMHRJgMRHMF/qZ4HJD3GDru4sSw9IQl2jPjAYnQrdIeLbwow==} - normalize-package-data@2.5.0: resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} @@ -2823,9 +2790,6 @@ packages: resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} engines: {node: '>=14.0.0'} - once@1.4.0: - resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - onetime@5.1.2: resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} engines: {node: '>=6'} @@ -2898,10 +2862,6 @@ packages: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} - path-is-absolute@1.0.1: - resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} - engines: {node: '>=0.10.0'} - path-key@2.0.1: resolution: {integrity: sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==} engines: {node: '>=4'} @@ -3095,9 +3055,6 @@ packages: resolution: {integrity: sha512-VIMnQi/Z4HT2Fxuwg5KrY174U1VdUIASQVWXXyqtNRtxSr9IYkn1rsI6Tb6HsrHCmB7gVpNwX6JxPTHcH6IoTA==} engines: {node: '>=6'} - readable-stream@1.0.34: - resolution: {integrity: sha512-ok1qVCJuRkNmvebYikljxJA/UEsKwLl2nI1OmaqAu4/UE+h0wKCHok4XkL/gvi39OacXvw59RJUOFUkDib2rHg==} - readable-stream@2.3.8: resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} @@ -3128,10 +3085,6 @@ packages: resolution: {integrity: sha512-vqlC04+RQoFalODCbCumG2xIOvapzVMHwsyIGM/SIE8fRhFFsXeH8/QQ+s0T0kDAhKc4k30s73/0ydkHQz6HlQ==} engines: {node: '>= 0.4'} - require-directory@2.1.1: - resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} - engines: {node: '>=0.10.0'} - require-from-string@2.0.2: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} @@ -3291,6 +3244,7 @@ packages: source-map@0.8.0-beta.0: resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} engines: {node: '>= 8'} + deprecated: The work that was done in this beta branch won't be included in future versions sparse-array@1.3.2: resolution: {integrity: sha512-ZT711fePGn3+kQyLuv1fpd3rNSkNF8vd5Kv2D+qnOANeyKs3fx6bUMGWRPvgTTcYV64QMqZKZwcuaQSP3AZ0tg==} @@ -3365,9 +3319,6 @@ packages: resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} engines: {node: '>= 0.4'} - string_decoder@0.10.31: - resolution: {integrity: sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==} - string_decoder@1.1.1: resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} @@ -3816,9 +3767,6 @@ packages: resolution: {integrity: sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==} engines: {node: '>=18'} - wrappy@1.0.2: - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - ws@8.17.1: resolution: {integrity: sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==} engines: {node: '>=10.0.0'} @@ -3862,24 +3810,21 @@ packages: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'} - y18n@5.0.8: - resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} - engines: {node: '>=10'} - yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} yallist@4.0.0: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + yaml@2.8.2: + resolution: {integrity: sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==} + engines: {node: '>= 14.6'} + hasBin: true + yargs-parser@20.2.9: resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} engines: {node: '>=10'} - yargs@16.2.0: - resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} - engines: {node: '>=10'} - yn@3.1.1: resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} engines: {node: '>=6'} @@ -5338,12 +5283,6 @@ snapshots: cli-width@4.1.0: {} - cliui@7.0.4: - dependencies: - string-width: 4.2.3 - strip-ansi: 6.0.1 - wrap-ansi: 7.0.0 - color-convert@1.9.3: dependencies: color-name: 1.1.3 @@ -5380,16 +5319,6 @@ snapshots: cookiejar@2.1.4: {} - copyfiles@2.4.1: - dependencies: - glob: 7.2.3 - minimatch: 3.1.2 - mkdirp: 1.0.4 - noms: 0.0.0 - through2: 2.0.5 - untildify: 4.0.0 - yargs: 16.2.0 - core-util-is@1.0.3: {} create-require@1.1.1: {} @@ -5877,8 +5806,6 @@ snapshots: jsonfile: 4.0.0 universalify: 0.1.2 - fs.realpath@1.0.0: {} - fsevents@2.3.3: optional: true @@ -5897,8 +5824,6 @@ snapshots: gensync@1.0.0-beta.2: {} - get-caller-file@2.0.5: {} - get-east-asian-width@1.3.0: {} get-intrinsic@1.2.4: @@ -5973,15 +5898,6 @@ snapshots: package-json-from-dist: 1.0.1 path-scurry: 2.0.0 - glob@7.2.3: - dependencies: - fs.realpath: 1.0.0 - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 3.1.2 - once: 1.4.0 - path-is-absolute: 1.0.1 - globals@11.12.0: {} globalthis@1.0.4: @@ -6074,11 +5990,6 @@ snapshots: indent-string@4.0.0: {} - inflight@1.0.6: - dependencies: - once: 1.4.0 - wrappy: 1.0.2 - inherits@2.0.4: {} interface-ipld-format@1.0.1: @@ -6241,8 +6152,6 @@ snapshots: is-windows@1.0.2: {} - isarray@0.0.1: {} - isarray@1.0.0: {} isarray@2.0.5: {} @@ -6514,8 +6423,6 @@ snapshots: minipass@7.1.2: {} - mkdirp@1.0.4: {} - mnemonist@0.39.6: dependencies: obliterator: 2.0.4 @@ -6578,11 +6485,6 @@ snapshots: node-releases@2.0.18: {} - noms@0.0.0: - dependencies: - inherits: 2.0.4 - readable-stream: 1.0.34 - normalize-package-data@2.5.0: dependencies: hosted-git-info: 2.8.9 @@ -6641,10 +6543,6 @@ snapshots: on-exit-leak-free@2.1.2: {} - once@1.4.0: - dependencies: - wrappy: 1.0.2 - onetime@5.1.2: dependencies: mimic-fn: 2.1.0 @@ -6724,8 +6622,6 @@ snapshots: path-exists@4.0.0: {} - path-is-absolute@1.0.1: {} - path-key@2.0.1: {} path-key@3.1.1: {} @@ -6798,11 +6694,12 @@ snapshots: possible-typed-array-names@1.0.0: {} - postcss-load-config@6.0.1(postcss@8.4.47): + postcss-load-config@6.0.1(postcss@8.4.47)(yaml@2.8.2): dependencies: lilconfig: 3.1.2 optionalDependencies: postcss: 8.4.47 + yaml: 2.8.2 postcss@8.4.47: dependencies: @@ -6905,13 +6802,6 @@ snapshots: pify: 4.0.1 strip-bom: 3.0.0 - readable-stream@1.0.34: - dependencies: - core-util-is: 1.0.3 - inherits: 2.0.4 - isarray: 0.0.1 - string_decoder: 0.10.31 - readable-stream@2.3.8: dependencies: core-util-is: 1.0.3 @@ -6950,8 +6840,6 @@ snapshots: es-errors: 1.3.0 set-function-name: 2.0.2 - require-directory@2.1.1: {} - require-from-string@2.0.2: {} resolve-from@5.0.0: {} @@ -7201,8 +7089,6 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.0.0 - string_decoder@0.10.31: {} - string_decoder@1.1.1: dependencies: safe-buffer: 5.1.2 @@ -7372,7 +7258,7 @@ snapshots: tslib@2.8.1: {} - tsup@8.3.0(postcss@8.4.47)(typescript@5.5.4): + tsup@8.3.0(postcss@8.4.47)(typescript@5.5.4)(yaml@2.8.2): dependencies: bundle-require: 5.0.0(esbuild@0.23.1) cac: 6.7.14 @@ -7383,7 +7269,7 @@ snapshots: execa: 5.1.1 joycon: 3.1.1 picocolors: 1.1.1 - postcss-load-config: 6.0.1(postcss@8.4.47) + postcss-load-config: 6.0.1(postcss@8.4.47)(yaml@2.8.2) resolve-from: 5.0.0 rollup: 4.24.0 source-map: 0.8.0-beta.0 @@ -7708,8 +7594,6 @@ snapshots: string-width: 7.2.0 strip-ansi: 7.1.0 - wrappy@1.0.2: {} - ws@8.17.1: {} ws@8.18.3: {} @@ -7722,23 +7606,13 @@ snapshots: xtend@4.0.2: {} - y18n@5.0.8: {} - yallist@3.1.1: {} yallist@4.0.0: {} - yargs-parser@20.2.9: {} + yaml@2.8.2: {} - yargs@16.2.0: - dependencies: - cliui: 7.0.4 - escalade: 3.2.0 - get-caller-file: 2.0.5 - require-directory: 2.1.1 - string-width: 4.2.3 - y18n: 5.0.8 - yargs-parser: 20.2.9 + yargs-parser@20.2.9: {} yn@3.1.1: {}