primocode 8.27.0 → 8.30.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.
- package/bin/primocode.js +154 -0
- package/lib/catalogo.js +4 -0
- package/lib/conta.js +197 -0
- package/lib/icone.js +247 -0
- package/lib/instalar-app.js +231 -0
- package/lib/remotion.js +463 -0
- package/lib/tools.js +30 -0
- package/package.json +2 -2
- package/studio/ferramentas/corte.md +31 -0
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* instalar-app.js — o PrimoCode como aplicativo do sistema.
|
|
3
|
+
*
|
|
4
|
+
* "Ele está abrindo no navegador, pode ser — só que eu gostaria que fosse um
|
|
5
|
+
* aplicativo mesmo desktop, tenha um ícone, e o ícone tem que ser o mascote
|
|
6
|
+
* do PrimoCode."
|
|
7
|
+
*
|
|
8
|
+
* ── O QUE ESTE ARQUIVO ENTREGA, E O QUE ELE NÃO ENTREGA ──────────────────
|
|
9
|
+
* Ele entrega o que faz uma coisa SER um aplicativo para quem usa: entrada no
|
|
10
|
+
* Launchpad e no Dock do Mac, no menu Iniciar e na barra de tarefas do Windows,
|
|
11
|
+
* no menu de aplicativos do Linux. Ícone próprio, nome próprio, abre com dois
|
|
12
|
+
* cliques, aparece no Spotlight e no Alt-Tab com a cara dele.
|
|
13
|
+
*
|
|
14
|
+
* O que ele NÃO faz é trocar o motor da janela. Dentro, continua sendo o
|
|
15
|
+
* Chromium da máquina em modo `--app`, sem barra de endereço e sem abas — é o
|
|
16
|
+
* mesmo desenho de sempre. Trocar isso por uma janela nativa exigiria Electron
|
|
17
|
+
* ou Tauri: um download de centenas de MB, uma cadeia de compilação por
|
|
18
|
+
* sistema e assinatura de código para o macOS e o Windows não recusarem o
|
|
19
|
+
* binário. Isso derruba a promessa de "já instala junto com o npm", que foi
|
|
20
|
+
* pedida na mesma frase.
|
|
21
|
+
*
|
|
22
|
+
* Está escrito aqui porque é a diferença que importa se um dia alguém quiser
|
|
23
|
+
* mudar: o obstáculo não é técnico, é o tamanho da instalação.
|
|
24
|
+
*
|
|
25
|
+
* ── POR QUE NÃO NO `postinstall` ─────────────────────────────────────────
|
|
26
|
+
* Um `npm i -g` que cria pasta em ~/Applications e escreve no menu Iniciar sem
|
|
27
|
+
* ninguém pedir é um instalador se comportando mal. Quem quiser o aplicativo
|
|
28
|
+
* digita `/app --instalar` uma vez, e o `/app` sozinho continua abrindo a
|
|
29
|
+
* janela sem instalar nada.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
'use strict';
|
|
33
|
+
|
|
34
|
+
const fs = require('fs');
|
|
35
|
+
const os = require('os');
|
|
36
|
+
const path = require('path');
|
|
37
|
+
const { execFile, execFileSync } = require('child_process');
|
|
38
|
+
|
|
39
|
+
const icone = require('./icone.js');
|
|
40
|
+
|
|
41
|
+
const NOME = 'PrimoCode';
|
|
42
|
+
const CASA = path.join(os.homedir(), '.primocode');
|
|
43
|
+
|
|
44
|
+
function escrever(destino, dados) {
|
|
45
|
+
fs.mkdirSync(path.dirname(destino), { recursive: true });
|
|
46
|
+
fs.writeFileSync(destino, dados);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/* O caminho do Node e o do CLI, absolutos.
|
|
50
|
+
*
|
|
51
|
+
* Aplicativo aberto pelo Finder ou pelo menu Iniciar NÃO herda o PATH do
|
|
52
|
+
* terminal: o `.zshrc` não roda, e num Mac com Node do Homebrew ou do nvm o
|
|
53
|
+
* comando `primo` simplesmente não existe para quem clicou no ícone. O
|
|
54
|
+
* aplicativo abria e fechava na hora, sem dizer nada. Com os dois caminhos
|
|
55
|
+
* escritos no lançador, ele funciona vindo de qualquer lugar. */
|
|
56
|
+
function caminhos() {
|
|
57
|
+
return {
|
|
58
|
+
node: process.execPath,
|
|
59
|
+
cli: path.resolve(__dirname, '..', 'bin', 'primocode.js'),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ── macOS ────────────────────────────────────────────────────────────────
|
|
64
|
+
|
|
65
|
+
const PLIST = (versao) => `<?xml version="1.0" encoding="UTF-8"?>
|
|
66
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
67
|
+
<plist version="1.0">
|
|
68
|
+
<dict>
|
|
69
|
+
<key>CFBundleName</key><string>${NOME}</string>
|
|
70
|
+
<key>CFBundleDisplayName</key><string>${NOME}</string>
|
|
71
|
+
<key>CFBundleIdentifier</key><string>ai.conectaprimo.primocode</string>
|
|
72
|
+
<key>CFBundleVersion</key><string>${versao}</string>
|
|
73
|
+
<key>CFBundleShortVersionString</key><string>${versao}</string>
|
|
74
|
+
<key>CFBundlePackageType</key><string>APPL</string>
|
|
75
|
+
<key>CFBundleExecutable</key><string>${NOME}</string>
|
|
76
|
+
<key>CFBundleIconFile</key><string>${NOME}</string>
|
|
77
|
+
<key>NSHighResolutionCapable</key><true/>
|
|
78
|
+
<!-- Sem isto o ícone do Dock vira um foguete genérico e o app aparece com
|
|
79
|
+
o nome do processo. "LSUIElement" NÃO entra: com ele o aplicativo some
|
|
80
|
+
do Dock, e o Dock é metade do que foi pedido aqui.
|
|
81
|
+
|
|
82
|
+
(Aspas e não acento grave neste comentário de propósito: ele mora dentro
|
|
83
|
+
de um template literal, e um acento grave ali FECHA a string — o erro
|
|
84
|
+
sai vinte linhas abaixo, apontando para o lugar errado.) -->
|
|
85
|
+
<key>LSMinimumSystemVersion</key><string>10.15</string>
|
|
86
|
+
</dict>
|
|
87
|
+
</plist>
|
|
88
|
+
`;
|
|
89
|
+
|
|
90
|
+
function instalarMac(versao) {
|
|
91
|
+
const { node, cli } = caminhos();
|
|
92
|
+
const raiz = path.join(os.homedir(), 'Applications', `${NOME}.app`);
|
|
93
|
+
const conteudo = path.join(raiz, 'Contents');
|
|
94
|
+
|
|
95
|
+
escrever(path.join(conteudo, 'Info.plist'), PLIST(versao));
|
|
96
|
+
escrever(path.join(conteudo, 'Resources', `${NOME}.icns`), icone.icns());
|
|
97
|
+
|
|
98
|
+
/* O lançador. `exec` no fim para o processo do Node HERDAR o lugar do
|
|
99
|
+
script: sem ele o shell fica de pé só esperando, e o macOS mostra dois
|
|
100
|
+
processos no monitor de atividade para um aplicativo só. */
|
|
101
|
+
const bin = path.join(conteudo, 'MacOS', NOME);
|
|
102
|
+
escrever(bin, `#!/bin/sh
|
|
103
|
+
# Gerado pelo PrimoCode (/app --instalar). Não edite: rode o comando de novo.
|
|
104
|
+
exec ${JSON.stringify(node)} ${JSON.stringify(cli)} --app "$@"
|
|
105
|
+
`);
|
|
106
|
+
fs.chmodSync(bin, 0o755);
|
|
107
|
+
|
|
108
|
+
/* O macOS guarda em cache o ícone e o nome pelo caminho do bundle. Sem
|
|
109
|
+
tocar na pasta, um app reinstalado continua mostrando o ícone velho até
|
|
110
|
+
o sistema reiniciar — o que parece, de fora, o comando não ter
|
|
111
|
+
funcionado. `touch` na pasta invalida esse cache. */
|
|
112
|
+
try { fs.utimesSync(raiz, new Date(), new Date()); } catch {}
|
|
113
|
+
|
|
114
|
+
return { ok: true, caminho: raiz, onde: 'Launchpad e pasta Aplicativos' };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ── Windows ──────────────────────────────────────────────────────────────
|
|
118
|
+
|
|
119
|
+
/* O atalho do Windows (.lnk) é um formato binário com estrutura própria. Em
|
|
120
|
+
* vez de montá-lo byte a byte, quem o escreve é o próprio Windows, pelo
|
|
121
|
+
* WScript.Shell — que existe em toda instalação desde sempre e é a API que os
|
|
122
|
+
* instaladores usam. */
|
|
123
|
+
function instalarWindows(versao) {
|
|
124
|
+
const { node, cli } = caminhos();
|
|
125
|
+
const recursos = path.join(CASA, 'app');
|
|
126
|
+
const ico = path.join(recursos, 'primocode.ico');
|
|
127
|
+
escrever(ico, icone.ico());
|
|
128
|
+
|
|
129
|
+
/* Um .cmd abriria uma janela preta de console junto do aplicativo. O .vbs
|
|
130
|
+
com `Run ..., 0` lança o Node com a janela escondida — é o que faz o
|
|
131
|
+
aplicativo abrir só a janela dele, como qualquer programa. */
|
|
132
|
+
const vbs = path.join(recursos, 'primocode.vbs');
|
|
133
|
+
escrever(vbs, `' Gerado pelo PrimoCode (/app --instalar). Não edite: rode o comando de novo.\r
|
|
134
|
+
Set sh = CreateObject("WScript.Shell")\r
|
|
135
|
+
sh.Run """${node}"" ""${cli}"" --app", 0, False\r
|
|
136
|
+
`);
|
|
137
|
+
|
|
138
|
+
const menu = path.join(process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming'),
|
|
139
|
+
'Microsoft', 'Windows', 'Start Menu', 'Programs', `${NOME}.lnk`);
|
|
140
|
+
const area = path.join(os.homedir(), 'Desktop', `${NOME}.lnk`);
|
|
141
|
+
|
|
142
|
+
const ps = [
|
|
143
|
+
'$w = New-Object -ComObject WScript.Shell',
|
|
144
|
+
...[menu, area].map((alvo) => [
|
|
145
|
+
`$s = $w.CreateShortcut(${JSON.stringify(alvo)})`,
|
|
146
|
+
'$s.TargetPath = "wscript.exe"',
|
|
147
|
+
`$s.Arguments = '"${vbs}"'`,
|
|
148
|
+
`$s.IconLocation = ${JSON.stringify(ico)}`,
|
|
149
|
+
`$s.Description = "PrimoCode ${versao}"`,
|
|
150
|
+
`$s.WorkingDirectory = ${JSON.stringify(path.dirname(cli))}`,
|
|
151
|
+
'$s.Save()',
|
|
152
|
+
].join('; ')),
|
|
153
|
+
].join('; ');
|
|
154
|
+
|
|
155
|
+
try {
|
|
156
|
+
fs.mkdirSync(path.dirname(menu), { recursive: true });
|
|
157
|
+
execFileSync('powershell', ['-NoProfile', '-NonInteractive', '-Command', ps],
|
|
158
|
+
{ stdio: 'ignore', timeout: 30000 });
|
|
159
|
+
} catch (e) {
|
|
160
|
+
return { ok: false, error: `não consegui criar o atalho: ${e.message}` };
|
|
161
|
+
}
|
|
162
|
+
return { ok: true, caminho: menu, onde: 'menu Iniciar e Área de Trabalho' };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// ── Linux ────────────────────────────────────────────────────────────────
|
|
166
|
+
|
|
167
|
+
function instalarLinux(versao) {
|
|
168
|
+
const { node, cli } = caminhos();
|
|
169
|
+
const icones = path.join(os.homedir(), '.local', 'share', 'icons', 'hicolor');
|
|
170
|
+
for (const t of [16, 32, 48, 64, 128, 256, 512]) {
|
|
171
|
+
escrever(path.join(icones, `${t}x${t}`, 'apps', 'primocode.png'), icone.png(t));
|
|
172
|
+
}
|
|
173
|
+
const arquivo = path.join(os.homedir(), '.local', 'share', 'applications', 'primocode.desktop');
|
|
174
|
+
escrever(arquivo, `[Desktop Entry]
|
|
175
|
+
Type=Application
|
|
176
|
+
Name=${NOME}
|
|
177
|
+
Comment=Agente de engenharia em português
|
|
178
|
+
Exec=${node} ${cli} --app
|
|
179
|
+
Icon=primocode
|
|
180
|
+
Terminal=false
|
|
181
|
+
Categories=Development;Utility;
|
|
182
|
+
StartupWMClass=primocode
|
|
183
|
+
X-PrimoCode-Version=${versao}
|
|
184
|
+
`);
|
|
185
|
+
fs.chmodSync(arquivo, 0o755);
|
|
186
|
+
// Sem atualizar o banco, o menu só mostra o aplicativo na sessão seguinte.
|
|
187
|
+
try {
|
|
188
|
+
execFileSync('update-desktop-database', [path.dirname(arquivo)],
|
|
189
|
+
{ stdio: 'ignore', timeout: 15000 });
|
|
190
|
+
} catch { /* nem toda distribuição tem, e a entrada funciona sem */ }
|
|
191
|
+
return { ok: true, caminho: arquivo, onde: 'menu de aplicativos' };
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// ── porta única ──────────────────────────────────────────────────────────
|
|
195
|
+
|
|
196
|
+
function instalar(versao) {
|
|
197
|
+
try {
|
|
198
|
+
const p = os.platform();
|
|
199
|
+
if (p === 'darwin') return instalarMac(versao);
|
|
200
|
+
if (p === 'win32') return instalarWindows(versao);
|
|
201
|
+
return instalarLinux(versao);
|
|
202
|
+
} catch (e) {
|
|
203
|
+
return { ok: false, error: e.message };
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** Já instalado? Serve para o /app oferecer a instalação uma vez só. */
|
|
208
|
+
function instalado() {
|
|
209
|
+
const p = os.platform();
|
|
210
|
+
const alvo = p === 'darwin'
|
|
211
|
+
? path.join(os.homedir(), 'Applications', `${NOME}.app`)
|
|
212
|
+
: p === 'win32'
|
|
213
|
+
? path.join(process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming'),
|
|
214
|
+
'Microsoft', 'Windows', 'Start Menu', 'Programs', `${NOME}.lnk`)
|
|
215
|
+
: path.join(os.homedir(), '.local', 'share', 'applications', 'primocode.desktop');
|
|
216
|
+
try { return fs.existsSync(alvo) ? alvo : null; } catch { return null; }
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function desinstalar() {
|
|
220
|
+
const alvo = instalado();
|
|
221
|
+
if (!alvo) return { ok: true, jaSaiu: true };
|
|
222
|
+
try {
|
|
223
|
+
fs.rmSync(alvo, { recursive: true, force: true });
|
|
224
|
+
if (os.platform() === 'win32') {
|
|
225
|
+
try { fs.rmSync(path.join(os.homedir(), 'Desktop', `${NOME}.lnk`), { force: true }); } catch {}
|
|
226
|
+
}
|
|
227
|
+
return { ok: true, caminho: alvo };
|
|
228
|
+
} catch (e) { return { ok: false, error: e.message }; }
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
module.exports = { instalar, instalado, desinstalar, NOME };
|