@intent-systems/nexus 2026.1.5-3 → 2026.1.5-4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/capabilities/detector.js +214 -0
- package/dist/capabilities/registry.js +98 -0
- package/dist/channels/location.js +44 -0
- package/dist/channels/web/index.js +2 -0
- package/dist/control-plane/broker/broker.js +969 -0
- package/dist/control-plane/compaction.js +284 -0
- package/dist/control-plane/factory.js +31 -0
- package/dist/control-plane/index.js +10 -0
- package/dist/control-plane/odu/agents.js +187 -0
- package/dist/control-plane/odu/interaction-tools.js +196 -0
- package/dist/control-plane/odu/prompt-loader.js +95 -0
- package/dist/control-plane/odu/runtime.js +467 -0
- package/dist/control-plane/odu/types.js +6 -0
- package/dist/control-plane/odu-control-plane.js +314 -0
- package/dist/control-plane/single-agent.js +249 -0
- package/dist/control-plane/types.js +11 -0
- package/dist/credentials/store.js +323 -0
- package/dist/logging/redact.js +109 -0
- package/dist/markdown/fences.js +58 -0
- package/dist/memory/embeddings.js +146 -0
- package/dist/memory/index.js +382 -0
- package/dist/memory/internal.js +163 -0
- package/dist/pairing/pairing-store.js +194 -0
- package/dist/plugins/cli.js +42 -0
- package/dist/plugins/discovery.js +253 -0
- package/dist/plugins/install.js +181 -0
- package/dist/plugins/loader.js +290 -0
- package/dist/plugins/registry.js +105 -0
- package/dist/plugins/status.js +29 -0
- package/dist/plugins/tools.js +39 -0
- package/dist/plugins/types.js +1 -0
- package/dist/routing/resolve-route.js +144 -0
- package/dist/routing/session-key.js +63 -0
- package/dist/utils/provider-utils.js +28 -0
- package/package.json +4 -29
- package/patches/@mariozechner__pi-ai.patch +215 -0
- package/patches/playwright-core@1.57.0.patch +13 -0
- package/patches/qrcode-terminal.patch +12 -0
- package/scripts/postinstall.js +202 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility functions for provider-specific logic and capabilities.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Returns true if the provider requires reasoning to be wrapped in tags
|
|
6
|
+
* (e.g. <think> and <final>) in the text stream, rather than using native
|
|
7
|
+
* API fields for reasoning/thinking.
|
|
8
|
+
*/
|
|
9
|
+
export function isReasoningTagProvider(provider) {
|
|
10
|
+
if (!provider)
|
|
11
|
+
return false;
|
|
12
|
+
const normalized = provider.trim().toLowerCase();
|
|
13
|
+
// Check for exact matches or known prefixes/substrings for reasoning providers
|
|
14
|
+
if (normalized === "ollama" ||
|
|
15
|
+
normalized === "google-gemini-cli" ||
|
|
16
|
+
normalized === "google-generative-ai") {
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
// Handle google-antigravity and its model variations (e.g. google-antigravity/gemini-3)
|
|
20
|
+
if (normalized.includes("google-antigravity")) {
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
// Handle Minimax (M2.1 is chatty/reasoning-like)
|
|
24
|
+
if (normalized.includes("minimax")) {
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
return false;
|
|
28
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intent-systems/nexus",
|
|
3
|
-
"version": "2026.1.5-
|
|
3
|
+
"version": "2026.1.5-4",
|
|
4
4
|
"description": "WhatsApp gateway CLI (Baileys web) with Pi RPC agent",
|
|
5
5
|
"homepage": "https://github.com/Napageneral/nexus",
|
|
6
6
|
"repository": {
|
|
@@ -13,36 +13,11 @@
|
|
|
13
13
|
"nexus": "dist/entry.js"
|
|
14
14
|
},
|
|
15
15
|
"files": [
|
|
16
|
-
"dist
|
|
17
|
-
"dist/auto-reply/**",
|
|
18
|
-
"dist/browser/**",
|
|
19
|
-
"dist/canvas-host/**",
|
|
20
|
-
"dist/cli/**",
|
|
21
|
-
"dist/commands/**",
|
|
22
|
-
"dist/config/**",
|
|
23
|
-
"dist/control-ui/**",
|
|
24
|
-
"dist/cron/**",
|
|
25
|
-
"dist/daemon/**",
|
|
26
|
-
"dist/discord/**",
|
|
27
|
-
"dist/gateway/**",
|
|
28
|
-
"dist/hooks/**",
|
|
29
|
-
"dist/imessage/**",
|
|
30
|
-
"dist/infra/**",
|
|
31
|
-
"dist/macos/**",
|
|
32
|
-
"dist/media/**",
|
|
33
|
-
"dist/process/**",
|
|
34
|
-
"dist/sessions/**",
|
|
35
|
-
"dist/providers/**",
|
|
36
|
-
"dist/signal/**",
|
|
37
|
-
"dist/slack/**",
|
|
38
|
-
"dist/telegram/**",
|
|
39
|
-
"dist/tui/**",
|
|
40
|
-
"dist/web/**",
|
|
41
|
-
"dist/wizard/**",
|
|
42
|
-
"dist/*.js",
|
|
43
|
-
"dist/*.json",
|
|
16
|
+
"dist/**",
|
|
44
17
|
"docs/**",
|
|
45
18
|
"skills/**",
|
|
19
|
+
"patches/**",
|
|
20
|
+
"scripts/postinstall.js",
|
|
46
21
|
"README.md",
|
|
47
22
|
"README-header.png",
|
|
48
23
|
"CHANGELOG.md",
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
diff --git a/dist/providers/google-shared.js b/dist/providers/google-shared.js
|
|
2
|
+
index 7bc0a9f5d6241f191cd607ecb37b3acac8d58267..56866774e47444b5d333961c9b20fce582363124 100644
|
|
3
|
+
--- a/dist/providers/google-shared.js
|
|
4
|
+
+++ b/dist/providers/google-shared.js
|
|
5
|
+
@@ -10,13 +10,27 @@ import { transformMessages } from "./transorm-messages.js";
|
|
6
|
+
export function convertMessages(model, context) {
|
|
7
|
+
const contents = [];
|
|
8
|
+
const transformedMessages = transformMessages(context.messages, model);
|
|
9
|
+
+
|
|
10
|
+
+ /**
|
|
11
|
+
+ * Helper to add content while merging consecutive messages of the same role.
|
|
12
|
+
+ * Gemini/Cloud Code Assist requires strict role alternation (user/model/user/model).
|
|
13
|
+
+ * Consecutive messages of the same role cause "function call turn" errors.
|
|
14
|
+
+ */
|
|
15
|
+
+ function addContent(role, parts) {
|
|
16
|
+
+ if (parts.length === 0) return;
|
|
17
|
+
+ const lastContent = contents[contents.length - 1];
|
|
18
|
+
+ if (lastContent?.role === role) {
|
|
19
|
+
+ // Merge into existing message of same role
|
|
20
|
+
+ lastContent.parts.push(...parts);
|
|
21
|
+
+ } else {
|
|
22
|
+
+ contents.push({ role, parts });
|
|
23
|
+
+ }
|
|
24
|
+
+ }
|
|
25
|
+
+
|
|
26
|
+
for (const msg of transformedMessages) {
|
|
27
|
+
if (msg.role === "user") {
|
|
28
|
+
if (typeof msg.content === "string") {
|
|
29
|
+
- contents.push({
|
|
30
|
+
- role: "user",
|
|
31
|
+
- parts: [{ text: sanitizeSurrogates(msg.content) }],
|
|
32
|
+
- });
|
|
33
|
+
+ addContent("user", [{ text: sanitizeSurrogates(msg.content) }]);
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
const parts = msg.content.map((item) => {
|
|
37
|
+
@@ -35,10 +49,7 @@ export function convertMessages(model, context) {
|
|
38
|
+
const filteredParts = !model.input.includes("image") ? parts.filter((p) => p.text !== undefined) : parts;
|
|
39
|
+
if (filteredParts.length === 0)
|
|
40
|
+
continue;
|
|
41
|
+
- contents.push({
|
|
42
|
+
- role: "user",
|
|
43
|
+
- parts: filteredParts,
|
|
44
|
+
- });
|
|
45
|
+
+ addContent("user", filteredParts);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
else if (msg.role === "assistant") {
|
|
49
|
+
@@ -51,9 +62,19 @@ export function convertMessages(model, context) {
|
|
50
|
+
parts.push({ text: sanitizeSurrogates(block.text) });
|
|
51
|
+
}
|
|
52
|
+
else if (block.type === "thinking") {
|
|
53
|
+
- // Thinking blocks require signatures for Claude via Antigravity.
|
|
54
|
+
- // If signature is missing (e.g. from GPT-OSS), convert to regular text with delimiters.
|
|
55
|
+
- if (block.thinkingSignature) {
|
|
56
|
+
+ // Thinking blocks handling varies by model:
|
|
57
|
+
+ // - Claude via Antigravity: requires thinkingSignature
|
|
58
|
+
+ // - Gemini: skip entirely (doesn't understand thoughtSignature, and mimics <thinking> tags)
|
|
59
|
+
+ // - Other models: convert to text with delimiters
|
|
60
|
+
+ const isGemini = model.id.toLowerCase().includes("gemini");
|
|
61
|
+
+ const isClaude = model.id.toLowerCase().includes("claude");
|
|
62
|
+
+ if (isGemini) {
|
|
63
|
+
+ // Skip thinking blocks entirely for Gemini - it doesn't support them
|
|
64
|
+
+ // and will mimic <thinking> tags if we convert to text
|
|
65
|
+
+ continue;
|
|
66
|
+
+ }
|
|
67
|
+
+ else if (block.thinkingSignature && isClaude) {
|
|
68
|
+
+ // Claude via Antigravity requires the signature
|
|
69
|
+
parts.push({
|
|
70
|
+
thought: true,
|
|
71
|
+
text: sanitizeSurrogates(block.thinking),
|
|
72
|
+
@@ -61,6 +82,7 @@ export function convertMessages(model, context) {
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
+ // Other models: convert to text with delimiters
|
|
77
|
+
parts.push({
|
|
78
|
+
text: `<thinking>\n${sanitizeSurrogates(block.thinking)}\n</thinking>`,
|
|
79
|
+
});
|
|
80
|
+
@@ -85,10 +107,7 @@ export function convertMessages(model, context) {
|
|
81
|
+
}
|
|
82
|
+
if (parts.length === 0)
|
|
83
|
+
continue;
|
|
84
|
+
- contents.push({
|
|
85
|
+
- role: "model",
|
|
86
|
+
- parts,
|
|
87
|
+
- });
|
|
88
|
+
+ addContent("model", parts);
|
|
89
|
+
}
|
|
90
|
+
else if (msg.role === "toolResult") {
|
|
91
|
+
// Extract text and image content
|
|
92
|
+
@@ -125,27 +144,94 @@ export function convertMessages(model, context) {
|
|
93
|
+
}
|
|
94
|
+
// Cloud Code Assist API requires all function responses to be in a single user turn.
|
|
95
|
+
// Check if the last content is already a user turn with function responses and merge.
|
|
96
|
+
+ // Use addContent for proper role alternation handling.
|
|
97
|
+
const lastContent = contents[contents.length - 1];
|
|
98
|
+
if (lastContent?.role === "user" && lastContent.parts?.some((p) => p.functionResponse)) {
|
|
99
|
+
lastContent.parts.push(functionResponsePart);
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
- contents.push({
|
|
103
|
+
- role: "user",
|
|
104
|
+
- parts: [functionResponsePart],
|
|
105
|
+
- });
|
|
106
|
+
+ addContent("user", [functionResponsePart]);
|
|
107
|
+
}
|
|
108
|
+
// For older models, add images in a separate user message
|
|
109
|
+
+ // Note: This may create consecutive user messages, but addContent will merge them
|
|
110
|
+
if (hasImages && !supportsMultimodalFunctionResponse) {
|
|
111
|
+
- contents.push({
|
|
112
|
+
- role: "user",
|
|
113
|
+
- parts: [{ text: "Tool result image:" }, ...imageParts],
|
|
114
|
+
- });
|
|
115
|
+
+ addContent("user", [{ text: "Tool result image:" }, ...imageParts]);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return contents;
|
|
120
|
+
}
|
|
121
|
+
+/**
|
|
122
|
+
+ * Sanitize JSON Schema for Google Cloud Code Assist API.
|
|
123
|
+
+ * Removes unsupported keywords like patternProperties, const, anyOf, etc.
|
|
124
|
+
+ * and converts to a format compatible with Google's function declarations.
|
|
125
|
+
+ */
|
|
126
|
+
+function sanitizeSchemaForGoogle(schema) {
|
|
127
|
+
+ if (!schema || typeof schema !== 'object') {
|
|
128
|
+
+ return schema;
|
|
129
|
+
+ }
|
|
130
|
+
+ // If it's an array, sanitize each element
|
|
131
|
+
+ if (Array.isArray(schema)) {
|
|
132
|
+
+ return schema.map(item => sanitizeSchemaForGoogle(item));
|
|
133
|
+
+ }
|
|
134
|
+
+ const sanitized = {};
|
|
135
|
+
+ // List of unsupported JSON Schema keywords that Google's API doesn't understand
|
|
136
|
+
+ const unsupportedKeywords = [
|
|
137
|
+
+ 'patternProperties',
|
|
138
|
+
+ 'const',
|
|
139
|
+
+ 'anyOf',
|
|
140
|
+
+ 'oneOf',
|
|
141
|
+
+ 'allOf',
|
|
142
|
+
+ 'not',
|
|
143
|
+
+ '$schema',
|
|
144
|
+
+ '$id',
|
|
145
|
+
+ '$ref',
|
|
146
|
+
+ '$defs',
|
|
147
|
+
+ 'definitions',
|
|
148
|
+
+ 'if',
|
|
149
|
+
+ 'then',
|
|
150
|
+
+ 'else',
|
|
151
|
+
+ 'dependentSchemas',
|
|
152
|
+
+ 'dependentRequired',
|
|
153
|
+
+ 'unevaluatedProperties',
|
|
154
|
+
+ 'unevaluatedItems',
|
|
155
|
+
+ 'contentEncoding',
|
|
156
|
+
+ 'contentMediaType',
|
|
157
|
+
+ 'contentSchema',
|
|
158
|
+
+ 'deprecated',
|
|
159
|
+
+ 'readOnly',
|
|
160
|
+
+ 'writeOnly',
|
|
161
|
+
+ 'examples',
|
|
162
|
+
+ '$comment',
|
|
163
|
+
+ 'additionalProperties',
|
|
164
|
+
+ ];
|
|
165
|
+
+ // TODO(steipete): lossy schema scrub; revisit when Google supports these keywords.
|
|
166
|
+
+ for (const [key, value] of Object.entries(schema)) {
|
|
167
|
+
+ // Skip unsupported keywords
|
|
168
|
+
+ if (unsupportedKeywords.includes(key)) {
|
|
169
|
+
+ continue;
|
|
170
|
+
+ }
|
|
171
|
+
+ // Recursively sanitize nested objects
|
|
172
|
+
+ if (key === 'properties' && typeof value === 'object' && value !== null) {
|
|
173
|
+
+ sanitized[key] = {};
|
|
174
|
+
+ for (const [propKey, propValue] of Object.entries(value)) {
|
|
175
|
+
+ sanitized[key][propKey] = sanitizeSchemaForGoogle(propValue);
|
|
176
|
+
+ }
|
|
177
|
+
+ } else if (key === 'items' && typeof value === 'object') {
|
|
178
|
+
+ sanitized[key] = sanitizeSchemaForGoogle(value);
|
|
179
|
+
+ } else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
180
|
+
+ sanitized[key] = sanitizeSchemaForGoogle(value);
|
|
181
|
+
+ } else {
|
|
182
|
+
+ sanitized[key] = value;
|
|
183
|
+
+ }
|
|
184
|
+
+ }
|
|
185
|
+
+ // Ensure type: "object" is present when properties or required exist
|
|
186
|
+
+ // Google API requires type to be set when these fields are present
|
|
187
|
+
+ if (('properties' in sanitized || 'required' in sanitized) && !('type' in sanitized)) {
|
|
188
|
+
+ sanitized.type = 'object';
|
|
189
|
+
+ }
|
|
190
|
+
+ return sanitized;
|
|
191
|
+
+}
|
|
192
|
+
/**
|
|
193
|
+
* Convert tools to Gemini function declarations format.
|
|
194
|
+
*/
|
|
195
|
+
@@ -157,7 +243,7 @@ export function convertTools(tools) {
|
|
196
|
+
functionDeclarations: tools.map((tool) => ({
|
|
197
|
+
name: tool.name,
|
|
198
|
+
description: tool.description,
|
|
199
|
+
- parameters: tool.parameters,
|
|
200
|
+
+ parameters: sanitizeSchemaForGoogle(tool.parameters),
|
|
201
|
+
})),
|
|
202
|
+
},
|
|
203
|
+
];
|
|
204
|
+
diff --git a/dist/providers/openai-responses.js b/dist/providers/openai-responses.js
|
|
205
|
+
index 20fb0a22aaa28f7ff7c2f44a8b628fa1d9d7d936..31bae0aface1319487ce62d35f1f3b6ed334863e 100644
|
|
206
|
+
--- a/dist/providers/openai-responses.js
|
|
207
|
+
+++ b/dist/providers/openai-responses.js
|
|
208
|
+
@@ -486,7 +486,6 @@ function convertTools(tools) {
|
|
209
|
+
name: tool.name,
|
|
210
|
+
description: tool.description,
|
|
211
|
+
parameters: tool.parameters, // TypeBox already generates JSON Schema
|
|
212
|
+
- strict: null,
|
|
213
|
+
}));
|
|
214
|
+
}
|
|
215
|
+
function mapStopReason(status) {
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
diff --git a/lib/utilsBundle.js b/lib/utilsBundle.js
|
|
2
|
+
index 7dd8831f29c19f2e20468508b77b0a3f9d204ae6..c50a1ac2b3439a5b2fbf8afa61c369360710071f 100644
|
|
3
|
+
--- a/lib/utilsBundle.js
|
|
4
|
+
+++ b/lib/utilsBundle.js
|
|
5
|
+
@@ -59,7 +59,7 @@ const program = require("./utilsBundleImpl").program;
|
|
6
|
+
const ProgramOption = require("./utilsBundleImpl").ProgramOption;
|
|
7
|
+
const progress = require("./utilsBundleImpl").progress;
|
|
8
|
+
const SocksProxyAgent = require("./utilsBundleImpl").SocksProxyAgent;
|
|
9
|
+
-const ws = require("./utilsBundleImpl").ws;
|
|
10
|
+
+const ws = "Bun" in globalThis ? require("ws") : require("./utilsBundleImpl").ws;
|
|
11
|
+
const wsServer = require("./utilsBundleImpl").wsServer;
|
|
12
|
+
const wsReceiver = require("./utilsBundleImpl").wsReceiver;
|
|
13
|
+
const wsSender = require("./utilsBundleImpl").wsSender;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
diff --git a/lib/main.js b/lib/main.js
|
|
2
|
+
index 488cc1aea9802b3d6ae13aee27556403bec55d1c..3de1f934868d81e8204f00e6a4bf2696a05f7340 100644
|
|
3
|
+
--- a/lib/main.js
|
|
4
|
+
+++ b/lib/main.js
|
|
5
|
+
@@ -1,5 +1,5 @@
|
|
6
|
+
-var QRCode = require('./../vendor/QRCode'),
|
|
7
|
+
- QRErrorCorrectLevel = require('./../vendor/QRCode/QRErrorCorrectLevel'),
|
|
8
|
+
+var QRCode = require('./../vendor/QRCode/index.js'),
|
|
9
|
+
+ QRErrorCorrectLevel = require('./../vendor/QRCode/QRErrorCorrectLevel.js'),
|
|
10
|
+
black = "\033[40m \033[0m",
|
|
11
|
+
white = "\033[47m \033[0m",
|
|
12
|
+
toCell = function (isBlack) {
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
function isBunInstall() {
|
|
8
|
+
const ua = process.env.npm_config_user_agent ?? "";
|
|
9
|
+
return ua.includes("bun/");
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function getRepoRoot() {
|
|
13
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
14
|
+
return path.resolve(here, "..");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function run(cmd, args, opts = {}) {
|
|
18
|
+
const res = spawnSync(cmd, args, { stdio: "inherit", ...opts });
|
|
19
|
+
if (typeof res.status === "number") return res.status;
|
|
20
|
+
return 1;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function applyPatchSet(opts) {
|
|
24
|
+
const patchText =
|
|
25
|
+
typeof opts?.patchText === "string" ? opts.patchText : "";
|
|
26
|
+
if (!patchText.trim()) return;
|
|
27
|
+
|
|
28
|
+
const targetDir = path.resolve(String(opts?.targetDir ?? "."));
|
|
29
|
+
const lines = patchText.split(/\r?\n/);
|
|
30
|
+
let i = 0;
|
|
31
|
+
|
|
32
|
+
while (i < lines.length) {
|
|
33
|
+
const header = lines[i];
|
|
34
|
+
if (!header?.startsWith("diff --git ")) {
|
|
35
|
+
i += 1;
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
const match = header.match(/^diff --git a\/(.+?) b\/(.+)$/);
|
|
39
|
+
if (!match) {
|
|
40
|
+
i += 1;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
const filePath = path.join(targetDir, match[2] ?? match[1]);
|
|
44
|
+
i += 1;
|
|
45
|
+
|
|
46
|
+
while (i < lines.length && !lines[i]?.startsWith("@@")) {
|
|
47
|
+
if (lines[i]?.startsWith("diff --git ")) break;
|
|
48
|
+
i += 1;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const raw = fs.readFileSync(filePath, "utf-8");
|
|
52
|
+
const hasTrailingNewline = raw.endsWith("\n");
|
|
53
|
+
const fileLines = raw.split(/\r?\n/);
|
|
54
|
+
if (hasTrailingNewline) {
|
|
55
|
+
fileLines.pop();
|
|
56
|
+
}
|
|
57
|
+
let offset = 0;
|
|
58
|
+
|
|
59
|
+
while (i < lines.length && lines[i]?.startsWith("@@")) {
|
|
60
|
+
const hunk = lines[i] ?? "";
|
|
61
|
+
const hunkMatch = hunk.match(/^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/);
|
|
62
|
+
if (!hunkMatch) throw new Error("patch does not apply cleanly");
|
|
63
|
+
const start = Number.parseInt(hunkMatch[1] ?? "0", 10) - 1 + offset;
|
|
64
|
+
if (!Number.isFinite(start) || start < 0) {
|
|
65
|
+
throw new Error("patch does not apply cleanly");
|
|
66
|
+
}
|
|
67
|
+
i += 1;
|
|
68
|
+
let cursor = start;
|
|
69
|
+
while (i < lines.length && !lines[i]?.startsWith("@@") && !lines[i]?.startsWith("diff --git ")) {
|
|
70
|
+
const line = lines[i] ?? "";
|
|
71
|
+
if (!line) {
|
|
72
|
+
i += 1;
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (line.startsWith(" ")) {
|
|
76
|
+
const expected = line.slice(1);
|
|
77
|
+
if (fileLines[cursor] !== expected) {
|
|
78
|
+
throw new Error("patch does not apply cleanly");
|
|
79
|
+
}
|
|
80
|
+
cursor += 1;
|
|
81
|
+
} else if (line.startsWith("-")) {
|
|
82
|
+
const expected = line.slice(1);
|
|
83
|
+
if (fileLines[cursor] !== expected) {
|
|
84
|
+
throw new Error("patch does not apply cleanly");
|
|
85
|
+
}
|
|
86
|
+
fileLines.splice(cursor, 1);
|
|
87
|
+
offset -= 1;
|
|
88
|
+
} else if (line.startsWith("+")) {
|
|
89
|
+
fileLines.splice(cursor, 0, line.slice(1));
|
|
90
|
+
cursor += 1;
|
|
91
|
+
offset += 1;
|
|
92
|
+
} else if (line.startsWith("\\")) {
|
|
93
|
+
// "" markers; ignore.
|
|
94
|
+
} else {
|
|
95
|
+
throw new Error("patch does not apply cleanly");
|
|
96
|
+
}
|
|
97
|
+
i += 1;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const next = `${fileLines.join("\n")}${hasTrailingNewline ? "\n" : ""}`;
|
|
102
|
+
fs.writeFileSync(filePath, next, "utf-8");
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function applyPatchIfNeeded(opts) {
|
|
107
|
+
const patchPath = path.resolve(opts.patchPath);
|
|
108
|
+
if (!fs.existsSync(patchPath)) {
|
|
109
|
+
throw new Error(`missing patch: ${patchPath}`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
let targetDir = path.resolve(opts.targetDir);
|
|
113
|
+
if (!fs.existsSync(targetDir) || !fs.statSync(targetDir).isDirectory()) {
|
|
114
|
+
console.warn(`[postinstall] skip missing target: ${targetDir}`);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Resolve symlinks to avoid "beyond a symbolic link" errors from git apply
|
|
119
|
+
// (bun/pnpm use symlinks in node_modules)
|
|
120
|
+
targetDir = fs.realpathSync(targetDir);
|
|
121
|
+
|
|
122
|
+
const gitArgsBase = ["apply", "--unsafe-paths", "--whitespace=nowarn"];
|
|
123
|
+
const reverseCheck = [
|
|
124
|
+
...gitArgsBase,
|
|
125
|
+
"--reverse",
|
|
126
|
+
"--check",
|
|
127
|
+
"--directory",
|
|
128
|
+
targetDir,
|
|
129
|
+
patchPath,
|
|
130
|
+
];
|
|
131
|
+
const forwardCheck = [
|
|
132
|
+
...gitArgsBase,
|
|
133
|
+
"--check",
|
|
134
|
+
"--directory",
|
|
135
|
+
targetDir,
|
|
136
|
+
patchPath,
|
|
137
|
+
];
|
|
138
|
+
const apply = [...gitArgsBase, "--directory", targetDir, patchPath];
|
|
139
|
+
|
|
140
|
+
// Already applied?
|
|
141
|
+
if (run("git", reverseCheck, { stdio: "ignore" }) === 0) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (run("git", forwardCheck, { stdio: "ignore" }) !== 0) {
|
|
146
|
+
throw new Error(`patch does not apply cleanly: ${path.basename(patchPath)}`);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const status = run("git", apply);
|
|
150
|
+
if (status !== 0) {
|
|
151
|
+
throw new Error(`failed applying patch: ${path.basename(patchPath)}`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function extractPackageName(key) {
|
|
156
|
+
if (key.startsWith("@")) {
|
|
157
|
+
const idx = key.indexOf("@", 1);
|
|
158
|
+
if (idx === -1) return key;
|
|
159
|
+
return key.slice(0, idx);
|
|
160
|
+
}
|
|
161
|
+
const idx = key.lastIndexOf("@");
|
|
162
|
+
if (idx <= 0) return key;
|
|
163
|
+
return key.slice(0, idx);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function main() {
|
|
167
|
+
if (!isBunInstall()) return;
|
|
168
|
+
|
|
169
|
+
const repoRoot = getRepoRoot();
|
|
170
|
+
process.chdir(repoRoot);
|
|
171
|
+
|
|
172
|
+
const pkgPath = path.join(repoRoot, "package.json");
|
|
173
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
174
|
+
const patched = pkg?.pnpm?.patchedDependencies ?? {};
|
|
175
|
+
|
|
176
|
+
// Bun does not support pnpm.patchedDependencies. Apply these patch files to
|
|
177
|
+
// node_modules packages as a best-effort compatibility layer.
|
|
178
|
+
for (const [key, relPatchPath] of Object.entries(patched)) {
|
|
179
|
+
if (typeof relPatchPath !== "string" || !relPatchPath.trim()) continue;
|
|
180
|
+
const pkgName = extractPackageName(String(key));
|
|
181
|
+
if (!pkgName) continue;
|
|
182
|
+
applyPatchIfNeeded({
|
|
183
|
+
targetDir: path.join("node_modules", ...pkgName.split("/")),
|
|
184
|
+
patchPath: relPatchPath,
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
try {
|
|
190
|
+
const skip =
|
|
191
|
+
process.env.NEXUS_SKIP_POSTINSTALL === "1" ||
|
|
192
|
+
process.env.NEXUS_SKIP_POSTINSTALL === "1" ||
|
|
193
|
+
process.env.VITEST === "true" ||
|
|
194
|
+
process.env.NODE_ENV === "test";
|
|
195
|
+
|
|
196
|
+
if (!skip) {
|
|
197
|
+
main();
|
|
198
|
+
}
|
|
199
|
+
} catch (err) {
|
|
200
|
+
console.error(String(err));
|
|
201
|
+
process.exit(1);
|
|
202
|
+
}
|