Use Gitea Releases for installation and ship VS Code highlighting

This commit is contained in:
Antigravity committed 2026-10-06 14:12:58 +00:00
1 parent a46bd6e132
commit 201bdee372
243 files changed
+3289 -12521

No files matched your search

+1 -1
View File
@@ -44,7 +44,7 @@
替换路径,勿直接复制示例运行。下文 CI 字段均位于 `performance.linux_ci`。`inputs` 列出固定原始作者输入及准备好的资产文件/目录,逐文件计 SHA-256;工具、协议产物、CMS manifest/可执行文件/配置和 `performance.network_peer/live_peer` 也自动计摘要,`linux_ci` 对象不作为 peer 路径解析。输出目录不得包含在 inputs 中。报告只存摘要,不复制数据库密码。
`required_inputs` 为 `author`、`native`、`postgres`、`sandbox`、`bounded-perf`、`soak` 各提供非空绝对路径列表,必须落在 `inputs` 声明范围内。缺路径记录 blocked,不算 pass。作者 fixture 的具体固定路径/准备命令仍以 `sdk/native-release.md`、`test/ecosystem-release/prepare.py`、`test/ecosystem-release/build_inputs.py` 和对应 ignored 原因文本为准;gate 不把现存文件冒充由当前源码重建的 provenance。
`required_inputs` 为 `author`、`native`、`postgres`、`sandbox`、`bounded-perf`、`soak` 各提供非空绝对路径列表,必须落在 `inputs` 声明范围内。缺路径记录 blocked,不算 pass。作者 fixture 的具体固定路径/准备命令仍以 `test/ecosystem-release/prepare.py`、`test/ecosystem-release/build_inputs.py` 和对应 ignored 原因文本为准;gate 不把现存文件冒充由当前源码重建的 provenance。
显式 LLVM Worker 验收读取 `target/native-release-inputs/component-fixture`:先用当前源码构建 `dever-tests` 的 `component-fixture` bin,再将程序复制到该准备目录,记录源码/程序摘要,并把固定副本列入 `inputs` 和 `required_inputs.native`。不能将 `target/debug/component-fixture` 当作不可变输入:Cargo 在 default/all-feature 组合间会重新链接这个输出。普通 Worker 测试仍使用 Cargo 为各自组合生成的 `CARGO_BIN_EXE_component-fixture`,不改质量门的输入变更拒绝规则。
+293 -42
View File
@@ -108,15 +108,17 @@ fn signed_catalog_paths_and_total_size_are_bounded_before_extraction() {
#[test]
fn official_redirects_reject_http_credentials_ports_and_foreign_hosts() {
let current = "https://git.dever.cc/shemic/dever/dist/latest/a";
let current = "https://git.dever.cc/attachments/a";
let origin = url::Url::parse(OFFICIAL_RELEASES).unwrap();
assert!(
redirect(
current,
"https://git.dever.cc/shemic/dever/dist/1.2.3/a?token=opaque"
"https://git.dever.cc/attachments/b?token=opaque",
&origin,
)
.is_ok()
);
assert!(redirect(current, "/shemic/dever/dist/1.2.3/a").is_ok());
assert!(redirect(current, "/attachments/b", &origin).is_ok());
for address in [
"http://git.dever.cc/a",
"https://evil.example/a",
@@ -127,34 +129,109 @@ fn official_redirects_reject_http_credentials_ports_and_foreign_hosts() {
"https://github.com/a",
"https://release-assets.githubusercontent.com/a",
] {
assert!(redirect(current, address).is_err(), "{address}");
assert!(redirect(current, address, &origin).is_err(), "{address}");
assert!(
validate_download_url(address, &origin).is_err(),
"{address}"
);
}
assert!(validate_download_url("/attachments/b", &origin).is_err());
}
#[test]
fn official_assets_use_exact_platform_names_and_return_the_published_url() {
let suffix = "ext-runtime-pip-linux-x86_64.tar.zst";
let metadata = release_metadata("https://git.dever.cc", "v1.2.3", &[(suffix, b"archive")]);
let release = ResolvedRelease::parse(&metadata, "latest").unwrap();
assert_eq!(release.version.as_str(), "1.2.3");
assert_eq!(
release.asset(suffix).unwrap().browser_download_url,
"https://git.dever.cc/attachments/0"
);
assert!(release.asset("manifest.json").is_err());
assert!(
release
.asset("ext-runtime-pip-linux-aarch64.tar.zst")
.is_err()
);
}
#[test]
fn release_metadata_rejects_unstable_tags_mismatched_versions_and_malformed_assets() {
let good: serde_json::Value = serde_json::from_slice(&release_metadata(
"https://git.dever.cc",
"v1.2.3",
&[("tar.zst", b"archive")],
))
.unwrap();
for tag in [
"1.2.3",
"v01.2.3",
"v1.2",
"v1.2.3-rc.1",
"v1.2.3+build",
"v../escape",
] {
let mut metadata = good.clone();
metadata["tag_name"] = tag.into();
assert!(
ResolvedRelease::parse(&serde_json::to_vec(&metadata).unwrap(), "latest").is_err(),
"{tag}"
);
}
assert!(ResolvedRelease::parse(&serde_json::to_vec(&good).unwrap(), "1.2.4").is_err());
for field in ["draft", "prerelease"] {
let mut metadata = good.clone();
metadata[field] = true.into();
assert!(ResolvedRelease::parse(&serde_json::to_vec(&metadata).unwrap(), "latest").is_err());
metadata.as_object_mut().unwrap().remove(field);
assert!(ResolvedRelease::parse(&serde_json::to_vec(&metadata).unwrap(), "latest").is_err());
}
let asset = good["assets"][0].clone();
for assets in [
serde_json::json!([]),
serde_json::json!([asset, asset]),
serde_json::json!(vec![asset.clone(); ASSET_LIMIT + 1]),
] {
let mut metadata = good.clone();
metadata["assets"] = assets;
assert!(ResolvedRelease::parse(&serde_json::to_vec(&metadata).unwrap(), "latest").is_err());
}
for size in [
serde_json::json!(0),
serde_json::json!(-1),
serde_json::json!(1.5),
serde_json::json!(RELEASE_LIMIT + 1),
serde_json::json!("7"),
] {
let mut metadata = good.clone();
metadata["assets"][0]["size"] = size;
assert!(ResolvedRelease::parse(&serde_json::to_vec(&metadata).unwrap(), "latest").is_err());
}
for name in ["", "../asset", "a/b", "a\\b"] {
let mut metadata = good.clone();
metadata["assets"][0]["name"] = name.into();
assert!(ResolvedRelease::parse(&serde_json::to_vec(&metadata).unwrap(), "latest").is_err());
}
}
#[test]
fn official_asset_names_are_versioned_and_platform_specific() {
let source = ReleaseSource::official();
assert_eq!(
source.asset("latest", "manifest.json"),
format!(
"https://git.dever.cc/shemic/dever/dist/latest/dever-{}.manifest.json",
platform_identity()
)
);
assert_eq!(
source.asset("1.2.3", "tar.zst"),
format!(
"https://git.dever.cc/shemic/dever/dist/1.2.3/dever-{}.tar.zst",
platform_identity()
)
);
assert_eq!(
source.asset("1.2.3", "ext-runtime-pip-linux-x86_64.tar.zst"),
format!(
"https://git.dever.cc/shemic/dever/dist/1.2.3/dever-{}.ext-runtime-pip-linux-x86_64.tar.zst",
platform_identity()
)
);
fn release_asset_reader_requires_the_exact_declared_size() {
for size in [2, 4] {
let mut reader = AssetReader {
reader: io::Cursor::new(b"abc"),
remaining: size,
};
assert!(reader.read_to_end(&mut Vec::new()).is_err());
}
let mut reader = AssetReader {
reader: io::Cursor::new(b"abc"),
remaining: 3,
};
assert_eq!(reader.read(&mut []).unwrap(), 0);
let mut bytes = Vec::new();
reader.read_to_end(&mut bytes).unwrap();
assert_eq!(bytes, b"abc");
}
fn signed_release() -> (ReleaseManifest, Vec<u8>, Vec<u8>, Vec<u8>) {
@@ -177,13 +254,57 @@ fn signed_release() -> (ReleaseManifest, Vec<u8>, Vec<u8>, Vec<u8>) {
}
fn server(responses: Vec<Vec<u8>>) -> (ReleaseSource, std::thread::JoinHandle<()>) {
release_server("latest", "v1.2.3", responses)
}
fn release_metadata(base: &str, tag: &str, assets: &[(&str, &[u8])]) -> Vec<u8> {
serde_json::to_vec(&serde_json::json!({
"tag_name": tag,
"draft": false,
"prerelease": false,
"assets": assets.iter().enumerate().map(|(index, (suffix, bytes))| serde_json::json!({
"name": format!("dever-{}.{suffix}", platform_identity()),
"browser_download_url": format!("{base}/attachments/{index}"),
"size": bytes.len(),
})).collect::<Vec<_>>(),
}))
.unwrap()
}
fn release_server(
selection: &str,
tag: &str,
responses: Vec<Vec<u8>>,
) -> (ReleaseSource, std::thread::JoinHandle<()>) {
serve(|base| {
let assets: Vec<_> = ["manifest.json", "manifest.sig", "tar.zst"]
.into_iter()
.zip(&responses)
.map(|(suffix, bytes)| (suffix, bytes.as_slice()))
.collect();
let metadata = release_metadata(base, tag, &assets);
std::iter::once((format!("/releases/{selection}"), metadata))
.chain(
responses
.into_iter()
.enumerate()
.map(|(index, bytes)| (format!("/attachments/{index}"), bytes)),
)
.collect()
})
}
fn serve(
responses: impl FnOnce(&str) -> Vec<(String, Vec<u8>)>,
) -> (ReleaseSource, std::thread::JoinHandle<()>) {
use std::io::BufRead;
let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
listener.set_nonblocking(true).unwrap();
let base = format!("http://{}/releases", listener.local_addr().unwrap());
let base = format!("http://{}", listener.local_addr().unwrap());
let responses = responses(&base);
let worker = std::thread::spawn(move || {
let deadline = Instant::now() + Duration::from_secs(5);
for (index, response) in responses.into_iter().enumerate() {
for (path, response) in responses {
let mut stream = loop {
match listener.accept() {
Ok((stream, _)) => break stream,
@@ -203,18 +324,7 @@ fn server(responses: Vec<Vec<u8>>) -> (ReleaseSource, std::thread::JoinHandle<()
let mut reader = io::BufReader::new(stream.try_clone().unwrap());
let mut request = String::new();
reader.read_line(&mut request).unwrap();
let (selection, suffix) = [
("latest", "manifest.json"),
("latest", "manifest.sig"),
("1.2.3", "tar.zst"),
][index];
assert_eq!(
request,
format!(
"GET /releases/{selection}/dever-{}.{suffix} HTTP/1.1\r\n",
platform_identity()
)
);
assert_eq!(request, format!("GET {path} HTTP/1.1\r\n"));
loop {
let mut line = String::new();
reader.read_line(&mut line).unwrap();
@@ -231,7 +341,123 @@ fn server(responses: Vec<Vec<u8>>) -> (ReleaseSource, std::thread::JoinHandle<()
stream.write_all(&response).unwrap();
}
});
(ReleaseSource::new(base), worker)
(ReleaseSource::new(format!("{base}/releases")), worker)
}
#[test]
fn latest_and_extensions_share_one_release_snapshot() {
use super::super::{BuildTarget, ExtensionId, ExtensionKind, ExtensionSource};
use crate::libs::Ecosystem;
let extension = ExtensionId {
kind: ExtensionKind::Runtime(Ecosystem::Pip),
target: BuildTarget::LinuxX86_64,
};
let (source, worker) = serve(|base| {
vec![
(
"/releases/latest".into(),
release_metadata(base, "v1.2.3", &[(&extension.asset_suffix(), b"extension")]),
),
("/attachments/0".into(), b"extension".to_vec()),
("/attachments/0".into(), b"extension".to_vec()),
]
});
let selected = source.resolve("latest", Instant::now()).unwrap();
assert!(Rc::ptr_eq(
&selected,
&source.resolve("latest", Instant::now()).unwrap()
));
assert!(Rc::ptr_eq(
&selected,
&source.resolve("1.2.3", Instant::now()).unwrap()
));
for _ in 0..2 {
let mut contents = Vec::new();
source
.open(&selected.version, &extension)
.unwrap()
.read_to_end(&mut contents)
.unwrap();
assert_eq!(contents, b"extension");
}
worker.join().unwrap();
}
#[test]
fn exact_versions_resolve_the_tag_endpoint_and_missing_or_foreign_assets_fail() {
let (source, worker) = serve(|base| {
vec![(
"/releases/tags/v1.2.3".into(),
release_metadata(base, "v1.2.3", &[("tar.zst", b"archive")]),
)]
});
let selected = source.resolve("1.2.3", Instant::now()).unwrap();
worker.join().unwrap();
assert!(selected.asset("manifest.sig").is_err());
let foreign = ReleaseAsset {
name: "asset".into(),
browser_download_url: "https://evil.example/archive".into(),
size: 7,
};
assert!(
source
.open_asset(&foreign, Instant::now())
.err()
.unwrap()
.contains("origin")
);
for version in ["../latest", "v1.2.3", "1.2.3-rc.1"] {
assert!(source.resolve(version, Instant::now()).is_err());
}
let (source, worker) = serve(|_| {
vec![(
"/releases/latest".into(),
release_metadata("https://evil.example", "v1.2.3", &[("tar.zst", b"archive")]),
)]
});
assert!(
source
.resolve("latest", Instant::now())
.err()
.unwrap()
.contains("origin")
);
worker.join().unwrap();
}
#[test]
fn release_metadata_downloads_are_bounded_and_asset_lengths_are_checked() {
let (source, worker) = serve(|_| {
vec![(
"/releases/latest".into(),
vec![b' '; METADATA_LIMIT as usize + 1],
)]
});
assert!(
source
.resolve("latest", Instant::now())
.err()
.unwrap()
.contains("size limit")
);
worker.join().unwrap();
let root = Download::new(&std::env::temp_dir()).unwrap();
for size in [2, 4] {
let (source, worker) = serve(|_| vec![("/attachments/manifest".into(), b"abc".to_vec())]);
let asset = ReleaseAsset {
name: "asset".into(),
browser_download_url: source.base.join("/attachments/manifest").unwrap().into(),
size,
};
assert!(
source
.metadata(&asset, &root.0.join("manifest.json"), Instant::now())
.unwrap_err()
.contains("size")
);
worker.join().unwrap();
assert!(!root.0.join("manifest.json").exists());
}
}
fn compressed_release(core: &[u8]) -> Vec<u8> {
@@ -396,6 +622,31 @@ fn explicit_download_verifies_the_complete_bundle_before_publishing_catalog() {
assert_eq!(fs::read_dir(layout.staging()).unwrap().count(), 0);
}
#[test]
fn latest_manifest_must_match_the_selected_tag_before_publication() {
let root = Download::new(&std::env::temp_dir()).unwrap();
let layout = Layout::new(root.0.join("machine"));
layout.initialize().unwrap();
let (_, bytes, signature, key) = signed_release();
fs::write(
layout.state().join("trusted-release-key"),
super::super::release::hex(&key),
)
.unwrap();
fs::write(layout.downloads().join("latest"), "0.9.0\n").unwrap();
let (source, worker) = release_server("latest", "v1.2.4", vec![bytes, signature]);
let error = prepare_from(&layout, "latest", &source).unwrap_err();
worker.join().unwrap();
assert!(error.contains("selected release tag"), "{error}");
assert_eq!(
fs::read_to_string(layout.downloads().join("latest")).unwrap(),
"0.9.0\n"
);
assert!(!layout.downloads().join("1.2.3").exists());
assert!(!layout.downloads().join("1.2.4").exists());
assert_eq!(fs::read_dir(layout.staging()).unwrap().count(), 0);
}
#[test]
fn bad_signatures_and_corrupt_payloads_preserve_the_previous_catalog_and_active_version() {
for bad_signature in [true, false] {
@@ -0,0 +1,150 @@
public type UserStatus {
Pending
Active
Disabled(reason: Text)
}
public type CreateInput {
id: Id
name: Text
email: Text
phone: Text?
score: Int
labels: Map<Text, Text>
}
public type CreateResult {
Created(user: User)
InvalidName(reason: Text)
InvalidEmail(reason: Text)
}
public type User {
id: Id
name: Text
email: Text
phone: Text?
score: Int
labels: Map<Text, Text>
status: UserStatus
}
public type UserView {
id: Id
name: Text
email: Text
phone: Text
score: Int
can_sign_in: Bool
}
public create(input: CreateInput) (result: CreateResult) {
normalized = normalize(input)
result = create_name(normalized, normalized.name)
}
normalize(input: CreateInput) (result: CreateInput) {
result = input
result.name = text.trim(input.name)
result.email = text.lower(text.trim(input.email))
}
create_name(input: CreateInput, name: "") (result: CreateResult) {
result = CreateResult.InvalidName("name is required")
}
create_name(input: CreateInput, name: other) (result: CreateResult) {
result = create_email(input, name, input.email)
}
create_email(input: CreateInput, name: Text, email: "") (result: CreateResult) {
result = CreateResult.InvalidEmail("email is required")
}
create_email(input: CreateInput, name: Text, email: other) (result: CreateResult) {
user = User {
id = input.id
name = name
email = email
phone = input.phone
score = input.score
labels = input.labels
status = UserStatus.Pending
}
result = CreateResult.Created(user)
}
public activate(user: User) (result: User) {
result = user
result.status = UserStatus.Active
}
public disable(user: User, reason: Text) (result: User) {
result = user
result.status = UserStatus.Disabled(reason)
}
public can_sign_in(status: UserStatus.Active) (allowed: Bool) {
allowed = true
}
public can_sign_in(status: other) (allowed: Bool) {
allowed = false
}
phone_text(phone: Text) (result: Text) {
result = phone
}
phone_text(phone: null) (result: Text) {
result = "Not provided"
}
public to_view(user: User) (view: UserView) {
view = UserView {
id = user.id
name = user.name
email = user.email
phone = phone_text(user.phone)
score = user.score
can_sign_in = can_sign_in(user.status)
}
}
public to_views(users: List<User>) (views: List<UserView>) {
views = each(to_view, users)
}
is_active(user: User) (result: Bool) {
result = can_sign_in(user.status)
}
public active_views(users: List<User>) (views: List<UserView>) {
active = filter(is_active, users)
views = each(to_view, active)
}
has_id(user: User, expected: Id) (matches: Bool) {
matches = user.id == expected
}
public find_user(users: List<User>, id: Id) (user: User?) {
user = find(has_id, users, id)
}
public put_label(user: User, key: Text, value: Text) (result: User) {
result = user
result.labels = put(user.labels, key, value)
}
public label(user: User, key: Text) (value: Text?) {
value = get(user.labels, key)
}
score_bonus(user: User, rate: Decimal) (amount: Decimal) {
amount = user.score * rate
}
public total_score_bonus(users: List<User>, rate: Decimal) (total: Decimal) {
total = sum(score_bonus, users, rate)
}
+1 -11
View File
@@ -4,17 +4,7 @@ use support::{checked, rejected, sources};
#[test]
fn full_language_contract_package_checks_with_the_official_sources() {
let prd = include_str!("../../../.trellis/tasks/09-04-dever-language-mvp/prd.md");
let source = prd
.split_once("## Complete Package Example\n")
.unwrap()
.1
.split_once("```dever\n")
.unwrap()
.1
.split_once("\n```")
.unwrap()
.0;
let source = include_str!("../fixtures/core/complete_package.dever");
let mut sources = dever_core::source::SourceMap::default();
sources.add("account/user.dever", source);
checked(&sources);
+1 -4
View File
@@ -189,10 +189,7 @@ fn current_examples_official_packages_and_complete_contract_roundtrip() {
assert_roundtrip_at(&path, source.text());
}
}
let prd = include_str!("../../../.trellis/tasks/09-04-dever-language-mvp/prd.md");
let (_, example) = prd.split_once("## Complete Package Example\n").unwrap();
let (_, fenced) = example.split_once("```dever\n").unwrap();
assert_roundtrip(fenced.split_once("\n```").unwrap().0);
assert_roundtrip(include_str!("../fixtures/core/complete_package.dever"));
}
fn source_root_relative(path: &FilePath) -> PathBuf {
+1 -4
View File
@@ -47,10 +47,7 @@ fn assigned(expression: &str) -> Expression {
#[test]
fn parses_the_complete_package_from_the_product_contract() {
let prd = include_str!("../../../.trellis/tasks/09-04-dever-language-mvp/prd.md");
let (_, example_section) = prd.split_once("## Complete Package Example\n").unwrap();
let (_, fenced) = example_section.split_once("```dever\n").unwrap();
let (source, _) = fenced.split_once("\n```").unwrap();
let source = include_str!("../fixtures/core/complete_package.dever");
let package = parsed(source);
assert_eq!(
package
+264
View File
@@ -0,0 +1,264 @@
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const { createRequire } = require('node:module');
const test = require('node:test');
const root = path.resolve(__dirname, '../..');
const extension = path.join(root, 'editor/vscode');
const extensionRequire = createRequire(path.join(extension, 'package.json'));
const textmate = extensionRequire('vscode-textmate');
const oniguruma = extensionRequire('vscode-oniguruma');
const cliArguments = process.argv.slice(2);
assert.equal(cliArguments.length, 2, 'pass --markdown-grammar followed by the real VS Code grammar path');
assert.equal(cliArguments[0], '--markdown-grammar');
const markdownPath = path.resolve(cliArguments[1]);
const manifest = JSON.parse(fs.readFileSync(path.join(extension, 'package.json'), 'utf8'));
const configuration = JSON.parse(fs.readFileSync(path.join(extension, 'language-configuration.json'), 'utf8'));
async function loadGrammars() {
const wasm = fs.readFileSync(extensionRequire.resolve('vscode-oniguruma/release/onig.wasm'));
await oniguruma.loadWASM(wasm.buffer.slice(wasm.byteOffset, wasm.byteOffset + wasm.byteLength));
const catalog = new Map();
for (const contribution of manifest.contributes.grammars) {
const filename = path.join(extension, contribution.path);
catalog.set(contribution.scopeName, textmate.parseRawGrammar(fs.readFileSync(filename, 'utf8'), filename));
}
const markdown = textmate.parseRawGrammar(fs.readFileSync(markdownPath, 'utf8'), markdownPath);
assert.equal(markdown.scopeName, 'text.html.markdown');
catalog.set(markdown.scopeName, markdown);
const registry = new textmate.Registry({
onigLib: Promise.resolve({
createOnigScanner: patterns => new oniguruma.OnigScanner(patterns),
createOnigString: value => new oniguruma.OnigString(value)
}),
loadGrammar: scope => catalog.get(scope) || null,
getInjections: scope => manifest.contributes.grammars
.filter(contribution => contribution.injectTo?.includes(scope))
.map(contribution => contribution.scopeName)
});
return {
source: await registry.loadGrammarWithConfiguration('source.dever', 1, {}),
markdown: await registry.loadGrammarWithConfiguration('text.html.markdown', 2, {
embeddedLanguages: { 'meta.embedded.block.dever': 1 },
tokenTypes: { 'meta.embedded.block.dever': 0 }
})
};
}
const grammars = loadGrammars();
function tokenize(grammar, document) {
let state = textmate.INITIAL;
return document.split(/\r\n|\r|\n/).map(line => {
const encoded = grammar.tokenizeLine2(line, state);
const result = grammar.tokenizeLine(line, state);
state = result.ruleStack;
return { line, tokens: result.tokens, encoded: encoded.tokens };
});
}
function tokenAt(rows, line, fragment) {
const row = rows[line];
const offset = row.line.indexOf(fragment);
assert.ok(offset >= 0, `line ${line + 1} does not contain ${fragment}`);
const token = row.tokens.find(candidate => candidate.startIndex <= offset && candidate.endIndex > offset);
assert.ok(token, `no token for ${fragment} on line ${line + 1}`);
return token;
}
function hasScope(rows, line, fragment, scope) {
const actual = tokenAt(rows, line, fragment).scopes;
assert.ok(actual.includes(scope), `${fragment}: expected ${scope}, received ${actual.join(' ')}`);
}
function sourceFiles(directory, suffix) {
return fs.readdirSync(directory, { withFileTypes: true }).flatMap(entry => {
const filename = path.join(directory, entry.name);
return entry.isDirectory() ? sourceFiles(filename, suffix) : filename.endsWith(suffix) ? [filename] : [];
});
}
test('types, fields and named function inputs/outputs receive Dever scopes', async () => {
const rows = tokenize((await grammars).source, [
'type Profile {',
' id: Int?',
' name: Text',
'}',
'greet(name: Text) (message: Text) {',
' message = app.format(name)',
'}'
].join('\n'));
hasScope(rows, 0, 'type', 'storage.type.dever');
hasScope(rows, 0, 'Profile', 'entity.name.type.dever');
hasScope(rows, 1, 'id', 'variable.other.member.dever');
hasScope(rows, 1, 'Int', 'support.type.dever');
hasScope(rows, 4, 'greet', 'entity.name.function.dever');
hasScope(rows, 4, 'name', 'variable.other.member.dever');
hasScope(rows, 4, 'message', 'variable.other.member.dever');
hasScope(rows, 5, 'app', 'entity.name.namespace.dever');
hasScope(rows, 5, 'format', 'entity.name.function.dever');
});
test('API, Model, Job and external Adapter declarations are covered', async () => {
const source = (await grammars).source;
for (const method of ['get', 'post', 'put', 'delete', 'cmd', 'rest']) {
const rows = tokenize(source, `${method} profile = app.profile`);
hasScope(rows, 0, method, 'keyword.control.api.dever');
}
const rows = tokenize(source, [
'public get profile = app.profile',
' private owner user_id: user.account.model.id = dever.auth.user_id()',
'index(user_id, created_at)',
'sql owned_articles(id: Int) (articles: List<Article>) {',
'job send(message: Text) () fails app.Error {',
'schedule send_every_minute {',
'external npm "worker.js" {}'
].join('\n'));
hasScope(rows, 0, 'public', 'storage.modifier.dever');
hasScope(rows, 1, 'owner', 'storage.modifier.dever');
hasScope(rows, 2, 'index', 'keyword.other.declaration.dever');
hasScope(rows, 3, 'sql', 'keyword.other.declaration.dever');
hasScope(rows, 4, 'job', 'storage.modifier.dever');
hasScope(rows, 4, 'fails', 'storage.modifier.dever');
hasScope(rows, 5, 'schedule', 'keyword.other.declaration.dever');
hasScope(rows, 6, 'npm', 'keyword.other.external.dever');
});
test('hash comments, quoted text and escapes do not leak into subsequent lines', async () => {
const rows = tokenize((await grammars).source, [
'# type Fake and return',
String.raw`message = "中文 # and type \n \u{4E2D} \""`,
String.raw`invalid = "\q"`,
'broken = "unfinished',
'type Next { value: Text }'
].join('\n'));
hasScope(rows, 0, 'type', 'comment.line.number-sign.dever');
hasScope(rows, 1, '#', 'string.quoted.double.dever');
hasScope(rows, 1, '\\n', 'constant.character.escape.dever');
hasScope(rows, 1, '\\u{4E2D}', 'constant.character.escape.dever');
hasScope(rows, 2, '\\q', 'invalid.illegal.escape.dever');
hasScope(rows, 4, 'type', 'storage.type.dever');
assert.ok(!tokenAt(rows, 4, 'type').scopes.includes('string.quoted.double.dever'));
});
test('integer division is an operator, not a JavaScript line comment', async () => {
const rows = tokenize((await grammars).source, 'count = 12 // 3\namount = 1.25e-2\nvalid = true and not false');
hasScope(rows, 0, '//', 'keyword.operator.dever');
hasScope(rows, 0, '3', 'constant.numeric.dever');
assert.ok(!tokenAt(rows, 0, '3').scopes.some(scope => scope.startsWith('comment.')));
hasScope(rows, 1, '1.25e-2', 'constant.numeric.dever');
hasScope(rows, 2, 'true', 'constant.language.dever');
hasScope(rows, 2, 'and', 'keyword.operator.logical.dever');
});
test('excluded control-flow syntax is not presented as a valid function', async () => {
const rows = tokenize((await grammars).source, 'if(value)\nreturn value\nimport other\npackage main');
for (const [line, keyword] of ['if', 'return', 'import', 'package'].entries()) {
hasScope(rows, line, keyword, 'invalid.illegal.keyword.dever');
}
});
test('Markdown fences reuse source.dever while prose keeps Markdown scopes', async () => {
for (const marker of ['```', '~~~']) {
for (const language of ['dever', 'typescript dever']) {
const rows = tokenize((await grammars).markdown, [
'# 文档', '', '## 类型', `${marker}${language}`,
'type Profile { name: Text }', marker, '', '正文 type Profile'
].join('\n'));
hasScope(rows, 4, 'type', 'storage.type.dever');
hasScope(rows, 4, 'Text', 'support.type.dever');
hasScope(rows, 4, 'Profile', 'meta.embedded.block.dever');
assert.ok(!tokenAt(rows, 7, 'type').scopes.includes('source.dever'));
}
}
});
test('embedded tokens carry the Dever language ID and are not string tokens', async () => {
const rows = tokenize((await grammars).markdown, '```dever\ntype Profile {}\n```');
const metadata = rows[1].encoded[1];
assert.equal(metadata & 0xff, 1);
assert.equal((metadata >>> 8) & 3, 0);
});
test('fence closing honors its character and minimum opening length', async () => {
const rows = tokenize((await grammars).markdown, [
'````dever', 'type First {}', '```', 'type Second {}',
'~~~~', 'type Third {}', '`````', '正文 type Outside'
].join('\n'));
for (const line of [1, 3, 5]) hasScope(rows, line, 'type', 'storage.type.dever');
assert.ok(!tokenAt(rows, 7, 'type').scopes.includes('source.dever'));
const waves = tokenize((await grammars).markdown, '~~~dever\ntype Inside {}\n~~~~\n正文 type Outside');
assert.ok(!tokenAt(waves, 3, 'type').scopes.includes('source.dever'));
});
test('valid indentation and CRLF work without taking over other language fences', async () => {
const rows = tokenize((await grammars).markdown, ' ```dever\r\ntype Profile {}\r\n ```\r\n正文');
hasScope(rows, 1, 'type', 'storage.type.dever');
for (const language of ['typescript', 'javascript', 'dever extra', 'typescript dever']) {
const other = tokenize((await grammars).markdown, `\`\`\`${language}\ntype Profile {}\n\`\`\``);
assert.ok(!tokenAt(other, 1, 'type').scopes.includes('source.dever'), language);
}
});
test('quotes, lists, showcase fences, indented code and comments retain Markdown scopes', async () => {
for (const [document, line] of [
['> ```dever\n> type Quoted {}\n> ```', 1],
['- 示例\n\n ```dever\n type Nested {}\n ```', 3],
['````markdown\n```dever\ntype Showcase {}\n```\n````', 2],
[' ```dever\n type Indented {}\n ```', 1],
['<!--\n```dever\ntype Comment {}\n```\n-->', 2]
]) {
const rows = tokenize((await grammars).markdown, document);
assert.ok(!tokenAt(rows, line, 'type').scopes.includes('source.dever'), document);
}
});
test('language configuration uses Dever comments, pairs and indentation', () => {
assert.equal(configuration.comments.lineComment, '#');
assert.equal(configuration.comments.blockComment, undefined);
assert.deepEqual(configuration.brackets, [['{', '}'], ['[', ']'], ['(', ')']]);
const increase = new RegExp(configuration.indentationRules.increaseIndentPattern);
const decrease = new RegExp(configuration.indentationRules.decreaseIndentPattern);
for (const line of ['greet(name: Text) (message: Text) {', ' values = [', 'type Profile { # 字段']) {
assert.ok(increase.test(line), line);
}
for (const line of ['# example {', 'message = "literal {"', 'message = "value" # {']) {
assert.ok(!increase.test(line), line);
}
assert.ok(decrease.test(' }'));
assert.ok(decrease.test(' ]'));
assert.ok(configuration.autoClosingPairs.every(pair => pair.notIn.includes('string') && pair.notIn.includes('comment')));
assert.deepEqual(manifest.contributes.languages[0].extensions, ['.dever']);
const injection = manifest.contributes.grammars.find(contribution => contribution.injectTo?.includes('text.html.markdown'));
assert.deepEqual(injection.embeddedLanguages, { 'meta.embedded.block.dever': 'dever' });
assert.deepEqual(injection.tokenTypes, { 'meta.embedded.block.dever': 'other' });
assert.equal(manifest.main, undefined);
assert.equal(manifest.dependencies, undefined);
});
test('maintained plain and Markdown CMS files tokenize without illegal tokens', async () => {
const loaded = await grammars;
for (const [directory, suffix, grammar] of [
['examples/cms/dever', '.dever', loaded.source],
['examples/cms/md', '.dever.md', loaded.markdown]
]) {
const files = sourceFiles(path.join(root, directory), suffix);
assert.ok(files.length > 0, directory);
let highlighted = 0;
for (const filename of files) {
const rows = tokenize(grammar, fs.readFileSync(filename, 'utf8'));
let fileHighlighted = 0;
for (const row of rows) {
for (const token of row.tokens) {
assert.ok(!token.scopes.some(scope => scope.startsWith('invalid.illegal.')), `${filename}: ${row.line}`);
if (token.scopes.includes('source.dever')) fileHighlighted += 1;
}
}
assert.ok(fileHighlighted > 0, `${filename} must actually enter the Dever grammar`);
highlighted += fileHighlighted;
}
assert.ok(highlighted > files.length, `${directory} must actually enter the Dever grammar`);
process.stdout.write(`CMS ${directory}: ${files.length} files, ${highlighted} Dever tokens\n`);
}
});
+85 -16
View File
@@ -88,6 +88,15 @@ class InstallerTests(unittest.TestCase):
def create_assets(self, output):
return assets.create(self.release, output, Path("/usr/bin/zstd"))
def release_record(self, directory, version="1.2.3"):
return {
"tag_name": f"v{version}", "draft": False, "prerelease": False,
"assets": [{"name": path.name, "size": path.stat().st_size,
"browser_download_url":
f"https://git.dever.cc/shemic/dever/releases/download/v{version}/{path.name}"}
for path in sorted(directory.iterdir())],
}
def archive_payloads(self, path):
contents = subprocess.run(["/usr/bin/zstd", "-q", "-d", "-c", str(path)],
env={}, capture_output=True, check=True, timeout=15).stdout
@@ -212,10 +221,14 @@ class InstallerTests(unittest.TestCase):
assets_path = self.root / "assets"
self.create_assets(assets_path)
calls = []
record = self.release_record(assets_path)
with mock.patch.object(installer, "download", return_value=io.BytesIO(json.dumps(record).encode())):
version, attachments = installer.resolve_release("latest")
self.assertEqual(version, "1.2.3")
def download(address):
self.assertTrue(address.startswith(
f"https://git.dever.cc/shemic/dever/dist/1.2.3/dever-{self.platform}."))
f"https://git.dever.cc/shemic/dever/releases/download/v1.2.3/dever-{self.platform}."))
return (assets_path / address.rsplit("/", 1)[-1]).open("rb")
def extract(command, **kwargs):
@@ -233,8 +246,8 @@ class InstallerTests(unittest.TestCase):
with mock.patch.object(installer, "download", side_effect=download), \
mock.patch.object(installer.subprocess, "run", side_effect=extract):
installer.payload(self.directory("network"), None, "1.2.3", self.platform,
self.catalog, self.public)
installer.payload(self.directory("network"), None, self.platform,
self.catalog, self.public, attachments)
self.assertEqual(len(calls), 1)
self.assertEqual(list(self.root.glob(".extract-*")), [])
@@ -244,7 +257,7 @@ class InstallerTests(unittest.TestCase):
with mock.patch.object(installer, "download", side_effect=download), \
mock.patch.object(installer.subprocess, "run") as execute, \
self.assertRaises(ValueError):
installer.payload(self.release, None, "1.2.3", self.platform, self.catalog, self.public)
installer.payload(self.release, None, self.platform, self.catalog, self.public, attachments)
execute.assert_not_called()
def test_download_archive_is_bounded(self):
@@ -255,19 +268,19 @@ class InstallerTests(unittest.TestCase):
def test_local_payload_rejects_symlink_and_source_directory(self):
copied = self.directory("copied")
installer.payload(copied, self.release, "1.2.3", self.platform, self.catalog)
installer.payload(copied, self.release, self.platform, self.catalog)
self.assertEqual((copied / "dever-core").read_bytes(), self.payloads["dever-core"])
core = self.release / "dever-core"
core.unlink()
core.symlink_to(self.public)
with self.assertRaisesRegex(ValueError, "符号链接"):
installer.payload(self.directory("symlink"), self.release, "1.2.3", self.platform, self.catalog)
installer.payload(self.directory("symlink"), self.release, self.platform, self.catalog)
with self.assertRaisesRegex(ValueError, "symlinks"):
self.create_assets(self.root / "invalid-assets")
core.unlink()
core.mkdir()
with self.assertRaisesRegex(ValueError, "类型"):
installer.payload(self.directory("directory"), self.release, "1.2.3", self.platform, self.catalog)
installer.payload(self.directory("directory"), self.release, self.platform, self.catalog)
def test_image_install_authenticates_before_mock_bootstrap_and_cleans_owned_staging(self):
image = self.directory("image")
@@ -302,27 +315,83 @@ class InstallerTests(unittest.TestCase):
self.assertEqual(len(calls), 1)
self.assertEqual(list(image.iterdir()), [])
def test_metadata_uses_latest_or_exact_directory_and_never_a_github_fallback(self):
for version in ("latest", "1.2.3"):
destination = self.directory(version)
def test_metadata_pins_latest_or_exact_release_before_downloading_matching_assets(self):
assets_path = self.root / "assets"
self.create_assets(assets_path)
record = self.release_record(assets_path)
for requested, endpoint in (("latest", "latest"), ("1.2.3", "tags/v1.2.3")):
destination = self.directory(requested)
addresses = []
def download(address):
addresses.append(address)
return io.BytesIO(b"metadata")
if address == f"{installer.RELEASES}/{endpoint}":
return io.BytesIO(json.dumps(record).encode())
return (assets_path / address.rsplit("/", 1)[-1]).open("rb")
with mock.patch.object(installer, "download", side_effect=download):
installer.metadata(destination, None, version, self.platform)
self.assertEqual(addresses, [
f"https://git.dever.cc/shemic/dever/dist/{version}/dever-{self.platform}.{suffix}"
selected, attachments = installer.resolve_release(requested)
installer.metadata(destination, None, attachments, self.platform)
authenticated, _, _ = installer.authenticate(destination, self.public, self.scratch,
self.platform, selected)
self.assertEqual(authenticated, "1.2.3")
self.assertEqual(addresses, [f"{installer.RELEASES}/{endpoint}"] + [
f"https://git.dever.cc/shemic/dever/releases/download/v1.2.3/dever-{self.platform}.{suffix}"
for suffix in ("manifest.json", "manifest.sig")
])
def test_release_metadata_rejects_unpublished_ambiguous_and_foreign_assets(self):
directory = self.root / "assets"
self.create_assets(directory)
valid = self.release_record(directory)
mutations = [
lambda record: record.update(draft=True),
lambda record: record.update(prerelease=True),
lambda record: record.update(tag_name="v01.2.3"),
lambda record: record.update(tag_name="v1.2.4"),
lambda record: record["assets"].append(record["assets"][0]),
lambda record: record["assets"][0].update(size=True),
lambda record: record["assets"][0].update(browser_download_url="https://evil.example/asset"),
lambda record: record.update(assets=[]),
lambda record: record.update(assets=[record["assets"][0]] * 513),
]
for mutate in mutations:
record = json.loads(json.dumps(valid))
mutate(record)
with self.subTest(record=record["tag_name"]), \
mock.patch.object(installer, "download", return_value=io.BytesIO(json.dumps(record).encode())), \
self.assertRaises(ValueError):
installer.resolve_release("1.2.3")
with mock.patch.object(installer, "MAX_METADATA", 10), \
mock.patch.object(installer, "download", return_value=io.BytesIO(b" " * 11)), \
self.assertRaisesRegex(ValueError, "大小限制"):
installer.resolve_release("latest")
def test_latest_tag_must_match_signed_manifest_and_required_assets_must_exist(self):
directory = self.root / "assets"
self.create_assets(directory)
record = self.release_record(directory, "1.2.4")
with mock.patch.object(installer, "download", return_value=io.BytesIO(json.dumps(record).encode())):
selected, attachments = installer.resolve_release("latest")
with self.assertRaisesRegex(ValueError, "版本与请求不匹配"):
installer.authenticate(self.release, self.public, self.scratch, self.platform, selected)
with self.assertRaisesRegex(ValueError, "缺少附件"):
installer.asset(attachments, self.platform, "missing.tar.zst")
with self.assertRaisesRegex(ValueError, "大小限制"):
installer.asset(attachments, self.platform, "manifest.json", 1)
def test_metadata_rejects_truncated_api_attachment(self):
name = f"dever-{self.platform}.manifest.json"
attachments = {name: {"address": "https://git.dever.cc/asset", "bytes": 20}}
with mock.patch.object(installer, "download", return_value=io.BytesIO(b"short")), \
self.assertRaisesRegex(ValueError, "长度不匹配"):
installer.metadata(self.directory("truncated"), None, attachments, self.platform)
def test_redirects_remain_on_the_official_https_site(self):
handler = installer.OfficialRedirect()
current = "https://git.dever.cc/shemic/dever/dist/latest/a"
current = "https://git.dever.cc/shemic/dever/releases/download/v1.2.3/a"
request = installer.urllib.request.Request(current)
allowed = "https://git.dever.cc/shemic/dever/dist/1.2.3/a"
allowed = "https://git.dever.cc/attachments/fixture"
redirected = handler.redirect_request(request, None, 302, "Found", {}, allowed)
self.assertEqual(redirected.full_url, allowed)
for address in (
+14 -11
View File
@@ -3,6 +3,7 @@
import argparse
import hashlib
import io
import json
import os
from pathlib import Path
@@ -198,18 +199,20 @@ def install_assets(assets, image, manifest):
# Only transport is local: signature verification, full tar extraction and
# the final bootstrap executable are the real first-install implementation.
installer = load_installer()
stem = f"dever-{manifest['platform']}"
base = installer.RELEASES
routes = {
f"{base}/latest/{stem}.manifest.json": assets / f"{stem}.manifest.json",
f"{base}/latest/{stem}.manifest.sig": assets / f"{stem}.manifest.sig",
f"{base}/{manifest['version']}/{stem}.tar.zst": assets / f"{stem}.tar.zst",
base = f"https://git.dever.cc/shemic/dever/releases/download/v{manifest['version']}"
routes = {f"{base}/{path.name}": path for path in assets.iterdir() if path.is_file()}
record = {
"tag_name": f"v{manifest['version']}", "draft": False, "prerelease": False,
"assets": [{"name": path.name, "size": path.stat().st_size,
"browser_download_url": address} for address, path in routes.items()],
}
for artifact in manifest["artifacts"]:
if artifact["path"] == "bootstrap/dever" or artifact["path"].startswith("bootstrap/lib/"):
name = f"{stem}.blob-{artifact['sha256']}"
routes[f"{base}/{manifest['version']}/{name}"] = assets / name
installer.download = lambda address: routes[address].open("rb")
def download(address):
if address == f"{installer.RELEASES}/latest":
return io.BytesIO(json.dumps(record).encode())
return routes[address].open("rb")
installer.download = download
installer.install(argparse.Namespace(
version="latest", release=None, system_root=image, no_start=True,
trusted_key=WORKSPACE / "skills/assets/release.pub",