max-remotes-helper 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +62 -13
- package/dist/index.js +1 -1
- package/docs/CONTRIBUTING.md +371 -0
- package/docs/FAQ.md +530 -0
- package/docs/README.md +144 -0
- package/docs/Secured.md +430 -0
- package/docs/code-examples.md +703 -0
- package/docs/quick-start.md +278 -0
- package/docs/useRemoteAuth.md +537 -0
- package/package.json +1 -1
- package/src/context/RemoteAuthContext.tsx +69 -41
- package/tmpclaude-d092-cwd +1 -0
package/README.md
CHANGED
|
@@ -25,7 +25,7 @@ npm install bo-remotes-helper
|
|
|
25
25
|
### Basic Setup
|
|
26
26
|
|
|
27
27
|
```tsx
|
|
28
|
-
import { RemoteWrapper, useRemoteAuth, Secured } from
|
|
28
|
+
import { RemoteWrapper, useRemoteAuth, Secured } from "bo-remotes-helper";
|
|
29
29
|
|
|
30
30
|
// Wrap your remote component
|
|
31
31
|
export const MyRemoteComponent = ({ token }) => {
|
|
@@ -39,11 +39,11 @@ export const MyRemoteComponent = ({ token }) => {
|
|
|
39
39
|
// Use authentication context
|
|
40
40
|
const MyContent = () => {
|
|
41
41
|
const { state, hasPermission } = useRemoteAuth();
|
|
42
|
-
|
|
42
|
+
|
|
43
43
|
return (
|
|
44
44
|
<div>
|
|
45
45
|
<h1>Welcome {state.user?.name}</h1>
|
|
46
|
-
|
|
46
|
+
|
|
47
47
|
<Secured permission="edit">
|
|
48
48
|
<button>Edit Content</button>
|
|
49
49
|
</Secured>
|
|
@@ -55,12 +55,12 @@ const MyContent = () => {
|
|
|
55
55
|
### Module Federation Configuration
|
|
56
56
|
|
|
57
57
|
```javascript
|
|
58
|
-
import { useRemoteConfig } from
|
|
58
|
+
import { useRemoteConfig } from "bo-remotes-helper";
|
|
59
59
|
|
|
60
60
|
const { getWebpackConfig } = useRemoteConfig();
|
|
61
61
|
|
|
62
|
-
const config = getWebpackConfig(
|
|
63
|
-
|
|
62
|
+
const config = getWebpackConfig("my-remote", {
|
|
63
|
+
"./MyComponent": "./src/MyComponent",
|
|
64
64
|
});
|
|
65
65
|
```
|
|
66
66
|
|
|
@@ -69,38 +69,75 @@ const config = getWebpackConfig('my-remote', {
|
|
|
69
69
|
### Components
|
|
70
70
|
|
|
71
71
|
#### `RemoteWrapper`
|
|
72
|
+
|
|
72
73
|
Main wrapper component that provides authentication context.
|
|
73
74
|
|
|
74
75
|
#### `Secured`
|
|
76
|
+
|
|
75
77
|
Conditional rendering component based on permissions.
|
|
76
78
|
|
|
77
79
|
### Hooks
|
|
78
80
|
|
|
79
81
|
#### `useRemoteAuth()`
|
|
82
|
+
|
|
80
83
|
Access authentication state and methods.
|
|
81
84
|
|
|
85
|
+
**[📖 Ver documentación completa de useRemoteAuth](./docs/useRemoteAuth.md)**
|
|
86
|
+
|
|
87
|
+
```tsx
|
|
88
|
+
const { state, hasPermission, setToken, clearAuth } = useRemoteAuth();
|
|
89
|
+
|
|
90
|
+
// Verificar autenticación
|
|
91
|
+
if (state.isAuthenticated) {
|
|
92
|
+
console.log("User:", state.user?.name);
|
|
93
|
+
console.log("Permissions:", state.permissions);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Verificar permisos específicos
|
|
97
|
+
if (hasPermission("users:write")) {
|
|
98
|
+
// Usuario puede editar
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
82
102
|
#### `useRemoteConfig()`
|
|
103
|
+
|
|
83
104
|
Get standard Module Federation configuration.
|
|
84
105
|
|
|
106
|
+
### Components
|
|
107
|
+
|
|
108
|
+
#### `<Secured />`
|
|
109
|
+
|
|
110
|
+
Conditional rendering based on permissions.
|
|
111
|
+
|
|
112
|
+
**[📖 Ver documentación completa de Secured](./docs/Secured.md)**
|
|
113
|
+
|
|
114
|
+
```tsx
|
|
115
|
+
<Secured permission="admin:all" fallback={<div>No autorizado</div>}>
|
|
116
|
+
<AdminPanel />
|
|
117
|
+
</Secured>
|
|
118
|
+
```
|
|
119
|
+
|
|
85
120
|
### Utils
|
|
86
121
|
|
|
87
122
|
#### Token utilities
|
|
123
|
+
|
|
88
124
|
- `decodeToken(token)` - Decode JWT safely
|
|
89
125
|
- `isTokenExpired(token)` - Check token expiration
|
|
90
126
|
- `tokenStorage` - Browser storage utilities
|
|
91
127
|
|
|
92
128
|
#### Module utilities
|
|
129
|
+
|
|
93
130
|
- `loadRemoteModule()` - Dynamic remote loading
|
|
94
131
|
- `isRemoteAvailable()` - Check remote availability
|
|
95
132
|
|
|
96
133
|
## Differences from bo-library
|
|
97
134
|
|
|
98
|
-
| Feature
|
|
99
|
-
|
|
100
|
-
| Authentication | Full JWT verification
|
|
101
|
-
| Node.js APIs
|
|
102
|
-
| Bundle size
|
|
103
|
-
| Use case
|
|
135
|
+
| Feature | bo-library | bo-remotes-helper |
|
|
136
|
+
| -------------- | ----------------------- | ----------------------- |
|
|
137
|
+
| Authentication | Full JWT verification | Client-side decode only |
|
|
138
|
+
| Node.js APIs | Required (crypto, etc.) | Browser-only |
|
|
139
|
+
| Bundle size | Large | Lightweight |
|
|
140
|
+
| Use case | Host applications | Remote modules |
|
|
104
141
|
|
|
105
142
|
## Development
|
|
106
143
|
|
|
@@ -118,6 +155,18 @@ npm run dev
|
|
|
118
155
|
npm run test
|
|
119
156
|
```
|
|
120
157
|
|
|
158
|
+
## Documentation
|
|
159
|
+
|
|
160
|
+
📚 **[Documentación Completa](./docs/README.md)** - Guías, tutoriales y referencia completa
|
|
161
|
+
|
|
162
|
+
### Quick Links
|
|
163
|
+
|
|
164
|
+
- 🔐 **[Hook useRemoteAuth](./docs/useRemoteAuth.md)** - Autenticación y permisos
|
|
165
|
+
- 🛡️ **[Componente Secured](./docs/Secured.md)** - Renderizado condicional
|
|
166
|
+
- 🚀 **[Quick Start Guide](./docs/quick-start.md)** - Comenzar en 5 minutos
|
|
167
|
+
- 🧪 **[Testing Guide](./docs/guides/testing.md)** - Probar componentes
|
|
168
|
+
- ❓ **[FAQ](./docs/FAQ.md)** - Preguntas frecuentes
|
|
169
|
+
|
|
121
170
|
## Integration with bo-module-react-template
|
|
122
171
|
|
|
123
|
-
This library is designed to be the standard authentication solution for all remote modules created from `bo-module-react-template`.
|
|
172
|
+
This library is designed to be the standard authentication solution for all remote modules created from `bo-module-react-template`.
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/*! For license information please see index.js.LICENSE.txt */
|
|
2
|
-
(()=>{var e={20:(e,
|
|
2
|
+
(()=>{var e={20:(e,t,o)=>{"use strict";var r=o(953),n=Symbol.for("react.element"),s=Symbol.for("react.fragment"),a=Object.prototype.hasOwnProperty,i=r.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,l={key:!0,ref:!0,__self:!0,__source:!0};t.Fragment=s,t.jsx=function(e,t,o){var r,s={},c=null,d=null;for(r in void 0!==o&&(c=""+o),void 0!==t.key&&(c=""+t.key),void 0!==t.ref&&(d=t.ref),t)a.call(t,r)&&!l.hasOwnProperty(r)&&(s[r]=t[r]);if(e&&e.defaultProps)for(r in t=e.defaultProps)void 0===s[r]&&(s[r]=t[r]);return{$$typeof:n,type:e,key:c,ref:d,props:s,_owner:i.current}}},60:(e,t,o)=>{var r={"./moduleUtils":226,"./moduleUtils.ts":226,"./tokenUtils":433,"./tokenUtils.ts":433};function n(e){return Promise.resolve().then(()=>{if(!o.o(r,e)){var t=new Error("Cannot find module '"+e+"'");throw t.code="MODULE_NOT_FOUND",t}return o(r[e])})}n.keys=()=>Object.keys(r),n.id=60,e.exports=n},226:(e,t,o)=>{"use strict";o.r(t),o.d(t,{createRemoteComponentLoader:()=>i,getRemoteEnvironment:()=>l,isRemoteAvailable:()=>a,loadRemoteModule:()=>s});var r=o(953),n=o.n(r);const s=async(e,t)=>{try{return await o(60)(`${e}/${t}`)}catch(o){throw console.error(`Failed to load remote module ${e}/${t}:`,o),o}},a=async e=>{try{const t=window[e];return"function"==typeof t?.get}catch(e){return!1}},i=(e,t,o)=>n().lazy(async()=>{try{return await s(e,t)}catch(r){return console.error(`Remote component loading failed: ${e}/${t}`,r),o?{default:o}:{default:()=>n().createElement("div",{style:{padding:"20px",color:"red"}},`Failed to load remote component: ${e}/${t}`)}}}),l=()=>({isDevelopment:!1,apiBaseUrl:process.env.REACT_APP_API_URL||"",frontUrl:process.env.REACT_APP_FRONT_URL||""})},433:(e,t,o)=>{"use strict";o.r(t),o.d(t,{decodeToken:()=>r,extractPermissions:()=>s,isTokenExpired:()=>n,tokenStorage:()=>a});const r=e=>{try{if(!e)return null;const t=e.split(".");if(3!==t.length)return null;const o=t[1],r=o+"=".repeat((4-o.length%4)%4),n=atob(r);return JSON.parse(n)}catch(e){return console.error("Error decoding token:",e),null}},n=e=>{const t=r(e);if(!t||!t.exp)return!0;const o=Math.floor(Date.now()/1e3);return t.exp<o},s=e=>{if(!e)return[];const t=["permissions","roles","authorities","scope"];for(const o of t){const t=e[o];if(Array.isArray(t))return t;if("string"==typeof t)return t.split(" ").filter(Boolean)}return[]},a={key:"bo-remote-token",save:e=>{try{sessionStorage.setItem(a.key,e)}catch(e){console.error("Error saving token:",e)}},get:()=>{try{return sessionStorage.getItem(a.key)}catch(e){return console.error("Error getting token:",e),null}},remove:()=>{try{sessionStorage.removeItem(a.key)}catch(e){console.error("Error removing token:",e)}},clear:()=>{try{sessionStorage.clear()}catch(e){console.error("Error clearing storage:",e)}}}},848:(e,t,o)=>{"use strict";e.exports=o(20)},953:e=>{"use strict";e.exports=require("react")}},t={};function o(r){var n=t[r];if(void 0!==n)return n.exports;var s=t[r]={exports:{}};return e[r](s,s.exports,o),s.exports}o.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return o.d(t,{a:t}),t},o.d=(e,t)=>{for(var r in t)o.o(t,r)&&!o.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:t[r]})},o.e=()=>Promise.resolve(),o.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),o.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})};var r={};(()=>{"use strict";o.r(r),o.d(r,{RemoteAuthProvider:()=>l,RemoteWrapper:()=>u,Secured:()=>d,createRemoteComponentLoader:()=>p.createRemoteComponentLoader,decodeToken:()=>n.decodeToken,extractPermissions:()=>n.extractPermissions,getRemoteEnvironment:()=>p.getRemoteEnvironment,isRemoteAvailable:()=>p.isRemoteAvailable,isTokenExpired:()=>n.isTokenExpired,loadRemoteModule:()=>p.loadRemoteModule,tokenStorage:()=>n.tokenStorage,useRemoteAuth:()=>c,useRemoteConfig:()=>m});var e=o(848),t=o(953),n=o(433);const s={token:null,isAuthenticated:!1,permissions:[],user:void 0},a=(e,t)=>{switch(t.type){case"SET_TOKEN":const o=(0,n.decodeToken)(t.payload),r=o?(0,n.extractPermissions)(o):[];return{...e,token:t.payload,isAuthenticated:!(0,n.isTokenExpired)(t.payload),permissions:r,user:o?{id:o.sub||"",email:o.email||"",name:o.name||o.email||""}:void 0};case"CLEAR_AUTH":return{...s};case"SET_USER_INFO":return{...e,user:{id:t.payload.sub||"",email:t.payload.email||"",name:t.payload.name||t.payload.email||""}};default:return e}},i=(0,t.createContext)(void 0),l=({children:o,initialToken:r})=>{const[l,c]=(0,t.useReducer)(a,s);(0,t.useEffect)(()=>{if(console.log("🔍 RemoteAuthContext: useEffect triggered",{initialToken:r?r.substring(0,30)+"...":"No initialToken"}),r)console.log("✅ RemoteAuthContext: Using initialToken from prop"),(0,n.isTokenExpired)(r)?(console.log("🔴 RemoteAuthContext: Provided token is expired, clearing auth"),n.tokenStorage.remove(),c({type:"CLEAR_AUTH"})):(console.log("🟢 RemoteAuthContext: Token is valid, dispatching SET_TOKEN"),c({type:"SET_TOKEN",payload:r}),n.tokenStorage.save(r));else{console.log("⚪ RemoteAuthContext: No initialToken, checking stored token");const e=n.tokenStorage.get();e&&!(0,n.isTokenExpired)(e)?(console.log("💾 RemoteAuthContext: Using stored token"),c({type:"SET_TOKEN",payload:e})):e&&(console.log("🗑️ RemoteAuthContext: Stored token expired, clearing"),n.tokenStorage.remove(),c({type:"CLEAR_AUTH"}))}},[r]);const d={state:l,setToken:e=>{e&&(n.tokenStorage.save(e),c({type:"SET_TOKEN",payload:e}))},clearAuth:()=>{n.tokenStorage.remove(),c({type:"CLEAR_AUTH"})},hasPermission:e=>!!l.isAuthenticated&&l.permissions.includes(e)};return(0,e.jsx)(i.Provider,{value:d,children:o})},c=()=>{const e=(0,t.useContext)(i);if(void 0===e)throw new Error("useRemoteAuth must be used within a RemoteAuthProvider");return e},d=({children:t,permission:o,fallback:r=null})=>{const{hasPermission:n,state:s}=c();return s.isAuthenticated&&n(o)?(0,e.jsx)(e.Fragment,{children:t}):(0,e.jsx)(e.Fragment,{children:r})},u=({children:t,token:o,apiBaseUrl:r,module:n})=>(0,e.jsx)(l,{initialToken:o,children:(0,e.jsx)("div",{"data-remote-module":n,"data-api-base":r,children:t})}),m=()=>{const e=(0,t.useMemo)(()=>({react:{singleton:!0,eager:!1},"react-dom":{singleton:!0,eager:!1},"aurora-web":{singleton:!0,eager:!1},"react-i18next":{singleton:!0,eager:!1},i18next:{singleton:!0,eager:!1}}),[]);return{sharedDependencies:e,getWebpackConfig:(t,o)=>({name:t,filename:"remoteEntry.js",exposes:o,shared:e})}};var p=o(226)})(),module.exports=r})();
|
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
# Guía de Contribución a bo-remotes-helper
|
|
2
|
+
|
|
3
|
+
¡Gracias por tu interés en contribuir a `bo-remotes-helper`! Esta guía te ayudará a comenzar.
|
|
4
|
+
|
|
5
|
+
## 📋 Tabla de Contenidos
|
|
6
|
+
|
|
7
|
+
- [Código de Conducta](#código-de-conducta)
|
|
8
|
+
- [Cómo Contribuir](#cómo-contribuir)
|
|
9
|
+
- [Configuración del Entorno](#configuración-del-entorno)
|
|
10
|
+
- [Estructura del Proyecto](#estructura-del-proyecto)
|
|
11
|
+
- [Convenciones de Código](#convenciones-de-código)
|
|
12
|
+
- [Testing](#testing)
|
|
13
|
+
- [Documentación](#documentación)
|
|
14
|
+
- [Pull Requests](#pull-requests)
|
|
15
|
+
|
|
16
|
+
## Código de Conducta
|
|
17
|
+
|
|
18
|
+
Este proyecto se adhiere al código de conducta de Intive. Al participar, se espera que mantengas un ambiente profesional y respetuoso.
|
|
19
|
+
|
|
20
|
+
## Cómo Contribuir
|
|
21
|
+
|
|
22
|
+
### Reportar Bugs
|
|
23
|
+
|
|
24
|
+
1. Verifica que el bug no esté ya reportado en [Issues](#)
|
|
25
|
+
2. Abre un nuevo issue con:
|
|
26
|
+
- Descripción clara del problema
|
|
27
|
+
- Pasos para reproducir
|
|
28
|
+
- Comportamiento esperado vs actual
|
|
29
|
+
- Versión de la librería
|
|
30
|
+
- Navegador/entorno
|
|
31
|
+
|
|
32
|
+
Ejemplo:
|
|
33
|
+
|
|
34
|
+
```markdown
|
|
35
|
+
**Descripción:**
|
|
36
|
+
useRemoteAuth lanza error cuando...
|
|
37
|
+
|
|
38
|
+
**Pasos para reproducir:**
|
|
39
|
+
|
|
40
|
+
1. Crear componente con...
|
|
41
|
+
2. Llamar a hasPermission()...
|
|
42
|
+
3. Ver error...
|
|
43
|
+
|
|
44
|
+
**Esperado:** Debería retornar false
|
|
45
|
+
**Actual:** Lanza TypeError
|
|
46
|
+
|
|
47
|
+
**Entorno:**
|
|
48
|
+
|
|
49
|
+
- bo-remotes-helper: v1.0.0
|
|
50
|
+
- React: 18.2.0
|
|
51
|
+
- Browser: Chrome 120
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Proponer Mejoras
|
|
55
|
+
|
|
56
|
+
1. Abre un issue con etiqueta `enhancement`
|
|
57
|
+
2. Describe el caso de uso
|
|
58
|
+
3. Propón una solución (opcional)
|
|
59
|
+
4. Espera feedback del equipo
|
|
60
|
+
|
|
61
|
+
### Contribuir Código
|
|
62
|
+
|
|
63
|
+
1. Fork el repositorio
|
|
64
|
+
2. Crea una rama desde `main`: `feature/mi-mejora` o `fix/mi-bug`
|
|
65
|
+
3. Implementa tus cambios
|
|
66
|
+
4. Escribe/actualiza tests
|
|
67
|
+
5. Actualiza documentación
|
|
68
|
+
6. Crea un Pull Request
|
|
69
|
+
|
|
70
|
+
## Configuración del Entorno
|
|
71
|
+
|
|
72
|
+
### Prerrequisitos
|
|
73
|
+
|
|
74
|
+
- Node.js >= 16
|
|
75
|
+
- npm >= 8
|
|
76
|
+
|
|
77
|
+
### Setup
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
# Clonar el repositorio
|
|
81
|
+
git clone https://github.com/intive/bo-remotes-helper.git
|
|
82
|
+
cd bo-remotes-helper
|
|
83
|
+
|
|
84
|
+
# Instalar dependencias
|
|
85
|
+
npm install
|
|
86
|
+
|
|
87
|
+
# Ejecutar tests
|
|
88
|
+
npm test
|
|
89
|
+
|
|
90
|
+
# Build
|
|
91
|
+
npm run build
|
|
92
|
+
|
|
93
|
+
# Watch mode para desarrollo
|
|
94
|
+
npm run dev
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Estructura del Proyecto
|
|
98
|
+
|
|
99
|
+
```
|
|
100
|
+
bo-remotes-helper/
|
|
101
|
+
├── src/
|
|
102
|
+
│ ├── components/ # Componentes React
|
|
103
|
+
│ │ ├── RemoteWrapper.tsx
|
|
104
|
+
│ │ └── Secured.tsx
|
|
105
|
+
│ ├── context/ # Context providers
|
|
106
|
+
│ │ └── RemoteAuthContext.tsx
|
|
107
|
+
│ ├── hooks/ # Custom hooks (si los hay)
|
|
108
|
+
│ │ └── useRemoteConfig.ts
|
|
109
|
+
│ ├── types/ # Definiciones TypeScript
|
|
110
|
+
│ │ └── index.ts
|
|
111
|
+
│ ├── utils/ # Utilidades
|
|
112
|
+
│ │ ├── tokenUtils.ts
|
|
113
|
+
│ │ └── moduleUtils.ts
|
|
114
|
+
│ └── index.ts # Punto de entrada
|
|
115
|
+
├── docs/ # Documentación
|
|
116
|
+
├── dist/ # Build output
|
|
117
|
+
├── webpack.config.js
|
|
118
|
+
├── tsconfig.json
|
|
119
|
+
└── package.json
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Convenciones de Código
|
|
123
|
+
|
|
124
|
+
### TypeScript
|
|
125
|
+
|
|
126
|
+
- Usar tipos explícitos (evitar `any`)
|
|
127
|
+
- Exportar interfaces/tipos necesarios
|
|
128
|
+
- Documentar funciones públicas con JSDoc
|
|
129
|
+
|
|
130
|
+
```tsx
|
|
131
|
+
/**
|
|
132
|
+
* Decodifica un token JWT sin verificar firma
|
|
133
|
+
* @param token - Token JWT a decodificar
|
|
134
|
+
* @returns Objeto con el payload del token o null si es inválido
|
|
135
|
+
*/
|
|
136
|
+
export const decodeToken = (token: string): TokenInfo | null => {
|
|
137
|
+
// implementación
|
|
138
|
+
};
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### React
|
|
142
|
+
|
|
143
|
+
- Componentes funcionales con TypeScript
|
|
144
|
+
- Props interface exportada
|
|
145
|
+
- Usar `React.FC` con props tipadas
|
|
146
|
+
|
|
147
|
+
```tsx
|
|
148
|
+
interface MyComponentProps {
|
|
149
|
+
title: string;
|
|
150
|
+
onAction?: () => void;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export const MyComponent: React.FC<MyComponentProps> = ({
|
|
154
|
+
title,
|
|
155
|
+
onAction,
|
|
156
|
+
}) => {
|
|
157
|
+
// implementación
|
|
158
|
+
};
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Nombres
|
|
162
|
+
|
|
163
|
+
- **Componentes**: PascalCase (`RemoteWrapper`)
|
|
164
|
+
- **Hooks**: camelCase con prefijo `use` (`useRemoteAuth`)
|
|
165
|
+
- **Utilidades**: camelCase (`decodeToken`)
|
|
166
|
+
- **Tipos/Interfaces**: PascalCase (`RemoteAuthState`)
|
|
167
|
+
- **Constantes**: UPPER_SNAKE_CASE (`DEFAULT_TOKEN_KEY`)
|
|
168
|
+
|
|
169
|
+
### Formato
|
|
170
|
+
|
|
171
|
+
Usamos Prettier para formateo automático:
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
npm run format
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Configuración:
|
|
178
|
+
|
|
179
|
+
```json
|
|
180
|
+
{
|
|
181
|
+
"semi": true,
|
|
182
|
+
"singleQuote": true,
|
|
183
|
+
"tabWidth": 2,
|
|
184
|
+
"trailingComma": "es5"
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## Testing
|
|
189
|
+
|
|
190
|
+
### Escribir Tests
|
|
191
|
+
|
|
192
|
+
- Todos los PRs deben incluir tests
|
|
193
|
+
- Objetivo: >80% de cobertura
|
|
194
|
+
- Usar Jest + React Testing Library
|
|
195
|
+
|
|
196
|
+
```tsx
|
|
197
|
+
// src/components/__tests__/Secured.test.tsx
|
|
198
|
+
import { render, screen } from "@testing-library/react";
|
|
199
|
+
import { RemoteAuthProvider } from "../../context/RemoteAuthContext";
|
|
200
|
+
import { Secured } from "../Secured";
|
|
201
|
+
|
|
202
|
+
describe("Secured Component", () => {
|
|
203
|
+
it("renders children when user has permission", () => {
|
|
204
|
+
const mockToken = "valid-token";
|
|
205
|
+
|
|
206
|
+
render(
|
|
207
|
+
<RemoteAuthProvider initialToken={mockToken}>
|
|
208
|
+
<Secured permission="test:view">
|
|
209
|
+
<div>Protected Content</div>
|
|
210
|
+
</Secured>
|
|
211
|
+
</RemoteAuthProvider>,
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
expect(screen.getByText("Protected Content")).toBeInTheDocument();
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### Ejecutar Tests
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
# Todos los tests
|
|
223
|
+
npm test
|
|
224
|
+
|
|
225
|
+
# Watch mode
|
|
226
|
+
npm test -- --watch
|
|
227
|
+
|
|
228
|
+
# Cobertura
|
|
229
|
+
npm test -- --coverage
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
## Documentación
|
|
233
|
+
|
|
234
|
+
### Actualizar Docs
|
|
235
|
+
|
|
236
|
+
Si tu cambio afecta la API pública:
|
|
237
|
+
|
|
238
|
+
1. Actualiza `docs/[archivo-relevante].md`
|
|
239
|
+
2. Actualiza ejemplos si es necesario
|
|
240
|
+
3. Actualiza `README.md` si cambió algo fundamental
|
|
241
|
+
4. Agrega entrada en `CHANGELOG.md`
|
|
242
|
+
|
|
243
|
+
### Escribir Docs
|
|
244
|
+
|
|
245
|
+
- Usa Markdown
|
|
246
|
+
- Incluye ejemplos de código
|
|
247
|
+
- Usa emojis para claridad visual
|
|
248
|
+
- Agrega enlaces a secciones relacionadas
|
|
249
|
+
|
|
250
|
+
Ejemplo:
|
|
251
|
+
|
|
252
|
+
```markdown
|
|
253
|
+
## useRemoteAuth()
|
|
254
|
+
|
|
255
|
+
Hook que proporciona acceso al contexto de autenticación.
|
|
256
|
+
|
|
257
|
+
**Uso:**
|
|
258
|
+
\`\`\`tsx
|
|
259
|
+
const { state, hasPermission } = useRemoteAuth();
|
|
260
|
+
\`\`\`
|
|
261
|
+
|
|
262
|
+
**Ver también:** [Quick Start](./quick-start.md)
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
## Pull Requests
|
|
266
|
+
|
|
267
|
+
### Checklist
|
|
268
|
+
|
|
269
|
+
Antes de crear un PR, verifica:
|
|
270
|
+
|
|
271
|
+
- [ ] Los tests pasan (`npm test`)
|
|
272
|
+
- [ ] El build funciona (`npm run build`)
|
|
273
|
+
- [ ] Código formateado (`npm run format`)
|
|
274
|
+
- [ ] Documentación actualizada
|
|
275
|
+
- [ ] CHANGELOG.md actualizado
|
|
276
|
+
- [ ] Commits descriptivos
|
|
277
|
+
|
|
278
|
+
### Formato de Commits
|
|
279
|
+
|
|
280
|
+
Usamos Conventional Commits:
|
|
281
|
+
|
|
282
|
+
```
|
|
283
|
+
tipo(scope): descripción corta
|
|
284
|
+
|
|
285
|
+
Descripción más detallada si es necesario
|
|
286
|
+
|
|
287
|
+
BREAKING CHANGE: Si hay cambios que rompen compatibilidad
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
Tipos:
|
|
291
|
+
|
|
292
|
+
- `feat`: Nueva funcionalidad
|
|
293
|
+
- `fix`: Corrección de bug
|
|
294
|
+
- `docs`: Solo cambios en documentación
|
|
295
|
+
- `refactor`: Refactorización sin cambiar funcionalidad
|
|
296
|
+
- `test`: Agregar o modificar tests
|
|
297
|
+
- `chore`: Cambios en build, deps, etc.
|
|
298
|
+
|
|
299
|
+
Ejemplos:
|
|
300
|
+
|
|
301
|
+
```
|
|
302
|
+
feat(auth): agregar soporte para refresh token
|
|
303
|
+
fix(types): corregir tipo de retorno de hasPermission
|
|
304
|
+
docs(quick-start): actualizar ejemplo de setup
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
### Template de PR
|
|
308
|
+
|
|
309
|
+
```markdown
|
|
310
|
+
## Descripción
|
|
311
|
+
|
|
312
|
+
Breve descripción del cambio
|
|
313
|
+
|
|
314
|
+
## Tipo de Cambio
|
|
315
|
+
|
|
316
|
+
- [ ] Bug fix
|
|
317
|
+
- [ ] Nueva funcionalidad
|
|
318
|
+
- [ ] Breaking change
|
|
319
|
+
- [ ] Documentación
|
|
320
|
+
|
|
321
|
+
## Testing
|
|
322
|
+
|
|
323
|
+
¿Cómo se probó este cambio?
|
|
324
|
+
|
|
325
|
+
## Checklist
|
|
326
|
+
|
|
327
|
+
- [ ] Tests pasan
|
|
328
|
+
- [ ] Documentación actualizada
|
|
329
|
+
- [ ] CHANGELOG actualizado
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
### Proceso de Review
|
|
333
|
+
|
|
334
|
+
1. Crea el PR contra `main`
|
|
335
|
+
2. Asigna reviewers del equipo BO
|
|
336
|
+
3. Espera al menos 1 aprobación
|
|
337
|
+
4. Resuelve comentarios
|
|
338
|
+
5. Merge (squash commits)
|
|
339
|
+
|
|
340
|
+
## Versionado
|
|
341
|
+
|
|
342
|
+
Seguimos Semantic Versioning:
|
|
343
|
+
|
|
344
|
+
- **MAJOR**: Cambios incompatibles (v1.0.0 → v2.0.0)
|
|
345
|
+
- **MINOR**: Nueva funcionalidad compatible (v1.0.0 → v1.1.0)
|
|
346
|
+
- **PATCH**: Bug fixes compatibles (v1.0.0 → v1.0.1)
|
|
347
|
+
|
|
348
|
+
## Release Process
|
|
349
|
+
|
|
350
|
+
1. Actualizar versión en `package.json`
|
|
351
|
+
2. Actualizar `CHANGELOG.md`
|
|
352
|
+
3. Crear tag: `git tag v1.0.0`
|
|
353
|
+
4. Push tag: `git push origin v1.0.0`
|
|
354
|
+
5. Publish: `npm publish`
|
|
355
|
+
|
|
356
|
+
## Contacto
|
|
357
|
+
|
|
358
|
+
- **Slack**: #bo-team
|
|
359
|
+
- **Email**: bo-team@intive.com
|
|
360
|
+
- **Issues**: [GitHub Issues](#)
|
|
361
|
+
|
|
362
|
+
## Recursos
|
|
363
|
+
|
|
364
|
+
- [React Docs](https://react.dev)
|
|
365
|
+
- [TypeScript Handbook](https://www.typescriptlang.org/docs/)
|
|
366
|
+
- [Testing Library](https://testing-library.com/react)
|
|
367
|
+
- [Module Federation](https://webpack.js.org/concepts/module-federation/)
|
|
368
|
+
|
|
369
|
+
---
|
|
370
|
+
|
|
371
|
+
¡Gracias por contribuir! 🎉
|