이 가이드는 깨끗하게 설치한 Topaz에서 패키지 생성, 작성, 정적 검사, 문서화, 네이티브·Python·대화형 Web 제품의 직접 오프라인 실행까지 한 번에 진행합니다. 현재 공개 제품 식별자는 Topaz 5.7.0, 언어 모드는 topaz-5.7입니다. 이 릴리스는 도구 작업 흐름을 확장하며 v5.7 문법은 바꾸지 않습니다.
준비 사항
Topaz를 설치하고 제품과 언어 식별자를 확인합니다.
curl -fsSL https://topaz.ooo/install.sh | sh
topaz version --verbose
네이티브 빌드에는 Rust 도구 모음이 필요합니다. Python 제품을 실행하려면 Python 3.11 이상이 필요합니다. 패키지를 작성·검사·포맷 검사·테스트·문서화할 때는 이 두 도구가 필요하지 않습니다.
패키지 생성
빈 작업 디렉터리에서 설치된 CLI로 최종 애플리케이션 루트를 만듭니다.
topaz init --root release-inventory
같은 명령을 다시 실행하면 기존 스캐폴드를 덮어쓰지 않고 거부합니다. 완성할 예제의 구조는 다음과 같습니다.
release-inventory/
topaz.toml
src/main.tpz
src/policy.tpz
src/report.tpz
data/releases.csv
data/policy.toml
out/report.txt
registry/
report_slug/1.0.0/topaz.toml
report_slug/1.0.0/src/lib.tpz아래 파일을 작성하기 전에 초기 스캐폴드에 포함되지 않은 디렉터리를 만듭니다.
mkdir -p release-inventory/src release-inventory/data release-inventory/out
mkdir -p registry/report_slug/1.0.0/src
스캐폴드의 매니페스트를 다음 내용으로 바꿉니다. 파일 기능은 data 아래 읽기와 out 아래 쓰기만 허용하며, 일반 경로 탈출과 심볼릭 링크 탈출은 거부합니다.
[package]
name = "release_inventory"
version = "0.1.0"
language = "5.7"
entry = "src/main.tpz"
[build]
target = "native"
deterministic = true
[dependencies]
std = "5.7"
report_slug = "1.0.0"
[capabilities.fs]
read = ["data"]
write = ["out"]
엔트리는 샘플 및 실패 검사 모드를 선택하고, 구조화된 입력을 읽어 보고서를 씁니다.
import src.report { buildReport }
import std.fs
const SAMPLE_CSV = "name,status,score\nCore API,ready,91\nDraft Tool,draft,99\nCLI Pack,ready,84"
const SAMPLE_POLICY = "[policy]\nrequired_status = \"ready\"\nminimum_score = 85"
function renderSample(csv: string) -> Result<int, string> {
let report = buildReport(csv, SAMPLE_POLICY)?
print(report)
Ok(0)
}
function hasArg(args: Array<string>, expected: string) -> bool {
match args.indexOf(expected) {
case Some(_) => true
case None => false
}
}
export function main(args: Array<string>, stdin: string) -> Result<int, string> {
if hasArg(args, "--sample") {
return renderSample(SAMPLE_CSV)
}
if hasArg(args, "--bad-sample") {
return renderSample("name,status,score\nBroken Row,ready,nope")
}
if hasArg(args, "--probe-denied") {
let secret = fs.readText("secret.txt")?
print(secret)
return Ok(0)
}
if hasArg(args, "--probe-symlink") {
let secret = fs.readText("data/escape-link")?
print(secret)
return Ok(0)
}
let inventory = fs.readText("data/releases.csv")?
let policy = fs.readText("data/policy.toml")?
let report = buildReport(inventory, policy)?
fs.writeText("out/report.txt", "{report}\n")?
print(report)
Ok(0)
}src/policy.tpz는 TOML 정책을 검증합니다.
export type Policy = { requiredStatus: string, minimumScore: int }
function field(obj: JSONValue, name: string) -> Result<JSONValue, string> {
match obj.get(name) {
case Some(value) => Ok(value)
case None => Err("missing policy.{name}")
}
}
export function parsePolicy(text: string) -> Result<Policy, string> {
let document = TOML.toJson(TOML.parse(text)?)
let policy = field(document, "policy")?
let requiredValue = field(policy, "required_status")?
let minimumValue = field(policy, "minimum_score")?
let required = requiredValue.asString() ?? ""
let minimum = minimumValue.asInt() ?? -1
if required.byteLength() == 0 {
return Err("policy.required_status must not be empty")
}
if minimum < 0 {
return Err("policy.minimum_score must be a non-negative int")
}
Ok({ requiredStatus: required, minimumScore: minimum })
}src/report.tpz는 로컬 모듈, 컬렉션, CSV 입력, 결정적 오류와 패키지 의존성을 함께 사용합니다.
import report_slug { slug }
import src.policy { Policy, parsePolicy }
function cell(row: Map<string, string>, name: string) -> Result<string, string> {
match row.get(name) {
case Some(value) => Ok(value)
case None => Err("missing CSV column {name}")
}
}
function acceptedLine(row: Map<string, string>, policy: Policy) -> Result<Option<string>, string> {
let name = cell(row, "name")?
let status = cell(row, "status")?
let scoreText = cell(row, "score")?
let score = match toInt(scoreText) {
case Some(value) => value
case None => return Err("invalid score `{scoreText}` for `{name}`")
}
if status != policy.requiredStatus || score < policy.minimumScore {
return Ok(None)
}
Ok(Some("{slug(name)?}:{score}"))
}
export function buildReport(csvText: string, policyText: string) -> Result<string, string> {
let policy = parsePolicy(policyText)?
let rows = CSV.parseWithHeader(csvText)?
let mut accepted: Array<string> = []
for row in rows {
match acceptedLine(row, policy)? {
case Some(line) => accepted.push(line)
case None => ()
}
}
let lines = accepted.sorted()
Ok("accepted={lines.length}\n{lines.join("\n")}")
}로컬 registry 의존성의 매니페스트와 src/lib.tpz를 만듭니다.
[package]
name = "report_slug"
version = "1.0.0"
language = "5.7"
entry = "src/lib.tpz"
[build]
target = "native"
deterministic = true
[dependencies]
std = "5.7"
[exports]
module = "src/lib.tpz"
export function slug(name: string) -> Result<string, string> {
let spaces = Regex.compile(" +")?
Ok(spaces.replaceAll(name.trim(), "-"))
}마지막으로 런타임 입력을 쓰고 출력 디렉터리를 만듭니다.
# data/releases.csv
name,status,score
Core API,ready,91
CLI Pack,ready,84
Python Host,ready,88
Draft Tool,draft,99
# data/policy.toml
[policy]
required_status = "ready"
minimum_score = 85잠금·벤더링·작성 단계 검사
의존성을 vendoring한 뒤 registry를 지웁니다. 이후 패키지 명령은 lockfile과 vendored 바이트만 사용합니다.
topaz vendor --root release-inventory --from registry
rm -rf registry
topaz check --root release-inventory --locked
topaz fmt --check --root release-inventory
topaz test --root release-inventory --locked -- --sample
topaz doc --root release-inventory --locked --out-dir docs-out
fmt --check는 fmt와 같은 포맷터를 호출하지만 아무것도 쓰지 않습니다. 편집기는 topaz lsp --root release-inventory를 시작할 수 있습니다. 저장된 패키지는 vendored 모듈까지 해석하며, 잘못된 미저장 편집은 디스크 파일을 바꾸지 않고 overlay에서 진단됩니다.
검사된 패키지와 결정적인 잘못된 입력 사례를 실행합니다.
topaz run --root release-inventory --locked
topaz run --root release-inventory --locked -- --bad-sample
정상 보고서는 다음과 같습니다.
accepted=2
Core-API:91
Python-Host:88두 제품 빌드
네이티브와 Python 제품은 서로 다른 명령을 사용합니다.
topaz build --release --root release-inventory --locked --out-dir native-out
topaz build --target python --root release-inventory --locked --out-dir python-out
네이티브 런타임 산출물은 native-out/target/release/program이며 Windows에서는 program.exe입니다. Python 런타임 산출물은 python-out/program.py와 python-out/topaz_py_rt.py입니다. 각 빌드는 5.7.0 도구 모음, topaz-5.7 언어 모드, 타겟, 런타임 요구 사항과 관리 파일 해시를 기록한 topaz-artifact.json도 만듭니다.
런타임 산출물과 data/, 쓰기 가능한 빈 out/만 별도의 런타임 디렉터리로 복사합니다. 그다음 애플리케이션 소스, registry, 생성 문서와 두 빌드 디렉터리를 삭제합니다.
mkdir -p native-runtime/data native-runtime/out
mkdir -p python-runtime/data python-runtime/out
cp native-out/target/release/program native-runtime/program
cp python-out/program.py python-out/topaz_py_rt.py python-runtime/
cp release-inventory/data/* native-runtime/data/
cp release-inventory/data/* python-runtime/data/
rm -rf release-inventory registry docs-out native-out python-out
선언한 상대 data/와 out/ 루트가 유지되도록 네트워크와 proxy를 차단하고 각 제품을 자체 런타임 디렉터리에서 실행합니다.
(cd native-runtime && env HTTP_PROXY=http://127.0.0.1:9 HTTPS_PROXY=http://127.0.0.1:9 ALL_PROXY=http://127.0.0.1:9 NO_PROXY= CARGO_NET_OFFLINE=true ./program)
(cd python-runtime && env HTTP_PROXY=http://127.0.0.1:9 HTTPS_PROXY=http://127.0.0.1:9 ALL_PROXY=http://127.0.0.1:9 NO_PROXY= CARGO_NET_OFFLINE=true python3 program.py)
두 제품은 Topaz checkout, 애플리케이션 소스, registry, Cargo 빌드 트리나 네트워크 없이 같은 보고서와 출력 파일을 만듭니다. 네이티브 제품에는 실행 파일과 선언한 런타임 데이터만 필요합니다. Python 제품에는 topaz_py_rt.py와 Python 3.11 이상도 필요합니다.
웹 애플리케이션 루프
Topaz 5.7.0은 topaz-5.7 언어 모드를 유지하면서 검사형 웹 애플리케이션 루프를 제공합니다. 일반 설치본에서 다음 전체 루프가 동작합니다.
topaz init --target web-app --root hello-web
topaz check --root hello-web
topaz fmt --check --root hello-web
topaz test hello-web/tests/app.tpz --root hello-web
topaz dev --root hello-web --port 8000
topaz build --root hello-web --out-dir web-product
스캐폴드는 [build].target = "web-app"을 지정하고 src/main.tpz, tests/app.tpz, styles/app.css를 만들며, [web]에는 정규화된 스타일과 자산만 선언합니다. 따라서 패키지 모드 build에는 별도 대상 옵션이 필요 없습니다. 진입 모듈은 정확히 init() -> AppStep<Model, Msg>, update(Model, Msg, BrowserEvent) -> AppStep<Model, Msg>, view(Model) -> Html<Msg>를 내보내며, 세 시그니처의 타입이 다르면 WASM 생성 전에 검사가 실패합니다.
topaz dev는 루프백에만 바인딩하고, 패키지 입력 변경을 다시 빌드하며, 잘못된 수정 뒤에도 마지막 정상 제품을 계속 제공하고, 정상 빌드 뒤에는 페이지를 다시 불러옵니다. 프로덕션 서버는 아닙니다. 최종 web-product/에는 index.html, 안전한 topaz-app.js 호스트, 기존 검사형 Web 퍼사드와 WASM, 선언한 스타일·자산, 라이선스·고지, topaz-artifact.json이 들어갑니다. 이 디렉터리만 새 정적 루트로 복사하고 hello-web을 지우면, 패키지 소스·Topaz·레지스트리·npm·CDN·개발 프로세스 없이 실행됩니다.
생성된 애플리케이션 제품은 raw web/web-worker 임베딩 및 컴파일러 플레이그라운드와 서로 다른 제품입니다. 호스트는 innerHTML 없이 DOM 노드를 만들고 제한된 브라우저 사실만 Topaz에 전달하며, 안전하지 않은 트리·URL·속성, 잘못된 ABI 값, 오류, 명령 예산 소진이 발생하면 텍스트 전용 실패 상태에서 정지합니다.
기존 JavaScript 셸이 UI 또는 Worker 프로토콜을 소유하고 선택한 Topaz export만 호출한다면 raw Web을 선택하세요. 모델·메시지·update·view를 Topaz가 소유하고 완전한 관리형 정적 제품이 필요하다면 Web App을 선택하세요. Data Lens는 두 번째 경로를 다중 모듈 로컬 우선 애플리케이션으로 완성합니다.