@aion0/forge 0.10.43 → 0.10.45
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/RELEASE_NOTES.md
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
# Forge v0.10.
|
|
1
|
+
# Forge v0.10.45
|
|
2
2
|
|
|
3
|
-
Released: 2026-06-
|
|
3
|
+
Released: 2026-06-08
|
|
4
4
|
|
|
5
|
-
## Changes since v0.10.
|
|
5
|
+
## Changes since v0.10.44
|
|
6
|
+
|
|
7
|
+
### Bug Fixes
|
|
8
|
+
- fix: validate enterprise key against GitHub before persisting
|
|
6
9
|
|
|
7
10
|
### Other
|
|
8
|
-
-
|
|
11
|
+
- fix(enterprise): strip whitespace + zero-width chars from pasted keys
|
|
9
12
|
|
|
10
13
|
|
|
11
|
-
**Full Changelog**: https://github.com/aiwatching/forge/compare/v0.10.
|
|
14
|
+
**Full Changelog**: https://github.com/aiwatching/forge/compare/v0.10.44...v0.10.45
|
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
removeEnterpriseKey,
|
|
29
29
|
updateEnterpriseKey,
|
|
30
30
|
} from '@/lib/enterprise';
|
|
31
|
-
import { syncRegistry, getLastSync } from '@/lib/connectors/sync';
|
|
31
|
+
import { syncRegistry, getLastSync, probeEnterpriseKey } from '@/lib/connectors/sync';
|
|
32
32
|
import { syncMarketplace as syncWorkflows } from '@/lib/workflow-marketplace';
|
|
33
33
|
import { syncSkills } from '@/lib/skills';
|
|
34
34
|
|
|
@@ -121,6 +121,13 @@ export async function POST(req: Request) {
|
|
|
121
121
|
const key = String(body?.key || '').trim();
|
|
122
122
|
if (!key) return NextResponse.json({ ok: false, error: 'key is required' }, { status: 400 });
|
|
123
123
|
|
|
124
|
+
// Probe BEFORE persisting — a key whose PAT is wrong / expired or whose
|
|
125
|
+
// repo we can't reach should never land on disk. Surfaces the real
|
|
126
|
+
// 401/403/404/network error to the user instead of silently failing
|
|
127
|
+
// later inside the best-effort sync.
|
|
128
|
+
const probe = await probeEnterpriseKey(key);
|
|
129
|
+
if (!probe.ok) return NextResponse.json({ ok: false, error: probe.error }, { status: 400 });
|
|
130
|
+
|
|
124
131
|
const r = addEnterpriseKey(key);
|
|
125
132
|
if (!r.ok) return NextResponse.json(r, { status: 400 });
|
|
126
133
|
|
|
@@ -176,6 +183,9 @@ export async function PATCH(req: Request) {
|
|
|
176
183
|
const key = String(body?.key || '').trim();
|
|
177
184
|
if (!key) return NextResponse.json({ ok: false, error: 'key is required' }, { status: 400 });
|
|
178
185
|
|
|
186
|
+
const probe = await probeEnterpriseKey(key);
|
|
187
|
+
if (!probe.ok) return NextResponse.json({ ok: false, error: probe.error }, { status: 400 });
|
|
188
|
+
|
|
179
189
|
const r = updateEnterpriseKey(tenant_id, key);
|
|
180
190
|
if (!r.ok) return NextResponse.json(r, { status: 400 });
|
|
181
191
|
|
|
@@ -46,7 +46,7 @@ export const LINK_PATTERNS: LinkPattern[] = [
|
|
|
46
46
|
id: 'mantis-bug',
|
|
47
47
|
regex: /\b(?:mantis(?:\s+bug)?\s*#?|bug\s*#?)(\d{4,8})\b/gi,
|
|
48
48
|
baseUrlFrom: 'mantis',
|
|
49
|
-
url: '{base_url}/
|
|
49
|
+
url: '{base_url}/bug_view_page.php?bug_id={1}',
|
|
50
50
|
label: 'Mantis #{1}',
|
|
51
51
|
},
|
|
52
52
|
{
|
|
@@ -36,7 +36,7 @@ export function buildReferencePromptSection(): string {
|
|
|
36
36
|
if (mantis) {
|
|
37
37
|
out.push({
|
|
38
38
|
label: 'Mantis bug',
|
|
39
|
-
example: `[Mantis #1226625](${mantis}/
|
|
39
|
+
example: `[Mantis #1226625](${mantis}/bug_view_page.php?bug_id=1226625)`,
|
|
40
40
|
});
|
|
41
41
|
}
|
|
42
42
|
|
package/lib/connectors/sync.ts
CHANGED
|
@@ -28,7 +28,7 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
|
28
28
|
import { join } from 'node:path';
|
|
29
29
|
import { loadSettings } from '../settings';
|
|
30
30
|
import { getDataDir } from '../dirs';
|
|
31
|
-
import { listEnterpriseSources, type EnterpriseSource } from '../enterprise';
|
|
31
|
+
import { listEnterpriseSources, parseKey, type EnterpriseSource } from '../enterprise';
|
|
32
32
|
import { getConnector, installConnector, listConfigOnlyIds, listInstalledConnectors } from './registry';
|
|
33
33
|
import type { ConnectorMarketEntry } from './types';
|
|
34
34
|
|
|
@@ -286,6 +286,26 @@ function cacheBust(): string {
|
|
|
286
286
|
return `?_t=${Date.now()}`;
|
|
287
287
|
}
|
|
288
288
|
|
|
289
|
+
/**
|
|
290
|
+
* Validate an enterprise key by fetching registry.json from its repo
|
|
291
|
+
* before persisting. Catches: bad format, expired/wrong PAT (401/403),
|
|
292
|
+
* repo not granted (404), network unreachable. Pure probe — no writes.
|
|
293
|
+
*/
|
|
294
|
+
export async function probeEnterpriseKey(
|
|
295
|
+
rawKey: string,
|
|
296
|
+
): Promise<{ ok: true; tenant_id: string } | { ok: false; error: string }> {
|
|
297
|
+
const parsed = parseKey(rawKey.trim(), 0);
|
|
298
|
+
if ('error' in parsed) return { ok: false, error: parsed.error };
|
|
299
|
+
const source = enterpriseToSource(parsed, 0);
|
|
300
|
+
try {
|
|
301
|
+
await fetchSourceFile(source, 'registry.json');
|
|
302
|
+
return { ok: true, tenant_id: parsed.tenant_id };
|
|
303
|
+
} catch (err) {
|
|
304
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
305
|
+
return { ok: false, error: msg };
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
289
309
|
/**
|
|
290
310
|
* Enterprise sources only, in configured priority order. Workflow-
|
|
291
311
|
* marketplace shares these — one enterprise repo serves connectors
|
package/lib/enterprise.ts
CHANGED
|
@@ -38,7 +38,11 @@ function preview(key: string): string {
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
export function parseKey(key: string, priority: number): EnterpriseSource | { error: string } {
|
|
41
|
-
|
|
41
|
+
// Strip every whitespace + zero-width char — pastes from Teams/email/
|
|
42
|
+
// wiki often carry trailing \r\n, line-wraps, or invisible separators.
|
|
43
|
+
// Valid keys have no internal whitespace (PAT alphanumeric + '_',
|
|
44
|
+
// repo URL has no spaces).
|
|
45
|
+
const trimmed = key.replace(/[\s\u200B-\u200F\u202A-\u202E\u2060\uFEFF]/g, '');
|
|
42
46
|
if (!trimmed) return { error: 'empty' };
|
|
43
47
|
|
|
44
48
|
const longMatch = trimmed.match(LONG_RE);
|
package/package.json
CHANGED