Resolved conflicts: - ui CompanySettingsSidebar.tsx: keep both Secrets (local) and Cloud upstream (master) nav items - ui CompanySettingsNav.tsx + test: take master's cloud-upstream/members (drops deprecated `access` tab now consolidated into `members`) - server plugin-worker-manager.ts: take master's 15min RPC timeout cap - pnpm-lock.yaml: regenerated via `pnpm install` against merged package.json files Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -52,3 +52,9 @@ test("resolveTargetPackage matches by package name or dir", () => {
|
||||
assert.equal(resolveTargetPackage("@paperclipai/a", packages).dir, "packages/a");
|
||||
assert.equal(resolveTargetPackage("./packages/b", packages).name, "@paperclipai/b");
|
||||
});
|
||||
|
||||
test("resolveTargetPackage includes the workspace diff plugin bootstrap package", () => {
|
||||
const pkg = resolveTargetPackage("@paperclipai/plugin-workspace-diff");
|
||||
|
||||
assert.equal(pkg.dir, "packages/plugins/plugin-workspace-diff");
|
||||
});
|
||||
|
||||
+17
-3
@@ -36,6 +36,7 @@ const autoRestartPollIntervalMs = 2500;
|
||||
const gracefulShutdownTimeoutMs = 10_000;
|
||||
const changedPathSampleLimit = 5;
|
||||
const devServerStatusFilePath = path.join(repoRoot, ".paperclip", "dev-server-status.json");
|
||||
const devServerRestartRequestFilePath = path.join(repoRoot, ".paperclip", "dev-server-restart-request.json");
|
||||
const devServerStatusToken = mode === "dev" ? randomUUID() : null;
|
||||
const devServerStatusTokenHeader = "x-paperclip-dev-server-status-token";
|
||||
|
||||
@@ -70,6 +71,7 @@ const ignoredDirectoryNames = new Set([
|
||||
]);
|
||||
|
||||
const ignoredRelativePaths = new Set([
|
||||
".paperclip/dev-server-restart-request.json",
|
||||
".paperclip/dev-server-status.json",
|
||||
]);
|
||||
|
||||
@@ -348,6 +350,13 @@ function writeDevServerStatus() {
|
||||
function clearDevServerStatus() {
|
||||
if (mode !== "dev") return;
|
||||
rmSync(devServerStatusFilePath, { force: true });
|
||||
rmSync(devServerRestartRequestFilePath, { force: true });
|
||||
}
|
||||
|
||||
function consumeDevServerRestartRequest() {
|
||||
if (mode !== "dev" || !existsSync(devServerRestartRequestFilePath)) return false;
|
||||
rmSync(devServerRestartRequestFilePath, { force: true });
|
||||
return true;
|
||||
}
|
||||
|
||||
async function updateDevServiceRecord(extra?: Record<string, unknown>) {
|
||||
@@ -633,7 +642,8 @@ async function startServerChild() {
|
||||
|
||||
async function maybeAutoRestartChild() {
|
||||
if (mode !== "dev" || restartInFlight || !child) return;
|
||||
if (dirtyPaths.size === 0 && pendingMigrations.length === 0) return;
|
||||
const manualRestartRequested = consumeDevServerRestartRequest();
|
||||
if (!manualRestartRequested && dirtyPaths.size === 0 && pendingMigrations.length === 0) return;
|
||||
|
||||
restartInFlight = true;
|
||||
let health: { devServer?: { enabled?: boolean; autoRestartEnabled?: boolean; activeRunCount?: number } } | null = null;
|
||||
@@ -645,11 +655,15 @@ async function maybeAutoRestartChild() {
|
||||
}
|
||||
|
||||
const devServer = health?.devServer;
|
||||
if (!devServer?.enabled || devServer.autoRestartEnabled !== true) {
|
||||
if (!devServer?.enabled) {
|
||||
restartInFlight = false;
|
||||
return;
|
||||
}
|
||||
if ((devServer.activeRunCount ?? 0) > 0) {
|
||||
if (!manualRestartRequested && devServer.autoRestartEnabled !== true) {
|
||||
restartInFlight = false;
|
||||
return;
|
||||
}
|
||||
if (!manualRestartRequested && (devServer.activeRunCount ?? 0) > 0) {
|
||||
restartInFlight = false;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -3,13 +3,26 @@ set -euo pipefail
|
||||
|
||||
# prepare-server-ui-dist.sh — Build the UI and copy it into server/ui-dist.
|
||||
# This keeps @paperclipai/server publish artifacts self-contained for static UI serving.
|
||||
# When PAPERCLIP_RELEASE_REUSE_UI_DIST=1 and ui/dist already exists, reuse that
|
||||
# output instead of rebuilding it again inside the release packaging flow.
|
||||
|
||||
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
UI_DIST="$REPO_ROOT/ui/dist"
|
||||
SERVER_UI_DIST="$REPO_ROOT/server/ui-dist"
|
||||
|
||||
echo " -> Building @paperclipai/ui..."
|
||||
pnpm --dir "$REPO_ROOT" --filter @paperclipai/ui build
|
||||
should_reuse_existing_ui_dist=false
|
||||
case "${PAPERCLIP_RELEASE_REUSE_UI_DIST:-}" in
|
||||
1|true|TRUE|yes|YES)
|
||||
should_reuse_existing_ui_dist=true
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ "$should_reuse_existing_ui_dist" = true ] && [ -f "$UI_DIST/index.html" ]; then
|
||||
echo " -> Reusing existing @paperclipai/ui dist output"
|
||||
else
|
||||
echo " -> Building @paperclipai/ui..."
|
||||
pnpm --dir "$REPO_ROOT" --filter @paperclipai/ui build
|
||||
fi
|
||||
|
||||
if [ ! -f "$UI_DIST/index.html" ]; then
|
||||
echo "Error: UI build output missing at $UI_DIST/index.html"
|
||||
|
||||
@@ -34,6 +34,11 @@
|
||||
"name": "@paperclipai/adapter-gemini-local",
|
||||
"publishFromCi": true
|
||||
},
|
||||
{
|
||||
"dir": "packages/adapters/grok-local",
|
||||
"name": "@paperclipai/adapter-grok-local",
|
||||
"publishFromCi": true
|
||||
},
|
||||
{
|
||||
"dir": "packages/adapters/opencode-local",
|
||||
"name": "@paperclipai/adapter-opencode-local",
|
||||
@@ -64,6 +69,11 @@
|
||||
"name": "@paperclipai/plugin-sdk",
|
||||
"publishFromCi": true
|
||||
},
|
||||
{
|
||||
"dir": "packages/plugins/plugin-workspace-diff",
|
||||
"name": "@paperclipai/plugin-workspace-diff",
|
||||
"publishFromCi": false
|
||||
},
|
||||
{
|
||||
"dir": "server",
|
||||
"name": "@paperclipai/server",
|
||||
@@ -109,6 +119,11 @@
|
||||
"name": "@paperclipai/plugin-e2b",
|
||||
"publishFromCi": true
|
||||
},
|
||||
{
|
||||
"dir": "packages/plugins/sandbox-providers/modal",
|
||||
"name": "@paperclipai/plugin-modal",
|
||||
"publishFromCi": true
|
||||
},
|
||||
{
|
||||
"dir": "ui",
|
||||
"name": "@paperclipai/ui",
|
||||
|
||||
@@ -198,6 +198,10 @@ fi
|
||||
|
||||
set_cleanup_trap
|
||||
|
||||
# The release flow already prepares ui/dist before packaging. Reuse that output
|
||||
# so server prepack does not rebuild the UI a second time during preview/publish.
|
||||
export PAPERCLIP_RELEASE_REUSE_UI_DIST=1
|
||||
|
||||
if [ "$skip_verify" = false ]; then
|
||||
release_info ""
|
||||
release_info "==> Step 1/7: Verification gate..."
|
||||
|
||||
@@ -86,7 +86,7 @@ if (buildGapPackages.length === 0) {
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
run("pnpm", ["--filter", "@paperclipai/plugin-sdk", "build"]);
|
||||
run("pnpm", ["--filter", "@paperclipai/plugin-sdk", "ensure-build-deps"]);
|
||||
|
||||
for (const workspacePkg of buildGapPackages) {
|
||||
run("pnpm", ["--filter", workspacePkg.name, "typecheck"]);
|
||||
|
||||
@@ -49,6 +49,12 @@ let invocationIndex = 0;
|
||||
const serializedModeName = "serialized";
|
||||
const generalModeName = "general";
|
||||
const allModeName = "all";
|
||||
const generalServerGroupName = "general-server";
|
||||
const generalWorkspacesAGroupName = "general-workspaces-a";
|
||||
const generalWorkspacesBGroupName = "general-workspaces-b";
|
||||
const generalWorkspacesAProjects = ["@paperclipai/ui", "paperclipai"];
|
||||
const generalWorkspacesBProjects = nonServerProjects.filter((project) => !generalWorkspacesAProjects.includes(project));
|
||||
const generalGroupNames = [generalServerGroupName, generalWorkspacesAGroupName, generalWorkspacesBGroupName];
|
||||
|
||||
function walk(dir) {
|
||||
const entries = readdirSync(dir);
|
||||
@@ -117,6 +123,7 @@ function parseCliOptions(argv) {
|
||||
let mode = allModeName;
|
||||
let shardIndex = null;
|
||||
let shardCount = null;
|
||||
let group = null;
|
||||
let dryRun = false;
|
||||
|
||||
for (let index = 0; index < argv.length; index += 1) {
|
||||
@@ -163,6 +170,17 @@ function parseCliOptions(argv) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg === "--group") {
|
||||
group = readOptionValue(argv, index, arg);
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg.startsWith("--group=")) {
|
||||
group = arg.slice("--group=".length);
|
||||
continue;
|
||||
}
|
||||
|
||||
fail(`Unknown argument "${arg}".`);
|
||||
}
|
||||
|
||||
@@ -178,6 +196,14 @@ function parseCliOptions(argv) {
|
||||
fail("--shard-index/--shard-count are only valid with --mode serialized.");
|
||||
}
|
||||
|
||||
if (group !== null && mode !== generalModeName) {
|
||||
fail("--group is only valid with --mode general.");
|
||||
}
|
||||
|
||||
if (group !== null && !generalGroupNames.includes(group)) {
|
||||
fail(`Unknown group "${group}". Expected one of: ${generalGroupNames.join(", ")}.`);
|
||||
}
|
||||
|
||||
if (mode === serializedModeName) {
|
||||
const resolvedShardCount = shardCount ?? 1;
|
||||
const resolvedShardIndex = shardIndex ?? 0;
|
||||
@@ -189,6 +215,7 @@ function parseCliOptions(argv) {
|
||||
mode,
|
||||
shardIndex: resolvedShardIndex,
|
||||
shardCount: resolvedShardCount,
|
||||
group: null,
|
||||
dryRun,
|
||||
};
|
||||
}
|
||||
@@ -197,6 +224,7 @@ function parseCliOptions(argv) {
|
||||
mode,
|
||||
shardIndex: null,
|
||||
shardCount: null,
|
||||
group,
|
||||
dryRun,
|
||||
};
|
||||
}
|
||||
@@ -208,12 +236,14 @@ function selectSerializedSuites(routeTests, shardIndex, shardCount) {
|
||||
function runVitest(args, label) {
|
||||
console.log(`\n[test:run] ${label}`);
|
||||
invocationIndex += 1;
|
||||
const testRoot = mkdtempSync(path.join(os.tmpdir(), `paperclip-vitest-${process.pid}-${invocationIndex}-`));
|
||||
const tempRootParent = process.platform === "win32" ? os.tmpdir() : "/tmp";
|
||||
const testRoot = mkdtempSync(path.join(tempRootParent, `pcvt-${process.pid}-${invocationIndex}-`));
|
||||
// Keep per-run paths compact so Unix socket fixtures stay under macOS path limits.
|
||||
const env = {
|
||||
...process.env,
|
||||
PAPERCLIP_HOME: path.join(testRoot, "home"),
|
||||
PAPERCLIP_INSTANCE_ID: `vitest-${process.pid}-${invocationIndex}`,
|
||||
TMPDIR: path.join(testRoot, "tmp"),
|
||||
PAPERCLIP_HOME: path.join(testRoot, "h"),
|
||||
PAPERCLIP_INSTANCE_ID: `vt-${process.pid}-${invocationIndex}`,
|
||||
TMPDIR: path.join(testRoot, "t"),
|
||||
};
|
||||
mkdirSync(env.PAPERCLIP_HOME, { recursive: true });
|
||||
mkdirSync(env.TMPDIR, { recursive: true });
|
||||
@@ -232,15 +262,38 @@ function runVitest(args, label) {
|
||||
}
|
||||
|
||||
function runGeneralSuites(routeTests) {
|
||||
const excludeRouteArgs = routeTests.flatMap((file) => ["--exclude", file.serverPath]);
|
||||
for (const project of nonServerProjects) {
|
||||
runVitest(["--project", project], `non-server project ${project}`);
|
||||
for (const groupName of generalGroupNames) {
|
||||
runGeneralGroup(routeTests, groupName);
|
||||
}
|
||||
}
|
||||
|
||||
function runProjectGroup(projects, groupName) {
|
||||
for (const project of projects) {
|
||||
runVitest(["--project", project], `${groupName} project ${project}`);
|
||||
}
|
||||
}
|
||||
|
||||
function runGeneralGroup(routeTests, groupName) {
|
||||
if (groupName === generalServerGroupName) {
|
||||
const excludeRouteArgs = routeTests.flatMap((file) => ["--exclude", file.serverPath]);
|
||||
runVitest(
|
||||
["--project", "@paperclipai/server", ...excludeRouteArgs],
|
||||
`${groupName} server suites excluding ${routeTests.length} serialized suites`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
runVitest(
|
||||
["--project", "@paperclipai/server", ...excludeRouteArgs],
|
||||
`server suites excluding ${routeTests.length} serialized suites`,
|
||||
);
|
||||
if (groupName === generalWorkspacesAGroupName) {
|
||||
runProjectGroup(generalWorkspacesAProjects, groupName);
|
||||
return;
|
||||
}
|
||||
|
||||
if (groupName === generalWorkspacesBGroupName) {
|
||||
runProjectGroup(generalWorkspacesBProjects, groupName);
|
||||
return;
|
||||
}
|
||||
|
||||
fail(`Unknown group "${groupName}".`);
|
||||
}
|
||||
|
||||
function runSerializedSuites(routeTests, shardIndex, shardCount) {
|
||||
@@ -283,6 +336,8 @@ if (options.dryRun) {
|
||||
mode: options.mode,
|
||||
shardIndex: options.shardIndex,
|
||||
shardCount: options.shardCount,
|
||||
group: options.group,
|
||||
availableGeneralGroups: generalGroupNames,
|
||||
serializedSuiteCount: routeTests.length,
|
||||
selectedSerializedSuites: serializedSuites.map((routeTest) => routeTest.repoPath),
|
||||
},
|
||||
@@ -294,7 +349,11 @@ if (options.dryRun) {
|
||||
}
|
||||
|
||||
if (options.mode === generalModeName || options.mode === allModeName) {
|
||||
runGeneralSuites(routeTests);
|
||||
if (options.group) {
|
||||
runGeneralGroup(routeTests, options.group);
|
||||
} else {
|
||||
runGeneralSuites(routeTests);
|
||||
}
|
||||
}
|
||||
|
||||
if (options.mode === serializedModeName || options.mode === allModeName) {
|
||||
|
||||
Reference in New Issue
Block a user