@trendify/cli 0.1.6 → 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.
Files changed (74) hide show
  1. package/README.md +26 -0
  2. package/dist/app.component.d.ts +14 -0
  3. package/dist/app.component.d.ts.map +1 -0
  4. package/dist/app.component.js +448 -0
  5. package/dist/app.d.ts +9 -1
  6. package/dist/app.d.ts.map +1 -1
  7. package/dist/app.js +84 -27
  8. package/dist/cli-update-service.d.ts +29 -0
  9. package/dist/cli-update-service.d.ts.map +1 -0
  10. package/dist/cli-update-service.js +206 -0
  11. package/dist/cli.entry.d.ts +3 -0
  12. package/dist/cli.entry.d.ts.map +1 -0
  13. package/dist/cli.entry.js +51 -0
  14. package/dist/cli.js +24 -1
  15. package/dist/modules/auth/pages/login.page.d.ts +12 -0
  16. package/dist/modules/auth/pages/login.page.d.ts.map +1 -0
  17. package/dist/modules/auth/pages/login.page.js +22 -0
  18. package/dist/modules/auth/pages/profile.page.d.ts +12 -0
  19. package/dist/modules/auth/pages/profile.page.d.ts.map +1 -0
  20. package/dist/modules/auth/pages/profile.page.js +180 -0
  21. package/dist/modules/auth/services/auth-storage.service.d.ts +11 -0
  22. package/dist/modules/auth/services/auth-storage.service.d.ts.map +1 -0
  23. package/dist/modules/auth/services/auth-storage.service.js +65 -0
  24. package/dist/modules/auth/services/auth.service.d.ts +60 -0
  25. package/dist/modules/auth/services/auth.service.d.ts.map +1 -0
  26. package/dist/modules/auth/services/auth.service.js +494 -0
  27. package/dist/modules/auth/utils/auth-user.util.d.ts +3 -0
  28. package/dist/modules/auth/utils/auth-user.util.d.ts.map +1 -0
  29. package/dist/modules/auth/utils/auth-user.util.js +10 -0
  30. package/dist/modules/discovery/components/discovery-step-header.component.d.ts +7 -0
  31. package/dist/modules/discovery/components/discovery-step-header.component.d.ts.map +1 -0
  32. package/dist/modules/discovery/components/discovery-step-header.component.js +5 -0
  33. package/dist/modules/discovery/pages/discovery.page.d.ts +11 -0
  34. package/dist/modules/discovery/pages/discovery.page.d.ts.map +1 -0
  35. package/dist/modules/discovery/pages/discovery.page.js +58 -0
  36. package/dist/modules/profile/pages/profile.page.d.ts +12 -0
  37. package/dist/modules/profile/pages/profile.page.d.ts.map +1 -0
  38. package/dist/modules/profile/pages/profile.page.js +180 -0
  39. package/dist/shared/components/action-menu.component.d.ts +13 -0
  40. package/dist/shared/components/action-menu.component.d.ts.map +1 -0
  41. package/dist/shared/components/action-menu.component.js +7 -0
  42. package/dist/shared/components/app-logo.component.d.ts +2 -0
  43. package/dist/shared/components/app-logo.component.d.ts.map +1 -0
  44. package/dist/shared/components/app-logo.component.js +13 -0
  45. package/dist/shared/components/app-menu.component.d.ts +17 -0
  46. package/dist/shared/components/app-menu.component.d.ts.map +1 -0
  47. package/dist/shared/components/app-menu.component.js +85 -0
  48. package/dist/shared/components/app-shell.component.d.ts +12 -0
  49. package/dist/shared/components/app-shell.component.d.ts.map +1 -0
  50. package/dist/shared/components/app-shell.component.js +15 -0
  51. package/dist/shared/components/radio-select.component.d.ts +12 -0
  52. package/dist/shared/components/radio-select.component.d.ts.map +1 -0
  53. package/dist/shared/components/radio-select.component.js +16 -0
  54. package/dist/shared/components/step-header.component.d.ts +7 -0
  55. package/dist/shared/components/step-header.component.d.ts.map +1 -0
  56. package/dist/shared/components/step-header.component.js +5 -0
  57. package/dist/shared/components/text-field.component.d.ts +12 -0
  58. package/dist/shared/components/text-field.component.d.ts.map +1 -0
  59. package/dist/shared/components/text-field.component.js +6 -0
  60. package/dist/shared/config/app-paths.config.d.ts +4 -0
  61. package/dist/shared/config/app-paths.config.d.ts.map +1 -0
  62. package/dist/shared/config/app-paths.config.js +5 -0
  63. package/dist/shared/config/env.config.d.ts +14 -0
  64. package/dist/shared/config/env.config.d.ts.map +1 -0
  65. package/dist/shared/config/env.config.js +58 -0
  66. package/dist/shared/constants/app-version.constant.d.ts +2 -0
  67. package/dist/shared/constants/app-version.constant.d.ts.map +1 -0
  68. package/dist/shared/constants/app-version.constant.js +1 -0
  69. package/dist/shared/services/cli-update.service.d.ts +29 -0
  70. package/dist/shared/services/cli-update.service.d.ts.map +1 -0
  71. package/dist/shared/services/cli-update.service.js +206 -0
  72. package/dist/version.d.ts +1 -1
  73. package/dist/version.js +1 -1
  74. 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
