@remarkkpt/foundry-installer 0.0.0-stage → 0.1.1

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 (3) hide show
  1. package/README.md +43 -2
  2. package/install.sh +244 -0
  3. package/package.json +7 -4
package/README.md CHANGED
@@ -1,3 +1,44 @@
1
- # Temporary Holding Version
1
+ # Instalador Foundry para macOS
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Este pacote público contém apenas o percurso de obtenção. A Foundry permanece
4
+ privada em `RemarkkPT/Foundry-V2`. É necessário um convite; um convidado pode ver
5
+ o código desse repositório. Cada pessoa utiliza o seu próprio login GitHub.
6
+
7
+ Num terminal do macOS, executa (precisa do Node/npm):
8
+
9
+ ```sh
10
+ npx @remarkkpt/foundry-installer
11
+ ```
12
+
13
+ Alternativa preparada para Macs sem Node/npm, só após publicar 0.1.1:
14
+
15
+ ```sh
16
+ installer="$(curl -fsSL https://unpkg.com/@remarkkpt/foundry-installer@0.1.1/install.sh)" && /bin/bash -c "$installer"
17
+ ```
18
+
19
+ A alternativa `curl` não exige Node/npm para arrancar. Mantém-se `@0.1.1`
20
+ para executar o script desta versão, sem mudar para futuras versões automaticamente.
21
+
22
+ O instalador oferece preparar os
23
+ requisitos com confirmação, confere o acesso e obtém os assets `foundry.tgz` e
24
+ `foundry-release.json` da release privada `v0.1.0-alpha.1`. Confere o
25
+ SHA-256, instala uma vez por Mac e abre a seleção visual de projeto no terminal.
26
+ Não clona nem compila a Foundry, nem altera a visibilidade de repositórios. O script não extrai palavras-passe nem tokens; usa o teu login no GitHub CLI para confirmar o acesso e descarregar a versão privada.
27
+
28
+ A instalação usa `~/.local/share/foundry` e `~/.local/bin/foundry`. A preparação
29
+ do PATH, quando necessária, acrescenta uma linha ao perfil da shell apenas com
30
+ confirmação; o conteúdo existente é preservado. Uma instalação adequada é
31
+ reutilizada. Falhas de descarga/instalação preservam a versão ativa anterior.
32
+ O prefixo npm anteriormente documentado em `~/.local/share/foundry/node_modules`
33
+ também é reutilizado: os seus ficheiros são conferidos contra o pacote da release
34
+ com SHA-256 validado e o comando existente é conservado. O launcher documentado anterior é reconhecido pelo conteúdo integral. Se a versão
35
+ for diferente, o instalador explica e pede confirmação para atualizar. Só depois
36
+ de verificar a nova CLI move o prefixo anterior para
37
+ `~/.local/share/foundry/backups/legacy-…`, juntamente com o launcher, e indica
38
+ o backup. Recusar preserva tudo. Se a ativação falhar antes de o novo comando ficar ativo, o instalador tenta repor o prefixo e o comando anteriores. Se o prefixo for reposto, regressa à localização original; caso contrário, permanece no backup indicado. Depois de o novo comando ficar ativo, não há restauro automático e o backup é conservado.
39
+ Com o launcher documentado, também se oferece atualização confirmada quando
40
+ os ficheiros diferem apesar de terem o mesmo número de versão. Sem esse launcher,
41
+ a mesma versão divergente é preservada e recusada para conferência manual.
42
+
43
+ Este pacote não contém credenciais nem publica código. O acesso à Foundry
44
+ continua limitado aos convidados do repositório privado.
package/install.sh ADDED
@@ -0,0 +1,244 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ fail() { printf '\n%s\n' "$*" >&2; exit 1; }
4
+ confirm() { local answer; printf '%s [s/N] ' "$1"; read -r answer || return 1; [[ "$answer" == s || "$answer" == S ]]; }
5
+ [[ "$(uname -s)" == Darwin ]] || fail 'Este instalador suporta macOS.'
6
+ [[ -t 0 && -t 1 ]] || fail 'Abra um terminal interativo para executar o instalador.'
7
+ # Keep existing working tools; never link/upgrade a personal installation silently.
8
+ export PATH="${PATH}:/opt/homebrew/bin:/usr/local/bin"
9
+ prepare_tool() {
10
+ local formula="$1"
11
+ printf 'É necessário preparar %s para obter e abrir a Foundry.\n' "$formula"
12
+ confirm "Preparar $formula com Homebrew?" || fail 'Instalação cancelada; requisito em falta.'
13
+ if ! command -v brew >/dev/null 2>&1; then
14
+ printf 'Homebrew instala ferramentas de desenvolvimento e pode pedir autorização do macOS.\n'
15
+ confirm 'Instalar Homebrew a partir do instalador oficial?' || fail 'Instalação cancelada; Homebrew em falta.'
16
+ local script
17
+ script=$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh) || fail 'Não foi possível obter o instalador Homebrew.'
18
+ /bin/bash -c "$script" || fail 'A preparação do Homebrew falhou.'
19
+ fi
20
+ command -v brew >/dev/null 2>&1 || fail 'Homebrew não ficou disponível.'
21
+ brew install "$formula" || fail "Não foi possível preparar $formula. A configuração existente foi preservada."
22
+ }
23
+ if ! command -v gh >/dev/null 2>&1; then prepare_tool gh; fi
24
+ command -v gh >/dev/null 2>&1 || fail 'GitHub CLI continua indisponível.'
25
+ if ! gh auth status --hostname github.com >/dev/null 2>&1; then
26
+ printf 'A Foundry está num repositório privado. O login é feito pelo próprio GitHub CLI.\n'
27
+ confirm 'Iniciar sessão no GitHub?' || fail 'Instalação cancelada; autenticação necessária.'
28
+ gh auth login --hostname github.com --web --git-protocol https || fail 'O login GitHub não terminou com sucesso.'
29
+ gh auth status --hostname github.com >/dev/null 2>&1 || fail 'A autenticação GitHub continua indisponível.'
30
+ fi
31
+ repository=RemarkkPT/Foundry-V2
32
+ if ! identity=$(gh repo view "$repository" --json nameWithOwner --jq .nameWithOwner 2>/dev/null) || [[ "$identity" != "$repository" ]]; then
33
+ fail 'Não foi possível confirmar acesso a RemarkkPT/Foundry-V2. Precisa de um convite para este repositório privado. Um convidado passa a poder ver o código. Confirme também a ligação ao GitHub e volte a tentar.'
34
+ fi
35
+ # Access is checked before any private release download or installation.
36
+ node_ok() { command -v node >/dev/null 2>&1 && node -e 'process.exit(process.versions.node.split(".")[0] === "24" ? 0 : 1)' >/dev/null 2>&1 && command -v npm >/dev/null 2>&1 && npm --version >/dev/null 2>&1; }
37
+ if ! node_ok; then
38
+ for node_bin in /opt/homebrew/opt/node@24/bin /usr/local/opt/node@24/bin; do
39
+ if [[ -x "$node_bin/node" && -x "$node_bin/npm" ]]; then export PATH="$node_bin:$PATH"; break; fi
40
+ done
41
+ fi
42
+ if ! node_ok; then
43
+ prepare_tool node@24
44
+ node_bin="$(brew --prefix node@24)/bin"
45
+ export PATH="$node_bin:$PATH"
46
+ fi
47
+ node_ok || fail 'Node 24 e npm não ficaram disponíveis; a Foundry ainda não pode abrir.'
48
+ if ! command -v git >/dev/null 2>&1 || ! git --version >/dev/null 2>&1; then prepare_tool git; fi
49
+ git --version >/dev/null 2>&1 || fail 'Git continua indisponível.'
50
+ node_executable=$(node -p 'process.execPath')
51
+ version=0.1.0-alpha.1
52
+ tag="v$version"
53
+ base="$HOME/.local/share/foundry"
54
+ bin="$HOME/.local/bin"
55
+ for item in "$HOME/.local" "$HOME/.local/share" "$base" "$base/versions" "$base/backups" "$bin"; do
56
+ [[ ! -L "$item" ]] || fail "Destino simbólico inesperado: $item. Nenhuma instalação substituída."
57
+ done
58
+ mkdir -p "$base/versions" "$bin"
59
+ launcher="$bin/foundry"
60
+ legacy_launcher=false
61
+ if [[ -f "$launcher" && ! -L "$launcher" ]]; then
62
+ # Recognize the complete documented 17/10 launcher, scoped to this HOME.
63
+ # A comment or a substring alone must never authorize replacing a command.
64
+ if node - "$launcher" "$HOME" <<'JS'
65
+ const fs = require('node:fs');
66
+ const expected = '#!/bin/sh\n# Foundry uses Node 24 without changing the default Node for other tools.\nPATH="/opt/homebrew/opt/node@24/bin:$PATH"\nexport PATH\nexec /opt/homebrew/opt/node@24/bin/node ' + process.argv[3] + '/.local/share/foundry/node_modules/@remarkkpt/foundry/dist/cli.js "$@"\n';
67
+ process.exit(fs.readFileSync(process.argv[2], 'utf8') === expected ? 0 : 1);
68
+ JS
69
+ then legacy_launcher=true; fi
70
+ fi
71
+ if [[ -e "$launcher" || -L "$launcher" ]]; then
72
+ if [[ "$legacy_launcher" != true ]]; then
73
+ [[ -f "$launcher" && ! -L "$launcher" ]] && grep -qx '# Foundry bootstrap launcher v1' "$launcher" || fail 'Já existe outro comando em ~/.local/bin/foundry; foi preservado. Confira esse comando e guarde-o noutro local antes de repetir o instalador.'
74
+ fi
75
+ fi
76
+ work=$(mktemp -d "$base/versions/.prepare-XXXXXXXX")
77
+ printf 'A conferir a versão privada %s…\n' "$tag"
78
+ gh release download "$tag" --repo "$repository" --pattern foundry-release.json --dir "$work" || fail 'Não foi possível obter a versão preparada. A primeira release pode ainda aguardar publicação autorizada.'
79
+ metadata=$(node - "$work/foundry-release.json" "$tag" "$version" <<'JS'
80
+ const fs = require('node:fs');
81
+ const m = JSON.parse(fs.readFileSync(process.argv[2], 'utf8'));
82
+ if (m.schema_version !== 1 || m.repository !== 'RemarkkPT/Foundry-V2' || m.tag !== process.argv[3] || m.version !== process.argv[4] || m.asset !== 'foundry.tgz' || !/^[a-f0-9]{40}$/.test(m.commit) || !/^[a-f0-9]{64}$/.test(m.sha256)) throw Error('Identidade da release inválida');
83
+ console.log(m.commit + ' ' + m.sha256);
84
+ JS
85
+ ) || fail 'O manifesto da versão não tem identidade válida; instalação recusada.'
86
+ read -r commit digest <<< "$metadata"
87
+ download_package() {
88
+ if [[ ! -f "$work/foundry.tgz" ]]; then
89
+ gh release download "$tag" --repo "$repository" --pattern foundry.tgz --dir "$work" || fail 'A descarga falhou; a instalação anterior foi preservada.'
90
+ fi
91
+ local actual
92
+ actual=$(/usr/bin/shasum -a 256 "$work/foundry.tgz"); actual=${actual%% *}
93
+ [[ "$actual" == "$digest" ]] || fail 'SHA-256 divergente; instalação recusada. A versão anterior foi preservada.'
94
+ }
95
+ # The previous documented npm prefix has no bootstrap receipt. Verify its
96
+ # installed files against the identified artifact rather than trusting version.
97
+ legacy="$base/node_modules/@remarkkpt/foundry"
98
+ reuse_legacy=false
99
+ update_legacy=false
100
+ legacy_backup=
101
+ legacy_moved=false
102
+ restore_legacy() {
103
+ local status=$?
104
+ if [[ "$status" != 0 && "$legacy_moved" == true ]]; then
105
+ if mv "$legacy_backup/node_modules" "$base/node_modules"; then
106
+ if [[ -f "$legacy_backup/foundry" ]]; then cp -p "$legacy_backup/foundry" "$launcher"; fi
107
+ printf 'A instalação anterior foi reposta; backup: %s\n' "$legacy_backup" >&2
108
+ else printf 'A instalação anterior está no backup: %s. Reponha node_modules antes de repetir.\n' "$legacy_backup" >&2; fi
109
+ fi
110
+ }
111
+ trap restore_legacy EXIT
112
+ if [[ -e "$legacy" || -L "$legacy" ]]; then
113
+ download_package
114
+ /usr/bin/tar -xzf "$work/foundry.tgz" -C "$work" || fail 'Não foi possível conferir o pacote; a instalação anterior foi preservada.'
115
+ legacy_version=$(node - "$legacy" <<'JS'
116
+ const fs = require('node:fs'), path = require('node:path');
117
+ for (let dir = process.argv[2]; dir !== path.dirname(dir); dir = path.dirname(dir)) {
118
+ if (fs.lstatSync(dir).isSymbolicLink()) throw Error('Symbolic package directory');
119
+ }
120
+ const pkg = JSON.parse(fs.readFileSync(path.join(process.argv[2], 'package.json')));
121
+ if (pkg.name !== '@remarkkpt/foundry' || !/^[0-9]+\.[0-9]+\.[0-9]+(?:-[a-zA-Z0-9.-]+)?$/.test(pkg.version)) throw Error('Invalid previous package');
122
+ console.log(pkg.version);
123
+ JS
124
+ ) || fail 'Não foi possível identificar a instalação anterior; foi preservada. Confira o pacote antes de repetir.'
125
+ if [[ "$legacy_version" != "$version" ]]; then
126
+ printf 'A Foundry anterior é a versão %s; a versão preparada é %s. A atualização guarda a anterior num backup identificado.\n' "$legacy_version" "$version"
127
+ confirm "Atualizar Foundry para $version, guardando a instalação anterior?" || fail 'Atualização cancelada; instalação e comando anteriores preservados.'
128
+ update_legacy=true
129
+ else
130
+ if ! node - "$work/package" "$legacy" "$version" "$base/node_modules/.bin/foundry" <<'JS'
131
+ const fs = require('node:fs'), path = require('node:path');
132
+ const [expected, installed, version, executable] = process.argv.slice(2);
133
+ // Never follow a symlink through a package directory when verifying identity.
134
+ for (let dir = installed; dir !== path.dirname(dir); dir = path.dirname(dir)) {
135
+ if (fs.lstatSync(dir).isSymbolicLink()) throw Error('Symbolic package directory');
136
+ }
137
+ const pkg = JSON.parse(fs.readFileSync(path.join(installed, 'package.json')));
138
+ if (pkg.name !== '@remarkkpt/foundry' || pkg.version !== version) throw Error('Package identity differs');
139
+ function compare(relative = '') {
140
+ const source = path.join(expected, relative), target = path.join(installed, relative);
141
+ const a = fs.lstatSync(source), b = fs.lstatSync(target);
142
+ if (a.isDirectory() && b.isDirectory()) {
143
+ for (const entry of fs.readdirSync(source)) compare(path.join(relative, entry));
144
+ } else if (!a.isFile() || !b.isFile() || !fs.readFileSync(source).equals(fs.readFileSync(target))) {
145
+ throw Error('Installed file differs: ' + relative);
146
+ }
147
+ }
148
+ compare();
149
+ if (fs.realpathSync(executable) !== path.join(installed, 'dist/cli.js')) throw Error('Command points elsewhere');
150
+ fs.accessSync(executable, fs.constants.X_OK);
151
+ JS
152
+ then
153
+ if [[ "$legacy_launcher" == true ]]; then
154
+ printf 'A instalação anterior tem a mesma versão, mas difere da versão preparada. A atualização guarda os ficheiros anteriores num backup identificado.\n'
155
+ confirm "Atualizar Foundry para $version, guardando a instalação anterior?" || fail 'Atualização cancelada; instalação e comando anteriores preservados.'
156
+ update_legacy=true
157
+ else fail 'A instalação anterior não corresponde à versão preparada; foi preservada. Confira-a antes de voltar a instalar.'; fi
158
+ else
159
+ "$node_executable" "$legacy/dist/cli.js" --help >/dev/null 2>&1 || fail 'A instalação anterior não arranca; foi preservada. Confira-a antes de voltar a instalar.'
160
+ reuse_legacy=true
161
+ printf 'A instalação adequada desta versão foi preservada no prefixo npm anterior.\n'
162
+ fi
163
+ fi
164
+ fi
165
+ if [[ "$legacy_launcher" == true && ! -d "$legacy" ]]; then fail 'O launcher anterior foi reconhecido, mas falta o pacote. Reponha a instalação anterior ou guarde o launcher antes de repetir.'; fi
166
+ target="$base/versions/$digest"
167
+ adequate=false
168
+ if [[ "$reuse_legacy" == true ]]; then
169
+ if [[ "$legacy_launcher" != true ]]; then
170
+ bin="$base/node_modules/.bin"
171
+ launcher="$bin/foundry"
172
+ fi
173
+ else
174
+ if [[ -d "$target" && ! -L "$target" && -f "$target/receipt.json" ]]; then
175
+ if node - "$target/receipt.json" "$commit" "$digest" <<'JS'
176
+ const fs = require('node:fs'); const m = JSON.parse(fs.readFileSync(process.argv[2]));
177
+ process.exit(m.commit === process.argv[3] && m.sha256 === process.argv[4] ? 0 : 1);
178
+ JS
179
+ then
180
+ if "$node_executable" "$target/node_modules/@remarkkpt/foundry/dist/cli.js" --help >/dev/null 2>&1; then adequate=true; fi
181
+ fi
182
+ fi
183
+ if [[ "$adequate" == false ]]; then
184
+ [[ ! -e "$target" && ! -L "$target" ]] || fail 'A cópia desta versão não passou a verificação; foi preservada para inspeção.'
185
+ if [[ "$update_legacy" != true ]]; then
186
+ confirm "Instalar Foundry $version (commit $commit) uma vez neste Mac?" || fail 'Instalação cancelada.'
187
+ fi
188
+ download_package
189
+ npm install --prefix "$work" --offline --ignore-scripts --no-save --no-audit --no-fund "$work/foundry.tgz" || fail 'A instalação falhou; a versão anterior foi preservada.'
190
+ node - "$work/node_modules/@remarkkpt/foundry/package.json" "$version" <<'JS'
191
+ const fs = require('node:fs'); const p = JSON.parse(fs.readFileSync(process.argv[2]));
192
+ if (p.name !== '@remarkkpt/foundry' || p.version !== process.argv[3]) throw Error('Pacote divergente');
193
+ JS
194
+ "$node_executable" "$work/node_modules/@remarkkpt/foundry/dist/cli.js" --help >/dev/null || fail 'O programa instalado não arranca; a versão anterior foi preservada.'
195
+ node - "$work/receipt.json" "$commit" "$digest" <<'JS'
196
+ require('node:fs').writeFileSync(process.argv[2], JSON.stringify({commit:process.argv[3],sha256:process.argv[4]}));
197
+ JS
198
+ mv "$work" "$target"
199
+ else
200
+ printf 'A instalação adequada desta versão foi preservada.\n'
201
+ fi
202
+ # Move the old prefix only after the new CLI passed all checks. The EXIT trap
203
+ # restores it if activation fails. Backups are never followed through a symlink.
204
+ if [[ "$update_legacy" == true ]]; then
205
+ [[ ! -L "$base/backups" ]] || fail 'Destino de backup simbólico; instalação anterior preservada.'
206
+ mkdir -p "$base/backups"
207
+ legacy_backup=$(mktemp -d "$base/backups/legacy-XXXXXXXX")
208
+ if [[ -f "$launcher" ]]; then cp -p "$launcher" "$legacy_backup/foundry"; fi
209
+ mv "$base/node_modules" "$legacy_backup/node_modules"
210
+ legacy_moved=true
211
+ printf 'Backup da instalação anterior: %s\n' "$legacy_backup"
212
+ fi
213
+ # Atomic pointer replacement leaves the previous version untouched on failure.
214
+ node - "$base" "$target" <<'JS'
215
+ const fs = require('node:fs'), path = require('node:path');
216
+ const link = path.join(process.argv[2], '.current-' + process.pid);
217
+ fs.symlinkSync(process.argv[3], link); fs.renameSync(link, path.join(process.argv[2], 'current'));
218
+ JS
219
+ {
220
+ printf '#!/bin/bash\n# Foundry bootstrap launcher v1\nexec %q %q "$@"\n' "$node_executable" "$base/current/node_modules/@remarkkpt/foundry/dist/cli.js"
221
+ } > "$bin/.foundry-$$"
222
+ chmod 755 "$bin/.foundry-$$"
223
+ mv -f "$bin/.foundry-$$" "$launcher"
224
+ legacy_moved=false
225
+ fi
226
+ case ":$PATH:" in
227
+ *":$bin:"*) ;;
228
+ *)
229
+ confirm 'Preparar o comando foundry nos próximos terminais?' || fail 'Instalação preparada; falta confirmar a preparação do comando foundry. Pode repetir este instalador.'
230
+ case "${SHELL:-/bin/zsh}" in
231
+ */zsh) profile="$HOME/.zprofile" ;;
232
+ */bash) profile="$HOME/.bash_profile" ;;
233
+ *) fail 'Shell não suportada para preparar o PATH; instalação preservada.' ;;
234
+ esac
235
+ [[ ! -L "$profile" ]] || fail 'O perfil da shell é um link simbólico; foi preservado.'
236
+ line='export PATH="$HOME/.local/bin:$PATH" # Foundry bootstrap'
237
+ if [[ "$reuse_legacy" == true && "$legacy_launcher" != true ]]; then line='export PATH="$HOME/.local/share/foundry/node_modules/.bin:$PATH" # Foundry bootstrap'; fi
238
+ if ! grep -Fqx "$line" "$profile" 2>/dev/null; then printf '\n%s\n' "$line" >> "$profile"; fi
239
+ ;;
240
+ esac
241
+ export PATH="$bin:$PATH"
242
+ printf 'A abrir o assistente Foundry e a seleção de projeto…\n'
243
+ "$launcher" || fail 'O assistente não terminou com sucesso; a instalação foi preservada.'
244
+ printf 'Foundry instalada e assistente terminado. Comando disponível: foundry (novo terminal).\n'
package/package.json CHANGED
@@ -1,6 +1,9 @@
1
1
  {
2
2
  "name": "@remarkkpt/foundry-installer",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.1",
4
+ "description": "Minimal macOS bootstrap for the private Foundry distribution",
5
+ "license": "UNLICENSED",
6
+ "bin": { "foundry-installer": "install.sh" },
7
+ "files": ["install.sh", "README.md"],
8
+ "publishConfig": { "access": "public" }
9
+ }