chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:29:49 +08:00
commit 505bac6b53
1470 changed files with 457757 additions and 0 deletions
+77
View File
@@ -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}`);
+147
View File
@@ -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.');