@nivo-lat/cli 1.0.0 → 1.1.0

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.
@@ -11,33 +11,35 @@ const fs_1 = __importDefault(require("fs"));
11
11
  const os_1 = __importDefault(require("os"));
12
12
  const path_1 = __importDefault(require("path"));
13
13
  const chalk_1 = __importDefault(require("chalk"));
14
- const ora_1 = __importDefault(require("ora"));
15
- const form_data_1 = __importDefault(require("form-data"));
16
14
  const adm_zip_1 = __importDefault(require("adm-zip"));
15
+ const form_data_1 = __importDefault(require("form-data"));
17
16
  const api_1 = require("../api");
18
17
  const config_1 = require("../config");
19
18
  const ui_1 = require("../ui");
20
19
  const TERMINAL_STATUSES = new Set(['success', 'error', 'cancelled']);
21
20
  const IGNORED_NAMES = new Set([
22
- '.git',
23
- '.nivo',
24
- '.next',
25
- 'node_modules',
26
- '.env',
27
- '.env.local',
28
- '.env.production',
29
- '.env.development',
30
- '.DS_Store',
21
+ '.git', '.nivo', '.next', 'node_modules',
22
+ '.env', '.env.local', '.env.production', '.env.development', '.DS_Store',
31
23
  ]);
24
+ const STATUS_COLOR = {
25
+ running: (s) => chalk_1.default.green(s),
26
+ success: (s) => chalk_1.default.green(s),
27
+ building: (s) => chalk_1.default.yellow(s),
28
+ pending: (s) => chalk_1.default.yellow(s),
29
+ error: (s) => chalk_1.default.red(s),
30
+ cancelled: (s) => chalk_1.default.dim(s),
31
+ stopped: (s) => chalk_1.default.dim(s),
32
+ provisioning: (s) => chalk_1.default.yellow(s),
33
+ };
34
+ function colorStatus(status) {
35
+ return (STATUS_COLOR[status] ?? chalk_1.default.dim)(status);
36
+ }
32
37
  function ensureLoggedIn() {
33
38
  if ((0, config_1.getCredentials)().token)
34
39
  return true;
35
- ui_1.logger.error('Nao autenticado. Rode `nivo login`.');
40
+ ui_1.logger.error('nao autenticado · rode `nivo login`');
36
41
  return false;
37
42
  }
