chore: import upstream snapshot with attribution
This commit is contained in:
+77
@@ -0,0 +1,77 @@
|
||||
#!/usr/bin/env bash
|
||||
# Cross-compile the `native` CLI for every supported platform and stage
|
||||
# the binaries where the release needs them:
|
||||
# - packages/native-sdk/npm/<platform>/bin/native[.exe] (npm packages)
|
||||
# - zig-out/release/native-sdk-<platform>[.exe] (GitHub release
|
||||
# assets, flat names + CHECKSUMS.txt)
|
||||
#
|
||||
# Zig cross-compiles all eight targets from any host; `zig build cli`
|
||||
# builds only the CLI executable so the loop stays fast. Run from anywhere
|
||||
# inside the repo. Pass a subset of npm platform keys to build fewer
|
||||
# targets (e.g. `build-binaries.sh darwin-arm64`).
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)"
|
||||
npm_dir="$repo_root/packages/native-sdk/npm"
|
||||
release_dir="$repo_root/zig-out/release"
|
||||
|
||||
# npm-platform-key zig-target release-asset-name
|
||||
targets=(
|
||||
"darwin-arm64 aarch64-macos native-sdk-darwin-arm64"
|
||||
"darwin-x64 x86_64-macos native-sdk-darwin-x64"
|
||||
"linux-arm64-gnu aarch64-linux-gnu native-sdk-linux-arm64"
|
||||
"linux-x64-gnu x86_64-linux-gnu native-sdk-linux-x64"
|
||||
"linux-arm64-musl aarch64-linux-musl native-sdk-linux-musl-arm64"
|
||||
"linux-x64-musl x86_64-linux-musl native-sdk-linux-musl-x64"
|
||||
"win32-arm64 aarch64-windows native-sdk-win32-arm64.exe"
|
||||
"win32-x64 x86_64-windows native-sdk-win32-x64.exe"
|
||||
)
|
||||
|
||||
selected=("$@")
|
||||
|
||||
want() {
|
||||
[ ${#selected[@]} -eq 0 ] && return 0
|
||||
local key="$1"
|
||||
for s in "${selected[@]}"; do
|
||||
[ "$s" = "$key" ] && return 0
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
mkdir -p "$release_dir"
|
||||
|
||||
built=0
|
||||
for entry in "${targets[@]}"; do
|
||||
read -r key target asset <<<"$entry"
|
||||
want "$key" || continue
|
||||
|
||||
ext=""
|
||||
case "$asset" in *.exe) ext=".exe" ;; esac
|
||||
|
||||
prefix="$repo_root/zig-out/cli/$key"
|
||||
echo "==> $key ($target)"
|
||||
(cd "$repo_root" && zig build cli -Dtarget="$target" -Doptimize=ReleaseSmall --prefix "$prefix")
|
||||
|
||||
built_binary="$prefix/bin/native$ext"
|
||||
[ -f "$built_binary" ] || { echo "error: expected $built_binary after build" >&2; exit 1; }
|
||||
|
||||
mkdir -p "$npm_dir/$key/bin"
|
||||
cp "$built_binary" "$npm_dir/$key/bin/native$ext"
|
||||
chmod 755 "$npm_dir/$key/bin/native$ext"
|
||||
|
||||
cp "$built_binary" "$release_dir/$asset"
|
||||
chmod 755 "$release_dir/$asset"
|
||||
built=$((built + 1))
|
||||
done
|
||||
|
||||
if [ "$built" -eq 0 ]; then
|
||||
echo "error: no targets matched: $*" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
(cd "$release_dir" && shasum -a 256 native-sdk-* > CHECKSUMS.txt)
|
||||
|
||||
echo
|
||||
echo "Staged $built binaries:"
|
||||
ls -lh "$release_dir" | awk 'NR>1 {printf " %-36s %s\n", $NF, $5}'
|
||||
@@ -0,0 +1,157 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { existsSync, lstatSync, readdirSync, readFileSync, readlinkSync } from 'fs';
|
||||
import { dirname, join, relative, sep } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const projectRoot = join(__dirname, '..');
|
||||
const repoRoot = join(projectRoot, '..', '..');
|
||||
|
||||
const mirrors = [
|
||||
{ source: 'src', target: 'src' },
|
||||
{ source: 'build', target: 'build' },
|
||||
{ source: 'assets', target: 'assets' },
|
||||
{ source: 'build.zig', target: 'build.zig' },
|
||||
{ source: 'build.zig.zon', target: 'build.zig.zon' },
|
||||
{ source: 'app.zon', target: 'app.zon' },
|
||||
{ source: 'LICENSE', target: 'LICENSE' },
|
||||
{ source: 'skills', target: 'skills' },
|
||||
{ source: 'skill-data', target: 'skill-data' },
|
||||
{ source: 'third_party/webview2', target: 'third_party/webview2' },
|
||||
// The @native-sdk/core closure TypeScript app builds resolve from the
|
||||
// installed package: the transpiler, the SDK library modules (also the
|
||||
// editor package), the rt kernel, and the manifest + lockfile that make
|
||||
// `npm ci` work inside packages/core on the npm-installed layout.
|
||||
// Entry-by-entry on purpose — test/ and scripts/ are repo-dev surface
|
||||
// and must NOT ship, so the whole-directory comparison stays off.
|
||||
{ source: 'packages/core/src', target: 'packages/core/src' },
|
||||
{ source: 'packages/core/sdk', target: 'packages/core/sdk' },
|
||||
{ source: 'packages/core/rt', target: 'packages/core/rt' },
|
||||
{ source: 'packages/core/package.json', target: 'packages/core/package.json' },
|
||||
{ source: 'packages/core/package-lock.json', target: 'packages/core/package-lock.json' },
|
||||
];
|
||||
|
||||
const errors = [];
|
||||
|
||||
function displayPath(path) {
|
||||
return relative(repoRoot, path).split(sep).join('/');
|
||||
}
|
||||
|
||||
function addError(message) {
|
||||
errors.push(message);
|
||||
}
|
||||
|
||||
function compareEntries(sourcePath, targetPath) {
|
||||
if (!existsSync(sourcePath)) {
|
||||
addError(`missing source ${displayPath(sourcePath)}`);
|
||||
return;
|
||||
}
|
||||
if (!existsSync(targetPath)) {
|
||||
addError(`missing mirror ${displayPath(targetPath)}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const sourceStat = lstatSync(sourcePath);
|
||||
const targetStat = lstatSync(targetPath);
|
||||
|
||||
if (sourceStat.isSymbolicLink() || targetStat.isSymbolicLink()) {
|
||||
if (!sourceStat.isSymbolicLink() || !targetStat.isSymbolicLink()) {
|
||||
addError(`type mismatch ${displayPath(sourcePath)} -> ${displayPath(targetPath)}`);
|
||||
return;
|
||||
}
|
||||
const sourceLink = readlinkSync(sourcePath);
|
||||
const targetLink = readlinkSync(targetPath);
|
||||
if (sourceLink !== targetLink) {
|
||||
addError(`symlink mismatch ${displayPath(sourcePath)} -> ${displayPath(targetPath)}`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (sourceStat.isDirectory() || targetStat.isDirectory()) {
|
||||
if (!sourceStat.isDirectory() || !targetStat.isDirectory()) {
|
||||
addError(`type mismatch ${displayPath(sourcePath)} -> ${displayPath(targetPath)}`);
|
||||
return;
|
||||
}
|
||||
const sourceNames = readdirSync(sourcePath).filter((name) => name !== '.DS_Store').sort();
|
||||
const targetNames = readdirSync(targetPath).filter((name) => name !== '.DS_Store').sort();
|
||||
const names = new Set([...sourceNames, ...targetNames]);
|
||||
for (const name of names) {
|
||||
compareEntries(join(sourcePath, name), join(targetPath, name));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!sourceStat.isFile() || !targetStat.isFile()) {
|
||||
addError(`unsupported entry type ${displayPath(sourcePath)} -> ${displayPath(targetPath)}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const sourceBytes = readFileSync(sourcePath);
|
||||
const targetBytes = readFileSync(targetPath);
|
||||
if (!sourceBytes.equals(targetBytes)) {
|
||||
addError(`content mismatch ${displayPath(sourcePath)} -> ${displayPath(targetPath)}`);
|
||||
}
|
||||
}
|
||||
|
||||
for (const mirror of mirrors) {
|
||||
compareEntries(join(repoRoot, mirror.source), join(projectRoot, mirror.target));
|
||||
}
|
||||
|
||||
// Every file build/app.zig resolves from the installed package via
|
||||
// dep.path must actually ship with the package: it has to exist in the
|
||||
// staged mirror and sit under an entry of package.json "files", or every
|
||||
// generated app build fails on a missing input after install. Literals
|
||||
// reach dep.path directly, through the externalModule helper, and through
|
||||
// an inline switch, so collect all three forms.
|
||||
function collectDepPathLiterals(source) {
|
||||
const literals = new Set();
|
||||
for (const match of source.matchAll(/dep\.path\(\s*"([^"]+)"\s*\)/g)) {
|
||||
literals.add(match[1]);
|
||||
}
|
||||
for (const match of source.matchAll(/externalModule\(\s*b\s*,\s*dep\s*,[^)]*?"([^"]+)"\s*\)/g)) {
|
||||
literals.add(match[1]);
|
||||
}
|
||||
for (const match of source.matchAll(/dep\.path\(\s*switch\s*\([^)]*\)\s*\{([^}]*)\}/g)) {
|
||||
for (const literal of match[1].matchAll(/"([^"]+)"/g)) {
|
||||
literals.add(literal[1]);
|
||||
}
|
||||
}
|
||||
return [...literals].sort();
|
||||
}
|
||||
|
||||
const appZigPath = join(repoRoot, 'build', 'app.zig');
|
||||
const packageFiles = JSON.parse(readFileSync(join(projectRoot, 'package.json'), 'utf8')).files ?? [];
|
||||
|
||||
function coveredByFiles(literal) {
|
||||
return packageFiles.some((entry) => literal === entry || literal.startsWith(`${entry}/`));
|
||||
}
|
||||
|
||||
for (const literal of collectDepPathLiterals(readFileSync(appZigPath, 'utf8'))) {
|
||||
if (!existsSync(join(projectRoot, literal))) {
|
||||
addError(`build/app.zig dep.path("${literal}") is missing from the package mirror`);
|
||||
}
|
||||
if (!coveredByFiles(literal)) {
|
||||
addError(`build/app.zig dep.path("${literal}") is not covered by package.json "files"`);
|
||||
}
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
console.error('Package framework mirror is out of sync.');
|
||||
for (const error of errors.slice(0, 20)) {
|
||||
console.error(` - ${error}`);
|
||||
}
|
||||
if (errors.length > 20) {
|
||||
console.error(` ... ${errors.length - 20} more`);
|
||||
}
|
||||
console.error('');
|
||||
console.error('The package mirror is GENERATED output: copy-framework.js stages it');
|
||||
console.error('from the repo-root framework sources (prepack and scripts:check run');
|
||||
console.error('the copy first), and the mirror paths are gitignored — committing the');
|
||||
console.error('mirror is not the fix. Regenerate it, then re-run this check:');
|
||||
console.error('');
|
||||
console.error(' node packages/native-sdk/scripts/copy-framework.js (from the repo root)');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log('Package framework mirror is in sync.');
|
||||
@@ -0,0 +1,121 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { readFileSync } from 'fs';
|
||||
import { dirname, join } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const projectRoot = join(__dirname, '..');
|
||||
const repoRoot = join(projectRoot, '..', '..');
|
||||
|
||||
const runtimeSource = readFileSync(join(repoRoot, 'src', 'runtime', 'bridge_responses.zig'), 'utf8');
|
||||
const platformSource = readFileSync(join(repoRoot, 'src', 'platform', 'types.zig'), 'utf8');
|
||||
const typeSource = readFileSync(join(projectRoot, 'native-sdk.d.ts'), 'utf8');
|
||||
|
||||
const errors = [];
|
||||
|
||||
function addError(message) {
|
||||
errors.push(message);
|
||||
}
|
||||
|
||||
function sliceBetween(source, startMarker, endMarker) {
|
||||
const start = source.indexOf(startMarker);
|
||||
if (start === -1) {
|
||||
addError(`missing marker: ${startMarker}`);
|
||||
return '';
|
||||
}
|
||||
|
||||
const end = source.indexOf(endMarker, start);
|
||||
if (end === -1) {
|
||||
addError(`missing marker: ${endMarker}`);
|
||||
return '';
|
||||
}
|
||||
|
||||
return source.slice(start, end);
|
||||
}
|
||||
|
||||
function unique(values) {
|
||||
return [...new Set(values)];
|
||||
}
|
||||
|
||||
function publicViewJsonKeys() {
|
||||
const body = sliceBetween(runtimeSource, 'fn writeViewJsonToWriter', 'fn writeOptionalRectJson');
|
||||
return unique([...body.matchAll(/\\\"([A-Za-z][A-Za-z0-9]*)\\\"/g)].map((match) => match[1]));
|
||||
}
|
||||
|
||||
function viewInfoTypeBody() {
|
||||
return sliceBetween(typeSource, 'export interface NativeSdkViewInfo', 'export type NativeSdkNativeViewKind');
|
||||
}
|
||||
|
||||
function interfaceHasProperty(body, key) {
|
||||
return new RegExp(`\\n\\s*${key}[?:]?\\s*:`).test(body);
|
||||
}
|
||||
|
||||
function platformCursorTags() {
|
||||
const body = sliceBetween(platformSource, 'pub const Cursor = enum {', '};');
|
||||
return body
|
||||
.split('\n')
|
||||
.map((line) => line.trim().replace(/,$/, ''))
|
||||
.filter((line) => /^[A-Za-z_][A-Za-z0-9_]*$/.test(line));
|
||||
}
|
||||
|
||||
function platformEnumTags(enumName) {
|
||||
const body = sliceBetween(platformSource, `pub const ${enumName} = enum {`, '};');
|
||||
return body
|
||||
.split('\n')
|
||||
.map((line) => line.trim().replace(/,$/, ''))
|
||||
.filter((line) => /^[A-Za-z_][A-Za-z0-9_]*$/.test(line));
|
||||
}
|
||||
|
||||
function typeUnionTags(typeName) {
|
||||
const match = typeSource.match(new RegExp(`export type ${typeName} = ([^;]+);`));
|
||||
if (!match) {
|
||||
addError(`missing type union: ${typeName}`);
|
||||
return [];
|
||||
}
|
||||
|
||||
return [...match[1].matchAll(/"([^"]+)"/g)].map((tag) => tag[1]);
|
||||
}
|
||||
|
||||
const viewInfoBody = viewInfoTypeBody();
|
||||
for (const key of publicViewJsonKeys()) {
|
||||
if (!interfaceHasProperty(viewInfoBody, key)) {
|
||||
addError(`NativeSdkViewInfo is missing runtime field "${key}"`);
|
||||
}
|
||||
}
|
||||
|
||||
const cursorTags = platformCursorTags();
|
||||
const typeCursorTags = typeUnionTags('NativeSdkCursor');
|
||||
for (const tag of cursorTags) {
|
||||
if (!typeCursorTags.includes(tag)) {
|
||||
addError(`NativeSdkCursor is missing platform cursor "${tag}"`);
|
||||
}
|
||||
}
|
||||
for (const tag of typeCursorTags) {
|
||||
if (!cursorTags.includes(tag)) {
|
||||
addError(`NativeSdkCursor includes unknown platform cursor "${tag}"`);
|
||||
}
|
||||
}
|
||||
|
||||
const profileRiskTags = platformEnumTags('CanvasFrameProfileRisk');
|
||||
const typeProfileRiskTags = typeUnionTags('NativeSdkCanvasFrameProfileRisk');
|
||||
for (const tag of profileRiskTags) {
|
||||
if (!typeProfileRiskTags.includes(tag)) {
|
||||
addError(`NativeSdkCanvasFrameProfileRisk is missing platform risk "${tag}"`);
|
||||
}
|
||||
}
|
||||
for (const tag of typeProfileRiskTags) {
|
||||
if (!profileRiskTags.includes(tag)) {
|
||||
addError(`NativeSdkCanvasFrameProfileRisk includes unknown platform risk "${tag}"`);
|
||||
}
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
console.error('Runtime TypeScript contract check failed.');
|
||||
for (const error of errors) {
|
||||
console.error(` - ${error}`);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log('Runtime TypeScript contract is in sync.');
|
||||
@@ -0,0 +1,115 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// Verify every version stamped by sync-version.js matches
|
||||
// packages/native-sdk/package.json: the CLI source, the per-platform
|
||||
// binary packages, the bundled @native-sdk/core (manifest + lockfile,
|
||||
// the version every TS scaffold and example pin follows), and the
|
||||
// optionalDependencies pins. Also verify each
|
||||
// platform package's repository.url and homepage match the main package,
|
||||
// because npm validates repository.url against publish provenance and a
|
||||
// repository rename that only updates the main package fails the publish.
|
||||
// CI runs this before publish so a half-bumped release cannot ship.
|
||||
|
||||
import { readdirSync, readFileSync } from 'fs';
|
||||
import { dirname, join } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const projectRoot = join(__dirname, '..');
|
||||
const repoRoot = join(projectRoot, '..', '..');
|
||||
|
||||
const packageJson = JSON.parse(readFileSync(join(projectRoot, 'package.json'), 'utf-8'));
|
||||
const expectedVersion = packageJson.version;
|
||||
|
||||
let errors = 0;
|
||||
|
||||
const mainZigPath = join(repoRoot, 'tools', 'native-sdk', 'main.zig');
|
||||
const mainZig = readFileSync(mainZigPath, 'utf-8');
|
||||
|
||||
const versionMatch = mainZig.match(/^const version = "([^"]*)";/m);
|
||||
|
||||
if (!versionMatch) {
|
||||
console.error('Could not find `const version = "...";` in tools/native-sdk/main.zig');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (versionMatch[1] !== expectedVersion) {
|
||||
console.error(`Version mismatch: package.json=${expectedVersion}, tools/native-sdk/main.zig=${versionMatch[1]}`);
|
||||
errors++;
|
||||
}
|
||||
|
||||
const npmDir = join(projectRoot, 'npm');
|
||||
for (const entry of readdirSync(npmDir, { withFileTypes: true })) {
|
||||
if (!entry.isDirectory()) continue;
|
||||
const platformJson = JSON.parse(readFileSync(join(npmDir, entry.name, 'package.json'), 'utf-8'));
|
||||
if (platformJson.version !== expectedVersion) {
|
||||
console.error(`Version mismatch: package.json=${expectedVersion}, npm/${entry.name}/package.json=${platformJson.version}`);
|
||||
errors++;
|
||||
}
|
||||
const expectedName = `@native-sdk/cli-${entry.name}`;
|
||||
if (platformJson.name !== expectedName) {
|
||||
console.error(`Name mismatch: npm/${entry.name}/package.json is ${platformJson.name}, expected ${expectedName}`);
|
||||
errors++;
|
||||
}
|
||||
if (!(expectedName in (packageJson.optionalDependencies ?? {}))) {
|
||||
console.error(`Missing optionalDependencies pin for ${expectedName} in package.json`);
|
||||
errors++;
|
||||
}
|
||||
const expectedRepositoryUrl = packageJson.repository?.url;
|
||||
if (platformJson.repository?.url !== expectedRepositoryUrl) {
|
||||
console.error(`Repository mismatch: npm/${entry.name}/package.json repository.url is ${platformJson.repository?.url}, expected ${expectedRepositoryUrl} from package.json`);
|
||||
errors++;
|
||||
}
|
||||
if (platformJson.homepage !== packageJson.homepage) {
|
||||
console.error(`Homepage mismatch: npm/${entry.name}/package.json homepage is ${platformJson.homepage}, expected ${packageJson.homepage} from package.json`);
|
||||
errors++;
|
||||
}
|
||||
}
|
||||
|
||||
for (const [name, pin] of Object.entries(packageJson.optionalDependencies ?? {})) {
|
||||
if (pin !== expectedVersion) {
|
||||
console.error(`Version mismatch: optionalDependencies["${name}"]=${pin}, expected ${expectedVersion}`);
|
||||
errors++;
|
||||
}
|
||||
}
|
||||
|
||||
// The bundled @native-sdk/core rides the release version (manifest and
|
||||
// lockfile own-package fields), and the committed TS examples pin it
|
||||
// exactly — half-bumped, a published CLI would scaffold pins npm cannot
|
||||
// resolve to the matching @native-sdk/core release.
|
||||
const coreJson = JSON.parse(readFileSync(join(repoRoot, 'packages', 'core', 'package.json'), 'utf-8'));
|
||||
if (coreJson.version !== expectedVersion) {
|
||||
console.error(`Version mismatch: packages/core/package.json=${coreJson.version}, expected ${expectedVersion}`);
|
||||
errors++;
|
||||
}
|
||||
// npm validates repository.url against publish provenance for
|
||||
// @native-sdk/core exactly as it does for the platform packages — a
|
||||
// missing or renamed URL fails the publish, so pin it to the main package.
|
||||
if (coreJson.repository?.url !== packageJson.repository?.url) {
|
||||
console.error(`Repository mismatch: packages/core/package.json repository.url is ${coreJson.repository?.url}, expected ${packageJson.repository?.url} from package.json`);
|
||||
errors++;
|
||||
}
|
||||
if (coreJson.homepage !== packageJson.homepage) {
|
||||
console.error(`Homepage mismatch: packages/core/package.json homepage is ${coreJson.homepage}, expected ${packageJson.homepage} from package.json`);
|
||||
errors++;
|
||||
}
|
||||
const coreLock = JSON.parse(readFileSync(join(repoRoot, 'packages', 'core', 'package-lock.json'), 'utf-8'));
|
||||
if (coreLock.version !== expectedVersion || coreLock.packages?.['']?.version !== expectedVersion) {
|
||||
console.error(`Version mismatch: packages/core/package-lock.json=${coreLock.version}/${coreLock.packages?.['']?.version}, expected ${expectedVersion}`);
|
||||
errors++;
|
||||
}
|
||||
for (const example of ['examples/soundboard-ts', 'examples/system-monitor-ts']) {
|
||||
const exampleJson = JSON.parse(readFileSync(join(repoRoot, ...example.split('/'), 'package.json'), 'utf-8'));
|
||||
const pin = exampleJson.dependencies?.['@native-sdk/core'];
|
||||
if (pin !== expectedVersion) {
|
||||
console.error(`Version mismatch: ${example}/package.json pins @native-sdk/core ${pin}, expected ${expectedVersion}`);
|
||||
errors++;
|
||||
}
|
||||
}
|
||||
|
||||
if (errors > 0) {
|
||||
console.error(`\nRun "npm run version:sync" in packages/native-sdk to fix.`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(`Versions in sync: ${expectedVersion}`);
|
||||
@@ -0,0 +1,74 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// Mirror the SDK payload from the repo root into this package before
|
||||
// packing (npm prepack). The published @native-sdk/cli carries everything
|
||||
// an app build graph needs from its `native_sdk` path dependency: src/
|
||||
// (the SDK modules), build/ + build.zig + build.zig.zon (the dependency's
|
||||
// build script that `addApp` lives in), app.zon (the SDK's own manifest,
|
||||
// which its build script reads at configure time), assets/ (files the
|
||||
// build graph resolves from the dependency, e.g. the Windows application
|
||||
// manifest build/app.zig wires via dep.path), third_party/webview2/ (the
|
||||
// vendored WebView2 SDK header and loader the Windows build resolves the
|
||||
// same way; the CEF runtimes stay out — they are large downloaded
|
||||
// artifacts, not repo files), and the agent skills. With the payload in
|
||||
// the package, `native init && native dev` work offline right after
|
||||
// install.
|
||||
//
|
||||
// packages/core/ ships too, selectively: TypeScript app cores need the
|
||||
// @native-sdk/core transpiler (src/, run under node at build time), the
|
||||
// SDK library modules cores import (sdk/, also the editor package the CLI
|
||||
// materializes into apps), the rt kernel the emitted core pairs with
|
||||
// (rt/rt.zig), package.json (the bundled version every scaffold pin
|
||||
// follows), and package-lock.json — so `npm ci` inside the installed
|
||||
// package's packages/core works exactly as the build's teaching message
|
||||
// says (npm only strips the tarball ROOT lockfile; nested ones ship).
|
||||
// test/ and scripts/ stay out: repo-dev surface, never build inputs.
|
||||
|
||||
import { cpSync, copyFileSync, rmSync } from 'fs';
|
||||
import { dirname, join } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const projectRoot = join(__dirname, '..');
|
||||
const repoRoot = join(projectRoot, '..', '..');
|
||||
|
||||
for (const dir of ['src', 'build', 'assets', 'skills', 'skill-data']) {
|
||||
const source = join(repoRoot, dir);
|
||||
const target = join(projectRoot, dir);
|
||||
rmSync(target, { recursive: true, force: true });
|
||||
cpSync(source, target, { recursive: true });
|
||||
console.log(`✓ Copied ${dir}/ to ${target}`);
|
||||
}
|
||||
|
||||
{
|
||||
const source = join(repoRoot, 'third_party', 'webview2');
|
||||
const target = join(projectRoot, 'third_party', 'webview2');
|
||||
rmSync(join(projectRoot, 'third_party'), { recursive: true, force: true });
|
||||
cpSync(source, target, { recursive: true });
|
||||
console.log(`✓ Copied third_party/webview2/ to ${target}`);
|
||||
}
|
||||
|
||||
// The @native-sdk/core closure a TS app build needs (see the header note).
|
||||
{
|
||||
rmSync(join(projectRoot, 'packages'), { recursive: true, force: true });
|
||||
for (const dir of ['src', 'sdk', 'rt']) {
|
||||
const source = join(repoRoot, 'packages', 'core', dir);
|
||||
const target = join(projectRoot, 'packages', 'core', dir);
|
||||
cpSync(source, target, { recursive: true });
|
||||
console.log(`✓ Copied packages/core/${dir}/ to ${target}`);
|
||||
}
|
||||
for (const file of ['package.json', 'package-lock.json']) {
|
||||
const source = join(repoRoot, 'packages', 'core', file);
|
||||
const target = join(projectRoot, 'packages', 'core', file);
|
||||
copyFileSync(source, target);
|
||||
console.log(`✓ Copied packages/core/${file} to ${target}`);
|
||||
}
|
||||
}
|
||||
|
||||
for (const file of ['build.zig', 'build.zig.zon', 'app.zon', 'LICENSE']) {
|
||||
const source = join(repoRoot, file);
|
||||
const target = join(projectRoot, file);
|
||||
rmSync(target, { force: true });
|
||||
copyFileSync(source, target);
|
||||
console.log(`✓ Copied ${file} to ${target}`);
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { copyFileSync, existsSync, mkdirSync } from 'fs';
|
||||
import { execSync } from 'child_process';
|
||||
import { dirname, join } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { platform, arch } from 'os';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const projectRoot = join(__dirname, '..');
|
||||
const repoRoot = join(projectRoot, '..', '..');
|
||||
|
||||
function isMusl() {
|
||||
if (platform() !== 'linux') return false;
|
||||
try {
|
||||
const result = execSync('ldd --version 2>&1 || true', { encoding: 'utf8' });
|
||||
return result.toLowerCase().includes('musl');
|
||||
} catch {
|
||||
return existsSync('/lib/ld-musl-x86_64.so.1') || existsSync('/lib/ld-musl-aarch64.so.1');
|
||||
}
|
||||
}
|
||||
|
||||
const sourceExt = platform() === 'win32' ? '.exe' : '';
|
||||
const sourcePath = join(repoRoot, `zig-out/bin/native${sourceExt}`);
|
||||
const binDir = join(projectRoot, 'bin');
|
||||
|
||||
const osKey = platform() === 'linux' && isMusl() ? 'linux-musl' : platform();
|
||||
const platformKey = `${osKey}-${arch()}`;
|
||||
const ext = platform() === 'win32' ? '.exe' : '';
|
||||
const targetName = `native-sdk-${platformKey}${ext}`;
|
||||
const targetPath = join(binDir, targetName);
|
||||
|
||||
if (!existsSync(sourcePath)) {
|
||||
console.error(`Error: Native binary not found at ${sourcePath}`);
|
||||
console.error('Run "zig build" from the repo root first');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (!existsSync(binDir)) {
|
||||
mkdirSync(binDir, { recursive: true });
|
||||
}
|
||||
|
||||
copyFileSync(sourcePath, targetPath);
|
||||
console.log(`✓ Copied native binary to ${targetPath}`);
|
||||
@@ -0,0 +1,147 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// One version number rules them all: packages/native-sdk/package.json is
|
||||
// the source of truth, and this script stamps it into the CLI source
|
||||
// (tools/native-sdk/main.zig), every per-platform binary package under
|
||||
// npm/, and the main package's own optionalDependencies pins. The pins
|
||||
// are exact so a given @native-sdk/cli always installs the binary built
|
||||
// from the same commit. The same pass propagates the repo identity
|
||||
// fields (repository and homepage) into each platform package, so a
|
||||
// repository rename or domain move applied to the main package cannot
|
||||
// leave the platform packages behind and fail publish provenance checks.
|
||||
//
|
||||
// packages/core (@native-sdk/core) rides the same release version: its
|
||||
// manifest and lockfile get the CLI version, so the version every TS
|
||||
// scaffold pins (templates read the bundled manifest) and the editor copy
|
||||
// the CLI materializes match the npm publish the moment the package goes
|
||||
// public. The committed TS examples pin the bundled version by hand, so
|
||||
// they are stamped here too (tests/ts-core/scaffold_ide_e2e_tests.zig
|
||||
// fails the build when an example pin drifts from the bundled version).
|
||||
|
||||
import { readdirSync, readFileSync, writeFileSync } from 'fs';
|
||||
import { dirname, join } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const projectRoot = join(__dirname, '..');
|
||||
const repoRoot = join(projectRoot, '..', '..');
|
||||
|
||||
const packageJsonPath = join(projectRoot, 'package.json');
|
||||
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
||||
const version = packageJson.version;
|
||||
|
||||
console.log(`Syncing version ${version}...`);
|
||||
|
||||
// tools/native-sdk/main.zig
|
||||
const mainZigPath = join(repoRoot, 'tools', 'native-sdk', 'main.zig');
|
||||
let mainZig = readFileSync(mainZigPath, 'utf-8');
|
||||
|
||||
const versionPattern = /^const version = "[^"]*";/m;
|
||||
const match = mainZig.match(versionPattern);
|
||||
|
||||
if (!match) {
|
||||
console.error(' Could not find `const version = "...";` in tools/native-sdk/main.zig');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const newVersionLine = `const version = "${version}";`;
|
||||
|
||||
if (match[0] !== newVersionLine) {
|
||||
mainZig = mainZig.replace(versionPattern, newVersionLine);
|
||||
writeFileSync(mainZigPath, mainZig);
|
||||
console.log(` Updated tools/native-sdk/main.zig: ${match[0]} -> ${newVersionLine}`);
|
||||
} else {
|
||||
console.log(` tools/native-sdk/main.zig already up to date`);
|
||||
}
|
||||
|
||||
// npm/<platform>/package.json
|
||||
const npmDir = join(projectRoot, 'npm');
|
||||
for (const entry of readdirSync(npmDir, { withFileTypes: true })) {
|
||||
if (!entry.isDirectory()) continue;
|
||||
const platformJsonPath = join(npmDir, entry.name, 'package.json');
|
||||
const platformJson = JSON.parse(readFileSync(platformJsonPath, 'utf-8'));
|
||||
let changed = false;
|
||||
if (platformJson.version !== version) {
|
||||
platformJson.version = version;
|
||||
changed = true;
|
||||
}
|
||||
if (platformJson.repository?.url !== packageJson.repository?.url) {
|
||||
platformJson.repository = { ...packageJson.repository };
|
||||
changed = true;
|
||||
}
|
||||
if (platformJson.homepage !== packageJson.homepage) {
|
||||
platformJson.homepage = packageJson.homepage;
|
||||
changed = true;
|
||||
}
|
||||
if (changed) {
|
||||
writeFileSync(platformJsonPath, JSON.stringify(platformJson, null, 2) + '\n');
|
||||
console.log(` Updated npm/${entry.name}/package.json`);
|
||||
} else {
|
||||
console.log(` npm/${entry.name}/package.json already up to date`);
|
||||
}
|
||||
}
|
||||
|
||||
// packages/core: the bundled @native-sdk/core manifest and its lockfile's
|
||||
// own-package version fields (npm ci reads the lock; the dependency
|
||||
// entries under node_modules/* are not ours to stamp).
|
||||
{
|
||||
const corePackageJsonPath = join(repoRoot, 'packages', 'core', 'package.json');
|
||||
const coreJson = JSON.parse(readFileSync(corePackageJsonPath, 'utf-8'));
|
||||
if (coreJson.version !== version) {
|
||||
coreJson.version = version;
|
||||
writeFileSync(corePackageJsonPath, JSON.stringify(coreJson, null, 2) + '\n');
|
||||
console.log(` Updated packages/core/package.json`);
|
||||
} else {
|
||||
console.log(` packages/core/package.json already up to date`);
|
||||
}
|
||||
|
||||
const coreLockPath = join(repoRoot, 'packages', 'core', 'package-lock.json');
|
||||
const coreLock = JSON.parse(readFileSync(coreLockPath, 'utf-8'));
|
||||
let lockChanged = false;
|
||||
if (coreLock.version !== version) {
|
||||
coreLock.version = version;
|
||||
lockChanged = true;
|
||||
}
|
||||
if (coreLock.packages?.['']?.version !== version) {
|
||||
coreLock.packages[''].version = version;
|
||||
lockChanged = true;
|
||||
}
|
||||
if (lockChanged) {
|
||||
writeFileSync(coreLockPath, JSON.stringify(coreLock, null, 2) + '\n');
|
||||
console.log(` Updated packages/core/package-lock.json`);
|
||||
} else {
|
||||
console.log(` packages/core/package-lock.json already up to date`);
|
||||
}
|
||||
}
|
||||
|
||||
// The committed TS examples' @native-sdk/core pins (exact, like the
|
||||
// scaffold's, so a post-publish `npm install` resolves the same content
|
||||
// the CLI materializes).
|
||||
for (const example of ['examples/soundboard-ts', 'examples/system-monitor-ts']) {
|
||||
const examplePath = join(repoRoot, ...example.split('/'), 'package.json');
|
||||
const exampleJson = JSON.parse(readFileSync(examplePath, 'utf-8'));
|
||||
if (exampleJson.dependencies?.['@native-sdk/core'] !== version) {
|
||||
exampleJson.dependencies['@native-sdk/core'] = version;
|
||||
writeFileSync(examplePath, JSON.stringify(exampleJson, null, 2) + '\n');
|
||||
console.log(` Updated ${example}/package.json`);
|
||||
} else {
|
||||
console.log(` ${example}/package.json already up to date`);
|
||||
}
|
||||
}
|
||||
|
||||
// The main package's optionalDependencies pins.
|
||||
let pinsChanged = false;
|
||||
for (const name of Object.keys(packageJson.optionalDependencies ?? {})) {
|
||||
if (packageJson.optionalDependencies[name] !== version) {
|
||||
packageJson.optionalDependencies[name] = version;
|
||||
pinsChanged = true;
|
||||
}
|
||||
}
|
||||
if (pinsChanged) {
|
||||
writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n');
|
||||
console.log(' Updated optionalDependencies pins in package.json');
|
||||
} else {
|
||||
console.log(' optionalDependencies pins already up to date');
|
||||
}
|
||||
|
||||
console.log('Version sync complete.');
|
||||
Reference in New Issue
Block a user