Skip to main content
Glama
dills122

Formly Agent Contracts

by dills122

Contrato Formly

El Contrato Formly convierte la configuración de campos de Angular Formly en JSON estable y versionado que un autor de pruebas E2E o un agente de codificación puede entender sin tener que adivinar cómo está estructurado un formulario.

Dado un FormlyFieldConfig[], el adaptador describe:

  • los controles, el contenido mostrado, los grupos y las plantillas repetibles del formulario;

  • la ruta de modelo de cada campo, el tipo Formly, la etiqueta, las restricciones y las opciones;

  • el comportamiento conocido de visibilidad, obligatoriedad, solo lectura, deshabilitado y opciones dinámicas;

  • localizadores de prueba exactos o derivados de la aplicación, como data-testid, data-test-id y data-cy;

  • qué provino directamente de la configuración, qué fue resuelto por una compilación controlada de Formly y qué sigue siendo desconocido; y

  • diagnósticos estables para comportamientos que no se pueden representar de forma segura.

El resultado es un Contrato de Formulario determinista con validación estricta en tiempo de ejecución, serialización canónica y un hash de contenido. El contrato está pensado para ser una entrada fiable para la planificación de pruebas con Cypress/Playwright y para futuras herramientas de agentes. No es un volcado de los objetos de tiempo de ejecución en vivo de Formly.

Qué existe hoy

Este repositorio proporciona actualmente el esquema v0.3 y dos paquetes del espacio de trabajo:

Paquete

Propósito

@formly-contract/contract-schema

DTOs del contrato, validación en tiempo de ejecución, JSON canónico y hash de contenido SHA-256

@formly-contract/formly-adapter

Extracción declarada segura y compilación de escenarios de confianza para Formly 6.1

También incluye:

  • una demo CLI determinista que utiliza un formulario dorado sintético;

  • una aplicación de prueba Angular renderizada en el navegador con doce fixtures sintéticos de Formly; y

  • cobertura de compatibilidad para la combinación fijada de Angular 20.3.29 y Formly 6.1.8.

El analizador y el contrato son el producto actual. Un servidor MCP de producción, la generación automática de Playwright, la observación del navegador y el descubrimiento de código fuente de la aplicación son capas futuras y no se incluyen en este MVP.

Related MCP server: SpecBridge MCP

Úsalo en tu propio código Angular/Formly

El paquete se ejecuta como herramienta de compilación/prueba junto a tu aplicación Angular. No es necesario añadirlo al bundle del navegador de la aplicación. Un flujo de adopción típico es:

application-owned Formly factories
              |
     generation script or CI job
              |
       versioned contract JSON
              |
 Playwright / Cypress / agent tooling

1. Añade los paquetes

Los paquetes aún no están publicados en npm. Hasta el primer lanzamiento, clona este repositorio junto a la aplicación consumidora y compila los dos paquetes:

git clone https://github.com/dills122/formly-contract.git
cd formly-contract
pnpm install --frozen-lockfile
pnpm --filter @formly-contract/contract-schema build
pnpm --filter @formly-contract/formly-adapter build

Luego enlázalos desde el package.json de la aplicación consumidora (ajusta la ruta relativa para tu checkout):

{
  "devDependencies": {
    "@formly-contract/contract-schema": "link:../formly-contract/packages/contract-schema",
    "@formly-contract/formly-adapter": "link:../formly-contract/packages/formly-adapter"
  }
}

Ejecuta pnpm install en la aplicación consumidora. La aplicación ya debe proporcionar dependencias de pares compatibles de Angular y Formly; la combinación actualmente probada es Angular 20.3.29 con Formly 6.1.8. Una vez que los paquetes estén publicados, las dependencias normales versionadas pnpm add --save-dev reemplazarán estos enlaces locales.

2. Selecciona los formularios a exponer

El descubrimiento de código fuente de la aplicación no es automático a propósito. Crea un registro pequeño propiedad de la aplicación que importe solo las fábricas de formularios que quieras que el generador de contratos inspeccione:

// tools/contract-forms.ts
import type { FormlyFieldConfig } from '@ngx-formly/core';
import { createClaimFields } from '../src/app/claims/claim.fields';
import { createCustomerFields } from '../src/app/customers/customer.fields';

export interface ContractFormTarget {
  id: string;
  createFields: () => FormlyFieldConfig[];
}

export const contractForms: ContractFormTarget[] = [
  { id: 'claims.create', createFields: () => createClaimFields() },
  { id: 'customers.edit', createFields: () => createCustomerFields() },
];