38
- function readLinkedAppId() {
39
- return readNivoProjectConfig()?.appId ?? null;
40
- }
41
43
  function readNivoProjectConfig() {
42
44
  const configPath = path_1.default.join(process.cwd(), '.nivo');
43
45
  if (!fs_1.default.existsSync(configPath))
@@ -66,16 +68,12 @@ async function applyLocalProjectConfig(appId) {
66
68
  async function getAppId(explicit) {
67
69
  if (explicit)
68
70
  return explicit;
69
- const linked = readLinkedAppId();
70
- if (linked)
71
- return linked;
72
- ui_1.logger.error('Projeto nao linkado. Crie .nivo e rode `nivo link`.');
71
+ const config = readNivoProjectConfig();
72
+ if (config?.appId)
73
+ return config.appId;
74
+ ui_1.logger.error('nao ha .nivo nesta pasta · rode `nivo link`');
73
75
  return null;
74
76
  }
75
- async function fetchApps() {
76
- const res = await api_1.api.get('/apps');
77
- return res.data;
78
- }
79
77
  function addDir(zip, dir, root) {
80
78
  for (const entry of fs_1.default.readdirSync(dir, { withFileTypes: true })) {
81
79
  if (IGNORED_NAMES.has(entry.name) || entry.name.endsWith('.log'))
@@ -97,16 +95,15 @@ function createZip(sourceDir) {
97
95
  }
98
96
  const zip = new adm_zip_1.default();
99
97
  addDir(zip, resolved, resolved);
100
- const out = path_1.default.join(os_1.default.tmpdir(), `nivo-deploy-${Date.now()}.zip`);
101
- zip.writeZip(out);
102
- return out;
98
+ const zipPath = path_1.default.join(os_1.default.tmpdir(), `nivo-deploy-${Date.now()}.zip`);
99
+ zip.writeZip(zipPath);
100
+ const fileCount = zip.getEntries().length;
101
+ const sizeMb = (fs_1.default.statSync(zipPath).size / 1024 / 1024).toFixed(1);
102
+ return { zipPath, fileCount, sizeMb };
103
103
  }
104
104
  async function uploadZip(appId, zipPath) {
105
105
  const form = new form_data_1.default();
106
- form.append('file', fs_1.default.createReadStream(zipPath), {
107
- filename: 'app.zip',
108
- contentType: 'application/zip',
109
- });
106
+ form.append('file', fs_1.default.createReadStream(zipPath), { filename: 'app.zip', contentType: 'application/zip' });
110
107
  const res = await api_1.api.post(`/apps/${encodeURIComponent(appId)}/upload`, form, {
111
108
  headers: form.getHeaders(),
112
109
  maxBodyLength: Infinity,
@@ -114,88 +111,127 @@ async function uploadZip(appId, zipPath) {
114
111
  });
115
112
  return res.data;
116
113
  }
114
+ function appUrl(app) {
115
+ return app.customDomain
116
+ ? `https://${app.customDomain}`
117
+ : app.subdomain
118
+ ? `https://${app.subdomain}.app.nivo.lat`
119
+ : '';
120
+ }
117
121
  async function waitDeployment(appId, deploymentId) {
118
122
  let printed = 0;
119
123
  let lastStatus = '';
124
+ let headerShown = false;
120
125
  while (true) {
121
126
  const res = await api_1.api.get(`/apps/${encodeURIComponent(appId)}/deployments/${encodeURIComponent(deploymentId)}/logs`);
122
127
  const data = res.data;
123
128
  const log = data.log ?? '';
124
129
  if (log.length > printed) {
130
+ if (!headerShown) {
131
+ ui_1.logger.blank();
132
+ ui_1.logger.rule();
133
+ headerShown = true;
134
+ }
125
135
  process.stdout.write(log.slice(printed));
126
136
  printed = log.length;
127
137
  }
128
- if (data.status !== lastStatus) {
138
+ if (data.status !== lastStatus)
129
139
  lastStatus = data.status;
130
- ui_1.logger.info(`status ${data.status}`);
131
- }
132
140
  if (TERMINAL_STATUSES.has(data.status)) {
133
- if (data.status === 'success')
134
- ui_1.logger.success('Deploy finalizado.');
135
- else
136
- ui_1.logger.error(`Deploy terminou como ${data.status}.`);
141
+ if (headerShown)
142
+ ui_1.logger.rule();
143
+ ui_1.logger.blank();
144
+ if (data.status === 'success') {
145
+ ui_1.logger.success('deploy finalizado');
146
+ }
147
+ else {
148
+ ui_1.logger.error(`deploy terminou como ${data.status}`);
149
+ }
137
150
  return;
138
151
  }
139
152
  await new Promise((resolve) => setTimeout(resolve, 3000));
140
153
  }
141
154
  }
155
+ // ── Commands ──────────────────────────────────────────────────────────────────
142
156
  async function listApps() {
143
157
  if (!ensureLoggedIn())
144
158
  return;
145
159
  try {
146
- const apps = await fetchApps();
160
+ const apps = await (0, ui_1.runStep)('buscando apps...', () => api_1.api.get('/apps').then((r) => r.data));
147
161
  if (!apps.length) {
148
- ui_1.logger.warn('Nenhum app encontrado.');
162
+ ui_1.logger.warn('nenhum app encontrado');
149
163
  return;
150
164
  }
151
- console.log(`${chalk_1.default.dim('name'.padEnd(24))} ${chalk_1.default.dim('status'.padEnd(10))} ${chalk_1.default.dim('source'.padEnd(8))} ${chalk_1.default.dim('url')}`);
165
+ ui_1.logger.blank();
166
+ console.log(` ${chalk_1.default.dim('nome'.padEnd(22))}` +
167
+ `${chalk_1.default.dim('status'.padEnd(14))}` +
168
+ `${chalk_1.default.dim('fonte'.padEnd(10))}` +
169
+ `${chalk_1.default.dim('url')}`);
170
+ ui_1.logger.rule();
152
171
  for (const app of apps) {
153
- const url = app.customDomain ?? (app.subdomain ? `https://${app.subdomain}.app.nivo.lat` : '-');
154
- console.log(`${chalk_1.default.bold(app.name.padEnd(24))} ${chalk_1.default.cyan(app.status.padEnd(10))} ${chalk_1.default.dim(app.sourceType.padEnd(8))} ${url}`);
155
- console.log(`${chalk_1.default.dim(app.id)}`);
172
+ const url = appUrl(app);
173
+ const runtime = app.runtime ? chalk_1.default.dim(` · ${app.runtime}`) : '';
174
+ console.log(` ${chalk_1.default.bold(app.name.padEnd(22))}` +
175
+ `${colorStatus(app.status).padEnd(14 + (colorStatus(app.status).length - app.status.length))}` +
176
+ `${chalk_1.default.dim(app.sourceType.padEnd(10))}` +
177
+ `${url ? chalk_1.default.underline(url) : chalk_1.default.dim('-')}${runtime}`);
178
+ console.log(` ${chalk_1.default.dim(app.id)}`);
156
179
  }
180
+ ui_1.logger.blank();
157
181
  }
158
182
  catch (err) {
159
- ui_1.logger.error(`Falha ao listar apps: ${err.message}`);
183
+ ui_1.logger.error(`falha ao listar apps: ${err.message}`);
160
184
  }
161
185
  }
162
186
  async function deploy(sourceDir = '.', opts = {}) {
163
187
  if (!ensureLoggedIn())
164
188
  return;
189
+ ui_1.logger.blank();
190
+ let zipPath = null;
165
191
  try {
166
192
  const appId = await getAppId(opts.app);
167
193
  if (!appId)
168
194
  return;
169
- await applyLocalProjectConfig(appId);
170
- const appRes = await api_1.api.get(`/apps/${encodeURIComponent(appId)}`);
171
- const app = appRes.data;
195
+ await (0, ui_1.runStep)('aplicando configuracao local...', async () => applyLocalProjectConfig(appId), () => 'configuracao aplicada');
196
+ const app = await (0, ui_1.runStep)('buscando app...', () => api_1.api.get(`/apps/${encodeURIComponent(appId)}`).then((r) => r.data), (a) => `app encontrado · ${chalk_1.default.bold(a.name)} ${chalk_1.default.dim(`(${a.sourceType} · ${a.runtime ?? 'node20'})`)}`);
172
197
  let deploymentId;
173
198
  if (app.sourceType === 'zip') {
174
- const spinner = (0, ora_1.default)({ text: 'Compactando projeto...', color: 'green' }).start();
175
- const zipPath = createZip(sourceDir);
176
- spinner.text = 'Enviando deploy...';
177
- try {
178
- const result = await uploadZip(appId, zipPath);
179
- deploymentId = result.deploymentId;
180
- }
181
- finally {
182
- fs_1.default.rmSync(zipPath, { force: true });
183
- }
184
- spinner.succeed(`Deploy criado ${deploymentId}`);
199
+ const { zipPath: zp, fileCount, sizeMb } = await (0, ui_1.runStep)('compactando projeto...', async () => createZip(sourceDir), ({ fileCount, sizeMb }) => `compactado · ${fileCount} arquivos · ${sizeMb} MB`);
200
+ zipPath = zp;
201
+ const result = await (0, ui_1.runStep)('enviando deploy...', async () => {
202
+ try {
203
+ return await uploadZip(appId, zp);
204
+ }
205
+ finally {
206
+ fs_1.default.rmSync(zp, { force: true });
207
+ zipPath = null;
208
+ }
209
+ }, ({ deploymentId }) => `enviado · ${chalk_1.default.dim(deploymentId)}`);
210
+ deploymentId = result.deploymentId;
185
211
  }
186
212
  else {
187
- const spinner = (0, ora_1.default)({ text: 'Iniciando redeploy...', color: 'green' }).start();
188
- const res = await api_1.api.post(`/apps/${encodeURIComponent(appId)}/deploy`);
189
- deploymentId = res.data.deploymentId;
190
- spinner.succeed(`Deploy criado ${deploymentId}`);
213
+ const result = await (0, ui_1.runStep)('iniciando redeploy...', () => api_1.api.post(`/apps/${encodeURIComponent(appId)}/deploy`).then((r) => r.data), ({ deploymentId }) => `redeploy iniciado · ${chalk_1.default.dim(deploymentId)}`);
214
+ deploymentId = result.deploymentId;
191
215
  }
192
- if (opts.watch)
216
+ if (opts.watch) {
193
217
  await waitDeployment(appId, deploymentId);
194
- else
195
- ui_1.logger.info(`logs: nivo logs --deployment ${deploymentId}`);
218
+ const url = appUrl(app);
219
+ if (url)
220
+ ui_1.logger.info(url);
221
+ }
222
+ else {
223
+ ui_1.logger.blank();
224
+ ui_1.logger.info(`acompanhe os logs: nivo logs --deployment ${deploymentId}`);
225
+ }
226
+ ui_1.logger.blank();
196
227
  }
197
228
  catch (err) {
198
- ui_1.logger.error(`Deploy falhou: ${err.message}`);
229
+ ui_1.logger.blank();
230
+ ui_1.logger.error(`deploy falhou: ${err.message}`);
231
+ }
232
+ finally {
233
+ if (zipPath)
234
+ fs_1.default.rmSync(zipPath, { force: true });
199
235
  }
200
236
  }
201
237
  async function deployments(opts = {}) {
@@ -205,18 +241,31 @@ async function deployments(opts = {}) {
205
241
  const appId = await getAppId(opts.app);
206
242
  if (!appId)
207
243
  return;
208
- const res = await api_1.api.get(`/apps/${encodeURIComponent(appId)}/deployments`);
209
- const items = res.data;
244
+ const items = await (0, ui_1.runStep)('buscando deployments...', () => api_1.api.get(`/apps/${encodeURIComponent(appId)}/deployments`).then((r) => r.data));
210
245
  if (!items.length) {
211
- ui_1.logger.warn('Nenhum deployment encontrado.');
246
+ ui_1.logger.warn('nenhum deployment encontrado');
212
247
  return;
213
248
  }
249
+ ui_1.logger.blank();
250
+ console.log(` ${chalk_1.default.dim('status'.padEnd(14))}` +
251
+ `${chalk_1.default.dim('trigger'.padEnd(10))}` +
252
+ `${chalk_1.default.dim('data'.padEnd(22))}` +
253
+ `${chalk_1.default.dim('id')}`);
254
+ ui_1.logger.rule();
214
255
  for (const dep of items) {
215
- console.log(`${chalk_1.default.cyan(dep.status.padEnd(10))} ${chalk_1.default.dim(dep.trigger.padEnd(8))} ${new Date(dep.createdAt).toLocaleString()} ${chalk_1.default.dim(dep.id)}`);
256
+ const date = new Date(dep.createdAt).toLocaleString('pt-BR');
257
+ const duration = dep.finishedAt
258
+ ? chalk_1.default.dim(` · ${Math.round((new Date(dep.finishedAt).getTime() - new Date(dep.createdAt).getTime()) / 1000)}s`)
259
+ : '';
260
+ console.log(` ${colorStatus(dep.status).padEnd(14 + (colorStatus(dep.status).length - dep.status.length))}` +
261
+ `${chalk_1.default.dim(dep.trigger.padEnd(10))}` +
262
+ `${chalk_1.default.dim(date.padEnd(22))}` +
263
+ `${chalk_1.default.dim(dep.id)}${duration}`);
216
264
  }
265
+ ui_1.logger.blank();
217
266
  }
218
267
  catch (err) {
219
- ui_1.logger.error(`Falha ao listar deployments: ${err.message}`);
268
+ ui_1.logger.error(`falha ao listar deployments: ${err.message}`);
220
269
  }
221
270
  }
222
271
  async function logs(opts = {}) {
@@ -228,13 +277,31 @@ async function logs(opts = {}) {
228
277
  return;
229
278
  if (opts.deployment) {
230
279
  const res = await api_1.api.get(`/apps/${encodeURIComponent(appId)}/deployments/${encodeURIComponent(opts.deployment)}/logs`);
231
- console.log(res.data.log ?? '');
280
+ const log = res.data.log ?? '';
281
+ if (!log.trim()) {
282
+ ui_1.logger.warn('nenhum log de build encontrado');
283
+ return;
284
+ }
285
+ ui_1.logger.blank();
286
+ ui_1.logger.rule();
287
+ process.stdout.write(log.endsWith('\n') ? log : log + '\n');
288
+ ui_1.logger.rule();
289
+ ui_1.logger.blank();
232
290
  return;
233
291
  }
234
292
  const res = await api_1.api.get(`/apps/${encodeURIComponent(appId)}/logs`);
235
- console.log(res.data.log ?? '');
293
+ const log = res.data.log ?? '';
294
+ if (!log.trim()) {
295
+ ui_1.logger.warn('nenhum log de runtime encontrado');
296
+ return;
297
+ }
298
+ ui_1.logger.blank();
299
+ ui_1.logger.rule();
300
+ process.stdout.write(log.endsWith('\n') ? log : log + '\n');
301
+ ui_1.logger.rule();
302
+ ui_1.logger.blank();
236
303
  }
237
304
  catch (err) {
238
- ui_1.logger.error(`Falha ao buscar logs: ${err.message}`);
305
+ ui_1.logger.error(`falha ao buscar logs: ${err.message}`);
239
306
  }
240
307
  }
@@ -7,7 +7,6 @@ exports.login = login;
7
7
  exports.logout = logout;
8
8
  exports.me = me;
9
9
  const chalk_1 = __importDefault(require("chalk"));
10
- const ora_1 = __importDefault(require("ora"));
11
10
  const child_process_1 = require("child_process");
12
11
  const api_1 = require("../api");
13
12
  const config_1 = require("../config");
@@ -50,69 +49,64 @@ async function pollForToken(deviceCode, intervalSec, expiresInSec) {
50
49
  async function login() {
51
50
  const existing = (0, config_1.getCredentials)();
52
51
  if (existing.token) {
53
- ui_1.logger.warn('Voce ja esta logado. Rode `nivo logout` primeiro pra trocar de conta.');
52
+ ui_1.logger.warn('Voce ja esta logado. Rode `nivo logout` primeiro.');
54
53
  return;
55
54
  }
56
- ui_1.logger.step('Login');
57
55
  let start;
58
56
  try {
59
- const res = await api_1.api.post('/auth/cli/start', {});
60
- start = res.data;
57
+ start = (await api_1.api.post('/auth/cli/start', {})).data;
61
58
  }
62
59
  catch (err) {
63
- ui_1.logger.error(`Nao consegui iniciar o login: ${err.message}`);
60
+ ui_1.logger.error(`Nao foi possivel iniciar o login: ${err.message}`);
64
61
  return;
65
62
  }
66
63
  const url = `${start.verifyUrl}?user_code=${encodeURIComponent(start.userCode)}`;
67
- console.log();
68
- console.log(' ' + chalk_1.default.dim('Codigo:') + ' ' + chalk_1.default.bold.hex('#4ade80')(start.userCode));
69
- console.log();
70
- console.log(' ' + chalk_1.default.dim('URL:') + ' ' + chalk_1.default.underline(url));
71
- console.log();
64
+ ui_1.logger.blank();
65
+ console.log(` ${chalk_1.default.dim('codigo')} ${chalk_1.default.bold.hex('#4ade80')(start.userCode)}`);
66
+ console.log(` ${chalk_1.default.dim('url')} ${chalk_1.default.underline(url)}`);
67
+ ui_1.logger.blank();
72
68
  openBrowser(url);
73
- const spinner = (0, ora_1.default)({ text: 'Aguardando autorizacao...', color: 'green' }).start();
74
69
  try {
75
- const apiKey = await pollForToken(start.deviceCode, start.interval, start.expiresIn);
70
+ const apiKey = await (0, ui_1.runStep)('aguardando autorizacao no browser...', () => pollForToken(start.deviceCode, start.interval, start.expiresIn), () => 'autorizado');
76
71
  (0, config_1.saveCredentials)({ token: apiKey });
77
- spinner.succeed('Login concluido');
78
72
  try {
79
73
  const res = await api_1.api.get('/auth/me');
80
- ui_1.logger.success(`Logado como ${chalk_1.default.bold(res.data.email)}.`);
74
+ ui_1.logger.success(`logado como ${chalk_1.default.bold(res.data.email)}`);
81
75
  }
82
76
  catch {
83
- ui_1.logger.success('Token salvo em ~/.nivo-auth.');
77
+ ui_1.logger.success('credenciais salvas');
84
78
  }
85
79
  }
86
80
  catch (err) {
87
- spinner.fail(err.message);
81
+ ui_1.logger.error(err.message);
88
82
  }
89
83
  }
90
84
  async function logout() {
91
85
  (0, config_1.deleteCredentials)();
92
- ui_1.logger.success('Sessao local removida.');
93
- ui_1.logger.info('Para revogar a chave, acesse Configuracoes > CLI no dashboard.');
86
+ ui_1.logger.success('sessao local removida');
87
+ ui_1.logger.info('para revogar a chave, acesse Configuracoes > CLI no dashboard');
94
88
  }
95
89
  async function me() {
96
90
  const creds = (0, config_1.getCredentials)();
97
91
  if (!creds.token) {
98
- ui_1.logger.error('Voce nao esta logado. Rode `nivo login`.');
92
+ ui_1.logger.error('voce nao esta logado · rode `nivo login`');
99
93
  return;
100
94
  }
101
95
  try {
102
96
  const res = await api_1.api.get('/auth/me');
103
97
  const user = res.data;
104
- ui_1.logger.step('Conta');
105
- console.log(`${chalk_1.default.dim('email')} ${chalk_1.default.white(user.email)}`);
106
- console.log(`${chalk_1.default.dim('id')} ${chalk_1.default.white(user.id)}`);
107
- console.log(`${chalk_1.default.dim('tipo')} ${chalk_1.default.cyan(user.isAdmin ? 'admin' : 'user')}`);
108
- console.log();
98
+ ui_1.logger.blank();
99
+ console.log(` ${chalk_1.default.dim('email')} ${chalk_1.default.white(user.email)}`);
100
+ console.log(` ${chalk_1.default.dim('id')} ${chalk_1.default.dim(user.id)}`);
101
+ console.log(` ${chalk_1.default.dim('tipo')} ${user.isAdmin ? chalk_1.default.yellow('admin') : chalk_1.default.dim('user')}`);
102
+ ui_1.logger.blank();
109
103
  }
110
104
  catch (err) {
111
105
  if (err?.response?.status === 401) {
112
- ui_1.logger.error('Sua sessao expirou ou foi revogada. Rode `nivo login` de novo.');
106
+ ui_1.logger.error('sessao expirada ou revogada · rode `nivo login` de novo');
113
107
  }
114
108
  else {
115
- ui_1.logger.error(`Falha ao buscar usuario: ${err.message}`);
109
+ ui_1.logger.error(`falha ao buscar usuario: ${err.message}`);
116
110
  }
117
111
  }
118
112
  }
@@ -8,35 +8,28 @@ const fs_1 = __importDefault(require("fs"));
8
8
  const os_1 = __importDefault(require("os"));
9
9
  const path_1 = __importDefault(require("path"));
10
10
  const chalk_1 = __importDefault(require("chalk"));
11
- const ora_1 = __importDefault(require("ora"));
12
- const form_data_1 = __importDefault(require("form-data"));
13
11
  const adm_zip_1 = __importDefault(require("adm-zip"));
12
+ const form_data_1 = __importDefault(require("form-data"));
14
13
  const api_1 = require("../api");
15
14
  const config_1 = require("../config");
16
15
  const ui_1 = require("../ui");
17
16
  const CONFIG_FILE = '.nivo';
18
17
  const IGNORED_NAMES = new Set([
19
- '.git',
20
- '.next',
21
- 'node_modules',
22
- '.env',
23
- '.env.local',
24
- '.env.production',
25
- '.env.development',
26
- '.DS_Store',
18
+ '.git', '.next', 'node_modules',
19
+ '.env', '.env.local', '.env.production', '.env.development', '.DS_Store',
27
20
  ]);
28
- const EXAMPLE_CONFIG = `{
29
- "name": "testapp",
30
- "projectId": "PROJECT_ID",
31
- "sourceType": "zip",
32
- "type": "site",
33
- "buildSystem": "nivopack",
34
- "runtime": "node20",
35
- "installCmd": "npm install",
36
- "buildCmd": "npm run build",
37
- "startCmd": "npm start",
38
- "ramMb": 256,
39
- "subdomain": "testapp"
21
+ const EXAMPLE_CONFIG = `{
22
+ "name": "meu-app",
23
+ "projectId": "PROJECT_ID",
24
+ "sourceType": "zip",
25
+ "type": "site",
26
+ "buildSystem": "nivopack",
27
+ "runtime": "node20",
28
+ "installCmd": "npm install",
29
+ "buildCmd": "npm run build",
30
+ "startCmd": "npm start",
31
+ "ramMb": 256,
32
+ "subdomain": "meu-app"
40
33
  }`;
41
34
  function readConfig(configPath) {
42
35
  if (!fs_1.default.existsSync(configPath))
@@ -68,9 +61,8 @@ function validateConfig(config) {
68
61
  missing.push('type');
69
62
  if (config.ramMb !== undefined && (!Number.isFinite(Number(config.ramMb)) || Number(config.ramMb) < 100))
70
63
  missing.push('ramMb');
71
- if (missing.length) {
64
+ if (missing.length)
72
65
  throw new Error(`.nivo incompleto ou invalido. Corrija: ${missing.join(', ')}`);
73
- }
74
66
  }
75
67
  function cleanOptional(value) {
76
68
  const trimmed = value?.trim();
@@ -109,10 +101,7 @@ function updatePayload(config) {
109
101
  return payload;
110
102
  }
111
103
  function writeConfig(configPath, config) {
112
- fs_1.default.writeFileSync(configPath, JSON.stringify({
113
- ...config,
114
- linkedAt: new Date().toISOString(),
115
- }, null, 2));
104
+ fs_1.default.writeFileSync(configPath, JSON.stringify({ ...config, linkedAt: new Date().toISOString() }, null, 2));
116
105
  }
117
106
  function addDir(zip, dir, root) {
118
107
  for (const entry of fs_1.default.readdirSync(dir, { withFileTypes: true })) {
@@ -132,16 +121,15 @@ function createZip(sourceDir) {
132
121
  const resolved = path_1.default.resolve(sourceDir);
133
122
  const zip = new adm_zip_1.default();
134
123
  addDir(zip, resolved, resolved);
135
- const out = path_1.default.join(os_1.default.tmpdir(), `nivo-link-${Date.now()}.zip`);
136
- zip.writeZip(out);
137
- return out;
124
+ const zipPath = path_1.default.join(os_1.default.tmpdir(), `nivo-link-${Date.now()}.zip`);
125
+ zip.writeZip(zipPath);
126
+ const entries = zip.getEntries().length;
127
+ const sizeMb = (fs_1.default.statSync(zipPath).size / 1024 / 1024).toFixed(1);
128
+ return { zipPath, fileCount: entries, sizeMb };
138
129
  }
139
130
  async function uploadZipSource(appId, zipPath) {
140
131
  const form = new form_data_1.default();
141
- form.append('file', fs_1.default.createReadStream(zipPath), {
142
- filename: 'app.zip',
143
- contentType: 'application/zip',
144
- });
132
+ form.append('file', fs_1.default.createReadStream(zipPath), { filename: 'app.zip', contentType: 'application/zip' });
145
133
  await api_1.api.post(`/apps/${encodeURIComponent(appId)}/upload-source`, form, {
146
134
  headers: form.getHeaders(),
147
135
  maxBodyLength: Infinity,
@@ -150,75 +138,78 @@ async function uploadZipSource(appId, zipPath) {
150
138
  }
151
139
  async function link() {
152
140
  if (!(0, config_1.getCredentials)().token) {
153
- ui_1.logger.error('Nao autenticado. Rode `nivo login`.');
141
+ ui_1.logger.error('nao autenticado · rode `nivo login`');
154
142
  return;
155
143
  }
144
+ ui_1.logger.blank();
156
145
  const configPath = path_1.default.join(process.cwd(), CONFIG_FILE);
157
146
  let config;
158
147
  try {
159
- config = readConfig(configPath);
160
- if (!config) {
161
- ui_1.logger.error('Arquivo .nivo nao encontrado.');
162
- ui_1.logger.info('Crie .nivo na raiz do projeto e rode `nivo link` de novo.');
163
- console.log();
164
- console.log(chalk_1.default.gray(EXAMPLE_CONFIG));
148
+ const raw = readConfig(configPath);
149
+ if (!raw) {
150
+ ui_1.logger.error('.nivo nao encontrado na pasta atual');
151
+ ui_1.logger.info('crie o arquivo .nivo e rode `nivo link` de novo');
152
+ ui_1.logger.blank();
153
+ console.log(chalk_1.default.dim(EXAMPLE_CONFIG));
165
154
  return;
166
155
  }
167
- validateConfig(config);
156
+ validateConfig(raw);
157
+ config = raw;
168
158
  }
169
159
  catch (err) {
170
160
  ui_1.logger.error(err.message);
171
- console.log();
172
- console.log(chalk_1.default.gray(EXAMPLE_CONFIG));
161
+ ui_1.logger.blank();
162
+ console.log(chalk_1.default.dim(EXAMPLE_CONFIG));
173
163
  return;
174
164
  }
175
- const spinner = (0, ora_1.default)({ text: config.appId ? 'Aplicando .nivo...' : 'Criando app...', color: 'green' }).start();
165
+ ui_1.logger.success('autenticado');
166
+ ui_1.logger.success(`.nivo lido · ${chalk_1.default.bold(config.name ?? config.appId)} ${chalk_1.default.dim(`(${config.sourceType ?? 'zip'} · ${config.runtime ?? 'node20'})`)}`);
176
167
  let zipPath = null;
177
168
  try {
178
- let app;
179
- if (config.appId) {
180
- const payload = updatePayload(config);
181
- if (Object.keys(payload).length) {
182
- try {
183
- const res = await api_1.api.patch(`/apps/${encodeURIComponent(config.appId)}`, payload);
184
- app = res.data;
169
+ const app = await (0, ui_1.runStep)(config.appId ? 'aplicando configuracao...' : 'criando app...', async () => {
170
+ if (config.appId) {
171
+ const payload = updatePayload(config);
172
+ if (Object.keys(payload).length) {
173
+ try {
174
+ return (await api_1.api.patch(`/apps/${encodeURIComponent(config.appId)}`, payload)).data;
175
+ }
176
+ catch (err) {
177
+ if (err?.response?.status === 404)
178
+ throw new Error('appId do .nivo nao existe. Remova "appId" para criar um novo app.');
179
+ throw err;
180
+ }
185
181
  }
186
- catch (err) {
187
- if (err?.response?.status === 404) {
188
- throw new Error('appId do .nivo nao existe. Remova "appId" do .nivo para criar um app novo, ou coloque o appId correto.');
182
+ else {
183
+ try {
184
+ return (await api_1.api.get(`/apps/${encodeURIComponent(config.appId)}`)).data;
185
+ }
186
+ catch (err) {
187
+ if (err?.response?.status === 404)
188
+ throw new Error('appId do .nivo nao existe. Remova "appId" para criar um novo app.');
189
+ throw err;
189
190
  }
190
- throw err;
191
191
  }
192
192
  }
193
193
  else {
194
- try {
195
- const res = await api_1.api.get(`/apps/${encodeURIComponent(config.appId)}`);
196
- app = res.data;
197
- }
198
- catch (err) {
199
- if (err?.response?.status === 404) {
200
- throw new Error('appId do .nivo nao existe. Remova "appId" do .nivo para criar um app novo, ou coloque o appId correto.');
201
- }
202
- throw err;
203
- }
194
+ const res = await api_1.api.post('/apps', appPayload(config));
195
+ config.appId = res.data.id;
196
+ return res.data;
204
197
  }
205
- }
206
- else {
207
- const res = await api_1.api.post('/apps', appPayload(config));
208
- app = res.data;
209
- config.appId = app.id;
210
- }
198
+ }, (app) => `${config.appId ? 'configuracao aplicada' : 'app criado'} · ${chalk_1.default.dim(app.id)}`);
211
199
  if ((config.sourceType ?? 'zip') === 'zip') {
212
- spinner.text = 'Enviando codigo...';
213
- zipPath = createZip(process.cwd());
214
- await uploadZipSource(app.id, zipPath);
200
+ const { zipPath: zp, fileCount, sizeMb } = await (0, ui_1.runStep)('compactando projeto...', async () => createZip(process.cwd()), ({ fileCount, sizeMb }) => `compactado · ${fileCount} arquivos · ${sizeMb} MB`);
201
+ zipPath = zp;
202
+ await (0, ui_1.runStep)('enviando codigo...', async () => uploadZipSource(app.id, zp), () => 'codigo enviado');
215
203
  }
216
204
  writeConfig(configPath, config);
217
- spinner.succeed(`Link concluido ${chalk_1.default.dim(app.id)}`);
218
- ui_1.logger.info('config .nivo atualizada');
205
+ ui_1.logger.success('.nivo atualizado');
206
+ ui_1.logger.blank();
207
+ ui_1.logger.info('para fazer deploy: nivo deploy --watch');
208
+ ui_1.logger.blank();
219
209
  }
220
210
  catch (err) {
221
- spinner.fail(`Falha no link: ${err.message}`);
211
+ ui_1.logger.blank();
212
+ ui_1.logger.error(err.message);
222
213
  }
223
214
  finally {
224
215
  if (zipPath)
package/dist/ui.js CHANGED
@@ -5,16 +5,32 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.logger = void 0;
7
7
  exports.printBanner = printBanner;
8
+ exports.runStep = runStep;
8
9
  const chalk_1 = __importDefault(require("chalk"));
10
+ const ora_1 = __importDefault(require("ora"));
9
11
  function printBanner() {
10
- if (process.env.NIVO_CLI_BANNER !== '1')
11
- return;
12
- console.log(chalk_1.default.bold.hex('#22c55e')('Nivo') + chalk_1.default.dim(' CLI v1.0.0'));
12
+ console.log();
13
+ console.log(' ' + chalk_1.default.bold.hex('#22c55e')('nivo') + chalk_1.default.dim(' platform cli'));
14
+ console.log();
13
15
  }
14
16
  exports.logger = {
15
- success: (msg) => console.log(`${chalk_1.default.green('ok')} ${msg}`),
16
- error: (msg) => console.log(`${chalk_1.default.red('error')} ${msg}`),
17
- info: (msg) => console.log(`${chalk_1.default.cyan('info')} ${msg}`),
18
- warn: (msg) => console.log(`${chalk_1.default.yellow('warn')} ${msg}`),
19
- step: (msg) => console.log(`${chalk_1.default.bold(msg)}`),
17
+ success: (msg) => console.log(` ${chalk_1.default.green('')} ${msg}`),
18
+ error: (msg) => console.log(` ${chalk_1.default.red('')} ${chalk_1.default.red(msg)}`),
19
+ info: (msg) => console.log(` ${chalk_1.default.dim('·')} ${chalk_1.default.dim(msg)}`),
20
+ warn: (msg) => console.log(` ${chalk_1.default.yellow('!')} ${chalk_1.default.yellow(msg)}`),
21
+ label: (msg) => console.log(`\n ${chalk_1.default.bold(msg)}`),
22
+ blank: () => console.log(),
23
+ rule: () => console.log(' ' + chalk_1.default.dim('─'.repeat(48))),
20
24
  };
25
+ async function runStep(label, fn, done) {
26
+ const spinner = (0, ora_1.default)({ text: chalk_1.default.dim(label), color: 'green', indent: 2 }).start();
27
+ try {
28
+ const result = await fn(spinner);
29
+ spinner.succeed(done ? done(result) : label);
30
+ return result;
31
+ }
32
+ catch (err) {
33
+ spinner.fail(chalk_1.default.dim(label));
34
+ throw err;
35
+ }
36
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
- "name": "@nivo-lat/cli",
3
- "version": "1.0.0",
2
+ "name": "@nivo-lat/cli",
3
+ "version": "1.1.0",
4
4
  "description": "Nivo CLI - Deploy and manage applications",
5
5
  "main": "dist/index.js",
6
6
  "bin": {