blob: 1218d8b338a19e14df5ed43d2e9e20982a716f35 [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
use std::{
collections::BTreeMap,
env, fs,
path::{Path, PathBuf},
};
const AUTHORING_RESOURCES: &[&str] = &[
"agents/openai.yaml",
"assets/starter/create.mjs",
"assets/starter/host.mjs",
"assets/starter/tui.mjs",
"assets/starter/verify-host.py",
"references/api-map.md",
"references/models-and-execution.md",
"references/native-rust.md",
"references/product-sources.md",
"references/quickstart.md",
"references/runtime-ui.md",
"references/state-and-authority.md",
"references/terminal-apps.md",
"references/testing-and-shipping.md",
"references/tools-and-input.md",
];
const SDK_RESOURCES: &[&str] = &[
"configurations.ts",
"contracts.ts",
"generated/runtime.ts",
"index.ts",
"native.ts",
"ui.ts",
];
fn declared_file(root: &Path, relative: &str) -> PathBuf {
let mut path = root.to_owned();
let mut components = Path::new(relative).components().peekable();
while let Some(component) = components.next() {
path.push(component);
let kind = fs::symlink_metadata(&path)
.expect("declared bundled skill input")
.file_type();
assert!(
if components.peek().is_some() {
kind.is_dir()
} else {
kind.is_file()
},
"declared bundled skills contain only regular files/directories, not symlinks: {}",
path.display()
);
}
println!("cargo:rerun-if-changed={}", path.display());
path
}
fn collect_declared(
root: &Path,
resources: &[&str],
prefix: &str,
files: &mut BTreeMap<String, PathBuf>,
) {
for relative in resources {
let path = declared_file(root, relative);
assert!(
files.insert(format!("{prefix}{relative}"), path).is_none(),
"duplicate bundled resource"
);
}
}
/// A build-time allowlist keeps artifacts, credentials and unrelated source out
/// even when the source distribution has no Git metadata.
fn collect_sources(repo: &Path, directory: &Path, files: &mut BTreeMap<String, PathBuf>) {
println!("cargo:rerun-if-changed={}", directory.display());
for entry in fs::read_dir(directory).expect("authoring source directory") {
let entry = entry.expect("authoring source entry");
let path = entry.path();
let kind = entry.file_type().unwrap();
assert!(
!kind.is_symlink(),
"authoring sources must not follow symlinks"
);
if kind.is_dir() {
// Runtime implementations and generated/build output are not examples.
continue;
}
if !kind.is_file() || path.extension().is_none_or(|extension| extension != "ts") {
continue;
}
let relative = path
.strip_prefix(repo)
.unwrap()
.to_str()
.unwrap()
.replace('\\', "/");
assert!(
files
.insert(format!("references/source/{relative}"), path)
.is_none()
);
}
}
// Contracts are one generated object in the SDK. Publish its members separately
// so every contract remains readable through the skill's 1 MiB resource bound.
fn split_contracts(files: &mut BTreeMap<String, PathBuf>) {
let original = files
.remove("references/sdk/contracts.ts")
.expect("SDK contracts");
println!("cargo:rerun-if-changed={}", original.display());
let source = fs::read_to_string(original).unwrap();
let mut members = Vec::new();
let mut offset = 0;
for line in source.split_inclusive('\n') {
if let Some(name) = line
.strip_prefix(" ")
.and_then(|line| line.strip_suffix(": {\n"))
&& !name.is_empty()
&& name
.bytes()
.all(|byte| byte.is_ascii_alphanumeric() || byte == b'_')
{
members.push((name.to_owned(), offset));
}
offset += line.len();
}
assert!(!members.is_empty(), "generated contract layout changed");
let out = PathBuf::from(env::var_os("OUT_DIR").unwrap()).join("authoring-sdk");
fs::create_dir_all(out.join("contracts")).unwrap();
let header = source.split("// Generated from").next().unwrap();
let mut index = header.to_owned();
for (at, (name, start)) in members.iter().enumerate() {
let end = members
.get(at + 1)
.map(|(_, offset)| *offset)
.unwrap_or_else(|| source.rfind("} satisfies").expect("contract object ending"));
let entry = source[*start..end].trim();
let body = entry
.strip_prefix(&format!("{name}: "))
.unwrap()
.strip_suffix(',')
.unwrap();
let path = out.join("contracts").join(format!("{name}.ts"));
fs::write(&path, format!("{header}import type {{ Contract }} from '../generated/runtime.js';\nexport const {name} = {body} satisfies Contract;\n")).unwrap();
files.insert(format!("references/sdk/contracts/{name}.ts"), path);
index.push_str(&format!(
"import {{ {name} }} from './contracts/{name}.js';\n"
));
}
index.push_str(&format!(
"export const contracts = {{ {} }};\n",
members
.iter()
.map(|(name, _)| name.as_str())
.collect::<Vec<_>>()
.join(", ")
));
let path = out.join("contracts.ts");
fs::write(&path, index).unwrap();
files.insert("references/sdk/contracts.ts".into(), path);
}
fn main() {
let crate_dir = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap());
let repo = crate_dir
.parent()
.unwrap()
.parent()
.unwrap()
.parent()
.unwrap();
let mut source = String::from("const BUNDLED: &[Bundled] = &[\n");
for (id, relative, resources) in [
("maka-cua", "platform/computer-use/skills/maka-cua", &[][..]),
(
"maka-plugin-authoring",
"skills/maka-plugin-authoring",
AUTHORING_RESOURCES,
),
] {
let root = repo.join(relative);
let mut files = BTreeMap::new();
collect_declared(&root, resources, "", &mut files);
if id == "maka-plugin-authoring" {
let sdk = repo.join("packages/plugin-sdk/src");
collect_declared(&sdk, SDK_RESOURCES, "references/sdk/", &mut files);
split_contracts(&mut files);
for relative in [
"plugins/shared",
"plugins/application/authoring",
"plugins/application/gateway",
"products/maka/bundle/src",
] {
collect_sources(repo, &repo.join(relative), &mut files);
}
let mut index = String::from(
"# Sources from this build\n\nRead a path with the skill resource reader. Paths under `references/source/` preserve checkout paths; SDK imports resolve to the existing `references/sdk/` resources.\n\n",
);
for (name, path) in &files {
if name.starts_with("references/source/") {
index.push_str(&format!(
"- `{name}` ({} bytes)\n",
fs::metadata(path).unwrap().len()
));
}
}
let index_path =
PathBuf::from(env::var_os("OUT_DIR").unwrap()).join("authoring-source-index.md");
fs::write(&index_path, index).unwrap();
files.insert("references/source-index.md".into(), index_path);
}
let document = declared_file(&root, "SKILL.md");
source.push_str(&format!(
"Bundled {{ id: {id:?}, content: include_str!({:?}), files: &[\n",
document.to_str().unwrap()
));
for (name, path) in files {
if path.starts_with(repo) {
println!("cargo:rerun-if-changed={}", path.display());
}
source.push_str(&format!(
"({name:?}, include_bytes!({:?})),\n",
path.to_str().unwrap()
));
}
source.push_str("] },\n");
}
source.push_str("];\n");
fs::write(
PathBuf::from(env::var_os("OUT_DIR").unwrap()).join("bundled.rs"),
source,
)
.unwrap();
}