feat: integrate Changesets for multi-package npm publishing
Migrate from single-bundle CLI publishing to publishing all @paperclipai/* packages individually via Changesets. This fixes the "Cannot find package @paperclipai/server" error when installing from npm. Changes: - Add @changesets/cli with fixed versioning (all packages share version) - Make 7 packages publishable (shared, adapter-utils, db, 3 adapters, server) - Add build scripts, publishConfig, and files fields to all packages - Mark @paperclipai/server as external in CLI esbuild config - Simplify CLI importServerEntry() to use string-literal dynamic import - Add generate-npm-package-json support for external workspace packages - Create scripts/release.sh for one-command releases - Remove old bump-and-publish.sh and version-bump.sh - All packages start at version 0.2.0 Usage: ./scripts/release.sh patch|minor|major [--dry-run] Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -12,10 +12,10 @@ import { fileURLToPath } from "node:url";
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const repoRoot = resolve(__dirname, "..");
|
||||
|
||||
// Workspace packages whose code should be bundled into the CLI
|
||||
// Workspace packages whose code should be bundled into the CLI.
|
||||
// Note: "server" is excluded — it's published separately and resolved at runtime.
|
||||
const workspacePaths = [
|
||||
"cli",
|
||||
"server",
|
||||
"packages/db",
|
||||
"packages/shared",
|
||||
"packages/adapter-utils",
|
||||
@@ -24,17 +24,31 @@ const workspacePaths = [
|
||||
"packages/adapters/openclaw",
|
||||
];
|
||||
|
||||
// Workspace packages that should NOT be bundled — they'll be published
|
||||
// to npm and resolved at runtime (e.g. @paperclipai/server uses dynamic import).
|
||||
const externalWorkspacePackages = new Set([
|
||||
"@paperclipai/server",
|
||||
]);
|
||||
|
||||
// Collect all external (non-workspace) npm package names
|
||||
const externals = new Set();
|
||||
for (const p of workspacePaths) {
|
||||
const pkg = JSON.parse(readFileSync(resolve(repoRoot, p, "package.json"), "utf8"));
|
||||
for (const name of Object.keys(pkg.dependencies || {})) {
|
||||
if (!name.startsWith("@paperclipai/")) externals.add(name);
|
||||
if (externalWorkspacePackages.has(name)) {
|
||||
externals.add(name);
|
||||
} else if (!name.startsWith("@paperclipai/")) {
|
||||
externals.add(name);
|
||||
}
|
||||
}
|
||||
for (const name of Object.keys(pkg.optionalDependencies || {})) {
|
||||
externals.add(name);
|
||||
}
|
||||
}
|
||||
// Also add all published workspace packages as external
|
||||
for (const name of externalWorkspacePackages) {
|
||||
externals.add(name);
|
||||
}
|
||||
|
||||
/** @type {import('esbuild').BuildOptions} */
|
||||
export default {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "paperclipai",
|
||||
"version": "0.1.2",
|
||||
"version": "0.2.0",
|
||||
"description": "Paperclip CLI — orchestrate AI agent teams to run a business",
|
||||
"type": "module",
|
||||
"bin": {
|
||||
@@ -23,9 +23,13 @@
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "tsx src/index.ts",
|
||||
"build": "tsc",
|
||||
"build": "node --input-type=module -e \"import esbuild from 'esbuild'; import config from './esbuild.config.mjs'; await esbuild.build(config);\" && chmod +x dist/index.js",
|
||||
"clean": "rm -rf dist",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@@ -93,41 +93,23 @@ function maybeEnableUiDevMiddleware(entrypoint: string): void {
|
||||
}
|
||||
|
||||
async function importServerEntry(): Promise<void> {
|
||||
// Dev mode: try local workspace path (monorepo with tsx)
|
||||
const projectRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../..");
|
||||
const fileCandidates = [
|
||||
path.resolve(projectRoot, "server/src/index.ts"),
|
||||
path.resolve(projectRoot, "server/dist/index.js"),
|
||||
];
|
||||
const existingFileCandidates = fileCandidates.filter((filePath) => fs.existsSync(filePath));
|
||||
if (existingFileCandidates.length > 0) {
|
||||
for (const filePath of existingFileCandidates) {
|
||||
try {
|
||||
maybeEnableUiDevMiddleware(filePath);
|
||||
await import(pathToFileURL(filePath).href);
|
||||
return;
|
||||
} catch (err) {
|
||||
throw new Error(`Failed to start Paperclip server from ${filePath}: ${formatError(err)}`);
|
||||
}
|
||||
}
|
||||
const devEntry = path.resolve(projectRoot, "server/src/index.ts");
|
||||
if (fs.existsSync(devEntry)) {
|
||||
maybeEnableUiDevMiddleware(devEntry);
|
||||
await import(pathToFileURL(devEntry).href);
|
||||
return;
|
||||
}
|
||||
|
||||
const specifierCandidates: string[] = ["@paperclipai/server/dist/index.js", "@paperclipai/server/src/index.ts"];
|
||||
const missingErrors: string[] = [];
|
||||
for (const specifier of specifierCandidates) {
|
||||
try {
|
||||
maybeEnableUiDevMiddleware(specifier);
|
||||
await import(specifier);
|
||||
return;
|
||||
} catch (err) {
|
||||
if (isModuleNotFoundError(err)) {
|
||||
missingErrors.push(`${specifier}: ${formatError(err)}`);
|
||||
continue;
|
||||
}
|
||||
throw new Error(`Failed to start Paperclip server from ${specifier}: ${formatError(err)}`);
|
||||
}
|
||||
// Production mode: import the published @paperclipai/server package
|
||||
try {
|
||||
await import("@paperclipai/server");
|
||||
} catch (err) {
|
||||
throw new Error(
|
||||
`Could not locate a Paperclip server entrypoint.\n` +
|
||||
`Tried: ${devEntry}, @paperclipai/server\n` +
|
||||
`${formatError(err)}`,
|
||||
);
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`Could not locate a Paperclip server entrypoint. Tried: ${[...fileCandidates, ...specifierCandidates].join(", ")}\n${missingErrors.join("\n")}`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ const DATA_DIR_OPTION_HELP =
|
||||
program
|
||||
.name("paperclipai")
|
||||
.description("Paperclip CLI — setup, diagnose, and configure your instance")
|
||||
.version("0.1.2");
|
||||
.version("0.2.0");
|
||||
|
||||
program.hook("preAction", (_thisCommand, actionCommand) => {
|
||||
const options = actionCommand.optsWithGlobals() as DataDirOptionLike;
|
||||
|
||||
Reference in New Issue
Block a user