@open-mercato/starter 0.6.5
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/README.md +46 -0
- package/bin/om-start.mjs +78 -0
- package/package.json +27 -0
- package/platform/start.cmd +24 -0
- package/platform/start.ps1 +174 -0
- package/platform/start.sh +112 -0
- package/src/__tests__/starter.test.mjs +359 -0
- package/src/certs.mjs +266 -0
- package/src/cli.mjs +355 -0
- package/src/company.mjs +80 -0
- package/src/compose.mjs +144 -0
- package/src/constants.mjs +85 -0
- package/src/doctor.mjs +426 -0
- package/src/env-file.mjs +75 -0
- package/src/env-setup.mjs +119 -0
- package/src/infra.mjs +85 -0
- package/src/providers.mjs +189 -0
- package/src/secrets.mjs +11 -0
- package/src/spawn.mjs +89 -0
- package/src/steps.mjs +613 -0
- package/src/supervise.mjs +220 -0
- package/src/ui.mjs +122 -0
- package/src/waits.mjs +102 -0
package/README.md
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# @open-mercato/starter
|
|
2
|
+
|
|
3
|
+
The central, cross-platform way to stand up an Open Mercato dev environment. One command, idempotent, platform-aware:
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
# Anywhere (clones the repo if you are not inside one):
|
|
7
|
+
npx @open-mercato/starter
|
|
8
|
+
|
|
9
|
+
# Inside a clone:
|
|
10
|
+
yarn om # alias for node packages/starter/bin/om-start.mjs
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
No Node yet? The package ships pre-Node bootstraps that install a **portable, checksum-verified Node 24 without admin rights**, then run the CLI:
|
|
14
|
+
|
|
15
|
+
- **Windows**: double-click `packages\starter\platform\start.cmd`, or `irm https://raw.githubusercontent.com/open-mercato/open-mercato/main/packages/starter/platform/start.ps1 | iex`
|
|
16
|
+
- **macOS/Linux**: `./packages/starter/platform/start.sh`, or `curl -fsSL https://raw.githubusercontent.com/open-mercato/open-mercato/main/packages/starter/platform/start.sh | bash`
|
|
17
|
+
|
|
18
|
+
## Commands
|
|
19
|
+
|
|
20
|
+
| Command | What it does |
|
|
21
|
+
|---------|--------------|
|
|
22
|
+
| `up` (default) | Idempotent converge + start: toolchain → container-runtime gate → corporate TLS trust → `.env`/secrets → `yarn install` → infra containers → database → supervised dev runtime. Re-running is always safe; completed steps are skipped. |
|
|
23
|
+
| `up --detach` | Same, but the dev runtime keeps running in the background (pidfile-managed). |
|
|
24
|
+
| `up --mode docker` | Everything containerized (for machines where host Node workloads are not allowed). Mode is remembered. |
|
|
25
|
+
| `stop` | Stop the dev runtime and the infra containers (`--keep-infra`, `--volumes --yes`). |
|
|
26
|
+
| `status` | Processes, containers, health, URLs. |
|
|
27
|
+
| `logs [--follow]` | Tail the newest dev log. |
|
|
28
|
+
| `doctor` | Read-only audit with per-item remediation and a **"hand this to IT" sheet** for admin-gated items. |
|
|
29
|
+
| `reset` | Destructive cleanup (containers, volumes, starter state) — asks first. |
|
|
30
|
+
| `infra up\|down` | Just the infra containers. |
|
|
31
|
+
|
|
32
|
+
Useful flags: `--non-interactive`, `--skip-llm-prompt`, `--skip-db`, `--no-infra`, `--rebuild`, `--clean`, `--profile <name>`, `-- <args for yarn dev>`.
|
|
33
|
+
|
|
34
|
+
`up --clean` forces a full re-converge: it clears the completed-step markers (install, build, migrations) and rebuilds the OpenCode service image, without touching any data — the one-time database seed stays done, and `reset` remains the destructive variant. Conversely, a plain `up` now also skips the database step entirely when the database is initialized and no `Migration*.ts` file changed since the last run (new migrations are detected by fingerprint and applied automatically).
|
|
35
|
+
|
|
36
|
+
## Design rules
|
|
37
|
+
|
|
38
|
+
- **Node stdlib only, no build step.** The CLI must run from a fresh clone before any `yarn install` — that is what makes it usable behind broken proxies.
|
|
39
|
+
- **Install nothing system-level.** WSL2, Docker Desktop, and Rancher Desktop are detected and *proposed* (with exact commands and an IT handout), never installed. The starter installs only what it fully owns: portable Node (platform scripts), yarn via corepack, env files, CA bundles, repo state.
|
|
40
|
+
- **Corporate TLS interception is a first-class citizen.** `up` probes real egress hosts, captures the interception CA (wire + Windows certificate store + vendor bundles like Netskope's), and provisions one PEM bundle everywhere: `NODE_EXTRA_CA_CERTS` + `--use-system-ca` for host tooling, `docker/certs/` + `docker/opencode/certs/` for image builds and container egress, a Rancher Desktop provisioning script for engine pulls. Prefer handing the starter your official bundle via `starters/company/config.mjs`.
|
|
41
|
+
- **Probes use `127.0.0.1`, never `localhost`** (Windows resolves `localhost` to `::1` first).
|
|
42
|
+
- **The clone's vendored starter wins.** `npx` defers to `packages/starter` inside the repo it operates on, so the starter version always matches the compose files it drives.
|
|
43
|
+
|
|
44
|
+
## Company tailoring
|
|
45
|
+
|
|
46
|
+
Organizations customize behavior (mirrors, CA bundles, extra checks/steps, env defaults) via `starters/company/config.mjs` in the repo — see `starters/company/README.md`. The package stays generic; the repo carries the policy.
|
package/bin/om-start.mjs
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Central entry for every way of starting Open Mercato:
|
|
3
|
+
// npx @open-mercato/starter [command] — anywhere; clones first if needed
|
|
4
|
+
// yarn om [command] — inside a clone
|
|
5
|
+
// packages/starter/platform/start.* — pre-Node bootstrap wrappers
|
|
6
|
+
//
|
|
7
|
+
// This bin only solves "where is the repo?" (cloning when necessary) and
|
|
8
|
+
// version skew (a clone's own vendored starter wins over the npx copy), then
|
|
9
|
+
// defers every real decision to src/cli.mjs. Node stdlib only — it must run
|
|
10
|
+
// on a fresh clone before any yarn install.
|
|
11
|
+
import { spawn, spawnSync } from 'node:child_process'
|
|
12
|
+
import fs from 'node:fs'
|
|
13
|
+
import path from 'node:path'
|
|
14
|
+
import { fileURLToPath } from 'node:url'
|
|
15
|
+
|
|
16
|
+
import { resolveRepoRoot } from '../src/compose.mjs'
|
|
17
|
+
|
|
18
|
+
const REPO_URL = 'https://github.com/open-mercato/open-mercato.git'
|
|
19
|
+
|
|
20
|
+
function extractBootstrapFlag(args, flag) {
|
|
21
|
+
const index = args.indexOf(flag)
|
|
22
|
+
if (index === -1 || index + 1 >= args.length) return null
|
|
23
|
+
const value = args[index + 1]
|
|
24
|
+
args.splice(index, 2)
|
|
25
|
+
return value
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function ensureRepo(args) {
|
|
29
|
+
const found = resolveRepoRoot()
|
|
30
|
+
if (found) return found
|
|
31
|
+
|
|
32
|
+
const dir = extractBootstrapFlag(args, '--dir') ?? path.join(process.cwd(), 'open-mercato')
|
|
33
|
+
const branch = extractBootstrapFlag(args, '--branch') ?? 'main'
|
|
34
|
+
if (fs.existsSync(path.join(dir, '.git'))) {
|
|
35
|
+
return resolveRepoRoot(dir)
|
|
36
|
+
}
|
|
37
|
+
const git = spawnSync('git', ['--version'], { stdio: 'ignore' })
|
|
38
|
+
if (git.error || git.status !== 0) {
|
|
39
|
+
console.error('❌ No Open Mercato checkout found here and git is not installed.')
|
|
40
|
+
console.error(' Install git, or clone manually and re-run this command inside the clone:')
|
|
41
|
+
console.error(` git clone ${REPO_URL}`)
|
|
42
|
+
process.exit(2)
|
|
43
|
+
}
|
|
44
|
+
console.log(`No checkout found — cloning open-mercato (${branch}) into ${dir} ...`)
|
|
45
|
+
const clone = spawnSync('git', ['clone', '--branch', branch, REPO_URL, dir], { stdio: 'inherit' })
|
|
46
|
+
if (clone.status !== 0) {
|
|
47
|
+
console.error('❌ git clone failed. Behind a corporate proxy? Configure git first (ask IT for the proxy address):')
|
|
48
|
+
console.error(' git config --global http.proxy http://proxy.example.com:8080')
|
|
49
|
+
console.error(' On Windows also: git config --global http.sslBackend schannel')
|
|
50
|
+
process.exit(clone.status ?? 1)
|
|
51
|
+
}
|
|
52
|
+
return resolveRepoRoot(dir)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function main() {
|
|
56
|
+
const args = process.argv.slice(2)
|
|
57
|
+
const repoRoot = await ensureRepo(args)
|
|
58
|
+
if (!repoRoot) {
|
|
59
|
+
console.error('❌ Could not locate an Open Mercato repo root.')
|
|
60
|
+
process.exit(2)
|
|
61
|
+
}
|
|
62
|
+
process.chdir(repoRoot)
|
|
63
|
+
|
|
64
|
+
// Prefer the repo's own vendored starter (gradlew pattern): the clone knows
|
|
65
|
+
// which starter version matches its compose files and runtime contract.
|
|
66
|
+
const selfDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
|
|
67
|
+
const vendored = path.join(repoRoot, 'packages', 'starter', 'bin', 'om-start.mjs')
|
|
68
|
+
if (fs.existsSync(vendored) && path.resolve(selfDir, 'bin', 'om-start.mjs') !== path.resolve(vendored)) {
|
|
69
|
+
const child = spawn(process.execPath, [vendored, ...args], { stdio: 'inherit', cwd: repoRoot })
|
|
70
|
+
child.on('close', (code) => process.exit(code ?? 0))
|
|
71
|
+
return
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
process.argv = [process.argv[0], process.argv[1], ...args]
|
|
75
|
+
await import('../src/cli.mjs')
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
await main()
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@open-mercato/starter",
|
|
3
|
+
"version": "0.6.5",
|
|
4
|
+
"description": "One-command, cross-platform bootstrap for Open Mercato dev environments: prerequisite doctor, corporate TLS trust, idempotent setup steps, and a supervised hybrid or fully containerized stack.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"om-start": "./bin/om-start.mjs"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"bin",
|
|
12
|
+
"src",
|
|
13
|
+
"platform",
|
|
14
|
+
"README.md"
|
|
15
|
+
],
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=20.19.0"
|
|
18
|
+
},
|
|
19
|
+
"scripts": {
|
|
20
|
+
"test": "echo \"tested from the repo root jest suite\" && exit 0"
|
|
21
|
+
},
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "https://github.com/open-mercato/open-mercato",
|
|
25
|
+
"directory": "packages/starter"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
@echo off
|
|
2
|
+
rem Open Mercato starter — double-click entry for Windows.
|
|
3
|
+
rem Runs the PowerShell bootstrap next to this file, which guarantees Node 24
|
|
4
|
+
rem (portable, no admin) and hands off to the cross-platform starter CLI.
|
|
5
|
+
title Open Mercato Starter
|
|
6
|
+
setlocal
|
|
7
|
+
rem UTF-8 console first: cmd.exe decodes batch files with the code page it
|
|
8
|
+
rem started with, and Yarn Berry's temp .cmd wrappers embed UTF-8 paths —
|
|
9
|
+
rem OEM code pages mangle non-ASCII checkout paths into MODULE_NOT_FOUND.
|
|
10
|
+
chcp 65001 >nul
|
|
11
|
+
set "PS1=%~dp0start.ps1"
|
|
12
|
+
if not exist "%PS1%" (
|
|
13
|
+
echo start.ps1 was not found next to this launcher.
|
|
14
|
+
pause
|
|
15
|
+
exit /b 1
|
|
16
|
+
)
|
|
17
|
+
powershell -NoProfile -ExecutionPolicy Bypass -File "%PS1%" %*
|
|
18
|
+
set "RC=%ERRORLEVEL%"
|
|
19
|
+
if not "%RC%"=="0" (
|
|
20
|
+
echo.
|
|
21
|
+
echo The starter exited with code %RC%. Review the messages above.
|
|
22
|
+
pause
|
|
23
|
+
)
|
|
24
|
+
endlocal & exit /b %RC%
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# Open Mercato starter — Windows bootstrap.
|
|
2
|
+
#
|
|
3
|
+
# The only job of this script is to guarantee Node 24 — WITHOUT admin rights,
|
|
4
|
+
# WITHOUT winget, and without touching machine state — then hand off to the
|
|
5
|
+
# cross-platform starter CLI (packages/starter/bin/om-start.mjs), which does
|
|
6
|
+
# everything else (doctor, corporate TLS trust, env/secrets, install, infra
|
|
7
|
+
# containers, database, supervised dev runtime).
|
|
8
|
+
#
|
|
9
|
+
# Managed-device design notes:
|
|
10
|
+
# * Node is installed as a portable ZIP under %LOCALAPPDATA%\OpenMercato —
|
|
11
|
+
# no elevation, no MSI, no PATH pollution beyond this session.
|
|
12
|
+
# * Downloads go through Invoke-WebRequest (schannel), which trusts the
|
|
13
|
+
# Windows certificate store — GPO-deployed corporate interception CAs work
|
|
14
|
+
# out of the box.
|
|
15
|
+
# * The script avoids .NET static calls where possible and degrades with a
|
|
16
|
+
# clear message under AppLocker/WDAC Constrained Language Mode.
|
|
17
|
+
# * WSL2 / Docker Desktop / Rancher Desktop are NEVER installed here — the
|
|
18
|
+
# CLI detects and proposes them with an IT handout (doctor command).
|
|
19
|
+
#
|
|
20
|
+
# Inside a clone: .\packages\starter\platform\start.ps1 [command] [flags]
|
|
21
|
+
# Standalone: irm https://raw.githubusercontent.com/open-mercato/open-mercato/main/packages/starter/platform/start.ps1 | iex
|
|
22
|
+
#
|
|
23
|
+
# Env: OM_NODE_DIST_MIRROR — base URL mirroring https://nodejs.org/dist.
|
|
24
|
+
|
|
25
|
+
$ErrorActionPreference = 'Stop'
|
|
26
|
+
|
|
27
|
+
# UTF-8 console first: cmd.exe decodes batch files with the code page captured
|
|
28
|
+
# at its start, and Yarn Berry's temp .cmd wrappers embed absolute UTF-8 paths.
|
|
29
|
+
# Under an OEM code page a non-ASCII checkout path (e.g. C:\Users\Michał) turns
|
|
30
|
+
# into mojibake and installs/builds die with MODULE_NOT_FOUND. chcp.com is a
|
|
31
|
+
# plain executable, so this stays Constrained-Language-Mode safe.
|
|
32
|
+
try { & "$env:SystemRoot\System32\chcp.com" 65001 | Out-Null } catch { }
|
|
33
|
+
|
|
34
|
+
$NodeMajor = 24
|
|
35
|
+
$NodeDistBase = if ($env:OM_NODE_DIST_MIRROR) { $env:OM_NODE_DIST_MIRROR.TrimEnd('/') } else { 'https://nodejs.org/dist' }
|
|
36
|
+
|
|
37
|
+
function Write-Info([string]$Message) { Write-Host "[starter] $Message" -ForegroundColor Cyan }
|
|
38
|
+
function Write-Warn([string]$Message) { Write-Host "[starter] $Message" -ForegroundColor Yellow }
|
|
39
|
+
function Fail([string]$Message) { Write-Host "[starter] $Message" -ForegroundColor Red; exit 1 }
|
|
40
|
+
|
|
41
|
+
# Two-tone Open Mercato wordmark (7-bit ASCII — safe on conhost/OEM code pages).
|
|
42
|
+
$LogoLeft = @(
|
|
43
|
+
' ___ ____ _____ _ _ ',
|
|
44
|
+
' / _ \| _ \| ____| \ | |',
|
|
45
|
+
'| | | | |_) | _| | \| |',
|
|
46
|
+
'| |_| | __/| |___| |\ |',
|
|
47
|
+
' \___/|_| |_____|_| \_|'
|
|
48
|
+
)
|
|
49
|
+
$LogoRight = @(
|
|
50
|
+
' __ __ _____ ____ ____ _ _____ ___',
|
|
51
|
+
'| \/ | ____| _ \ / ___| / \|_ _/ _ \',
|
|
52
|
+
'| |\/| | _| | |_) | | / _ \ | || | | |',
|
|
53
|
+
'| | | | |___| _ <| |___ / ___ \| || |_| |',
|
|
54
|
+
'|_| |_|_____|_| \_\\____/_/ \_\_| \___/'
|
|
55
|
+
)
|
|
56
|
+
Write-Host ''
|
|
57
|
+
for ($i = 0; $i -lt $LogoLeft.Count; $i++) {
|
|
58
|
+
Write-Host (" {0}" -f $LogoLeft[$i]) -ForegroundColor DarkCyan -NoNewline
|
|
59
|
+
Write-Host (" {0}" -f $LogoRight[$i]) -ForegroundColor Cyan
|
|
60
|
+
}
|
|
61
|
+
Write-Host ''
|
|
62
|
+
Write-Host " platform bootstrap - guarantees Node $NodeMajor (no admin), then hands off to the starter CLI" -ForegroundColor DarkGray
|
|
63
|
+
Write-Host ''
|
|
64
|
+
|
|
65
|
+
# Constrained Language Mode (AppLocker/WDAC): everything below sticks to
|
|
66
|
+
# cmdlets and core types, but surface the state early so failures make sense.
|
|
67
|
+
$languageMode = $ExecutionContext.SessionState.LanguageMode
|
|
68
|
+
if ("$languageMode" -ne 'FullLanguage') {
|
|
69
|
+
Write-Warn "PowerShell runs in $languageMode (AppLocker/WDAC policy). Continuing with cmdlet-only operations; if this script is blocked, ask IT to allow it or run from an exempted path."
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
# PS 5.1 defaults can lack TLS 1.2 on older builds; a .NET static call is
|
|
73
|
+
# blocked under CLM, so guard it — Windows 10/11 22H2 negotiate TLS 1.2 anyway.
|
|
74
|
+
try { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 } catch { }
|
|
75
|
+
|
|
76
|
+
function Get-NodeMajor {
|
|
77
|
+
# Scoped EAP: under 'Stop', PS 5.1 turns node's stderr (e.g. a NODE_OPTIONS
|
|
78
|
+
# warning inherited from the environment) into a terminating error once
|
|
79
|
+
# 2>$null wraps it — which would misreport a working Node as missing.
|
|
80
|
+
try {
|
|
81
|
+
$previousPreference = $ErrorActionPreference
|
|
82
|
+
$ErrorActionPreference = 'Continue'
|
|
83
|
+
try {
|
|
84
|
+
$version = (& node -v) 2>$null
|
|
85
|
+
} finally {
|
|
86
|
+
$ErrorActionPreference = $previousPreference
|
|
87
|
+
}
|
|
88
|
+
if ("$version" -match 'v(\d+)\.') { return [int]$Matches[1] }
|
|
89
|
+
} catch { }
|
|
90
|
+
return 0
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function Find-PortableNode {
|
|
94
|
+
$portableRoot = Join-Path $env:LOCALAPPDATA 'OpenMercato\node'
|
|
95
|
+
if (-not (Test-Path $portableRoot)) { return $null }
|
|
96
|
+
$candidate = Get-ChildItem -Path $portableRoot -Directory -Filter "node-v$NodeMajor.*" -ErrorAction SilentlyContinue |
|
|
97
|
+
Sort-Object Name -Descending | Select-Object -First 1
|
|
98
|
+
if ($candidate -and (Test-Path (Join-Path $candidate.FullName 'node.exe'))) { return $candidate.FullName }
|
|
99
|
+
return $null
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function Install-PortableNode {
|
|
103
|
+
$arch = if ($env:PROCESSOR_ARCHITECTURE -eq 'ARM64') { 'arm64' } else { 'x64' }
|
|
104
|
+
Write-Info "Resolving latest Node $NodeMajor release ..."
|
|
105
|
+
$index = Invoke-RestMethod -Uri "$NodeDistBase/index.json" -UseBasicParsing
|
|
106
|
+
$release = $index | Where-Object { $_.version -match "^v$NodeMajor\." } | Select-Object -First 1
|
|
107
|
+
if (-not $release) { Fail "Could not resolve a Node $NodeMajor version from $NodeDistBase. Behind a proxy? Set HTTPS_PROXY, or point OM_NODE_DIST_MIRROR at an internal mirror." }
|
|
108
|
+
$version = $release.version
|
|
109
|
+
$zipName = "node-$version-win-$arch.zip"
|
|
110
|
+
$portableRoot = Join-Path $env:LOCALAPPDATA 'OpenMercato\node'
|
|
111
|
+
$tempDir = Join-Path $env:TEMP "open-mercato-starter"
|
|
112
|
+
New-Item -ItemType Directory -Force -Path $portableRoot | Out-Null
|
|
113
|
+
New-Item -ItemType Directory -Force -Path $tempDir | Out-Null
|
|
114
|
+
$zipPath = Join-Path $tempDir $zipName
|
|
115
|
+
|
|
116
|
+
Write-Info "Downloading $zipName (no admin required) ..."
|
|
117
|
+
Invoke-WebRequest -Uri "$NodeDistBase/$version/$zipName" -OutFile $zipPath -UseBasicParsing
|
|
118
|
+
|
|
119
|
+
Write-Info 'Verifying checksum ...'
|
|
120
|
+
$shasums = (Invoke-WebRequest -Uri "$NodeDistBase/$version/SHASUMS256.txt" -UseBasicParsing).Content
|
|
121
|
+
$expectedLine = $shasums -split "`n" | Where-Object { $_ -match [regex]::Escape($zipName) } | Select-Object -First 1
|
|
122
|
+
if (-not $expectedLine) { Fail "SHASUMS256.txt has no entry for $zipName." }
|
|
123
|
+
$expected = ($expectedLine -split '\s+')[0].Trim().ToLowerInvariant()
|
|
124
|
+
$actual = (Get-FileHash -Path $zipPath -Algorithm SHA256).Hash.ToLowerInvariant()
|
|
125
|
+
if ($expected -ne $actual) { Fail "Checksum verification FAILED for $zipName (expected $expected, got $actual). Refusing to continue." }
|
|
126
|
+
|
|
127
|
+
Write-Info "Extracting to $portableRoot ..."
|
|
128
|
+
Expand-Archive -Path $zipPath -DestinationPath $portableRoot -Force
|
|
129
|
+
Remove-Item $zipPath -Force -ErrorAction SilentlyContinue
|
|
130
|
+
|
|
131
|
+
$installed = Find-PortableNode
|
|
132
|
+
if (-not $installed) { Fail 'Portable Node extraction failed.' }
|
|
133
|
+
return $installed
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if ((Get-NodeMajor) -ne $NodeMajor) {
|
|
137
|
+
$portable = Find-PortableNode
|
|
138
|
+
if (-not $portable) { $portable = Install-PortableNode }
|
|
139
|
+
$env:Path = "$portable;$env:Path"
|
|
140
|
+
Write-Warn "Using portable Node from $portable (PATH updated for this session)."
|
|
141
|
+
Write-Warn "Persist it for your own shells: setx PATH `"$portable;%PATH%`""
|
|
142
|
+
}
|
|
143
|
+
if ((Get-NodeMajor) -ne $NodeMajor) { Fail "Node $NodeMajor is required (found: $(try { & node -v } catch { 'none' }))." }
|
|
144
|
+
Write-Info "Node $(& node -v) ready"
|
|
145
|
+
|
|
146
|
+
# Locate the repo: walk up from this script first (in-clone use), then from
|
|
147
|
+
# the working directory (irm-standalone use).
|
|
148
|
+
function Find-RepoRoot([string]$StartDir) {
|
|
149
|
+
$dir = $StartDir
|
|
150
|
+
while ($dir -and (Test-Path $dir)) {
|
|
151
|
+
if (Test-Path (Join-Path $dir 'starters\docker\compose.infra.yml')) { return $dir }
|
|
152
|
+
$parent = Split-Path -Parent $dir
|
|
153
|
+
if (-not $parent -or $parent -eq $dir) { return $null }
|
|
154
|
+
$dir = $parent
|
|
155
|
+
}
|
|
156
|
+
return $null
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
$repoRoot = $null
|
|
160
|
+
if ($PSScriptRoot) { $repoRoot = Find-RepoRoot $PSScriptRoot }
|
|
161
|
+
if (-not $repoRoot) { $repoRoot = Find-RepoRoot (Get-Location).Path }
|
|
162
|
+
|
|
163
|
+
if ($repoRoot) {
|
|
164
|
+
& node (Join-Path $repoRoot 'packages\starter\bin\om-start.mjs') @args
|
|
165
|
+
exit $LASTEXITCODE
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
# Standalone: no clone yet — npx fetches the published starter, which clones
|
|
169
|
+
# and continues. --use-system-ca keeps npm's TLS working behind corporate
|
|
170
|
+
# interception (the GPO-deployed CA lives in the Windows store).
|
|
171
|
+
Write-Info 'No checkout found — bootstrapping via npx @open-mercato/starter ...'
|
|
172
|
+
$env:NODE_OPTIONS = "$($env:NODE_OPTIONS) --use-system-ca".Trim()
|
|
173
|
+
& npx --yes '@open-mercato/starter' @args
|
|
174
|
+
exit $LASTEXITCODE
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Open Mercato starter — macOS/Linux bootstrap.
|
|
3
|
+
#
|
|
4
|
+
# The only job of this script is to guarantee Node 24, then hand off to the
|
|
5
|
+
# cross-platform starter CLI (packages/starter/bin/om-start.mjs), which does
|
|
6
|
+
# everything else: doctor, corporate TLS trust, env/secrets, install, infra
|
|
7
|
+
# containers, database, and the supervised dev runtime.
|
|
8
|
+
#
|
|
9
|
+
# Inside a clone: ./packages/starter/platform/start.sh [command] [flags]
|
|
10
|
+
# Standalone: curl -fsSL https://raw.githubusercontent.com/open-mercato/open-mercato/main/packages/starter/platform/start.sh | bash
|
|
11
|
+
#
|
|
12
|
+
# Env: OM_NODE_DIST_MIRROR — base URL mirroring https://nodejs.org/dist for
|
|
13
|
+
# proxy-blocked networks (defaults to the official CDN).
|
|
14
|
+
set -euo pipefail
|
|
15
|
+
|
|
16
|
+
NODE_MAJOR=24
|
|
17
|
+
NODE_DIST_BASE="${OM_NODE_DIST_MIRROR:-https://nodejs.org/dist}"
|
|
18
|
+
|
|
19
|
+
log() { printf '\033[36m[starter]\033[0m %s\n' "$*"; }
|
|
20
|
+
warn() { printf '\033[33m[starter]\033[0m %s\n' "$*" >&2; }
|
|
21
|
+
fail() { printf '\033[31m[starter]\033[0m %s\n' "$*" >&2; exit 1; }
|
|
22
|
+
|
|
23
|
+
# Two-tone Open Mercato wordmark (7-bit ASCII — safe on any terminal).
|
|
24
|
+
if [ -t 1 ]; then
|
|
25
|
+
printf '\n'
|
|
26
|
+
printf '\033[2;36m ___ ____ _____ _ _ \033[0m\033[36m __ __ _____ ____ ____ _ _____ ___\033[0m\n'
|
|
27
|
+
printf '\033[2;36m / _ \\| _ \\| ____| \\ | |\033[0m\033[36m | \\/ | ____| _ \\ / ___| / \\|_ _/ _ \\\033[0m\n'
|
|
28
|
+
printf '\033[2;36m | | | | |_) | _| | \\| |\033[0m\033[36m | |\\/| | _| | |_) | | / _ \\ | || | | |\033[0m\n'
|
|
29
|
+
printf '\033[2;36m | |_| | __/| |___| |\\ |\033[0m\033[36m | | | | |___| _ <| |___ / ___ \\| || |_| |\033[0m\n'
|
|
30
|
+
printf '\033[2;36m \\___/|_| |_____|_| \\_|\033[0m\033[36m |_| |_|_____|_| \\_\\\\____/_/ \\_\\_| \\___/\033[0m\n'
|
|
31
|
+
printf '\n \033[2mplatform bootstrap - guarantees Node %s, then hands off to the starter CLI\033[0m\n\n' "$NODE_MAJOR"
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
OS="$(uname -s)"
|
|
35
|
+
case "$OS" in
|
|
36
|
+
Darwin|Linux) ;;
|
|
37
|
+
*) fail "Unsupported OS: $OS. On Windows run packages\\starter\\platform\\start.cmd instead." ;;
|
|
38
|
+
esac
|
|
39
|
+
|
|
40
|
+
ARCH="$(uname -m)"
|
|
41
|
+
case "$ARCH" in
|
|
42
|
+
x86_64|amd64) NODE_ARCH="x64" ;;
|
|
43
|
+
arm64|aarch64) NODE_ARCH="arm64" ;;
|
|
44
|
+
*) fail "Unsupported CPU architecture: $ARCH" ;;
|
|
45
|
+
esac
|
|
46
|
+
|
|
47
|
+
node_major() { command -v node >/dev/null 2>&1 && node -p 'process.versions.node.split(".")[0]' 2>/dev/null || echo 0; }
|
|
48
|
+
|
|
49
|
+
install_node() {
|
|
50
|
+
if [ "$OS" = "Darwin" ] && command -v brew >/dev/null 2>&1; then
|
|
51
|
+
log "Installing Node $NODE_MAJOR via Homebrew ..."
|
|
52
|
+
brew install "node@$NODE_MAJOR"
|
|
53
|
+
BREW_NODE="$(brew --prefix "node@$NODE_MAJOR")/bin"
|
|
54
|
+
export PATH="$BREW_NODE:$PATH"
|
|
55
|
+
warn "Added $BREW_NODE to PATH for this session — persist it in your shell profile."
|
|
56
|
+
return
|
|
57
|
+
fi
|
|
58
|
+
# Distro-agnostic official tarball into the user's home — no sudo, checksum
|
|
59
|
+
# verified. curl uses the system trust store, so a corporate CA deployed by
|
|
60
|
+
# IT is honored automatically.
|
|
61
|
+
NODE_PREFIX="$HOME/.local/share/open-mercato/node"
|
|
62
|
+
OS_SLUG=$([ "$OS" = "Darwin" ] && echo darwin || echo linux)
|
|
63
|
+
log "Resolving latest Node $NODE_MAJOR release ..."
|
|
64
|
+
NODE_VERSION="$(curl -fsSL "$NODE_DIST_BASE/index.json" | grep -o "\"v$NODE_MAJOR\.[0-9]*\.[0-9]*\"" | head -1 | tr -d '"')"
|
|
65
|
+
[ -n "$NODE_VERSION" ] || fail "Could not resolve a Node $NODE_MAJOR version from $NODE_DIST_BASE. Behind a proxy? Set HTTPS_PROXY, or point OM_NODE_DIST_MIRROR at an internal mirror."
|
|
66
|
+
TARBALL="node-$NODE_VERSION-$OS_SLUG-$NODE_ARCH.tar.gz"
|
|
67
|
+
TMP_DIR="$(mktemp -d)"
|
|
68
|
+
trap 'rm -rf "$TMP_DIR"' EXIT
|
|
69
|
+
log "Downloading $TARBALL ..."
|
|
70
|
+
curl -fsSL "$NODE_DIST_BASE/$NODE_VERSION/$TARBALL" -o "$TMP_DIR/$TARBALL"
|
|
71
|
+
curl -fsSL "$NODE_DIST_BASE/$NODE_VERSION/SHASUMS256.txt" -o "$TMP_DIR/SHASUMS256.txt"
|
|
72
|
+
(cd "$TMP_DIR" && { command -v sha256sum >/dev/null 2>&1 && grep " $TARBALL\$" SHASUMS256.txt | sha256sum -c - ; } || { command -v shasum >/dev/null 2>&1 && grep " $TARBALL\$" SHASUMS256.txt | shasum -a 256 -c - ; }) \
|
|
73
|
+
|| fail "Checksum verification failed for $TARBALL."
|
|
74
|
+
mkdir -p "$NODE_PREFIX"
|
|
75
|
+
tar -xzf "$TMP_DIR/$TARBALL" -C "$NODE_PREFIX" --strip-components=1
|
|
76
|
+
export PATH="$NODE_PREFIX/bin:$PATH"
|
|
77
|
+
warn "Installed Node $NODE_VERSION to $NODE_PREFIX (PATH updated for this session)."
|
|
78
|
+
warn "Persist it: export PATH=\"$NODE_PREFIX/bin:\$PATH\""
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if [ "$(node_major)" -ne "$NODE_MAJOR" ]; then
|
|
82
|
+
install_node
|
|
83
|
+
fi
|
|
84
|
+
[ "$(node_major)" -eq "$NODE_MAJOR" ] || fail "Node $NODE_MAJOR is required (found $(node -v 2>/dev/null || echo none))."
|
|
85
|
+
log "Node $(node -v) ready"
|
|
86
|
+
|
|
87
|
+
# Locate the repo: walk up from this script first (in-clone use), then from
|
|
88
|
+
# the working directory (curl-standalone use).
|
|
89
|
+
find_root_from() {
|
|
90
|
+
local dir="$1"
|
|
91
|
+
while [ "$dir" != "/" ]; do
|
|
92
|
+
if [ -f "$dir/starters/docker/compose.infra.yml" ]; then
|
|
93
|
+
printf '%s' "$dir"
|
|
94
|
+
return 0
|
|
95
|
+
fi
|
|
96
|
+
dir="$(dirname "$dir")"
|
|
97
|
+
done
|
|
98
|
+
return 1
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" 2>/dev/null && pwd || pwd)"
|
|
102
|
+
REPO_ROOT="$(find_root_from "$SCRIPT_DIR" || find_root_from "$PWD" || true)"
|
|
103
|
+
|
|
104
|
+
if [ -n "${REPO_ROOT:-}" ]; then
|
|
105
|
+
exec node "$REPO_ROOT/packages/starter/bin/om-start.mjs" "$@"
|
|
106
|
+
fi
|
|
107
|
+
|
|
108
|
+
# Standalone: no clone yet — npx fetches the published starter, which clones
|
|
109
|
+
# and continues. --use-system-ca keeps this working behind corporate TLS
|
|
110
|
+
# interception when the CA is in the system store.
|
|
111
|
+
log "No checkout found — bootstrapping via npx @open-mercato/starter ..."
|
|
112
|
+
NODE_OPTIONS="${NODE_OPTIONS:-} --use-system-ca" exec npx --yes @open-mercato/starter "$@"
|