@trendify/cli 0.1.7 → 0.1.8
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 +26 -0
- package/dist/app.component.d.ts +14 -0
- package/dist/app.component.d.ts.map +1 -0
- package/dist/app.component.js +448 -0
- package/dist/cli.entry.d.ts +3 -0
- package/dist/cli.entry.d.ts.map +1 -0
- package/dist/cli.entry.js +51 -0
- package/dist/modules/auth/pages/login.page.d.ts +12 -0
- package/dist/modules/auth/pages/login.page.d.ts.map +1 -0
- package/dist/modules/auth/pages/login.page.js +22 -0
- package/dist/modules/auth/pages/profile.page.d.ts +12 -0
- package/dist/modules/auth/pages/profile.page.d.ts.map +1 -0
- package/dist/modules/auth/pages/profile.page.js +180 -0
- package/dist/modules/auth/services/auth-storage.service.d.ts +11 -0
- package/dist/modules/auth/services/auth-storage.service.d.ts.map +1 -0
- package/dist/modules/auth/services/auth-storage.service.js +65 -0
- package/dist/modules/auth/services/auth.service.d.ts +60 -0
- package/dist/modules/auth/services/auth.service.d.ts.map +1 -0
- package/dist/modules/auth/services/auth.service.js +494 -0
- package/dist/modules/auth/utils/auth-user.util.d.ts +3 -0
- package/dist/modules/auth/utils/auth-user.util.d.ts.map +1 -0
- package/dist/modules/auth/utils/auth-user.util.js +10 -0
- package/dist/modules/discovery/components/discovery-step-header.component.d.ts +7 -0
- package/dist/modules/discovery/components/discovery-step-header.component.d.ts.map +1 -0
- package/dist/modules/discovery/components/discovery-step-header.component.js +5 -0
- package/dist/modules/discovery/pages/discovery.page.d.ts +11 -0
- package/dist/modules/discovery/pages/discovery.page.d.ts.map +1 -0
- package/dist/modules/discovery/pages/discovery.page.js +58 -0
- package/dist/modules/profile/pages/profile.page.d.ts +12 -0
- package/dist/modules/profile/pages/profile.page.d.ts.map +1 -0
- package/dist/modules/profile/pages/profile.page.js +180 -0
- package/dist/shared/components/action-menu.component.d.ts +13 -0
- package/dist/shared/components/action-menu.component.d.ts.map +1 -0
- package/dist/shared/components/action-menu.component.js +7 -0
- package/dist/shared/components/app-logo.component.d.ts +2 -0
- package/dist/shared/components/app-logo.component.d.ts.map +1 -0
- package/dist/shared/components/app-logo.component.js +13 -0
- package/dist/shared/components/app-menu.component.d.ts +17 -0
- package/dist/shared/components/app-menu.component.d.ts.map +1 -0
- package/dist/shared/components/app-menu.component.js +85 -0
- package/dist/shared/components/app-shell.component.d.ts +12 -0
- package/dist/shared/components/app-shell.component.d.ts.map +1 -0
- package/dist/shared/components/app-shell.component.js +15 -0
- package/dist/shared/components/radio-select.component.d.ts +12 -0
- package/dist/shared/components/radio-select.component.d.ts.map +1 -0
- package/dist/shared/components/radio-select.component.js +16 -0
- package/dist/shared/components/step-header.component.d.ts +7 -0
- package/dist/shared/components/step-header.component.d.ts.map +1 -0
- package/dist/shared/components/step-header.component.js +5 -0
- package/dist/shared/components/text-field.component.d.ts +12 -0
- package/dist/shared/components/text-field.component.d.ts.map +1 -0
- package/dist/shared/components/text-field.component.js +6 -0
- package/dist/shared/config/app-paths.config.d.ts +4 -0
- package/dist/shared/config/app-paths.config.d.ts.map +1 -0
- package/dist/shared/config/app-paths.config.js +5 -0
- package/dist/shared/config/env.config.d.ts +14 -0
- package/dist/shared/config/env.config.d.ts.map +1 -0
- package/dist/shared/config/env.config.js +58 -0
- package/dist/shared/constants/app-version.constant.d.ts +2 -0
- package/dist/shared/constants/app-version.constant.d.ts.map +1 -0
- package/dist/shared/constants/app-version.constant.js +1 -0
- package/dist/shared/services/cli-update.service.d.ts +29 -0
- package/dist/shared/services/cli-update.service.d.ts.map +1 -0
- package/dist/shared/services/cli-update.service.js +206 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -37,6 +37,32 @@ npm run build --workspace apps/cli
|
|
|
37
37
|
npm run check-types --workspace apps/cli
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
+
## Convencao de arquivos
|
|
41
|
+
|
|
42
|
+
Dentro de `apps/cli/src`, todos os arquivos seguem estas regras:
|
|
43
|
+
|
|
44
|
+
- nomes sempre em minusculas e `kebab-case`
|
|
45
|
+
- o sufixo final identifica a responsabilidade do arquivo
|
|
46
|
+
- cada tipo de arquivo fica na pasta correspondente sempre que fizer sentido
|
|
47
|
+
|
|
48
|
+
Padrao adotado:
|
|
49
|
+
|
|
50
|
+
- `*.component.tsx`: componentes visuais reutilizaveis e composicao de layout
|
|
51
|
+
- `*.page.tsx`: telas e fluxos de interface
|
|
52
|
+
- `*.service.ts`: servicos, integracoes e regras de orquestracao
|
|
53
|
+
- `*.config.ts`: configuracoes compartilhadas
|
|
54
|
+
- `*.constant.ts`: constantes compartilhadas
|
|
55
|
+
- `*.util.ts`: funcoes utilitarias puras
|
|
56
|
+
- `*.entry.tsx`: ponto de entrada executavel da CLI
|
|
57
|
+
|
|
58
|
+
Organizacao:
|
|
59
|
+
|
|
60
|
+
- `shared/`: artefatos reutilizados por mais de um modulo
|
|
61
|
+
- `modules/<modulo>/pages`: telas do modulo
|
|
62
|
+
- `modules/<modulo>/services`: servicos e estados internos do modulo
|
|
63
|
+
- `modules/<modulo>/components`: componentes locais do modulo
|
|
64
|
+
- `modules/<modulo>/utils`: utilitarios locais do modulo
|
|
65
|
+
|
|
40
66
|
## Release para npm
|
|
41
67
|
|
|
42
68
|
O fluxo oficial de publicacao fica na raiz do monorepo.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type CliUpdateCheckResult } from './shared/services/cli-update.service.js';
|
|
2
|
+
export type AppProps = {
|
|
3
|
+
readonly appVersion: string;
|
|
4
|
+
readonly initialInput: string;
|
|
5
|
+
readonly initialNotification?: string | null;
|
|
6
|
+
readonly initialNotificationTone?: NotificationTone;
|
|
7
|
+
readonly onSelfUpdate: (update: Extract<CliUpdateCheckResult, {
|
|
8
|
+
status: 'available';
|
|
9
|
+
}>) => void | Promise<void>;
|
|
10
|
+
};
|
|
11
|
+
type NotificationTone = 'error' | 'info' | 'success';
|
|
12
|
+
export declare function App({ appVersion, initialInput, initialNotification, initialNotificationTone, onSelfUpdate, }: AppProps): import("react/jsx-runtime").JSX.Element;
|
|
13
|
+
export {};
|
|
14
|
+
//# sourceMappingURL=app.component.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app.component.d.ts","sourceRoot":"","sources":["../src/app.component.tsx"],"names":[],"mappings":"AAYA,OAAO,EAAqB,KAAK,oBAAoB,EAAE,MAAM,yCAAyC,CAAC;AAMvG,MAAM,MAAM,QAAQ,GAAG;IACrB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,mBAAmB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7C,QAAQ,CAAC,uBAAuB,CAAC,EAAE,gBAAgB,CAAC;IACpD,QAAQ,CAAC,YAAY,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC,oBAAoB,EAAE;QAAE,MAAM,EAAE,WAAW,CAAA;KAAE,CAAC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACjH,CAAC;AA6DF,KAAK,gBAAgB,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,CAAC;AAyFrD,wBAAgB,GAAG,CAAC,EAClB,UAAU,EACV,YAAY,EACZ,mBAA0B,EAC1B,uBAAmC,EACnC,YAAY,GACb,EAAE,QAAQ,2CAmfV"}
|
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Text, useApp, useInput } from 'ink';
|
|
3
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
4
|
+
import { DiscoveryPage } from './modules/discovery/pages/discovery.page.js';
|
|
5
|
+
import { getUserDisplayName } from './modules/auth/utils/auth-user.util.js';
|
|
6
|
+
import { getAuthBootstrapResult, } from './modules/auth/services/auth.service.js';
|
|
7
|
+
import { LoginPage } from './modules/auth/pages/login.page.js';
|
|
8
|
+
import { ProfilePage } from './modules/auth/pages/profile.page.js';
|
|
9
|
+
import { checkForCliUpdate } from './shared/services/cli-update.service.js';
|
|
10
|
+
import { ActionMenuPage } from './shared/components/action-menu.component.js';
|
|
11
|
+
import { AppMenu } from './shared/components/app-menu.component.js';
|
|
12
|
+
import { AppShell } from './shared/components/app-shell.component.js';
|
|
13
|
+
const MENU_ITEMS = [
|
|
14
|
+
{
|
|
15
|
+
id: 'discovery',
|
|
16
|
+
titulo: 'Descobrir Temas',
|
|
17
|
+
comando: 'discovery',
|
|
18
|
+
aliases: ['descobrir', 'Descobrir', 'temas', 'Temas'],
|
|
19
|
+
descricao: 'Inicia o fluxo de descoberta e coleta o tema da busca.',
|
|
20
|
+
palavrasChave: ['tema', 'Tema', 'descoberta', 'Descoberta', 'pesquisa', 'Pesquisa', 'amplo', 'restrito'],
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
id: 'profile',
|
|
24
|
+
titulo: 'Meu Perfil',
|
|
25
|
+
comando: 'profile',
|
|
26
|
+
aliases: ['perfil', 'Perfil', 'conta', 'Conta', 'usuário', 'usuario', 'Usuário', 'Usuario'],
|
|
27
|
+
descricao: 'Abre os fluxos de alteração de senha e display name.',
|
|
28
|
+
palavrasChave: [
|
|
29
|
+
'senha',
|
|
30
|
+
'Senha',
|
|
31
|
+
'display name',
|
|
32
|
+
'Display Name',
|
|
33
|
+
'nome',
|
|
34
|
+
'Nome',
|
|
35
|
+
'usuário',
|
|
36
|
+
'usuario',
|
|
37
|
+
'Supabase',
|
|
38
|
+
'supabase',
|
|
39
|
+
],
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
id: 'logout',
|
|
43
|
+
titulo: 'Encerrar Sessão',
|
|
44
|
+
comando: 'logout',
|
|
45
|
+
aliases: ['sair-da-conta', 'sair da conta', 'Sair da Conta', 'deslogar', 'Deslogar'],
|
|
46
|
+
descricao: 'Remove a sessão salva localmente e volta para a tela de login.',
|
|
47
|
+
palavrasChave: ['auth', 'login', 'Login', 'conta', 'Conta', 'sessão', 'sessao', 'Sessão', 'Sessao', 'Supabase'],
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
id: 'exit',
|
|
51
|
+
titulo: 'Sair',
|
|
52
|
+
comando: 'sair',
|
|
53
|
+
aliases: ['exit', 'quit', 'sair', 'Sair'],
|
|
54
|
+
descricao: 'Sai do sistema sem perder a sessão do usuário ativa.',
|
|
55
|
+
palavrasChave: [
|
|
56
|
+
'fechar',
|
|
57
|
+
'Fechar',
|
|
58
|
+
'encerrar',
|
|
59
|
+
'Encerrar',
|
|
60
|
+
'finalizar',
|
|
61
|
+
'Finalizar',
|
|
62
|
+
'sessão',
|
|
63
|
+
'sessao',
|
|
64
|
+
'usuário',
|
|
65
|
+
'usuario',
|
|
66
|
+
],
|
|
67
|
+
},
|
|
68
|
+
];
|
|
69
|
+
const LOGOUT_OPTIONS = [
|
|
70
|
+
{
|
|
71
|
+
value: 'cancel',
|
|
72
|
+
label: 'Voltar ao menu',
|
|
73
|
+
description: 'Cancela a saida da conta e retorna para os comandos.',
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
value: 'confirm',
|
|
77
|
+
label: 'Encerrar sessao',
|
|
78
|
+
description: 'Remove a sessao local e exige novo login para continuar.',
|
|
79
|
+
},
|
|
80
|
+
];
|
|
81
|
+
function formatScopeLabel(scope) {
|
|
82
|
+
return scope === 'amplo' ? 'mais ampla' : 'mais restrita';
|
|
83
|
+
}
|
|
84
|
+
function formatWorkspaceRole(role) {
|
|
85
|
+
if (role === 'owner') {
|
|
86
|
+
return 'Proprietario';
|
|
87
|
+
}
|
|
88
|
+
if (role === 'admin') {
|
|
89
|
+
return 'Administrador';
|
|
90
|
+
}
|
|
91
|
+
return 'Membro';
|
|
92
|
+
}
|
|
93
|
+
function getAuthenticatedUserLabel(user) {
|
|
94
|
+
if (!user) {
|
|
95
|
+
return 'usuario autenticado';
|
|
96
|
+
}
|
|
97
|
+
return getUserDisplayName(user) ?? user.email ?? 'usuario autenticado';
|
|
98
|
+
}
|
|
99
|
+
function getActiveWorkspaceLabel(workspace) {
|
|
100
|
+
return workspace?.name ?? 'espaco de trabalho';
|
|
101
|
+
}
|
|
102
|
+
function getWelcomeTitle(user) {
|
|
103
|
+
const displayName = user ? getUserDisplayName(user) : null;
|
|
104
|
+
if (displayName) {
|
|
105
|
+
return `Bem-vindo de volta, ${displayName} 👋`;
|
|
106
|
+
}
|
|
107
|
+
return 'Bem-vindo de volta. 👋';
|
|
108
|
+
}
|
|
109
|
+
function buildMenuItems(cliUpdate) {
|
|
110
|
+
if (cliUpdate.status !== 'available') {
|
|
111
|
+
return MENU_ITEMS;
|
|
112
|
+
}
|
|
113
|
+
const [discoveryItem, profileItem, logoutItem, exitItem] = MENU_ITEMS;
|
|
114
|
+
if (!discoveryItem || !profileItem || !logoutItem || !exitItem) {
|
|
115
|
+
return MENU_ITEMS;
|
|
116
|
+
}
|
|
117
|
+
return [
|
|
118
|
+
discoveryItem,
|
|
119
|
+
profileItem,
|
|
120
|
+
{
|
|
121
|
+
id: 'update-cli',
|
|
122
|
+
titulo: `Atualizar CLI para v${cliUpdate.latestVersion}`,
|
|
123
|
+
comando: 'atualizar-cli',
|
|
124
|
+
aliases: ['update', 'upgrade', 'atualizar', 'atualizar-cli', 'Atualizar CLI'],
|
|
125
|
+
descricao: `Instala a versao ${cliUpdate.latestVersion} do CLI e reinicia a aplicacao.`,
|
|
126
|
+
palavrasChave: [
|
|
127
|
+
'versao',
|
|
128
|
+
'nova versao',
|
|
129
|
+
'ultima versao',
|
|
130
|
+
'npm',
|
|
131
|
+
'upgrade',
|
|
132
|
+
'update',
|
|
133
|
+
'atualizacao',
|
|
134
|
+
'atualizar',
|
|
135
|
+
],
|
|
136
|
+
},
|
|
137
|
+
logoutItem,
|
|
138
|
+
exitItem,
|
|
139
|
+
];
|
|
140
|
+
}
|
|
141
|
+
export function App({ appVersion, initialInput, initialNotification = null, initialNotificationTone = 'success', onSelfUpdate, }) {
|
|
142
|
+
const { exit } = useApp();
|
|
143
|
+
const authBootstrap = useMemo(() => getAuthBootstrapResult(), []);
|
|
144
|
+
const authService = authBootstrap.ok ? authBootstrap.authService : null;
|
|
145
|
+
const sessionStorageFilePath = authService?.getSessionStorageFilePath() ?? '~/.trendify/auth/storage.json';
|
|
146
|
+
const [screen, setScreen] = useState('menu');
|
|
147
|
+
const [notification, setNotification] = useState(initialNotification);
|
|
148
|
+
const [notificationTone, setNotificationTone] = useState(initialNotificationTone);
|
|
149
|
+
const [menuKey, setMenuKey] = useState(0);
|
|
150
|
+
const [authStatus, setAuthStatus] = useState(authBootstrap.ok ? 'booting' : 'config-error');
|
|
151
|
+
const [authUser, setAuthUser] = useState(null);
|
|
152
|
+
const [availableWorkspaces, setAvailableWorkspaces] = useState([]);
|
|
153
|
+
const [activeWorkspace, setActiveWorkspace] = useState(null);
|
|
154
|
+
const [email, setEmail] = useState('');
|
|
155
|
+
const [password, setPassword] = useState('');
|
|
156
|
+
const [authBusy, setAuthBusy] = useState(false);
|
|
157
|
+
const [logoutSelectionIndex, setLogoutSelectionIndex] = useState(0);
|
|
158
|
+
const [workspaceSelectionIndex, setWorkspaceSelectionIndex] = useState(0);
|
|
159
|
+
const [cliUpdate, setCliUpdate] = useState({
|
|
160
|
+
currentVersion: appVersion,
|
|
161
|
+
packageName: '@trendify/cli',
|
|
162
|
+
status: 'checking',
|
|
163
|
+
});
|
|
164
|
+
const menuItems = useMemo(() => buildMenuItems(cliUpdate), [cliUpdate]);
|
|
165
|
+
function resetAuthenticatedContext() {
|
|
166
|
+
setAuthUser(null);
|
|
167
|
+
setAvailableWorkspaces([]);
|
|
168
|
+
setActiveWorkspace(null);
|
|
169
|
+
setWorkspaceSelectionIndex(0);
|
|
170
|
+
}
|
|
171
|
+
function applyAuthenticatedSnapshot(snapshot) {
|
|
172
|
+
setAuthStatus('authenticated');
|
|
173
|
+
setAuthUser(snapshot.user);
|
|
174
|
+
setAvailableWorkspaces(snapshot.workspaces);
|
|
175
|
+
setActiveWorkspace(snapshot.activeWorkspace);
|
|
176
|
+
setWorkspaceSelectionIndex(0);
|
|
177
|
+
if (snapshot.requiresWorkspaceSelection) {
|
|
178
|
+
setScreen('select-workspace');
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
setScreen('menu');
|
|
182
|
+
setMenuKey((current) => current + 1);
|
|
183
|
+
}
|
|
184
|
+
useInput((input, key) => {
|
|
185
|
+
if (key.ctrl && input === 'c') {
|
|
186
|
+
exit();
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
useInput((_input, key) => {
|
|
190
|
+
if (screen !== 'confirm-logout' || authBusy) {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
if (key.escape) {
|
|
194
|
+
setScreen('menu');
|
|
195
|
+
setLogoutSelectionIndex(0);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
if (key.upArrow) {
|
|
199
|
+
setLogoutSelectionIndex((current) => (current === 0 ? LOGOUT_OPTIONS.length - 1 : current - 1));
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
if (key.downArrow) {
|
|
203
|
+
setLogoutSelectionIndex((current) => (current === LOGOUT_OPTIONS.length - 1 ? 0 : current + 1));
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
if (key.return) {
|
|
207
|
+
const option = LOGOUT_OPTIONS[logoutSelectionIndex];
|
|
208
|
+
if (option?.value === 'confirm') {
|
|
209
|
+
void handleLogout();
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
setScreen('menu');
|
|
213
|
+
setLogoutSelectionIndex(0);
|
|
214
|
+
setNotification('Encerramento de sessao cancelado.');
|
|
215
|
+
setNotificationTone('info');
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
useInput((_input, key) => {
|
|
219
|
+
if (screen !== 'select-workspace' || authBusy) {
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
if (!availableWorkspaces.length) {
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
if (key.escape) {
|
|
226
|
+
void handleLogout();
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
if (key.upArrow) {
|
|
230
|
+
setWorkspaceSelectionIndex((current) => (current === 0 ? availableWorkspaces.length - 1 : current - 1));
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
if (key.downArrow) {
|
|
234
|
+
setWorkspaceSelectionIndex((current) => (current === availableWorkspaces.length - 1 ? 0 : current + 1));
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
if (key.return) {
|
|
238
|
+
void handleWorkspaceSelection();
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
useEffect(() => {
|
|
242
|
+
let active = true;
|
|
243
|
+
const checkUpdates = async () => {
|
|
244
|
+
const result = await checkForCliUpdate(appVersion);
|
|
245
|
+
if (!active) {
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
setCliUpdate(result);
|
|
249
|
+
};
|
|
250
|
+
void checkUpdates();
|
|
251
|
+
return () => {
|
|
252
|
+
active = false;
|
|
253
|
+
};
|
|
254
|
+
}, [appVersion]);
|
|
255
|
+
useEffect(() => {
|
|
256
|
+
if (!authService) {
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
let active = true;
|
|
260
|
+
setAuthBusy(true);
|
|
261
|
+
const restoreSession = async () => {
|
|
262
|
+
const result = await authService.restoreSession();
|
|
263
|
+
if (!active) {
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
if (result.error) {
|
|
267
|
+
setNotification(result.error);
|
|
268
|
+
setNotificationTone('error');
|
|
269
|
+
}
|
|
270
|
+
if (result.data?.user) {
|
|
271
|
+
applyAuthenticatedSnapshot(result.data);
|
|
272
|
+
if (result.data.requiresWorkspaceSelection) {
|
|
273
|
+
setNotification('Escolha o espaco de trabalho que deseja usar nesta sessao.');
|
|
274
|
+
setNotificationTone('info');
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
else {
|
|
278
|
+
setAuthStatus('unauthenticated');
|
|
279
|
+
resetAuthenticatedContext();
|
|
280
|
+
}
|
|
281
|
+
setAuthBusy(false);
|
|
282
|
+
};
|
|
283
|
+
void restoreSession();
|
|
284
|
+
const { data: { subscription }, } = authService.onAuthStateChange((snapshot) => {
|
|
285
|
+
if (!active) {
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
if (snapshot.user) {
|
|
289
|
+
setAuthUser(snapshot.user);
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
setAuthStatus('unauthenticated');
|
|
293
|
+
resetAuthenticatedContext();
|
|
294
|
+
setScreen('menu');
|
|
295
|
+
setMenuKey((current) => current + 1);
|
|
296
|
+
});
|
|
297
|
+
return () => {
|
|
298
|
+
active = false;
|
|
299
|
+
subscription.unsubscribe();
|
|
300
|
+
};
|
|
301
|
+
}, [authService]);
|
|
302
|
+
async function handleLogin() {
|
|
303
|
+
if (!authService || authBusy) {
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
const trimmedEmail = email.trim();
|
|
307
|
+
if (!trimmedEmail || !password.trim()) {
|
|
308
|
+
setNotification('Preencha email e senha para continuar.');
|
|
309
|
+
setNotificationTone('error');
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
setAuthBusy(true);
|
|
313
|
+
setNotification(null);
|
|
314
|
+
const result = await authService.signInWithPassword(trimmedEmail, password);
|
|
315
|
+
setAuthBusy(false);
|
|
316
|
+
if (result.error || !result.data?.user) {
|
|
317
|
+
setPassword('');
|
|
318
|
+
setNotification(result.error ?? 'Nao foi possivel autenticar este usuario.');
|
|
319
|
+
setNotificationTone('error');
|
|
320
|
+
setAuthStatus('unauthenticated');
|
|
321
|
+
resetAuthenticatedContext();
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
applyAuthenticatedSnapshot(result.data);
|
|
325
|
+
setPassword('');
|
|
326
|
+
if (result.data.requiresWorkspaceSelection) {
|
|
327
|
+
setNotification('Login concluido. Escolha o espaco de trabalho para continuar.');
|
|
328
|
+
setNotificationTone('info');
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
setNotification(null);
|
|
332
|
+
}
|
|
333
|
+
async function handleWorkspaceSelection() {
|
|
334
|
+
if (!authService || authBusy || !availableWorkspaces.length) {
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
const workspace = availableWorkspaces[workspaceSelectionIndex];
|
|
338
|
+
if (!workspace) {
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
setAuthBusy(true);
|
|
342
|
+
const result = await authService.selectWorkspace(workspace.id);
|
|
343
|
+
setAuthBusy(false);
|
|
344
|
+
if (result.error || !result.data) {
|
|
345
|
+
setNotification(result.error ?? 'Nao foi possivel ativar este espaco de trabalho.');
|
|
346
|
+
setNotificationTone('error');
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
setActiveWorkspace(result.data);
|
|
350
|
+
setScreen('menu');
|
|
351
|
+
setMenuKey((current) => current + 1);
|
|
352
|
+
setNotification(`Espaco de trabalho ativo: ${result.data.name}.`);
|
|
353
|
+
setNotificationTone('success');
|
|
354
|
+
}
|
|
355
|
+
async function handleLogout() {
|
|
356
|
+
if (!authService || authBusy) {
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
setAuthBusy(true);
|
|
360
|
+
const result = await authService.signOut();
|
|
361
|
+
setAuthBusy(false);
|
|
362
|
+
if (result.error) {
|
|
363
|
+
setNotification(result.error);
|
|
364
|
+
setNotificationTone('error');
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
setScreen('menu');
|
|
368
|
+
setEmail('');
|
|
369
|
+
setPassword('');
|
|
370
|
+
resetAuthenticatedContext();
|
|
371
|
+
setMenuKey((current) => current + 1);
|
|
372
|
+
setLogoutSelectionIndex(0);
|
|
373
|
+
setNotification('Sessao local removida. Faca login novamente para continuar.');
|
|
374
|
+
setNotificationTone('success');
|
|
375
|
+
}
|
|
376
|
+
if (!authBootstrap.ok) {
|
|
377
|
+
return (_jsx(AppShell, { appVersion: appVersion, title: "Configuracao do Supabase pendente.", subtitle: "Defina as variaveis de ambiente antes de usar a CLI.", notification: authBootstrap.error, notificationTone: "error", children: _jsx(Text, { children: "Crie um arquivo .env com base em .env.example e preencha as chaves do projeto Supabase." }) }));
|
|
378
|
+
}
|
|
379
|
+
if (authStatus === 'booting') {
|
|
380
|
+
return (_jsx(AppShell, { appVersion: appVersion, title: "Validando sua sessao.", subtitle: "A CLI esta consultando o Supabase para restaurar a sessao e o espaco de trabalho atual.", notification: notification, notificationTone: notificationTone, children: _jsx(Text, { dimColor: true, children: authBusy ? 'Aguarde alguns instantes...' : 'Pronto para autenticar.' }) }));
|
|
381
|
+
}
|
|
382
|
+
if (authStatus === 'unauthenticated') {
|
|
383
|
+
return (_jsx(AppShell, { appVersion: appVersion, title: "Login obrigatorio.", subtitle: "Somente usuarios vinculados a um espaco de trabalho no Supabase podem usar a CLI.", notification: notification, notificationTone: notificationTone, children: _jsx(LoginPage, { busy: authBusy, email: email, password: password, sessionStorageFilePath: sessionStorageFilePath, onEmailChange: setEmail, onPasswordChange: setPassword, onSubmit: () => {
|
|
384
|
+
void handleLogin();
|
|
385
|
+
} }) }));
|
|
386
|
+
}
|
|
387
|
+
if (screen === 'select-workspace') {
|
|
388
|
+
const workspaceOptions = availableWorkspaces.map((workspace) => ({
|
|
389
|
+
value: workspace.id,
|
|
390
|
+
label: workspace.name,
|
|
391
|
+
description: `${workspace.slug} • ${formatWorkspaceRole(workspace.role)}`,
|
|
392
|
+
}));
|
|
393
|
+
return (_jsx(AppShell, { appVersion: appVersion, title: "Escolha o espaco de trabalho", subtitle: `Conta autenticada para ${getAuthenticatedUserLabel(authUser)}. Selecione onde deseja operar.`, notification: notification, notificationTone: notificationTone, children: _jsx(ActionMenuPage, { title: "Espaco de trabalho ativo", subtitle: "Esse contexto sera usado nas consultas protegidas e nas regras de seguranca por linha.", options: workspaceOptions, selectedIndex: workspaceSelectionIndex, hintText: "Use as setas para navegar. Enter ativa o espaco. Esc encerra a sessao.", children: _jsxs(Text, { children: ["Conta: ", authUser?.email ?? 'nao informado'] }) }) }));
|
|
394
|
+
}
|
|
395
|
+
if (screen === 'discovery') {
|
|
396
|
+
return (_jsx(AppShell, { appVersion: appVersion, title: "Discovery", subtitle: `Sessao ativa para ${getAuthenticatedUserLabel(authUser)} em ${getActiveWorkspaceLabel(activeWorkspace)}.`, notification: notification, notificationTone: notificationTone, children: _jsx(DiscoveryPage, { onCancel: () => {
|
|
397
|
+
setScreen('menu');
|
|
398
|
+
setMenuKey((current) => current + 1);
|
|
399
|
+
}, onComplete: ({ theme, scope }) => {
|
|
400
|
+
setNotification(`Discovery finalizado: tema "${theme}" com abordagem ${formatScopeLabel(scope)}.`);
|
|
401
|
+
setNotificationTone('success');
|
|
402
|
+
setScreen('menu');
|
|
403
|
+
setMenuKey((current) => current + 1);
|
|
404
|
+
} }) }));
|
|
405
|
+
}
|
|
406
|
+
if (screen === 'profile' && authService && authUser) {
|
|
407
|
+
return (_jsx(AppShell, { appVersion: appVersion, title: "Profile", subtitle: `Sessao ativa para ${getAuthenticatedUserLabel(authUser)} em ${getActiveWorkspaceLabel(activeWorkspace)}.`, notification: notification, notificationTone: notificationTone, children: _jsx(ProfilePage, { authService: authService, user: authUser, onBack: () => {
|
|
408
|
+
setScreen('menu');
|
|
409
|
+
setMenuKey((current) => current + 1);
|
|
410
|
+
}, onNotificationChange: (nextNotification, nextTone = 'success') => {
|
|
411
|
+
setNotification(nextNotification);
|
|
412
|
+
setNotificationTone(nextTone);
|
|
413
|
+
} }) }));
|
|
414
|
+
}
|
|
415
|
+
if (screen === 'confirm-logout') {
|
|
416
|
+
return (_jsx(AppShell, { appVersion: appVersion, title: "Confirmar encerramento de sessao", subtitle: `Voce esta autenticado como ${getAuthenticatedUserLabel(authUser)} em ${getActiveWorkspaceLabel(activeWorkspace)}.`, notification: notification, notificationTone: notificationTone, children: _jsx(ActionMenuPage, { title: "Tem certeza que deseja encerrar a sessao atual?", subtitle: "Se confirmar, a CLI vai remover a sessao salva localmente e pedir login novamente.", options: LOGOUT_OPTIONS, selectedIndex: logoutSelectionIndex, hintText: "Use as setas para navegar. Enter confirma. Esc volta ao menu." }) }));
|
|
417
|
+
}
|
|
418
|
+
return (_jsxs(AppShell, { appVersion: appVersion, title: getWelcomeTitle(authUser), subtitle: `Workspace: ${getActiveWorkspaceLabel(activeWorkspace)}\n\nUse a barra para abrir os comandos.`, notification: notification, notificationTone: notificationTone, children: [cliUpdate.status === 'available' ? (_jsxs(Text, { color: "yellowBright", children: ["Nova versao disponivel: v", cliUpdate.latestVersion, ". Abra o menu com / e escolha \"Atualizar CLI\"."] })) : null, _jsx(AppMenu, { initialInput: menuKey === 0 ? initialInput : '', items: menuItems, onInputChange: () => {
|
|
419
|
+
if (notification) {
|
|
420
|
+
setNotification(null);
|
|
421
|
+
}
|
|
422
|
+
}, onSelect: (item) => {
|
|
423
|
+
if (item.id === 'update-cli' && cliUpdate.status === 'available') {
|
|
424
|
+
void onSelfUpdate(cliUpdate);
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
if (item.id === 'logout') {
|
|
428
|
+
setNotification(null);
|
|
429
|
+
setNotificationTone('info');
|
|
430
|
+
setLogoutSelectionIndex(0);
|
|
431
|
+
setScreen('confirm-logout');
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
if (item.id === 'profile') {
|
|
435
|
+
setNotification(null);
|
|
436
|
+
setNotificationTone('success');
|
|
437
|
+
setScreen('profile');
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
if (item.id === 'exit') {
|
|
441
|
+
exit();
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
setNotification(null);
|
|
445
|
+
setNotificationTone('success');
|
|
446
|
+
setScreen('discovery');
|
|
447
|
+
} }, menuKey)] }));
|
|
448
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.entry.d.ts","sourceRoot":"","sources":["../src/cli.entry.tsx"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { render } from 'ink';
|
|
4
|
+
import { App } from './app.component.js';
|
|
5
|
+
import { APP_VERSION } from './shared/constants/app-version.constant.js';
|
|
6
|
+
import { runCliSelfUpdate } from './shared/services/cli-update.service.js';
|
|
7
|
+
const args = process.argv.slice(2);
|
|
8
|
+
const initialInput = args
|
|
9
|
+
.filter((arg) => !arg.startsWith('-'))
|
|
10
|
+
.join(' ')
|
|
11
|
+
.trim();
|
|
12
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
13
|
+
process.stdout.write(`CLI
|
|
14
|
+
|
|
15
|
+
Uso:
|
|
16
|
+
trendify [opcoes]
|
|
17
|
+
|
|
18
|
+
Opcoes:
|
|
19
|
+
-h, --help Mostra esta ajuda
|
|
20
|
+
-v, --version Mostra a versao instalada
|
|
21
|
+
`);
|
|
22
|
+
process.exit(0);
|
|
23
|
+
}
|
|
24
|
+
if (args.includes('--version') || args.includes('-v')) {
|
|
25
|
+
process.stdout.write(`${APP_VERSION}\n`);
|
|
26
|
+
process.exit(0);
|
|
27
|
+
}
|
|
28
|
+
console.clear();
|
|
29
|
+
let appInstance;
|
|
30
|
+
function mountApp(feedback = null) {
|
|
31
|
+
appInstance = render(_jsx(App, { appVersion: APP_VERSION, initialInput: initialInput, initialNotification: feedback?.message ?? null, initialNotificationTone: feedback?.tone ?? 'success', onSelfUpdate: async (update) => {
|
|
32
|
+
appInstance.unmount();
|
|
33
|
+
console.clear();
|
|
34
|
+
process.stdout.write([
|
|
35
|
+
`Atualizando o CLI de v${update.currentVersion} para v${update.latestVersion}.`,
|
|
36
|
+
`Pacote npm: ${update.packageName}`,
|
|
37
|
+
'',
|
|
38
|
+
].join('\n'));
|
|
39
|
+
const result = await runCliSelfUpdate(update.packageName, update.latestVersion);
|
|
40
|
+
if (result.ok) {
|
|
41
|
+
process.exit(0);
|
|
42
|
+
}
|
|
43
|
+
process.stdout.write(`\nFalha ao atualizar automaticamente: ${result.error}\n`);
|
|
44
|
+
process.stdout.write('A CLI atual continuara disponivel.\n');
|
|
45
|
+
mountApp({
|
|
46
|
+
message: `Nao foi possivel atualizar a CLI automaticamente. ${result.error}`,
|
|
47
|
+
tone: 'error',
|
|
48
|
+
});
|
|
49
|
+
} }));
|
|
50
|
+
}
|
|
51
|
+
mountApp();
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
type LoginPageProps = {
|
|
2
|
+
readonly busy: boolean;
|
|
3
|
+
readonly email: string;
|
|
4
|
+
readonly onEmailChange: (value: string) => void;
|
|
5
|
+
readonly onPasswordChange: (value: string) => void;
|
|
6
|
+
readonly onSubmit: () => void;
|
|
7
|
+
readonly password: string;
|
|
8
|
+
readonly sessionStorageFilePath: string;
|
|
9
|
+
};
|
|
10
|
+
export declare function LoginPage({ busy, email, onEmailChange, onPasswordChange, onSubmit, password, sessionStorageFilePath, }: LoginPageProps): import("react/jsx-runtime").JSX.Element;
|
|
11
|
+
export {};
|
|
12
|
+
//# sourceMappingURL=login.page.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"login.page.d.ts","sourceRoot":"","sources":["../../../../src/modules/auth/pages/login.page.tsx"],"names":[],"mappings":"AAIA,KAAK,cAAc,GAAG;IACpB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAChD,QAAQ,CAAC,gBAAgB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACnD,QAAQ,CAAC,QAAQ,EAAE,MAAM,IAAI,CAAC;IAC9B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,sBAAsB,EAAE,MAAM,CAAC;CACzC,CAAC;AAIF,wBAAgB,SAAS,CAAC,EACxB,IAAI,EACJ,KAAK,EACL,aAAa,EACb,gBAAgB,EAChB,QAAQ,EACR,QAAQ,EACR,sBAAsB,GACvB,EAAE,cAAc,2CAsDhB"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text, useInput } from 'ink';
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import { TextField } from '../../../shared/components/text-field.component.js';
|
|
5
|
+
export function LoginPage({ busy, email, onEmailChange, onPasswordChange, onSubmit, password, sessionStorageFilePath, }) {
|
|
6
|
+
const [focusField, setFocusField] = useState('email');
|
|
7
|
+
useInput((_input, key) => {
|
|
8
|
+
if (busy) {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
if (key.tab || key.downArrow || key.upArrow) {
|
|
12
|
+
setFocusField((current) => (current === 'email' ? 'password' : 'email'));
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { children: "Entre com um usuario ja cadastrado no Supabase para liberar os comandos internos." }), _jsx(Text, { dimColor: true, children: "Apos o login, a CLI tambem valida o espaco de trabalho vinculado a sua conta." }), _jsx(Text, { dimColor: true, children: "Cadastro nao esta habilitado nesta CLI." }), _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { color: focusField === 'email' ? 'greenBright' : 'white', children: "Email" }), _jsx(TextField, { focus: focusField === 'email', placeholder: "voce@empresa.com", value: email, onChange: onEmailChange, onSubmit: (value) => {
|
|
16
|
+
if (value.trim()) {
|
|
17
|
+
setFocusField('password');
|
|
18
|
+
}
|
|
19
|
+
} })] }), _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { color: focusField === 'password' ? 'greenBright' : 'white', children: "Senha" }), _jsx(TextField, { focus: focusField === 'password', mask: "*", placeholder: "Sua senha", value: password, onChange: onPasswordChange, onSubmit: () => {
|
|
20
|
+
onSubmit();
|
|
21
|
+
} })] }), _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { dimColor: true, children: "Enter faz login. Tab ou setas alternam o campo ativo." }), _jsxs(Text, { dimColor: true, children: ["Sessao persistida localmente em: ", sessionStorageFilePath] })] })] }));
|
|
22
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type AuthUser } from '@supabase/supabase-js';
|
|
2
|
+
import { type AuthService } from '../services/auth.service.js';
|
|
3
|
+
type NotificationTone = 'error' | 'info' | 'success';
|
|
4
|
+
type ProfilePageProps = {
|
|
5
|
+
readonly authService: AuthService;
|
|
6
|
+
readonly onBack: () => void;
|
|
7
|
+
readonly onNotificationChange: (notification: string | null, tone?: NotificationTone) => void;
|
|
8
|
+
readonly user: AuthUser;
|
|
9
|
+
};
|
|
10
|
+
export declare function ProfilePage({ authService, onBack, onNotificationChange, user }: ProfilePageProps): import("react/jsx-runtime").JSX.Element;
|
|
11
|
+
export {};
|
|
12
|
+
//# sourceMappingURL=profile.page.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"profile.page.d.ts","sourceRoot":"","sources":["../../../../src/modules/auth/pages/profile.page.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAGtD,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAO/D,KAAK,gBAAgB,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,CAAC;AAKrD,KAAK,gBAAgB,GAAG;IACtB,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC;IAClC,QAAQ,CAAC,MAAM,EAAE,MAAM,IAAI,CAAC;IAC5B,QAAQ,CAAC,oBAAoB,EAAE,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,EAAE,IAAI,CAAC,EAAE,gBAAgB,KAAK,IAAI,CAAC;IAC9F,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;CACzB,CAAC;AAwBF,wBAAgB,WAAW,CAAC,EAAE,WAAW,EAAE,MAAM,EAAE,oBAAoB,EAAE,IAAI,EAAE,EAAE,gBAAgB,2CAwQhG"}
|