+ }
package/dist/app.d.ts CHANGED
@@ -1,6 +1,14 @@
1
+ import { type CliUpdateCheckResult } from './cli-update-service.js';
1
2
  export type AppProps = {
2
3
  readonly appVersion: string;
3
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>;
4
10
  };
5
- export declare function App({ appVersion, initialInput }: AppProps): import("react/jsx-runtime").JSX.Element;
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 {};
6
14
  //# sourceMappingURL=app.d.ts.map
package/dist/app.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.tsx"],"names":[],"mappings":"AAiBA,MAAM,MAAM,QAAQ,GAAG;IACrB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B,CAAC;AAkHF,wBAAgB,GAAG,CAAC,EAAE,UAAU,EAAE,YAAY,EAAE,EAAE,QAAQ,2CA+czD"}
1
+ {"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.tsx"],"names":[],"mappings":"AAYA,OAAO,EAAqB,KAAK,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAMvF,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"}
package/dist/app.js CHANGED
@@ -6,6 +6,7 @@ import { getUserDisplayName } from './modules/auth/auth-user.js';
6
6
  import { getAuthBootstrapResult, } from './modules/auth/auth-service.js';
7
7
  import { LoginPage } from './modules/auth/page/login-page.js';
8
8
  import { ProfilePage } from './modules/profile/page/profile-page.js';
9
+ import { checkForCliUpdate } from './cli-update-service.js';
9
10
  import { ActionMenuPage } from './shared/components/action-menu-page.js';
10
11
  import { AppMenu } from './shared/template/app-menu.js';
11
12
  import { AppShell } from './shared/template/app-shell.js';
@@ -105,14 +106,46 @@ function getWelcomeTitle(user) {
105
106
  }
106
107
  return 'Bem-vindo de volta. 👋';
107
108
  }
108
- export function App({ appVersion, initialInput }) {
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, }) {
109
142
  const { exit } = useApp();
110
143
  const authBootstrap = useMemo(() => getAuthBootstrapResult(), []);
111
144
  const authService = authBootstrap.ok ? authBootstrap.authService : null;
112
145
  const sessionStorageFilePath = authService?.getSessionStorageFilePath() ?? '~/.trendify/auth/storage.json';
113
146
  const [screen, setScreen] = useState('menu');
114
- const [notification, setNotification] = useState(null);
115
- const [notificationTone, setNotificationTone] = useState('success');
147
+ const [notification, setNotification] = useState(initialNotification);
148
+ const [notificationTone, setNotificationTone] = useState(initialNotificationTone);
116
149
  const [menuKey, setMenuKey] = useState(0);
117
150
  const [authStatus, setAuthStatus] = useState(authBootstrap.ok ? 'booting' : 'config-error');
118
151
  const [authUser, setAuthUser] = useState(null);
@@ -123,6 +156,12 @@ export function App({ appVersion, initialInput }) {
123
156
  const [authBusy, setAuthBusy] = useState(false);
124
157
  const [logoutSelectionIndex, setLogoutSelectionIndex] = useState(0);
125
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]);
126
165
  function resetAuthenticatedContext() {
127
166
  setAuthUser(null);
128
167
  setAvailableWorkspaces([]);
@@ -199,6 +238,20 @@ export function App({ appVersion, initialInput }) {
199
238
  void handleWorkspaceSelection();
200
239
  }
201
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]);
202
255
  useEffect(() => {
203
256
  if (!authService) {
204
257
  return;
@@ -362,30 +415,34 @@ export function App({ appVersion, initialInput }) {
362
415
  if (screen === 'confirm-logout') {
363
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." }) }));
364
417
  }
365
- return (_jsx(AppShell, { appVersion: appVersion, title: getWelcomeTitle(authUser), subtitle: `Workspace: ${getActiveWorkspaceLabel(activeWorkspace)}\n\nUse a barra para abrir os comandos.`, notification: notification, notificationTone: notificationTone, children: _jsx(AppMenu, { initialInput: menuKey === 0 ? initialInput : '', items: MENU_ITEMS, onInputChange: () => {
366
- if (notification) {
367
- setNotification(null);
368
- }
369
- }, onSelect: (item) => {
370
- if (item.id === 'logout') {
371
- setNotification(null);
372
- setNotificationTone('info');
373
- setLogoutSelectionIndex(0);
374
- setScreen('confirm-logout');
375
- return;
376
- }
377
- if (item.id === 'profile') {
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
+ }
378
444
  setNotification(null);
379
445
  setNotificationTone('success');
380
- setScreen('profile');
381
- return;
382
- }
383
- if (item.id === 'exit') {
384
- exit();
385
- return;
386
- }
387
- setNotification(null);
388
- setNotificationTone('success');
389
- setScreen('discovery');
390
- } }, menuKey) }));
446
+ setScreen('discovery');
447
+ } }, menuKey)] }));
391
448
  }