project-foundry 1.0.2 → 1.0.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "project-foundry",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "Project Foundry CLI & Local Node Daemon Manager",
5
5
  "bin": {
6
6
  "project-foundry": "bin/foundry.js",
@@ -2,6 +2,7 @@ import { execSync, spawn } from 'node:child_process';
2
2
  import fs from 'node:fs';
3
3
  import path from 'node:path';
4
4
  import os from 'node:os';
5
+ import { getConfigDir } from './config.js';
5
6
 
6
7
  export async function isServerHealthy(port = 3010) {
7
8
  try {
@@ -15,13 +16,71 @@ export async function isServerHealthy(port = 3010) {
15
16
  }
16
17
  }
17
18
 
19
+ export function stopRunningDaemon() {
20
+ const isWin = process.platform === 'win32';
21
+ if (isWin) {
22
+ try {
23
+ execSync('schtasks /end /tn "ProjectFoundry"', { stdio: 'ignore' });
24
+ } catch {}
25
+ } else {
26
+ try {
27
+ execSync('systemctl stop foundry.service', { stdio: 'ignore' });
28
+ } catch {}
29
+ try {
30
+ execSync('systemctl --user stop foundry.service', { stdio: 'ignore' });
31
+ } catch {}
32
+ try {
33
+ execSync("pkill -f 'foundry.*daemon' || true", { stdio: 'ignore' });
34
+ } catch {}
35
+ }
36
+ }
37
+
38
+ export function restartAutostartService() {
39
+ const isWin = process.platform === 'win32';
40
+ if (isWin) {
41
+ try {
42
+ execSync('schtasks /run /tn "ProjectFoundry"', { stdio: 'ignore' });
43
+ } catch {}
44
+ } else {
45
+ try {
46
+ execSync('systemctl restart foundry.service', { stdio: 'ignore' });
47
+ } catch {
48
+ try {
49
+ execSync('systemctl --user restart foundry.service', { stdio: 'ignore' });
50
+ } catch {}
51
+ }
52
+ }
53
+ }
54
+
55
+ export function ensurePermanentAppPath(entryPath) {
56
+ const rootDir = path.resolve(path.dirname(entryPath), '..');
57
+
58
+ // Se estiver rodando de cache temporário do npx ou temp
59
+ if (entryPath.includes('_npx') || entryPath.includes('npm-cache') || entryPath.includes('Temp') || entryPath.includes('tmp')) {
60
+ const permanentAppDir = path.join(getConfigDir(), 'app');
61
+ if (!fs.existsSync(permanentAppDir)) {
62
+ fs.mkdirSync(permanentAppDir, { recursive: true });
63
+ }
64
+ try {
65
+ // Copia arquivos do pacote para local permanente
66
+ fs.cpSync(rootDir, permanentAppDir, { recursive: true });
67
+ const permanentEntry = path.join(permanentAppDir, 'bin', 'foundry.js');
68
+ if (fs.existsSync(permanentEntry)) {
69
+ return permanentEntry;
70
+ }
71
+ } catch {}
72
+ }
73
+ return entryPath;
74
+ }
75
+
18
76
  export function installAutostartService(serverEntryPath) {
19
77
  const isWin = process.platform === 'win32';
78
+ const permanentPath = ensurePermanentAppPath(serverEntryPath);
20
79
 
21
80
  if (isWin) {
22
81
  try {
23
82
  // Cria tarefa no Agendador do Windows com início automático em modo daemon
24
- const cmd = `schtasks /create /tn "ProjectFoundry" /tr "node \\"${serverEntryPath}\\" daemon" /sc onstart /f`;
83
+ const cmd = `schtasks /create /tn "ProjectFoundry" /tr "node \\"${permanentPath}\\" daemon" /sc onstart /f`;
25
84
  execSync(cmd, { stdio: 'pipe' });
26
85
  return { success: true, type: 'windows-task', name: 'ProjectFoundry' };
27
86
  } catch (err) {
@@ -32,13 +91,14 @@ export function installAutostartService(serverEntryPath) {
32
91
  // Linux (systemd)
33
92
  try {
34
93
  const isRoot = typeof process.getuid === 'function' && process.getuid() === 0;
94
+ const nodeBin = process.execPath || '/usr/bin/node';
35
95
  const serviceContent = `[Unit]
36
- Description=Project Foundry Local Server
96
+ Description=Project Foundry Local Farm Server
37
97
  After=network.target
38
98
 
39
99
  [Service]
40
100
  Type=simple
41
- ExecStart=/usr/bin/node ${serverEntryPath} daemon
101
+ ExecStart=${nodeBin} ${permanentPath} daemon
42
102
  Restart=always
43
103
  RestartSec=5
44
104
  Environment=NODE_ENV=production
@@ -49,13 +109,13 @@ WantedBy=default.target
49
109
 
50
110
  if (isRoot) {
51
111
  fs.writeFileSync('/etc/systemd/system/foundry.service', serviceContent, 'utf8');
52
- execSync('systemctl daemon-reload && systemctl enable foundry.service', { stdio: 'pipe' });
112
+ execSync('systemctl daemon-reload && systemctl enable foundry.service && systemctl restart foundry.service', { stdio: 'pipe' });
53
113
  return { success: true, type: 'systemd-system', name: 'foundry.service' };
54
114
  } else {
55
115
  const userSystemdDir = path.join(os.homedir(), '.config', 'systemd', 'user');
56
116
  fs.mkdirSync(userSystemdDir, { recursive: true });
57
117
  fs.writeFileSync(path.join(userSystemdDir, 'foundry.service'), serviceContent, 'utf8');
58
- execSync('systemctl --user daemon-reload && systemctl --user enable foundry.service', { stdio: 'pipe' });
118
+ execSync('systemctl --user daemon-reload && systemctl --user enable foundry.service && systemctl --user restart foundry.service', { stdio: 'pipe' });
59
119
  return { success: true, type: 'systemd-user', name: 'foundry.service' };
60
120
  }
61
121
  } catch (err) {
@@ -64,8 +124,8 @@ WantedBy=default.target
64
124
  }
65
125
 
66
126
  export function startDaemonBackground(serverEntryPath) {
67
- // Inicia desacoplado do terminal para não travar
68
- const child = spawn(process.execPath, [serverEntryPath, 'daemon'], {
127
+ const permanentPath = ensurePermanentAppPath(serverEntryPath);
128
+ const child = spawn(process.execPath, [permanentPath, 'daemon'], {
69
129
  detached: true,
70
130
  stdio: 'ignore',
71
131
  });
package/src/tui.js CHANGED
@@ -1,12 +1,27 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { execSync } from 'node:child_process';
1
5
  import * as p from '@clack/prompts';
2
6
  import pc from 'picocolors';
3
7
  import open from 'open';
4
8
  import { readConfig, updateStoragePath } from './config.js';
5
- import { isServerHealthy } from './service-manager.js';
9
+ import { isServerHealthy, stopRunningDaemon, restartAutostartService } from './service-manager.js';
10
+
11
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
12
+
13
+ function getPackageVersion() {
14
+ try {
15
+ const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8'));
16
+ return `v${pkg.version}`;
17
+ } catch {
18
+ return 'v1.0.3';
19
+ }
20
+ }
6
21
 
7
22
  export async function runInteractiveTui() {
8
23
  const config = readConfig();
9
- const version = 'v1.0.0';
24
+ const version = getPackageVersion();
10
25
  const localPort = config?.localPort || 3010;
11
26
  const localUrl = `http://localhost:${localPort}`;
12
27
 
@@ -33,7 +48,7 @@ export async function runInteractiveTui() {
33
48
  } else {
34
49
  p.note(
35
50
  `${pc.yellow('○')} O servidor local não respondeu em ${localUrl}.\n` +
36
- `Ele pode estar iniciando ou parado.`,
51
+ `O serviço pode estar iniciando em segundo plano ou parado.`,
37
52
  'Atenção'
38
53
  );
39
54
  }
@@ -43,16 +58,16 @@ export async function runInteractiveTui() {
43
58
  message: 'Escolha uma opção:',
44
59
  options: [
45
60
  { value: 'web', label: 'Web UI (Open in Browser)', hint: 'Abre o painel no navegador' },
46
- { value: 'status', label: 'Terminal UI (Status & Health)', hint: 'Verifica conexões locais' },
61
+ { value: 'status', label: 'Terminal UI (Status & Health)', hint: 'Verifica saúde e conexões locais' },
47
62
  { value: 'storage', label: 'Storage (HD Externo)', hint: 'Visualizar ou alterar pasta de arquivos' },
48
- { value: 'update', label: `Update (current: ${version})`, hint: 'Verifica atualizações' },
49
- { value: 'hide', label: 'Hide to Tray (Background)', hint: 'Mantém servidor rodando em segundo plano' },
50
- { value: 'exit', label: 'Exit', hint: 'Fechar console' },
63
+ { value: 'update', label: `Update (current: ${version})`, hint: 'Verifica e atualiza para versão mais recente' },
64
+ { value: 'hide', label: 'Hide to Tray (Background)', hint: 'Fecha o console e mantém o servidor rodando' },
65
+ { value: 'stop', label: 'Stop Server', hint: 'Desliga o servidor local da farm' },
51
66
  ],
52
67
  });
53
68
 
54
69
  if (p.isCancel(choice) || choice === 'hide') {
55
- p.outro(pc.green('Servidor mantido rodando em segundo plano. Digite "foundry" para reabrir este menu.'));
70
+ p.outro(pc.green('Painel fechado. O servidor da farm continua rodando em segundo plano.'));
56
71
  process.exit(0);
57
72
  }
58
73
 
@@ -100,19 +115,56 @@ export async function runInteractiveTui() {
100
115
  }
101
116
 
102
117
  if (choice === 'update') {
103
- p.log.info('Você já está na versão mais recente instalada.');
118
+ const s = p.spinner();
119
+ s.start('Verificando versão mais recente no npm...');
120
+ try {
121
+ const res = await fetch('https://registry.npmjs.org/project-foundry/latest', {
122
+ signal: AbortSignal.timeout(6000),
123
+ });
124
+ const data = await res.json();
125
+ const latestVersion = `v${data.version}`;
126
+ s.stop();
127
+
128
+ if (latestVersion !== version) {
129
+ p.log.warn(`Nova versão disponível: ${pc.green(latestVersion)} (você está na ${version})`);
130
+ const doUpdate = await p.confirm({
131
+ message: `Deseja atualizar para a versão ${latestVersion}?`,
132
+ initialValue: true,
133
+ });
134
+
135
+ if (doUpdate && !p.isCancel(doUpdate)) {
136
+ s.start('Atualizando pacote e reiniciando serviço...');
137
+ try {
138
+ execSync('npm install -g project-foundry@latest', { stdio: 'pipe' });
139
+ restartAutostartService();
140
+ s.stop(pc.green(`Atualizado para ${latestVersion} com sucesso!`));
141
+ p.outro(pc.cyan('Reinicie o menu com "foundry" ou "npx project-foundry" para aplicar.'));
142
+ process.exit(0);
143
+ } catch (err) {
144
+ s.stop(pc.yellow(`Atualização global não permitida sem sudo. Execute no terminal: npm install -g project-foundry@latest`));
145
+ }
146
+ }
147
+ } else {
148
+ p.log.success(`Você já está na versão mais recente (${version}).`);
149
+ }
150
+ } catch (err) {
151
+ s.stop(pc.red(`Erro ao verificar: ${err.message}`));
152
+ }
104
153
  }
105
154
 
106
- if (choice === 'exit') {
155
+ if (choice === 'stop') {
107
156
  const shouldStop = await p.confirm({
108
- message: 'Deseja apenas fechar o console ou parar o serviço?',
109
- active: 'Apenas fechar console (manter servidor ligado)',
110
- inactive: 'Fechar console',
111
- initialValue: true,
157
+ message: 'Atenção: deseja realmente parar o servidor local da farm?',
158
+ active: 'Sim, parar servidor completamente',
159
+ inactive: 'Não, manter ligado',
160
+ initialValue: false,
112
161
  });
113
162
 
114
- p.outro(pc.cyan('Até logo! O servidor continua em segundo plano.'));
115
- process.exit(0);
163
+ if (shouldStop && !p.isCancel(shouldStop)) {
164
+ stopRunningDaemon();
165
+ p.outro(pc.yellow('Servidor local encerrado. Para religar, execute "foundry" no terminal.'));
166
+ process.exit(0);
167
+ }
116
168
  }
117
169
  }
118
170
  }