Cada fábrica debe devolver un árbol de campos nuevo. Si una fábrica necesita entradas de la aplicación, envuélvela en un cierre con valores sintéticos que sean seguros de usar en el desarrollo local y en CI.

3. Genera los artefactos del contrato

Añade un script en tiempo de compilación en el repositorio de la aplicación:

// tools/generate-form-contracts.ts
import { mkdir, writeFile } from 'node:fs/promises';
import { resolve } from 'node:path';
import { canonicalStringify } from '@formly-contract/contract-schema';
import { extractFormContract } from '@formly-contract/formly-adapter';
import { contractForms } from './contract-forms';

const outputDirectory = resolve('artifacts/form-contracts');
await mkdir(outputDirectory, { recursive: true });

for (const target of contractForms) {
  const { contract, diagnostics } = extractFormContract({
    formId: target.id,
    fields: target.createFields(),
  });

  await writeFile(
    resolve(outputDirectory, `${target.id}.json`),
    `${canonicalStringify(contract)}\n`,
  );

  console.log(
    `${target.id}: ${contract.nodes.length} root nodes, ${diagnostics.length} diagnostics`,
  );
}

Ejecuta este archivo con el ejecutor de TypeScript que ya usa el repositorio consumidor, o compílalo como parte de un proyecto de herramientas dirigido a Node. El JSON resultante se puede confirmar para revisión, subir como artefacto de CI o leer por herramientas posteriores de autoría de pruebas. Debido a que es canónico y tiene hash de contenido, un cambio inesperado en el contrato de formulario es visible en el control de código fuente o en CI.

Esta ruta declarada es el mejor punto de partida. Captura la estructura estática y registra los callbacks de expresiones como metadatos dinámicos sin ejecutar código arbitrario de la aplicación.

4. Usa un contrato en Playwright

Valida el JSON almacenado antes de confiar en él, encuentra el nodo semántico que necesitas y usa uno de sus candidatos de localizador exactos. Para un localizador estándar data-testid:

import { readFile } from 'node:fs/promises';
import {
  parseFormContract,
  type ContractNode,
  type ModelPathSegment,
} from '@formly-contract/contract-schema';

function findNodeByPath(
  nodes: readonly ContractNode[],
  modelPath: readonly ModelPathSegment[],
): ContractNode | undefined {
  for (const node of nodes) {
    if (
      node.modelPath.length === modelPath.length &&
      node.modelPath.every((segment, index) => segment === modelPath[index])
    ) {
      return node;
    }

    const nested = findNodeByPath(
      node.arrayTemplate
        ? [...node.children, node.arrayTemplate]
        : node.children,
      modelPath,
    );
    if (nested) return nested;
  }
}

const contract = parseFormContract(
  JSON.parse(
    await readFile('artifacts/form-contracts/claims.create.json', 'utf8'),
  ),
);

const claimantName = findNodeByPath(contract.nodes, ['claimant', 'name']);

const testId = claimantName?.locators.find(
  (locator) =>
    locator.strategy === 'testId' && locator.attribute === 'data-testid',
);

if (!claimantName || !testId) {
  throw new Error('claimant.name has no exact data-testid locator');
}

await page.getByTestId(testId.value).fill('Ada Lovelace');

Los consumidores reales normalmente pondrán la búsqueda recursiva de nodos y la selección de localizadores en un helper compartido de Playwright o Cypress. Los controles compuestos pueden exponer varios objetivos de localizador, por lo que los helpers deben seleccionar por target en lugar de asumir que un nodo de Formly siempre se asigna a un elemento del DOM. Los arrays de localizadores vacíos y los diagnósticos deben tratarse como evidencia faltante, no reemplazarse con selectores inventados.

5. Resuelve el comportamiento dinámico cuando sea necesario

Si las expresiones determinan la visibilidad, el estado obligatorio/solo lectura o las listas de opciones, añade escenarios sintéticos y llama a compileFormContractScenario. Ejecuta esa API en un entorno de compilación/prueba Angular de confianza configurado con los módulos reales de Formly de la aplicación y los tipos personalizados. Genera un artefacto por escenario significativo, usando solo datos sintéticos de modelo y estado de formulario.

El harness de compatibilidad sintético muestra la configuración completa de Angular TestBed para obtener un FormlyFormBuilder. El ejemplo detallado de la API a continuación muestra la llamada al escenario.

Por qué es útil

Los formularios grandes de Formly a menudo se ensamblan a partir de grupos anidados, fragmentos compartidos, tipos de campo personalizados, expresiones, opciones dinámicas y convenciones de la aplicación. Leer ese código fuente repetidamente es lento, y adivinar a partir de una página renderizada lleva a pruebas frágiles.

