chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:52:40 +08:00
commit 9c06d70b3c
3233 changed files with 672160 additions and 0 deletions
+49
View File
@@ -0,0 +1,49 @@
export interface BuildOptions {
single: boolean;
skipInstall: boolean;
skipSdkBuild: boolean;
installNativeVariants: boolean;
}
export function parseBuildOptions(args: readonly string[]): BuildOptions {
return {
single: args.includes("--single"),
skipInstall: args.includes("--skip-install"),
skipSdkBuild: args.includes("--skip-sdk-build"),
installNativeVariants: args.includes("--install-native-variants"),
};
}
export function shouldInstallNativeVariants(input: {
options: BuildOptions;
opentuiVersion: string | undefined;
}): boolean {
return Boolean(
input.opentuiVersion &&
input.options.installNativeVariants &&
!input.options.skipInstall,
);
}
export function validateBuildOptions(input: {
options: BuildOptions;
opentuiVersion: string | undefined;
targetCount: number;
}): string | undefined {
if (input.targetCount === 0) {
return "No matching targets for this platform.";
}
if (
input.opentuiVersion &&
!input.options.single &&
!input.options.skipInstall &&
!input.options.installNativeVariants
) {
return [
"Cross-platform OpenTUI builds require native package variants.",
"Pass --install-native-variants to allow the build script to run bun install for all OpenTUI native packages.",
"Pass --skip-install only when those packages are already installed.",
].join("\n");
}
return undefined;
}
+309
View File
@@ -0,0 +1,309 @@
#!/usr/bin/env bun
import {
cpSync,
existsSync,
mkdirSync,
readdirSync,
readFileSync,
realpathSync,
statSync,
} from "node:fs";
import { join, relative, resolve } from "node:path";
import { $ } from "bun";
import {
parseBuildOptions,
shouldInstallNativeVariants,
validateBuildOptions,
} from "./build-options";
const cliDir = resolve(import.meta.dir, "..");
const rootDir = resolve(cliDir, "../..");
process.chdir(cliDir);
// Telemetry / OTEL environment variables that should be baked into the
// compiled binary at build time. Mirrors the list of secrets injected by the
// `cli-publish` GitHub Actions workflow. These are inlined via Bun's `define`
// so the CLI ships with the production telemetry configuration without
// requiring the end user to set any env vars.
const BUILD_TIME_INLINED_ENV_VARS = [
"TELEMETRY_SERVICE_API_KEY",
"ERROR_SERVICE_API_KEY",
"OTEL_TELEMETRY_ENABLED",
"OTEL_LOGS_EXPORTER",
"OTEL_METRICS_EXPORTER",
"OTEL_EXPORTER_OTLP_PROTOCOL",
"OTEL_EXPORTER_OTLP_ENDPOINT",
"OTEL_EXPORTER_OTLP_HEADERS",
] as const;
function buildInlinedEnvDefines(): Record<string, string> {
const defines: Record<string, string> = {};
for (const name of BUILD_TIME_INLINED_ENV_VARS) {
defines[`process.env.${name}`] = JSON.stringify(process.env[name] ?? "");
}
return defines;
}
const pkg = JSON.parse(readFileSync(join(cliDir, "package.json"), "utf-8"));
const version: string = pkg.version;
const repository: unknown = pkg.repository;
console.log(`Building @cline/cli v${version}`);
const buildOptions = parseBuildOptions(process.argv.slice(2));
const allTargets: {
os: string;
arch: "arm64" | "x64";
}[] = [
{ os: "linux", arch: "arm64" },
{ os: "linux", arch: "x64" },
{ os: "darwin", arch: "arm64" },
{ os: "darwin", arch: "x64" },
{ os: "win32", arch: "x64" },
{ os: "win32", arch: "arm64" },
];
const targets = buildOptions.single
? allTargets.filter(
(item) => item.os === process.platform && item.arch === process.arch,
)
: allTargets;
const opentuiVersion = pkg.dependencies["@opentui/core"];
const optionsError = validateBuildOptions({
options: buildOptions,
opentuiVersion,
targetCount: targets.length,
});
if (optionsError) {
console.error(optionsError);
process.exit(1);
}
await $`rm -rf dist`;
// Pre-install all platform variants of native packages so cross-compilation
// can resolve them. Without this, Bun only has the host platform's native
// binary and cross-compiled builds fail to resolve @opentui/core's FFI layer.
if (shouldInstallNativeVariants({ options: buildOptions, opentuiVersion })) {
console.log(
`Installing all platform variants of @opentui/core@${opentuiVersion}...`,
);
await $`bun install --os="*" --cpu="*" @opentui/core@${opentuiVersion}`;
}
// Build the SDK first (the CLI bundles workspace packages)
if (!buildOptions.skipSdkBuild) {
console.log("Building SDK packages...");
await $`bun run build:sdk`.cwd(rootDir);
console.log("Building CLI bundle...");
await $`bun -F @cline/cli build`.cwd(rootDir);
}
const hubWebviewSource = join(cliDir, "../cline-hub/src/webview");
const hubWebviewDist = join(cliDir, "../cline-hub/dist/webview");
const hubWebviewIndex = join(hubWebviewDist, "index.html");
function newestFileMtimeMs(dir: string): number {
let newest = 0;
for (const entry of readdirSync(dir, { withFileTypes: true })) {
if (
entry.name === "node_modules" ||
entry.name === "dist" ||
entry.name === ".turbo"
) {
continue;
}
const path = join(dir, entry.name);
if (entry.isDirectory()) {
newest = Math.max(newest, newestFileMtimeMs(path));
} else if (entry.isFile()) {
newest = Math.max(newest, statSync(path).mtimeMs);
}
}
return newest;
}
function shouldBuildHubWebview(): boolean {
if (!existsSync(hubWebviewIndex)) {
return true;
}
try {
return (
newestFileMtimeMs(hubWebviewSource) > statSync(hubWebviewIndex).mtimeMs
);
} catch {
return true;
}
}
if (shouldBuildHubWebview()) {
console.log("Building Cline Hub webview...");
await $`bun -F @cline/cline-hub build:webview`.cwd(rootDir);
}
const binaries: Record<string, string> = {};
function findOpenTuiParserWorker(): string {
const localPath = resolve(
cliDir,
"node_modules/@opentui/core/parser.worker.js",
);
const rootPath = resolve(
rootDir,
"node_modules/@opentui/core/parser.worker.js",
);
const parserWorkerPath = existsSync(localPath) ? localPath : rootPath;
return realpathSync(parserWorkerPath);
}
function getBunTarget(
item: (typeof allTargets)[number],
): Bun.Build.CompileTarget {
const targetOs = item.os === "win32" ? "windows" : item.os;
return `bun-${targetOs}-${item.arch}` as Bun.Build.CompileTarget;
}
async function buildCompiledBinary(input: {
bunTarget: Bun.Build.CompileTarget;
dirName: string;
outfile: string;
}): Promise<void> {
const parserWorker = findOpenTuiParserWorker();
const targetOs = input.bunTarget.includes("windows") ? "windows" : "posix";
const bunfsRoot = targetOs === "windows" ? "B:/~BUN/root/" : "/$bunfs/root/";
const parserWorkerPath = relative(rootDir, parserWorker).replaceAll(
"\\",
"/",
);
// Build to /tmp first so Bun's temp-file rename stays on one filesystem
// layer in containerized environments (virtiofs, overlayfs).
const entrypoint = join(cliDir, "src/index.ts");
const tmpDir = join("/tmp", `cline-build-${input.dirName}`);
const tmpOutfile = join(
tmpDir,
input.outfile.endsWith(".exe") ? "cline.exe" : "cline",
);
mkdirSync(tmpDir, { recursive: true });
process.chdir("/tmp");
const result = await Bun.build({
entrypoints: [entrypoint, parserWorker],
splitting: true,
compile: {
target: input.bunTarget,
outfile: tmpOutfile,
},
minify: true,
external: ["@anthropic-ai/vertex-sdk"],
define: {
OTUI_TREE_SITTER_WORKER_PATH: bunfsRoot + parserWorkerPath,
// Inline telemetry/OTEL env vars at build time so the compiled
// binary ships with production telemetry configuration baked in.
...buildInlinedEnvDefines(),
},
throw: false,
});
process.chdir(cliDir);
if (!result.success) {
console.error(`Build failed for ${input.dirName}:`);
for (const log of result.logs) {
console.error(log);
}
process.exit(1);
}
await $`cp ${tmpOutfile} ${input.outfile} && chmod 755 ${input.outfile}`;
await $`rm -rf ${tmpDir}`;
}
for (const item of targets) {
// npm treats "win32" specially in os field, but for package naming use "windows"
const displayOs = item.os === "win32" ? "windows" : item.os;
const name = `@cline/cli-${displayOs}-${item.arch}`;
const dirName = `cli-${displayOs}-${item.arch}`;
const binaryName = item.os === "win32" ? "cline.exe" : "cline";
const bunTarget = getBunTarget(item);
console.log(`\nBuilding ${name} (target: ${bunTarget})...`);
const outDir = join(cliDir, `dist/${dirName}/bin`);
mkdirSync(outDir, { recursive: true });
const outfile = join(outDir, binaryName);
await buildCompiledBinary({ bunTarget, dirName, outfile });
// Smoke test: only run on current platform
if (item.os === process.platform && item.arch === process.arch) {
console.log(` Smoke test: ${outfile} --version`);
try {
const output = await $`${outfile} --version`.text();
const actualVersion = output.trim();
if (actualVersion !== version) {
throw new Error(
`Expected --version to print ${version}, got ${actualVersion}`,
);
}
console.log(` Passed: ${actualVersion}`);
} catch (e) {
console.error(` Smoke test FAILED for ${name}:`, e);
process.exit(1);
}
}
// Copy plugin sandbox bootstrap if it exists
const bootstrapSrc = join(
rootDir,
"sdk/packages/core/dist/extensions/plugin-sandbox-bootstrap.js",
);
if (existsSync(bootstrapSrc)) {
const bootstrapDir = join(cliDir, `dist/${dirName}/extensions`);
mkdirSync(bootstrapDir, { recursive: true });
const content = readFileSync(bootstrapSrc);
await Bun.write(join(bootstrapDir, "plugin-sandbox-bootstrap.js"), content);
}
if (existsSync(hubWebviewDist)) {
const hubWebviewDest = join(cliDir, `dist/${dirName}/cline-hub/webview`);
mkdirSync(join(cliDir, `dist/${dirName}/cline-hub`), {
recursive: true,
});
cpSync(hubWebviewDist, hubWebviewDest, { recursive: true });
}
// Generate platform package.json
await Bun.write(
join(cliDir, `dist/${dirName}/package.json`),
`${JSON.stringify(
{
name,
version,
description: `Cline CLI binary for ${displayOs} ${item.arch}`,
os: [item.os],
cpu: [item.arch],
...(repository ? { repository } : {}),
bin: {
cline: `bin/${binaryName}`,
},
},
null,
2,
)}\n`,
);
binaries[name] = version;
console.log(` Built ${name}`);
}
console.log(`\nBuild complete. ${Object.keys(binaries).length} targets built.`);
console.log("Packages:");
for (const [name, ver] of Object.entries(binaries)) {
console.log(` ${name}@${ver}`);
}
export { binaries, version };
+17
View File
@@ -0,0 +1,17 @@
#!/usr/bin/env bun
export const DIRECT_PUBLISH_GUARD_MESSAGE = [
"Direct packaging or publishing from apps/cli is disabled.",
"The source package points its development bin at src/index.ts, while the npm package is generated under dist/cli.",
"Run `bun run build:platforms` first, then `bun run publish:npm:dry` to preview the generated npm packages.",
"Use `bun run publish:npm` to publish those generated packages.",
].join("\n");
export function shouldAllowDirectPublish(env: NodeJS.ProcessEnv): boolean {
return env.CLINE_ALLOW_DIRECT_PUBLISH === "1";
}
if (import.meta.main && !shouldAllowDirectPublish(process.env)) {
console.error(DIRECT_PUBLISH_GUARD_MESSAGE);
process.exit(1);
}
+89
View File
@@ -0,0 +1,89 @@
#!/usr/bin/env node
// Post-install script for Cline CLI.
//
// Creates a hard link (or copy fallback) from the platform-specific binary
// to bin/.cline for fast startup on subsequent runs.
//
// This script must use only Node.js APIs (no Bun) since it runs via
// "node script/postinstall.mjs" in the npm lifecycle.
import fs from "node:fs";
import { createRequire } from "node:module";
import os from "node:os";
import path from "node:path";
import { fileURLToPath } from "node:url";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const require = createRequire(import.meta.url);
function main() {
if (os.platform() === "win32") {
// On Windows, npm creates .cmd shims from the bin field.
// The resolver script handles binary lookup at runtime.
console.log("Windows detected: skipping binary cache setup");
return;
}
const platformMap = {
darwin: "darwin",
linux: "linux",
};
const platform = platformMap[os.platform()] || os.platform();
const arch = os.arch();
const packageName = `@cline/cli-${platform}-${arch}`;
const binaryName = "cline";
let binaryPath;
try {
const packageJsonPath = require.resolve(`${packageName}/package.json`);
const packageDir = path.dirname(packageJsonPath);
binaryPath = path.join(packageDir, "bin", binaryName);
if (!fs.existsSync(binaryPath)) {
throw new Error(`Binary not found at ${binaryPath}`);
}
} catch (_error) {
// Platform package not available. The resolver script will find
// it at runtime by walking node_modules. This is expected on
// platforms we don't ship binaries for.
console.log(`Note: ${packageName} not found, skipping binary cache`);
return;
}
const binDir =
path.basename(__dirname) === "script"
? path.join(__dirname, "..", "bin")
: path.join(__dirname, "bin");
const target = path.join(binDir, ".cline");
// Ensure bin directory exists
if (!fs.existsSync(binDir)) {
fs.mkdirSync(binDir, { recursive: true });
}
// Remove existing cached binary
if (fs.existsSync(target)) {
fs.unlinkSync(target);
}
// Hard link preferred (shares disk space), copy as fallback
// (hard links fail on some filesystems like NFS or cross-device)
try {
fs.linkSync(binaryPath, target);
} catch {
fs.copyFileSync(binaryPath, target);
}
fs.chmodSync(target, 0o755);
console.log(`Cached cline binary at ${target}`);
}
try {
main();
} catch (error) {
// postinstall failures should never block npm install.
// The resolver script will find the binary at runtime.
console.error(`postinstall: ${error.message}`);
process.exit(0);
}
+356
View File
@@ -0,0 +1,356 @@
#!/usr/bin/env bun
// Publishes cline and all platform-specific binary packages to npm.
//
// Usage:
// bun script/publish-npm.ts # publish with "latest" tag
// bun script/publish-npm.ts --tag next # publish with "next" tag
// bun script/publish-npm.ts --dry-run # preview without publishing
//
// Prerequisites:
// - Run script/build.ts first to generate dist/ packages
// - GitHub trusted publishing or `npm login` for authentication
import { existsSync, readdirSync, readFileSync, rmSync } from "node:fs";
import { join } from "node:path";
import { parseArgs } from "node:util";
import { $ } from "bun";
const cliDir = join(import.meta.dir, "..");
process.chdir(cliDir);
const { values } = parseArgs({
args: Bun.argv.slice(2),
options: {
"dry-run": { type: "boolean", default: false },
tag: { type: "string", default: "latest" },
},
strict: true,
});
const dryRun = values["dry-run"] ?? false;
const npmTag = values.tag ?? "latest";
const wrapperPackageName = "cline";
const expectedPlatformPackages = [
"@cline/cli-darwin-arm64",
"@cline/cli-darwin-x64",
"@cline/cli-linux-arm64",
"@cline/cli-linux-x64",
"@cline/cli-windows-arm64",
"@cline/cli-windows-x64",
] as const;
const hostSdkPackages = [
{ name: "@cline/sdk", directory: "sdk" },
{ name: "@cline/core", directory: "core" },
{ name: "@cline/agents", directory: "agents" },
{ name: "@cline/llms", directory: "llms" },
{ name: "@cline/shared", directory: "shared" },
] as const;
interface PlatformPackageManifest {
name: string;
version: string;
os: string[];
}
function isRecord(value: unknown): value is Record<string, unknown> {
return value !== null && typeof value === "object" && !Array.isArray(value);
}
function isStringArray(value: unknown): value is string[] {
return (
Array.isArray(value) && value.every((item) => typeof item === "string")
);
}
function isPlatformPackageManifest(
value: unknown,
): value is PlatformPackageManifest {
return (
isRecord(value) &&
typeof value.name === "string" &&
typeof value.version === "string" &&
isStringArray(value.os)
);
}
function readPackageVersion(name: string, packageJsonPath: string): string {
const pkg: unknown = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
if (!isRecord(pkg) || pkg.name !== name || typeof pkg.version !== "string") {
console.error(`Invalid package manifest for ${name}: ${packageJsonPath}`);
process.exit(1);
}
return pkg.version;
}
function buildHostSdkDependencies(): Record<string, string> {
const dependencies: Record<string, string> = {};
for (const pkg of hostSdkPackages) {
dependencies[pkg.name] = readPackageVersion(
pkg.name,
join(cliDir, "../../sdk/packages", pkg.directory, "package.json"),
);
}
return dependencies;
}
function removePackedTarballs(dir: string): void {
for (const entry of readdirSync(dir)) {
if (entry.endsWith(".tgz")) {
rmSync(join(dir, entry), { force: true });
}
}
}
async function npmPackageVersionExists(
name: string,
version: string,
): Promise<boolean> {
const result = Bun.spawnSync(
["npm", "view", `${name}@${version}`, "version"],
{
cwd: cliDir,
stdout: "ignore",
stderr: "ignore",
},
);
return result.exitCode === 0;
}
async function verifyPublishedDependencies(
dependencies: Record<string, string>,
): Promise<void> {
const missingDependencies: string[] = [];
for (const [name, version] of Object.entries(dependencies).sort()) {
if (!(await npmPackageVersionExists(name, version))) {
missingDependencies.push(`${name}@${version}`);
}
}
if (missingDependencies.length === 0) {
return;
}
console.error("Wrapper package dependencies are not published:");
for (const dependency of missingDependencies) {
console.error(` ${dependency}`);
}
console.error("Publish the SDK packages before publishing the CLI wrapper.");
process.exit(1);
}
async function publishPackage(input: {
name: string;
version: string;
dir: string;
tag: string;
dryRun: boolean;
}): Promise<void> {
if (process.platform !== "win32") {
await $`chmod -R 755 .`.cwd(input.dir);
}
if (input.dryRun) {
console.log(` [dry-run] Would publish ${input.name}@${input.version}`);
return;
}
if (await npmPackageVersionExists(input.name, input.version)) {
console.log(` ${input.name}@${input.version} already exists, skipping`);
return;
}
console.log(` Publishing ${input.name}@${input.version}...`);
removePackedTarballs(input.dir);
await $`bun pm pack`.cwd(input.dir);
await $`npm publish *.tgz --access public --tag ${input.tag}`.cwd(input.dir);
console.log(` Published ${input.name}@${input.version}`);
}
// Discover built platform packages from dist/
const binaries: Record<string, string> = {};
for await (const filepath of new Bun.Glob("*/package.json").scan({
cwd: join(cliDir, "dist"),
})) {
const pkg: unknown = JSON.parse(
readFileSync(join(cliDir, "dist", filepath), "utf-8"),
);
if (isPlatformPackageManifest(pkg)) {
binaries[pkg.name] = pkg.version;
}
}
if (Object.keys(binaries).length === 0) {
console.error("No platform packages found in dist/.");
console.error("Run `bun script/build.ts` first.");
process.exit(1);
}
const missingPackages = expectedPlatformPackages.filter(
(name) => !(name in binaries),
);
if (missingPackages.length > 0) {
console.error("Missing platform packages in dist/:");
for (const name of missingPackages) {
console.error(` ${name}`);
}
process.exit(1);
}
const versions = new Set(Object.values(binaries));
if (versions.size !== 1) {
console.error("Platform package versions do not match:");
for (const [name, packageVersion] of Object.entries(binaries).sort()) {
console.error(` ${name}@${packageVersion}`);
}
process.exit(1);
}
const version = Object.values(binaries)[0];
const sourcePkg: unknown = JSON.parse(
readFileSync(join(cliDir, "package.json"), "utf-8"),
);
const sourcePkgRecord = isRecord(sourcePkg) ? sourcePkg : {};
const sourceVersion =
"version" in sourcePkgRecord && typeof sourcePkgRecord.version === "string"
? sourcePkgRecord.version
: undefined;
if (sourceVersion !== version) {
console.error(
`Built package version ${version} does not match apps/cli/package.json version ${sourceVersion ?? "(missing)"}.`,
);
process.exit(1);
}
const sourceRepository =
"repository" in sourcePkgRecord ? sourcePkgRecord.repository : undefined;
const hostSdkDependencies = buildHostSdkDependencies();
console.log(`Publishing ${wrapperPackageName} v${version}`);
console.log(` Tag: ${npmTag}`);
console.log(` Dry run: ${dryRun}`);
console.log(` Platform packages: ${Object.keys(binaries).length}`);
for (const name of Object.keys(binaries)) {
console.log(` ${name}`);
}
if (!dryRun) {
await verifyPublishedDependencies(hostSdkDependencies);
}
// Step 1: Publish platform-specific packages (in parallel)
console.log("\nPublishing platform packages...");
const platformTasks = Object.keys(binaries)
.sort()
.map(async (name) => {
const dirName = name.replace("@cline/", "");
const pkgDir = join(cliDir, "dist", dirName);
await publishPackage({
name,
version,
dir: pkgDir,
tag: npmTag,
dryRun,
});
});
await Promise.all(platformTasks);
// Step 2: Generate and publish the main wrapper package
console.log("\nPreparing main package...");
const mainPkgDir = join(cliDir, "dist", "cli");
await $`rm -rf ${mainPkgDir}`;
await $`mkdir -p ${mainPkgDir}`;
await $`cp -r ${join(cliDir, "bin")} ${join(mainPkgDir, "bin")}`;
await $`cp ${join(cliDir, "script/postinstall.mjs")} ${join(mainPkgDir, "postinstall.mjs")}`;
// Copy LICENSE from repo root if it exists
const licenseFrom = join(cliDir, "../../LICENSE");
if (existsSync(licenseFrom)) {
await $`cp ${licenseFrom} ${join(mainPkgDir, "LICENSE")}`;
}
// Copy README.md so the npm registry listing has the same landing page
// as the repo. The published wrapper package is generated fresh in
// dist/cli/ each release, so the source README is not picked up
// automatically.
const readmeFrom = join(cliDir, "README.md");
if (existsSync(readmeFrom)) {
await $`cp ${readmeFrom} ${join(mainPkgDir, "README.md")}`;
} else {
console.error(
`Missing ${readmeFrom}. The CLI README must exist before publishing.`,
);
process.exit(1);
}
const mainPkg: unknown = JSON.parse(
readFileSync(join(cliDir, "package.json"), "utf-8"),
);
const mainPkgRecord = isRecord(mainPkg) ? mainPkg : {};
const description =
"description" in mainPkgRecord &&
typeof mainPkgRecord.description === "string"
? mainPkgRecord.description
: undefined;
const license =
"license" in mainPkgRecord && typeof mainPkgRecord.license === "string"
? mainPkgRecord.license
: undefined;
const keywords =
"keywords" in mainPkgRecord && isStringArray(mainPkgRecord.keywords)
? mainPkgRecord.keywords
: undefined;
const author = "author" in mainPkgRecord ? mainPkgRecord.author : undefined;
const homepage =
"homepage" in mainPkgRecord && typeof mainPkgRecord.homepage === "string"
? mainPkgRecord.homepage
: undefined;
const bugs = "bugs" in mainPkgRecord ? mainPkgRecord.bugs : undefined;
const wrapperPackageJson = {
name: wrapperPackageName,
version,
description: description || "Cline CLI",
license: license || "Apache-2.0",
...(keywords ? { keywords } : {}),
...(author ? { author } : {}),
...(homepage ? { homepage } : {}),
...(bugs ? { bugs } : {}),
...(sourceRepository ? { repository: sourceRepository } : {}),
bin: {
cline: "./bin/cline",
},
scripts: {
postinstall: "node ./postinstall.mjs || true",
},
dependencies: hostSdkDependencies,
optionalDependencies: binaries,
};
await Bun.write(
join(mainPkgDir, "package.json"),
`${JSON.stringify(wrapperPackageJson, null, 2)}\n`,
);
if (dryRun) {
console.log(
` [dry-run] Would publish ${wrapperPackageName}@${version} with tag ${npmTag}`,
);
console.log("\nDry run complete. No packages were published.");
} else {
await publishPackage({
name: wrapperPackageName,
version,
dir: mainPkgDir,
tag: npmTag,
dryRun: false,
});
console.log(
`\nPublished ${wrapperPackageName}@${version} with tag ${npmTag}`,
);
console.log("\nInstall with:");
console.log(` npm install -g ${wrapperPackageName}`);
}