diff --git a/scripts/sync-installer.mjs b/scripts/sync-installer.mjs index 5174b5e..560d239 100644 --- a/scripts/sync-installer.mjs +++ b/scripts/sync-installer.mjs @@ -6,56 +6,77 @@ import { fileURLToPath } from 'node:url'; const __dirname = dirname(fileURLToPath(import.meta.url)); const ROOT_DIR = resolve(__dirname, '..'); const STATIC_DIR = resolve(ROOT_DIR, 'static'); -const TARGET_FILE = resolve(STATIC_DIR, 'install.sh'); -const LOCAL_CANONICAL_PATH = resolve(ROOT_DIR, '../wright/install.sh'); -const REMOTE_CANONICAL_URL = - process.env.WRIGHT_INSTALLER_URL || - 'https://raw.githubusercontent.com/wrightkit/wright/main/install.sh'; +const installers = [ + { + name: 'install.sh', + targetFile: resolve(STATIC_DIR, 'install.sh'), + localPath: resolve(ROOT_DIR, '../wright/install.sh'), + pathEnv: 'WRIGHT_INSTALLER_PATH', + urlEnv: 'WRIGHT_INSTALLER_URL', + remoteUrl: 'https://raw.githubusercontent.com/wrightkit/wright/main/install.sh', + validate(content) { + if (!content.startsWith('#!/usr/bin/env bash') && !content.startsWith('#!/bin/bash')) { + throw new Error('install.sh must start with a bash shebang.'); + } + } + }, + { + name: 'install.ps1', + targetFile: resolve(STATIC_DIR, 'install.ps1'), + localPath: resolve(ROOT_DIR, '../wright/install.ps1'), + pathEnv: 'WRIGHT_WINDOWS_INSTALLER_PATH', + urlEnv: 'WRIGHT_WINDOWS_INSTALLER_URL', + remoteUrl: 'https://raw.githubusercontent.com/wrightkit/wright/main/install.ps1', + validate(content) { + if (!content.includes('[CmdletBinding()]') || !content.includes('x86_64-pc-windows-msvc')) { + throw new Error('install.ps1 is missing the expected PowerShell installer contract.'); + } + } + } +]; -async function getInstallerContent() { - const envPath = process.env.WRIGHT_INSTALLER_PATH; +async function getInstallerContent(installer) { + const envPath = process.env[installer.pathEnv]; if (envPath && existsSync(envPath)) { return readFileSync(envPath, 'utf8'); } - if (existsSync(LOCAL_CANONICAL_PATH)) { - return readFileSync(LOCAL_CANONICAL_PATH, 'utf8'); + if (existsSync(installer.localPath)) { + return readFileSync(installer.localPath, 'utf8'); } - const response = await fetch(REMOTE_CANONICAL_URL); + const remoteUrl = process.env[installer.urlEnv] || installer.remoteUrl; + const response = await fetch(remoteUrl); if (!response.ok) { - throw new Error( - `Failed to fetch installer from ${REMOTE_CANONICAL_URL}: ${response.status} ${response.statusText}` - ); + throw new Error(`Failed to fetch ${installer.name} from ${remoteUrl}: ${response.status} ${response.statusText}`); } return await response.text(); } -function validateInstaller(content) { +function validateInstaller(installer, content) { if (!content || typeof content !== 'string') { - throw new Error('Installer content is empty or invalid.'); - } - if (!content.startsWith('#!/usr/bin/env bash') && !content.startsWith('#!/bin/bash')) { - throw new Error('Installer must start with bash shebang.'); + throw new Error(`${installer.name} content is empty or invalid.`); } + installer.validate(content); if (!content.includes('wright') || !content.includes('wright-lsp')) { - throw new Error('Installer content missing expected binary definitions (wright / wright-lsp).'); + throw new Error(`${installer.name} is missing expected binary definitions (wright / wright-lsp).`); } if (content.length < 500) { - throw new Error(`Installer content is suspiciously short (${content.length} bytes).`); + throw new Error(`${installer.name} is suspiciously short (${content.length} bytes).`); } } async function main() { - const content = await getInstallerContent(); - validateInstaller(content); - if (!existsSync(STATIC_DIR)) { mkdirSync(STATIC_DIR, { recursive: true }); } - writeFileSync(TARGET_FILE, content, 'utf8'); + for (const installer of installers) { + const content = await getInstallerContent(installer); + validateInstaller(installer, content); + writeFileSync(installer.targetFile, content, 'utf8'); + } } main().catch((err) => { diff --git a/scripts/verify-build.mjs b/scripts/verify-build.mjs index ca2d27b..d9e503b 100644 --- a/scripts/verify-build.mjs +++ b/scripts/verify-build.mjs @@ -5,19 +5,39 @@ import { fileURLToPath } from 'node:url'; const __dirname = dirname(fileURLToPath(import.meta.url)); const ROOT_DIR = resolve(__dirname, '..'); -const BUILD_INSTALLER = resolve(ROOT_DIR, 'build/install.sh'); -if (!existsSync(BUILD_INSTALLER)) { - console.error('[verify-build] Error: build/install.sh does not exist.'); - process.exit(1); -} +const installers = [ + { + name: 'install.sh', + path: resolve(ROOT_DIR, 'build/install.sh'), + validate(content) { + return content.startsWith('#!/usr/bin/env bash') || content.startsWith('#!/bin/bash'); + }, + error: 'build/install.sh has an invalid shebang.' + }, + { + name: 'install.ps1', + path: resolve(ROOT_DIR, 'build/install.ps1'), + validate(content) { + return content.includes('[CmdletBinding()]') && content.includes('x86_64-pc-windows-msvc'); + }, + error: 'build/install.ps1 is missing the expected PowerShell installer contract.' + } +]; -const content = readFileSync(BUILD_INSTALLER, 'utf8'); -if (!content.startsWith('#!/usr/bin/env bash') && !content.startsWith('#!/bin/bash')) { - console.error('[verify-build] Error: build/install.sh has invalid shebang.'); - process.exit(1); -} -if (!content.includes('wright') || !content.includes('wright-lsp')) { - console.error('[verify-build] Error: build/install.sh is missing wright binary references.'); - process.exit(1); +for (const installer of installers) { + if (!existsSync(installer.path)) { + console.error(`[verify-build] Error: build/${installer.name} does not exist.`); + process.exit(1); + } + + const content = readFileSync(installer.path, 'utf8'); + if (!installer.validate(content)) { + console.error(`[verify-build] Error: ${installer.error}`); + process.exit(1); + } + if (!content.includes('wright') || !content.includes('wright-lsp')) { + console.error(`[verify-build] Error: build/${installer.name} is missing wright binary references.`); + process.exit(1); + } } diff --git a/src/lib/components/InstallSection.svelte b/src/lib/components/InstallSection.svelte index 47db8b4..2a81dad 100644 --- a/src/lib/components/InstallSection.svelte +++ b/src/lib/components/InstallSection.svelte @@ -1,11 +1,26 @@
diff --git a/src/lib/site.ts b/src/lib/site.ts index e578f26..aef00a2 100644 --- a/src/lib/site.ts +++ b/src/lib/site.ts @@ -135,11 +135,11 @@ export const install = { id: 'windows', label: 'Windows', badge: 'x86_64', - method: 'GitHub Release archive', - command: 'https://github.com/wrightkit/wright/releases', - altMethod: 'See release documentation', - altCommand: 'docs/release.md', - note: 'Use the currently published Windows distribution path from the release documentation.' + method: 'PowerShell installer', + command: 'irm https://wrightkit.dev/install.ps1 | iex', + altMethod: 'Custom install directory', + altCommand: '$script = irm https://wrightkit.dev/install.ps1; & ([scriptblock]::Create($script)) -InstallDir "$HOME\\bin"', + note: 'Downloads the matching Windows release and verifies its checksum.' }, { id: 'ci',