Este proyecto crea un límite pequeño y explícito:

Formly fields + synthetic scenario
                |
       safe contract projection
                |
   deterministic versioned JSON
                |
 E2E planning / agent inspection

Los consumidores pueden inspeccionar un contrato para responder preguntas como:

  • ¿Qué controles existen y en qué orden?

  • ¿Qué valor de modelo edita cada control?

  • ¿Qué valores y límites de validación se conocen?

  • ¿Una lista de opciones está vacía, es estática, dinámica o asíncrona?

  • ¿Qué campos pueden estar ocultos, ser obligatorios, de solo lectura o estar deshabilitados?

  • ¿Qué candidatos de localizador data-*, role, etiqueta, placeholder o ID de DOM están disponibles?

  • ¿Qué hechos son exactos, derivados, resueltos para un escenario o aún desconocidos?

Prueba este repositorio

Requisitos previos:

  • Node.js 22.22.1

  • pnpm 10.23.0

pnpm install --frozen-lockfile
pnpm demo

pnpm demo compila la parte de paquetes e imprime un contrato JSON canónico. Ejecuta la compuerta completa del repositorio con:

pnpm check

Ese comando ejecuta lint, todas las pruebas, las compilaciones de producción de paquetes y Angular, la prueba de humo de la demo y las comprobaciones de documentación.

Extrae la estructura de formulario declarada

Usa extractFormContract cuando tengas configuración de Formly y quieras inspeccionarla sin ejecutar callbacks:

import { extractFormContract } from '@formly-contract/formly-adapter';
import type { FormlyFieldConfig } from '@ngx-formly/core';

const fields: FormlyFieldConfig[] = [
  {
    key: 'profile.name',
    type: 'input',
    props: {
      label: 'Name',
      required: true,
      attributes: { 'data-testid': 'profile-name' },
    },
  },
];

const { contract, diagnostics } = extractFormContract({
  formId: 'example.profile',
  fields,
});

Esta ruta es pura y no muta. No llama a funciones de expresión, no se suscribe a Observables, no ejecuta validadores ni renderiza componentes de Angular. Los callbacks reconocidos se convierten en metadatos de reglas dinámicas; el comportamiento no compatible se convierte en un diagnóstico explícito. El nodo devuelto tiene el ID estable example.profile::path:s_profile.s_name, la ruta de modelo ['profile', 'name'], su restricción de obligatoriedad y un localizador exacto data-testid.

Resuelve un escenario sintético

Usa compileFormContractScenario cuando los atributos obligatorio, solo lectura, deshabilitado, oculto, opciones o localizadores dependan de callbacks de expresiones de Formly:

import { inject } from '@angular/core';
import { FormlyFormBuilder } from '@ngx-formly/core';
import { compileFormContractScenario } from '@formly-contract/formly-adapter';

const builder = inject(FormlyFormBuilder);
const { contract, diagnostics } = compileFormContractScenario({
  formId: 'example.profile',
  builder,
  createFields: () => createProfileFields(),
  model: { contactMethod: 'email' },
  formState: { readonly: false },
});

Esta es una API de compilación/CI de confianza. Usa el FormlyFormBuilder configurado de la aplicación, por lo que los callbacks de la aplicación y de Formly pueden ejecutarse. El modelo y el estado del formulario deben ser clonables por clonación estructurada; ambos se clonan antes de que se ejecute la fábrica de campos o el builder.

El árbol de campos compilado sigue pasando por la misma lista blanca que la extracción declarada. Por ejemplo, las opciones dinámicas se reducen a registros públicos label/value/disabled en lugar de copiar propiedades arbitrarias de objetos de la aplicación.

No expongas este compilador directamente desde un MCP u otro manejador de solicitudes no confiable. Las capas de consulta deben leer artefactos de contrato generados previamente.

Localizadores de prueba

Cada nodo tiene un array ordenado locators. El adaptador lee automáticamente estos atributos comunes de props.attributes:

  • data-testid

  • data-test-id

  • data-test

  • data-cy

  • data-pw

También puede conservar candidatos explícitos de role, nombre accesible, placeholder e ID de campo de Formly. Un array vacío significa que no se encontró ningún localizador fiable; el adaptador nunca inventa CSS o XPath.

Las aplicaciones con su propia convención de nombres pueden establecer testIdAttributes y proporcionar un callback determinista deriveLocators. El callback recibe solo datos de identidad congelados, no el campo Formly en vivo. Puede devolver varios objetivos nombrados para un widget compuesto como un rango de fechas; su salida se marca confidence: "derived". Consulta la especificación de localizadores v0.3 para ver el contrato completo y un ejemplo.

Modelo de evidencia

El contrato mantiene tres niveles de evidencia separados:

Evidencia

Significado

¿Disponible ahora?

declared

Leído de forma segura de la configuración de Formly proporcionada

resolved

Leído de una compilación controlada de Formly para un escenario sintético

observed

Visto en un DOM de navegador renderizado real

Listo para el esquema; capa de captura no implementada

Un localizador resuelto no se presenta silenciosamente como observado en el navegador. Del mismo modo, el comportamiento opaco o asíncrono se informa en lugar de adivinarse.

Información de contrato compatible

El esquema v0.3 puede representar:

  • controles ordenados, grupos, nodos solo de visualización y plantillas de arrays;

  • IDs de nodo semánticos estables y rutas de modelo acumulativas;

  • tipos de control de Formly y semánticos comunes;

  • etiquetas, descripciones, placeholders, valores predeterminados seguros para JSON y wrappers;

  • restricciones de obligatoriedad, min/max, longitud, patrón de cadena y nombradas;

  • opciones públicas estáticas y resueltas más metadatos de fuentes de opciones dinámicas/asíncronas;

  • condiciones de cadena/booleano y metadatos de reglas dinámicas de callback/async;

  • estado resuelto de oculto, solo lectura y deshabilitado;

  • candidatos de localizador exactos y derivados, incluidos múltiples objetivos nombrados; y

  • diagnósticos deterministas, JSON canónico y hash de contenido.

Limitaciones intencionales

  • Los formularios deben proporcionarse explícitamente; el adaptador no descubre exports arbitrarios de TypeScript ni rutas de la aplicación.

  • La extracción declarada nunca evalúa funciones ni código fuente de funciones.

  • El compilador de escenarios realiza la compilación inicial controlada de Formly pero no espera opciones remotas ni comportamiento del navegador impulsado por el ciclo de vida.

  • Los patrones RegExp de Formly se diagnostican; v0.3 representa solo patrones de cadena.

  • Las acciones de widgets personalizados y los códecs de valor aún no están modelados.

  • El proyecto actualmente no genera ni ejecuta pruebas de Cypress/Playwright.

  • No se incluye un servidor MCP de producción ni una capa de observación del navegador.

  • La compatibilidad está probada para Angular 20.3.29 con Formly 6.1.8, no para cada combinación de Angular/Formly.

  • La publicación en npm y la automatización de lanzamientos aún no están incluidas.

Aplicación de prueba sintética

La aplicación de prueba Angular contiene doce formularios inventados que cubren campos nativos y personalizados, wrappers, validadores, extensiones, presets, expresiones, validación, repetidores, comportamiento opaco y alias heredados de Formly v6.

pnpm app:serve

Abre http://127.0.0.1:4200/ y elige un fixture del catálogo.

Los formularios y datos del lugar de trabajo deben permanecer en un repositorio de trabajo privado. Un módulo de fixtures privado puede implementar TestFormDefinition y registrar un grupo a través de TEST_FORM_GROUPS sin copiar etiquetas, identificadores, opciones o reglas del lugar de trabajo en este proyecto público.

Estructura del repositorio

packages/
  contract-schema/   Versioned DTOs, validation, canonical JSON, and hashing
  formly-adapter/     Declared extraction and trusted Formly scenario builds
fixtures/
  synthetic-form/    Public golden form and real-builder compatibility fixture
apps/
  demo-cli/          Prints the deterministic golden contract
  formly-test-app/   Browser-rendered Angular/Formly fixture catalog
docs/                Specifications, ADRs, delivery plans, and evidence

Hoja de ruta

La ruta de entrega prevista es:

Form Contract packages (current)
              |
      read-only MCP queries
              |
        typed E2E intent
              |
 deterministic Playwright/Cypress drivers
              |
 browser observation and parity checks

Las capas futuras deben consumir contratos inmutables. No deben mover la ejecución de Angular, la evaluación arbitraria de callbacks ni la invención de selectores a solicitudes rutinarias de agentes.

Documentación

Contribuciones y seguridad

Las contribuciones son bienvenidas. Lee CONTRIBUTING.md y el Código de conducta antes de participar. Informa de problemas de seguridad mediante el proceso privado descrito en SECURITY.md.

Este proyecto está disponible bajo la Licencia MIT.

Maintenance

ActivityMaintained
ResponsivenessResponsive

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

Related MCP Servers

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/dills122/formly-contract'

If you have feedback or need assistance with the MCP directory API, please join our Discord server