@braidhq/server 0.0.1 → 0.1.0
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/README.md +136 -0
- package/dist/app.d.ts +19 -2
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +121 -3
- package/dist/app.js.map +1 -1
- package/dist/composeFs.d.ts +41 -6
- package/dist/composeFs.d.ts.map +1 -1
- package/dist/composeFs.js +234 -52
- package/dist/composeFs.js.map +1 -1
- package/dist/composition.d.ts +86 -2
- package/dist/composition.d.ts.map +1 -1
- package/dist/composition.js +71 -1
- package/dist/composition.js.map +1 -1
- package/dist/infrastructure/agent/SubprocessSkillRunner.d.ts +21 -0
- package/dist/infrastructure/agent/SubprocessSkillRunner.d.ts.map +1 -1
- package/dist/infrastructure/agent/SubprocessSkillRunner.js +35 -3
- package/dist/infrastructure/agent/SubprocessSkillRunner.js.map +1 -1
- package/dist/infrastructure/agent/mcpConfig.d.ts +25 -5
- package/dist/infrastructure/agent/mcpConfig.d.ts.map +1 -1
- package/dist/infrastructure/agent/mcpConfig.js +22 -14
- package/dist/infrastructure/agent/mcpConfig.js.map +1 -1
- package/dist/infrastructure/auth/AccessPolicy.d.ts +112 -0
- package/dist/infrastructure/auth/AccessPolicy.d.ts.map +1 -0
- package/dist/infrastructure/auth/AccessPolicy.js +184 -0
- package/dist/infrastructure/auth/AccessPolicy.js.map +1 -0
- package/dist/infrastructure/auth/SessionStore.d.ts +40 -0
- package/dist/infrastructure/auth/SessionStore.d.ts.map +1 -0
- package/dist/infrastructure/auth/SessionStore.js +107 -0
- package/dist/infrastructure/auth/SessionStore.js.map +1 -0
- package/dist/infrastructure/auth/ensureApprovedEmails.d.ts +16 -0
- package/dist/infrastructure/auth/ensureApprovedEmails.d.ts.map +1 -0
- package/dist/infrastructure/auth/ensureApprovedEmails.js +20 -0
- package/dist/infrastructure/auth/ensureApprovedEmails.js.map +1 -0
- package/dist/infrastructure/env.d.ts +11 -0
- package/dist/infrastructure/env.d.ts.map +1 -0
- package/dist/infrastructure/env.js +20 -0
- package/dist/infrastructure/env.js.map +1 -0
- package/dist/infrastructure/fs/FsBatchPlanRepository.d.ts +300 -0
- package/dist/infrastructure/fs/FsBatchPlanRepository.d.ts.map +1 -0
- package/dist/infrastructure/fs/FsBatchPlanRepository.js +42 -0
- package/dist/infrastructure/fs/FsBatchPlanRepository.js.map +1 -0
- package/dist/infrastructure/fs/FsClarifyTicketRepository.d.ts.map +1 -1
- package/dist/infrastructure/fs/FsClarifyTicketRepository.js +7 -1
- package/dist/infrastructure/fs/FsClarifyTicketRepository.js.map +1 -1
- package/dist/infrastructure/fs/FsGraphSerializer.d.ts +629 -0
- package/dist/infrastructure/fs/FsGraphSerializer.d.ts.map +1 -0
- package/dist/infrastructure/fs/FsGraphSerializer.js +60 -0
- package/dist/infrastructure/fs/FsGraphSerializer.js.map +1 -0
- package/dist/infrastructure/fs/FsProposalRepository.d.ts.map +1 -1
- package/dist/infrastructure/fs/FsProposalRepository.js +8 -0
- package/dist/infrastructure/fs/FsProposalRepository.js.map +1 -1
- package/dist/infrastructure/fs/FsRunRepository.d.ts +4 -1
- package/dist/infrastructure/fs/FsRunRepository.d.ts.map +1 -1
- package/dist/infrastructure/fs/FsRunRepository.js +75 -3
- package/dist/infrastructure/fs/FsRunRepository.js.map +1 -1
- package/dist/infrastructure/fs/FsSkillRegistry.d.ts.map +1 -1
- package/dist/infrastructure/fs/FsSkillRegistry.js +15 -3
- package/dist/infrastructure/fs/FsSkillRegistry.js.map +1 -1
- package/dist/infrastructure/fs/FsSourceUnitDigest.d.ts +19 -0
- package/dist/infrastructure/fs/FsSourceUnitDigest.d.ts.map +1 -0
- package/dist/infrastructure/fs/FsSourceUnitDigest.js +58 -0
- package/dist/infrastructure/fs/FsSourceUnitDigest.js.map +1 -0
- package/dist/infrastructure/fs/FsSourceUnitStateRepository.d.ts +30 -0
- package/dist/infrastructure/fs/FsSourceUnitStateRepository.d.ts.map +1 -0
- package/dist/infrastructure/fs/FsSourceUnitStateRepository.js +87 -0
- package/dist/infrastructure/fs/FsSourceUnitStateRepository.js.map +1 -0
- package/dist/infrastructure/fs/FsWorkspaceRepository.d.ts.map +1 -1
- package/dist/infrastructure/fs/FsWorkspaceRepository.js +12 -3
- package/dist/infrastructure/fs/FsWorkspaceRepository.js.map +1 -1
- package/dist/infrastructure/fs/WorkspaceDiscovery.d.ts +15 -0
- package/dist/infrastructure/fs/WorkspaceDiscovery.d.ts.map +1 -0
- package/dist/infrastructure/fs/WorkspaceDiscovery.js +46 -0
- package/dist/infrastructure/fs/WorkspaceDiscovery.js.map +1 -0
- package/dist/infrastructure/fs/WorkspaceRegistryFile.d.ts +26 -2
- package/dist/infrastructure/fs/WorkspaceRegistryFile.d.ts.map +1 -1
- package/dist/infrastructure/fs/WorkspaceRegistryFile.js +109 -5
- package/dist/infrastructure/fs/WorkspaceRegistryFile.js.map +1 -1
- package/dist/infrastructure/fs/intentScan.d.ts +9 -0
- package/dist/infrastructure/fs/intentScan.d.ts.map +1 -0
- package/dist/infrastructure/fs/intentScan.js +135 -0
- package/dist/infrastructure/fs/intentScan.js.map +1 -0
- package/dist/infrastructure/fs/paths.d.ts +14 -0
- package/dist/infrastructure/fs/paths.d.ts.map +1 -1
- package/dist/infrastructure/fs/paths.js +43 -0
- package/dist/infrastructure/fs/paths.js.map +1 -1
- package/dist/infrastructure/fs/productManifestWriter.d.ts +1 -1
- package/dist/infrastructure/fs/productManifestWriter.d.ts.map +1 -1
- package/dist/infrastructure/fs/productManifestWriter.js +1 -3
- package/dist/infrastructure/fs/productManifestWriter.js.map +1 -1
- package/dist/infrastructure/git/GitWorkspaceHistory.d.ts +18 -0
- package/dist/infrastructure/git/GitWorkspaceHistory.d.ts.map +1 -0
- package/dist/infrastructure/git/GitWorkspaceHistory.js +303 -0
- package/dist/infrastructure/git/GitWorkspaceHistory.js.map +1 -0
- package/dist/infrastructure/git/commitMessage.d.ts +4 -0
- package/dist/infrastructure/git/commitMessage.d.ts.map +1 -0
- package/dist/infrastructure/git/commitMessage.js +71 -0
- package/dist/infrastructure/git/commitMessage.js.map +1 -0
- package/dist/infrastructure/oauth/GoogleOAuth.d.ts +48 -1
- package/dist/infrastructure/oauth/GoogleOAuth.d.ts.map +1 -1
- package/dist/infrastructure/oauth/GoogleOAuth.js +68 -0
- package/dist/infrastructure/oauth/GoogleOAuth.js.map +1 -1
- package/dist/infrastructure/users/UserDirectoryFromRegistry.d.ts +14 -0
- package/dist/infrastructure/users/UserDirectoryFromRegistry.d.ts.map +1 -0
- package/dist/infrastructure/users/UserDirectoryFromRegistry.js +21 -0
- package/dist/infrastructure/users/UserDirectoryFromRegistry.js.map +1 -0
- package/dist/infrastructure/users/UserRegistryFile.d.ts +23 -0
- package/dist/infrastructure/users/UserRegistryFile.d.ts.map +1 -0
- package/dist/infrastructure/users/UserRegistryFile.js +91 -0
- package/dist/infrastructure/users/UserRegistryFile.js.map +1 -0
- package/dist/infrastructure/users/ensureLocalUser.d.ts +15 -0
- package/dist/infrastructure/users/ensureLocalUser.d.ts.map +1 -0
- package/dist/infrastructure/users/ensureLocalUser.js +38 -0
- package/dist/infrastructure/users/ensureLocalUser.js.map +1 -0
- package/dist/infrastructure/users/ensureWorkspaceOwners.d.ts +9 -0
- package/dist/infrastructure/users/ensureWorkspaceOwners.d.ts.map +1 -0
- package/dist/infrastructure/users/ensureWorkspaceOwners.js +22 -0
- package/dist/infrastructure/users/ensureWorkspaceOwners.js.map +1 -0
- package/dist/middleware/auth.d.ts +29 -0
- package/dist/middleware/auth.d.ts.map +1 -0
- package/dist/middleware/auth.js +81 -0
- package/dist/middleware/auth.js.map +1 -0
- package/dist/middleware/cors.js +1 -1
- package/dist/middleware/cors.js.map +1 -1
- package/dist/middleware/requireAdmin.d.ts +10 -0
- package/dist/middleware/requireAdmin.d.ts.map +1 -0
- package/dist/middleware/requireAdmin.js +24 -0
- package/dist/middleware/requireAdmin.js.map +1 -0
- package/dist/middleware/userId.d.ts +16 -0
- package/dist/middleware/userId.d.ts.map +1 -0
- package/dist/middleware/userId.js +27 -0
- package/dist/middleware/userId.js.map +1 -0
- package/dist/middleware/workspaceAccess.d.ts +47 -0
- package/dist/middleware/workspaceAccess.d.ts.map +1 -0
- package/dist/middleware/workspaceAccess.js +86 -0
- package/dist/middleware/workspaceAccess.js.map +1 -0
- package/dist/policy/Capability.d.ts +12 -0
- package/dist/policy/Capability.d.ts.map +1 -0
- package/dist/policy/Capability.js +31 -0
- package/dist/policy/Capability.js.map +1 -0
- package/dist/policy/CapabilityCheck.d.ts +14 -0
- package/dist/policy/CapabilityCheck.d.ts.map +1 -0
- package/dist/policy/CapabilityCheck.js +2 -0
- package/dist/policy/CapabilityCheck.js.map +1 -0
- package/dist/policy/PermissionRegistry.d.ts +23 -0
- package/dist/policy/PermissionRegistry.d.ts.map +1 -0
- package/dist/policy/PermissionRegistry.js +27 -0
- package/dist/policy/PermissionRegistry.js.map +1 -0
- package/dist/policy/ViewerContext.d.ts +27 -0
- package/dist/policy/ViewerContext.d.ts.map +1 -0
- package/dist/policy/ViewerContext.js +2 -0
- package/dist/policy/ViewerContext.js.map +1 -0
- package/dist/policy/checks.d.ts +15 -0
- package/dist/policy/checks.d.ts.map +1 -0
- package/dist/policy/checks.js +52 -0
- package/dist/policy/checks.js.map +1 -0
- package/dist/policy/defaultRegistry.d.ts +10 -0
- package/dist/policy/defaultRegistry.d.ts.map +1 -0
- package/dist/policy/defaultRegistry.js +16 -0
- package/dist/policy/defaultRegistry.js.map +1 -0
- package/dist/policy/index.d.ts +8 -0
- package/dist/policy/index.d.ts.map +1 -0
- package/dist/policy/index.js +5 -0
- package/dist/policy/index.js.map +1 -0
- package/dist/policy/resolveViewer.d.ts +16 -0
- package/dist/policy/resolveViewer.d.ts.map +1 -0
- package/dist/policy/resolveViewer.js +37 -0
- package/dist/policy/resolveViewer.js.map +1 -0
- package/dist/routes/_shared.d.ts +198 -0
- package/dist/routes/_shared.d.ts.map +1 -0
- package/dist/routes/_shared.js +29 -0
- package/dist/routes/_shared.js.map +1 -0
- package/dist/routes/admin.d.ts +13 -0
- package/dist/routes/admin.d.ts.map +1 -0
- package/dist/routes/admin.js +227 -0
- package/dist/routes/admin.js.map +1 -0
- package/dist/routes/auth.d.ts +33 -0
- package/dist/routes/auth.d.ts.map +1 -0
- package/dist/routes/auth.js +170 -0
- package/dist/routes/auth.js.map +1 -0
- package/dist/routes/batch.d.ts +7 -0
- package/dist/routes/batch.d.ts.map +1 -0
- package/dist/routes/batch.js +47 -0
- package/dist/routes/batch.js.map +1 -0
- package/dist/routes/clarify.d.ts +4 -3
- package/dist/routes/clarify.d.ts.map +1 -1
- package/dist/routes/clarify.js +247 -57
- package/dist/routes/clarify.js.map +1 -1
- package/dist/routes/decisions.d.ts +2 -2
- package/dist/routes/decisions.d.ts.map +1 -1
- package/dist/routes/decisions.js +50 -12
- package/dist/routes/decisions.js.map +1 -1
- package/dist/routes/edges.d.ts +2 -2
- package/dist/routes/edges.d.ts.map +1 -1
- package/dist/routes/edges.js +35 -13
- package/dist/routes/edges.js.map +1 -1
- package/dist/routes/health.d.ts +2 -2
- package/dist/routes/health.d.ts.map +1 -1
- package/dist/routes/health.js +21 -3
- package/dist/routes/health.js.map +1 -1
- package/dist/routes/history.d.ts +7 -0
- package/dist/routes/history.d.ts.map +1 -0
- package/dist/routes/history.js +88 -0
- package/dist/routes/history.js.map +1 -0
- package/dist/routes/model.d.ts +2 -2
- package/dist/routes/model.d.ts.map +1 -1
- package/dist/routes/model.js +20 -4
- package/dist/routes/model.js.map +1 -1
- package/dist/routes/nodes.d.ts +2 -2
- package/dist/routes/nodes.d.ts.map +1 -1
- package/dist/routes/nodes.js +75 -19
- package/dist/routes/nodes.js.map +1 -1
- package/dist/routes/ontology.d.ts +2 -2
- package/dist/routes/ontology.d.ts.map +1 -1
- package/dist/routes/ontology.js +22 -5
- package/dist/routes/ontology.js.map +1 -1
- package/dist/routes/proposals.d.ts +4 -3
- package/dist/routes/proposals.d.ts.map +1 -1
- package/dist/routes/proposals.js +170 -41
- package/dist/routes/proposals.js.map +1 -1
- package/dist/routes/runs.d.ts.map +1 -1
- package/dist/routes/runs.js +52 -1
- package/dist/routes/runs.js.map +1 -1
- package/dist/routes/skillInputOptions.d.ts +10 -0
- package/dist/routes/skillInputOptions.d.ts.map +1 -0
- package/dist/routes/skillInputOptions.js +161 -0
- package/dist/routes/skillInputOptions.js.map +1 -0
- package/dist/routes/skills.d.ts +2 -2
- package/dist/routes/skills.d.ts.map +1 -1
- package/dist/routes/skills.js +89 -19
- package/dist/routes/skills.js.map +1 -1
- package/dist/routes/sourceLoaders.d.ts +7 -0
- package/dist/routes/sourceLoaders.d.ts.map +1 -0
- package/dist/routes/sourceLoaders.js +29 -0
- package/dist/routes/sourceLoaders.js.map +1 -0
- package/dist/routes/sourceUnitStates.d.ts +7 -0
- package/dist/routes/sourceUnitStates.d.ts.map +1 -0
- package/dist/routes/sourceUnitStates.js +45 -0
- package/dist/routes/sourceUnitStates.js.map +1 -0
- package/dist/routes/users.d.ts +9 -0
- package/dist/routes/users.d.ts.map +1 -0
- package/dist/routes/users.js +127 -0
- package/dist/routes/users.js.map +1 -0
- package/dist/routes/workspaceMembers.d.ts +11 -0
- package/dist/routes/workspaceMembers.d.ts.map +1 -0
- package/dist/routes/workspaceMembers.js +81 -0
- package/dist/routes/workspaceMembers.js.map +1 -0
- package/dist/routes/workspaces.d.ts +19 -1
- package/dist/routes/workspaces.d.ts.map +1 -1
- package/dist/routes/workspaces.js +161 -45
- package/dist/routes/workspaces.js.map +1 -1
- package/dist/server.js +69 -25
- package/dist/server.js.map +1 -1
- package/package.json +48 -31
- package/LICENSE +0 -21
- package/dist/infrastructure/agent/ClaudeCodeAgentBinding.d.ts +0 -8
- package/dist/infrastructure/agent/ClaudeCodeAgentBinding.d.ts.map +0 -1
- package/dist/infrastructure/agent/ClaudeCodeAgentBinding.js +0 -62
- package/dist/infrastructure/agent/ClaudeCodeAgentBinding.js.map +0 -1
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { writeFile } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { CommitSha, Timestamp, WorkspaceId } from '@braidhq/schema';
|
|
5
|
+
import { simpleGit } from 'simple-git';
|
|
6
|
+
import { GRAPH_JSON_VERSION, GraphJsonFile } from '../fs/FsGraphSerializer.js';
|
|
7
|
+
import { parseCommitMessage, serializeCommitMessage } from './commitMessage.js';
|
|
8
|
+
// ASCII control chars never appear in commit subjects / refnames.
|
|
9
|
+
const LOG_FIELD_SEPARATOR = '';
|
|
10
|
+
const LOG_RECORD_SEPARATOR = '';
|
|
11
|
+
const GIT_LOG_FORMAT_ARG = `--pretty=tformat:%H${LOG_FIELD_SEPARATOR}%an${LOG_FIELD_SEPARATOR}%ae${LOG_FIELD_SEPARATOR}%aI${LOG_FIELD_SEPARATOR}%P${LOG_FIELD_SEPARATOR}%B${LOG_RECORD_SEPARATOR}`;
|
|
12
|
+
const TAG_RECORD_SEPARATOR = '';
|
|
13
|
+
const TAG_FIELD_SEPARATOR = '';
|
|
14
|
+
// `%(*objectname)` dereferences annotated tags to the underlying commit.
|
|
15
|
+
const TAG_FORMAT = [
|
|
16
|
+
'%(refname:short)',
|
|
17
|
+
'%(if)%(*objectname)%(then)%(*objectname)%(else)%(objectname)%(end)',
|
|
18
|
+
'%(taggerdate:iso-strict)',
|
|
19
|
+
'%(creatordate:iso-strict)',
|
|
20
|
+
'%(taggername)',
|
|
21
|
+
'%(taggeremail)',
|
|
22
|
+
'%(contents:subject)',
|
|
23
|
+
].join(TAG_FIELD_SEPARATOR) + TAG_RECORD_SEPARATOR;
|
|
24
|
+
// Intent docs + codebases are re-fetchable; tracking them would nest foreign `.git/` dirs and bloat history.
|
|
25
|
+
const DEFAULT_GITIGNORE = `# Braid auto-generated.
|
|
26
|
+
|
|
27
|
+
.braid/
|
|
28
|
+
.braid-sessions/
|
|
29
|
+
.braid-mcp-*.json
|
|
30
|
+
artifacts/runs/
|
|
31
|
+
intents/
|
|
32
|
+
codebases/
|
|
33
|
+
.DS_Store
|
|
34
|
+
`;
|
|
35
|
+
// Swapped for the calling user's identity once Theme 13 lands.
|
|
36
|
+
const BOOTSTRAP_USER_ID = 'braid-bootstrap';
|
|
37
|
+
const INITIAL_COMMIT = {
|
|
38
|
+
kind: 'initial',
|
|
39
|
+
subject: 'workspace registered',
|
|
40
|
+
userId: BOOTSTRAP_USER_ID,
|
|
41
|
+
};
|
|
42
|
+
// `core.quotePath=false` keeps non-ASCII paths intact in log / name-status output.
|
|
43
|
+
function openGit(baseDir) {
|
|
44
|
+
return simpleGit({
|
|
45
|
+
baseDir,
|
|
46
|
+
config: ['core.quotePath=false', 'i18n.logoutputencoding=utf-8'],
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
export class GitWorkspaceHistory {
|
|
50
|
+
async ensureInitialised(workspace) {
|
|
51
|
+
const root = workspace.rootPath;
|
|
52
|
+
if (existsSync(join(root, '.git')))
|
|
53
|
+
return;
|
|
54
|
+
const git = openGit(root);
|
|
55
|
+
await git.init();
|
|
56
|
+
const gitignorePath = join(root, '.gitignore');
|
|
57
|
+
if (!existsSync(gitignorePath))
|
|
58
|
+
await writeFile(gitignorePath, DEFAULT_GITIGNORE, 'utf-8');
|
|
59
|
+
await git.add(['-A']);
|
|
60
|
+
// Allow empty when the workspace dir has nothing tracked yet so HEAD still exists for later commits to parent on.
|
|
61
|
+
const allowEmpty = !(await this.hasPendingChanges(git));
|
|
62
|
+
await this.commitWithMeta(git, INITIAL_COMMIT, { allowEmpty });
|
|
63
|
+
}
|
|
64
|
+
async commit(workspace, message) {
|
|
65
|
+
const git = openGit(workspace.rootPath);
|
|
66
|
+
await git.add(['-A']);
|
|
67
|
+
// Redundant commit attempts (e.g. reject after the artifact was already persisted) resolve to a no-op.
|
|
68
|
+
if (!(await this.hasPendingChanges(git))) {
|
|
69
|
+
const head = await git.revparse(['HEAD']);
|
|
70
|
+
return CommitSha.parse(head.trim());
|
|
71
|
+
}
|
|
72
|
+
return this.commitWithMeta(git, message, { allowEmpty: false });
|
|
73
|
+
}
|
|
74
|
+
async listCommits(workspace, options) {
|
|
75
|
+
const git = openGit(workspace.rootPath);
|
|
76
|
+
const limit = options?.limit ?? 50;
|
|
77
|
+
const args = ['log', `--max-count=${limit}`, GIT_LOG_FORMAT_ARG];
|
|
78
|
+
if (options?.since)
|
|
79
|
+
args.splice(1, 0, `${options.since}^`);
|
|
80
|
+
const raw = await git.raw(args).catch((err) => {
|
|
81
|
+
// Fresh repo with no commits → `git log` exits non-zero; treat as empty.
|
|
82
|
+
if (looksLikeNoCommits(err))
|
|
83
|
+
return '';
|
|
84
|
+
throw err;
|
|
85
|
+
});
|
|
86
|
+
if (!raw.trim())
|
|
87
|
+
return [];
|
|
88
|
+
return splitLogRecords(raw).map(rec => parseLogRecord(rec, workspace.id));
|
|
89
|
+
}
|
|
90
|
+
async getCommit(workspace, sha) {
|
|
91
|
+
const git = openGit(workspace.rootPath);
|
|
92
|
+
try {
|
|
93
|
+
const raw = await git.raw(['show', '--no-patch', GIT_LOG_FORMAT_ARG, sha]);
|
|
94
|
+
if (!raw.trim())
|
|
95
|
+
return null;
|
|
96
|
+
return parseLogRecord(raw.trim(), workspace.id);
|
|
97
|
+
}
|
|
98
|
+
catch (err) {
|
|
99
|
+
if (looksLikeUnknownRevision(err))
|
|
100
|
+
return null;
|
|
101
|
+
throw err;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
async readGraphAtCommit(workspace, sha) {
|
|
105
|
+
const git = openGit(workspace.rootPath);
|
|
106
|
+
// Pre-bootstrap commits don't carry `graph.json`; empty snapshot lets diffs classify everything as `added`.
|
|
107
|
+
const raw = await git.raw(['show', `${sha}:artifacts/graph.json`]).catch((err) => {
|
|
108
|
+
if (looksLikePathNotInCommit(err) || looksLikeUnknownRevision(err))
|
|
109
|
+
return '';
|
|
110
|
+
throw err;
|
|
111
|
+
});
|
|
112
|
+
if (!raw.trim())
|
|
113
|
+
return { nodes: [], edges: [] };
|
|
114
|
+
const parsed = GraphJsonFile.parse(JSON.parse(raw));
|
|
115
|
+
if (parsed.version !== GRAPH_JSON_VERSION) {
|
|
116
|
+
throw new Error(`graph.json version mismatch at ${sha}: expected ${GRAPH_JSON_VERSION}, got ${parsed.version}`);
|
|
117
|
+
}
|
|
118
|
+
return { nodes: parsed.nodes, edges: parsed.edges };
|
|
119
|
+
}
|
|
120
|
+
async getCommitDiff(workspace, sha) {
|
|
121
|
+
const git = openGit(workspace.rootPath);
|
|
122
|
+
// `--root` keeps the root commit diffable against the empty tree instead of erroring.
|
|
123
|
+
const raw = await git.raw(['show', '--name-status', '--root', '--format=', sha]).catch((err) => {
|
|
124
|
+
if (looksLikeUnknownRevision(err))
|
|
125
|
+
return '';
|
|
126
|
+
throw err;
|
|
127
|
+
});
|
|
128
|
+
return parseNameStatus(raw);
|
|
129
|
+
}
|
|
130
|
+
async restore(workspace, targetSha, message) {
|
|
131
|
+
const git = openGit(workspace.rootPath);
|
|
132
|
+
const headRaw = await git.revparse(['HEAD']);
|
|
133
|
+
const head = CommitSha.parse(headRaw.trim());
|
|
134
|
+
if (head === targetSha)
|
|
135
|
+
return head;
|
|
136
|
+
// `checkout <sha> -- .` only restores files present in <sha>; we need an EXACT match so newer files must also go.
|
|
137
|
+
await git.raw(['read-tree', targetSha]);
|
|
138
|
+
await git.raw(['checkout-index', '--force', '--all']);
|
|
139
|
+
await git.raw(['clean', '-fd']);
|
|
140
|
+
await git.add(['-A']);
|
|
141
|
+
return this.commitWithMeta(git, {
|
|
142
|
+
...message,
|
|
143
|
+
kind: 'restore',
|
|
144
|
+
revertedFrom: head,
|
|
145
|
+
revertedTo: targetSha,
|
|
146
|
+
}, { allowEmpty: false });
|
|
147
|
+
}
|
|
148
|
+
async tag(workspace, sha, name, note) {
|
|
149
|
+
const git = openGit(workspace.rootPath);
|
|
150
|
+
if (note) {
|
|
151
|
+
// Annotated tags need a committer identity; pin inline so CI runners without global git config don't fail.
|
|
152
|
+
await git.raw([
|
|
153
|
+
'-c',
|
|
154
|
+
`user.name=${BOOTSTRAP_USER_ID}`,
|
|
155
|
+
'-c',
|
|
156
|
+
`user.email=${BOOTSTRAP_USER_ID}@braid.local`,
|
|
157
|
+
'tag',
|
|
158
|
+
'-a',
|
|
159
|
+
name,
|
|
160
|
+
sha,
|
|
161
|
+
'-m',
|
|
162
|
+
note,
|
|
163
|
+
]);
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
await git.raw(['tag', name, sha]);
|
|
167
|
+
}
|
|
168
|
+
return readTagByName(git, name);
|
|
169
|
+
}
|
|
170
|
+
async listTags(workspace) {
|
|
171
|
+
const git = openGit(workspace.rootPath);
|
|
172
|
+
const raw = await git.raw(['tag', '-l', `--format=${TAG_FORMAT}`]).catch(() => '');
|
|
173
|
+
if (!raw.trim())
|
|
174
|
+
return [];
|
|
175
|
+
return raw.split(TAG_RECORD_SEPARATOR)
|
|
176
|
+
.map(s => s.trim())
|
|
177
|
+
.filter(s => s.length > 0)
|
|
178
|
+
.map(parseTagRecord)
|
|
179
|
+
.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
|
|
180
|
+
}
|
|
181
|
+
async deleteTag(workspace, name) {
|
|
182
|
+
const git = openGit(workspace.rootPath);
|
|
183
|
+
await git.raw(['tag', '-d', name]);
|
|
184
|
+
}
|
|
185
|
+
async hasPendingChanges(git) {
|
|
186
|
+
const status = await git.status();
|
|
187
|
+
return status.staged.length > 0
|
|
188
|
+
|| status.created.length > 0
|
|
189
|
+
|| status.deleted.length > 0
|
|
190
|
+
|| status.modified.length > 0
|
|
191
|
+
|| status.renamed.length > 0
|
|
192
|
+
|| status.not_added.length > 0;
|
|
193
|
+
}
|
|
194
|
+
async commitWithMeta(git, message, options) {
|
|
195
|
+
// `authorName` / `authorEmail` are snapshotted at commit time so
|
|
196
|
+
// a future rename of the user record doesn't rewrite git history.
|
|
197
|
+
// When absent (bootstrap, source-sync, pre-Phase-A artifacts) we
|
|
198
|
+
// fall back to the opaque `userId` for both — same behaviour as
|
|
199
|
+
// before Theme 13 landed.
|
|
200
|
+
const name = message.authorName ?? message.userId;
|
|
201
|
+
const email = message.authorEmail ?? `${message.userId}@braid.local`;
|
|
202
|
+
const args = ['commit', '-m', serializeCommitMessage(message), `--author=${name} <${email}>`];
|
|
203
|
+
if (options.allowEmpty)
|
|
204
|
+
args.push('--allow-empty');
|
|
205
|
+
await git.raw(['-c', `user.name=${name}`, '-c', `user.email=${email}`, ...args]);
|
|
206
|
+
const head = await git.revparse(['HEAD']);
|
|
207
|
+
return CommitSha.parse(head.trim());
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
function parseLogRecord(record, workspaceId) {
|
|
211
|
+
const [shaLine, authorName, authorEmail, committedAt, parents, ...bodyLines] = record.split(LOG_FIELD_SEPARATOR);
|
|
212
|
+
let body = bodyLines.join(LOG_FIELD_SEPARATOR);
|
|
213
|
+
while (body.endsWith(LOG_RECORD_SEPARATOR))
|
|
214
|
+
body = body.slice(0, -LOG_RECORD_SEPARATOR.length);
|
|
215
|
+
body = body.trim();
|
|
216
|
+
return {
|
|
217
|
+
sha: CommitSha.parse(shaLine.trim()),
|
|
218
|
+
workspaceId: WorkspaceId.parse(workspaceId),
|
|
219
|
+
message: parseCommitMessage(body),
|
|
220
|
+
author: { name: authorName.trim(), email: authorEmail.trim() },
|
|
221
|
+
committedAt: Timestamp.parse(committedAt.trim()),
|
|
222
|
+
parents: parents.trim()
|
|
223
|
+
? parents.trim().split(' ').map(s => CommitSha.parse(s.trim()))
|
|
224
|
+
: [],
|
|
225
|
+
// Stats are derived lazily via `getCommitDiff` to avoid N extra diff calls per list page.
|
|
226
|
+
stats: null,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
async function readTagByName(git, name) {
|
|
230
|
+
const raw = await git.raw(['tag', '-l', `--format=${TAG_FORMAT}`, name]);
|
|
231
|
+
const record = raw.split(TAG_RECORD_SEPARATOR).map(s => s.trim()).find(s => s.length > 0);
|
|
232
|
+
if (!record)
|
|
233
|
+
throw new Error(`Tag "${name}" not found immediately after creation`);
|
|
234
|
+
return parseTagRecord(record);
|
|
235
|
+
}
|
|
236
|
+
function parseTagRecord(record) {
|
|
237
|
+
const parts = record.split(TAG_FIELD_SEPARATOR);
|
|
238
|
+
const [name, sha, taggerDate, creatorDate, taggerName, taggerEmail, note] = parts;
|
|
239
|
+
const createdRaw = (taggerDate ?? '').trim() || (creatorDate ?? '').trim();
|
|
240
|
+
return {
|
|
241
|
+
name: (name ?? '').trim(),
|
|
242
|
+
sha: CommitSha.parse((sha ?? '').trim()),
|
|
243
|
+
createdAt: Timestamp.parse(createdRaw),
|
|
244
|
+
...(note && note.trim() ? { note: note.trim() } : {}),
|
|
245
|
+
...((taggerName?.trim() && taggerEmail?.trim())
|
|
246
|
+
? {
|
|
247
|
+
taggedBy: {
|
|
248
|
+
name: taggerName.trim(),
|
|
249
|
+
email: taggerEmail.trim().replace(/^<|>$/g, ''),
|
|
250
|
+
},
|
|
251
|
+
}
|
|
252
|
+
: {}),
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
function parseNameStatus(raw) {
|
|
256
|
+
const out = [];
|
|
257
|
+
for (const line of raw.split('\n')) {
|
|
258
|
+
if (!line.trim())
|
|
259
|
+
continue;
|
|
260
|
+
const cols = line.split('\t');
|
|
261
|
+
const code = cols[0]?.trim() ?? '';
|
|
262
|
+
if (code.startsWith('R') && cols.length >= 3) {
|
|
263
|
+
out.push({ path: cols[2].trim(), status: 'renamed', previousPath: cols[1].trim() });
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
const path = cols[1]?.trim();
|
|
267
|
+
if (!path)
|
|
268
|
+
continue;
|
|
269
|
+
out.push({ path, status: statusFromCode(code[0]) });
|
|
270
|
+
}
|
|
271
|
+
return out;
|
|
272
|
+
}
|
|
273
|
+
function splitLogRecords(raw) {
|
|
274
|
+
return raw.split(LOG_RECORD_SEPARATOR).map(s => s.trim()).filter(s => s.length > 0);
|
|
275
|
+
}
|
|
276
|
+
// Unknown letters fall back to `modified` so the row still surfaces.
|
|
277
|
+
function statusFromCode(code) {
|
|
278
|
+
switch (code) {
|
|
279
|
+
case 'A':
|
|
280
|
+
case 'C':
|
|
281
|
+
return 'added';
|
|
282
|
+
case 'D':
|
|
283
|
+
return 'removed';
|
|
284
|
+
case 'M':
|
|
285
|
+
return 'modified';
|
|
286
|
+
default:
|
|
287
|
+
return 'modified';
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
function looksLikeNoCommits(err) {
|
|
291
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
292
|
+
return /does not have any commits yet|bad default revision/i.test(msg);
|
|
293
|
+
}
|
|
294
|
+
function looksLikeUnknownRevision(err) {
|
|
295
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
296
|
+
return /unknown revision|bad revision|ambiguous argument/i.test(msg);
|
|
297
|
+
}
|
|
298
|
+
// `git show <sha>:<path>` for an untracked path reports "exists on disk, but not in" or "does not exist in".
|
|
299
|
+
function looksLikePathNotInCommit(err) {
|
|
300
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
301
|
+
return /exists on disk, but not in|does not exist in|path .* does not exist/i.test(msg);
|
|
302
|
+
}
|
|
303
|
+
//# sourceMappingURL=GitWorkspaceHistory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GitWorkspaceHistory.js","sourceRoot":"","sources":["../../../src/infrastructure/git/GitWorkspaceHistory.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commitMessage.d.ts","sourceRoot":"","sources":["../../../src/infrastructure/git/commitMessage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAcpD,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,aAAa,GAAG,MAAM,CAgBrE;AAED,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,aAAa,CA6B7D"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { ClarifyTicketId, CommitKind, CommitSha, ProposalId, SourceId, UserId, } from '@braidhq/schema';
|
|
2
|
+
// Trailer keys recognised by `git interpret-trailers` so future
|
|
3
|
+
// tooling can lean on the same parser.
|
|
4
|
+
const TRAILER_KEY_PATTERN = /^[a-z][a-z-]*$/i;
|
|
5
|
+
export function serializeCommitMessage(message) {
|
|
6
|
+
const trailers = [
|
|
7
|
+
`Kind: ${message.kind}`,
|
|
8
|
+
`Author: ${message.userId}`,
|
|
9
|
+
];
|
|
10
|
+
if (message.proposalId)
|
|
11
|
+
trailers.push(`Proposal-Id: ${message.proposalId}`);
|
|
12
|
+
if (message.clarifyTicketId)
|
|
13
|
+
trailers.push(`Clarify-Ticket-Id: ${message.clarifyTicketId}`);
|
|
14
|
+
if (message.sourceId)
|
|
15
|
+
trailers.push(`Source-Id: ${message.sourceId}`);
|
|
16
|
+
if (message.revertedFrom)
|
|
17
|
+
trailers.push(`Reverted-From: ${message.revertedFrom}`);
|
|
18
|
+
if (message.revertedTo)
|
|
19
|
+
trailers.push(`Reverted-To: ${message.revertedTo}`);
|
|
20
|
+
return `${message.kind}: ${message.subject}\n\n${trailers.join('\n')}\n`;
|
|
21
|
+
}
|
|
22
|
+
export function parseCommitMessage(raw) {
|
|
23
|
+
const lines = raw.split('\n');
|
|
24
|
+
const subjectLine = lines[0] ?? '';
|
|
25
|
+
const colonIdx = subjectLine.indexOf(':');
|
|
26
|
+
const subject = colonIdx >= 0 ? subjectLine.slice(colonIdx + 1).trim() : subjectLine.trim();
|
|
27
|
+
const trailers = extractTrailers(lines);
|
|
28
|
+
const kindRaw = trailers.Kind ?? subjectLine.slice(0, colonIdx).trim();
|
|
29
|
+
// Fall back to `snapshot` on unknown kinds so a manual `git commit`
|
|
30
|
+
// from the CLI still renders in the timeline.
|
|
31
|
+
const kind = CommitKind.safeParse(kindRaw).data ?? 'snapshot';
|
|
32
|
+
const userId = UserId.parse(trailers.Author ?? 'unknown');
|
|
33
|
+
const out = {
|
|
34
|
+
kind,
|
|
35
|
+
subject: subject || `(unsubject ${kind})`,
|
|
36
|
+
userId,
|
|
37
|
+
};
|
|
38
|
+
if (trailers['Proposal-Id'])
|
|
39
|
+
Object.assign(out, { proposalId: ProposalId.parse(trailers['Proposal-Id']) });
|
|
40
|
+
if (trailers['Clarify-Ticket-Id'])
|
|
41
|
+
Object.assign(out, { clarifyTicketId: ClarifyTicketId.parse(trailers['Clarify-Ticket-Id']) });
|
|
42
|
+
if (trailers['Source-Id'])
|
|
43
|
+
Object.assign(out, { sourceId: SourceId.parse(trailers['Source-Id']) });
|
|
44
|
+
if (trailers['Reverted-From'])
|
|
45
|
+
Object.assign(out, { revertedFrom: CommitSha.parse(trailers['Reverted-From']) });
|
|
46
|
+
if (trailers['Reverted-To'])
|
|
47
|
+
Object.assign(out, { revertedTo: CommitSha.parse(trailers['Reverted-To']) });
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
50
|
+
// indexOf-based scan instead of a single regex: avoids polynomial
|
|
51
|
+
// backtracking risk against adversarial commit bodies.
|
|
52
|
+
function extractTrailers(lines) {
|
|
53
|
+
const out = {};
|
|
54
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
55
|
+
const line = lines[i];
|
|
56
|
+
if (line.trim().length === 0) {
|
|
57
|
+
if (Object.keys(out).length > 0)
|
|
58
|
+
return out;
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
const colon = line.indexOf(':');
|
|
62
|
+
if (colon < 1)
|
|
63
|
+
return out;
|
|
64
|
+
const key = line.slice(0, colon);
|
|
65
|
+
if (!TRAILER_KEY_PATTERN.test(key))
|
|
66
|
+
return out;
|
|
67
|
+
out[key] = line.slice(colon + 1).trim();
|
|
68
|
+
}
|
|
69
|
+
return out;
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=commitMessage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commitMessage.js","sourceRoot":"","sources":["../../../src/infrastructure/git/commitMessage.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,10 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Profile fields Braid needs after Google authn. `sub` is the stable
|
|
3
|
+
* Google account id — used as the join key into `users.json`.
|
|
4
|
+
*/
|
|
5
|
+
export interface GoogleProfile {
|
|
6
|
+
readonly sub: string;
|
|
7
|
+
readonly email: string;
|
|
8
|
+
readonly displayName: string;
|
|
9
|
+
readonly emailVerified?: boolean;
|
|
10
|
+
}
|
|
1
11
|
export interface GoogleOAuthConfig {
|
|
2
12
|
/** OAuth client id from the user's GCP project. */
|
|
3
13
|
readonly clientId: string;
|
|
4
14
|
/** OAuth client secret. */
|
|
5
15
|
readonly clientSecret: string;
|
|
6
|
-
/**
|
|
16
|
+
/**
|
|
17
|
+
* Redirect URI used by the Drive ingestion flow (existing OAuth
|
|
18
|
+
* router). Both paths must be registered in the GCP OAuth client's
|
|
19
|
+
* authorised redirect URIs list.
|
|
20
|
+
*/
|
|
7
21
|
readonly redirectUri: string;
|
|
22
|
+
/**
|
|
23
|
+
* Redirect URI used by the user-login flow. Optional — falls back
|
|
24
|
+
* to the Drive `redirectUri` if unset, which is what local dev
|
|
25
|
+
* wants (one path = one registered URI). Production usually points
|
|
26
|
+
* this at `${apiUrl}/auth/google/callback` so the two flows are
|
|
27
|
+
* mounted on different routes.
|
|
28
|
+
*/
|
|
29
|
+
readonly loginRedirectUri?: string;
|
|
8
30
|
}
|
|
9
31
|
export interface AuthorizationUrlInput {
|
|
10
32
|
/** OAuth scope(s) requested. Braid passes Drive read-only. */
|
|
@@ -46,6 +68,31 @@ export declare class GoogleOAuth {
|
|
|
46
68
|
code: string;
|
|
47
69
|
codeVerifier: string;
|
|
48
70
|
}): Promise<TokenSet>;
|
|
71
|
+
/**
|
|
72
|
+
* Login flow URL. Same client + redirect as the Drive flow but with
|
|
73
|
+
* `openid email profile` scopes and no `prompt=consent` — Google
|
|
74
|
+
* suppresses the consent screen when the user has already approved
|
|
75
|
+
* those scopes, which is what you want for login (not Drive where
|
|
76
|
+
* a refresh_token is required and `prompt=consent` is forced).
|
|
77
|
+
*/
|
|
78
|
+
buildLoginUrl(input: {
|
|
79
|
+
state: string;
|
|
80
|
+
codeVerifier: string;
|
|
81
|
+
}): string;
|
|
82
|
+
/** Path Google sends the login callback to. Routed under `/auth/google/callback`. */
|
|
83
|
+
loginRedirectUri(): string;
|
|
84
|
+
/**
|
|
85
|
+
* Exchange an authorization code from the login flow for the user's
|
|
86
|
+
* profile. Does NOT require `refresh_token` (login is one-shot — the
|
|
87
|
+
* Braid server issues its own session afterwards, so Google's tokens
|
|
88
|
+
* are discarded after this call). Uses the userinfo endpoint instead
|
|
89
|
+
* of decoding the id_token, so we don't bring in a JWT verifier just
|
|
90
|
+
* to read 3 fields.
|
|
91
|
+
*/
|
|
92
|
+
loginWithCode(input: {
|
|
93
|
+
code: string;
|
|
94
|
+
codeVerifier: string;
|
|
95
|
+
}): Promise<GoogleProfile>;
|
|
49
96
|
refreshAccessToken(refreshToken: string): Promise<RefreshedAccessToken>;
|
|
50
97
|
}
|
|
51
98
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GoogleOAuth.d.ts","sourceRoot":"","sources":["../../../src/infrastructure/oauth/GoogleOAuth.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"GoogleOAuth.d.ts","sourceRoot":"","sources":["../../../src/infrastructure/oauth/GoogleOAuth.ts"],"names":[],"mappings":"AAMA;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,CAAA;CACjC;AAED,MAAM,WAAW,iBAAiB;IAChC,mDAAmD;IACnD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,2BAA2B;IAC3B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;IAC7B;;;;OAIG;IACH,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B;;;;;;OAMG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;CACnC;AAED,MAAM,WAAW,qBAAqB;IACpC,8DAA8D;IAC9D,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAA;IAClC,yCAAyC;IACzC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,2DAA2D;IAC3D,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;CAC9B;AAED,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;IAC7B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAC3B;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAC3B;AAED;;;;;;;GAOG;AACH,qBAAa,WAAW;IACV,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAN,MAAM,EAAE,iBAAiB;IAEtD;;;;OAIG;IACH,qBAAqB,CAAC,KAAK,EAAE,qBAAqB,GAAG,MAAM;IAgBrD,YAAY,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,QAAQ,CAAC;IA2BpF;;;;;;OAMG;IACH,aAAa,CAAC,KAAK,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,GAAG,MAAM;IAerE,qFAAqF;IACrF,gBAAgB,IAAI,MAAM;IAI1B;;;;;;;OAOG;IACG,aAAa,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,aAAa,CAAC;IA0CpF,kBAAkB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,CAAC;CAsB9E;AA4BD;;;GAGG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CAE3C;AAED;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createHash, randomBytes } from 'node:crypto';
|
|
2
2
|
const AUTH_ENDPOINT = 'https://accounts.google.com/o/oauth2/v2/auth';
|
|
3
3
|
const TOKEN_ENDPOINT = 'https://oauth2.googleapis.com/token';
|
|
4
|
+
const USERINFO_ENDPOINT = 'https://openidconnect.googleapis.com/v1/userinfo';
|
|
4
5
|
/**
|
|
5
6
|
* Minimal Google OAuth 2.0 client for installed-app / web flow.
|
|
6
7
|
*
|
|
@@ -60,6 +61,73 @@ export class GoogleOAuth {
|
|
|
60
61
|
}
|
|
61
62
|
return tokenSetFromPayload(payload);
|
|
62
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* Login flow URL. Same client + redirect as the Drive flow but with
|
|
66
|
+
* `openid email profile` scopes and no `prompt=consent` — Google
|
|
67
|
+
* suppresses the consent screen when the user has already approved
|
|
68
|
+
* those scopes, which is what you want for login (not Drive where
|
|
69
|
+
* a refresh_token is required and `prompt=consent` is forced).
|
|
70
|
+
*/
|
|
71
|
+
buildLoginUrl(input) {
|
|
72
|
+
const params = new URLSearchParams({
|
|
73
|
+
client_id: this.config.clientId,
|
|
74
|
+
redirect_uri: this.loginRedirectUri(),
|
|
75
|
+
response_type: 'code',
|
|
76
|
+
scope: 'openid email profile',
|
|
77
|
+
access_type: 'online',
|
|
78
|
+
include_granted_scopes: 'true',
|
|
79
|
+
state: input.state,
|
|
80
|
+
code_challenge: pkceChallenge(input.codeVerifier),
|
|
81
|
+
code_challenge_method: 'S256',
|
|
82
|
+
});
|
|
83
|
+
return `${AUTH_ENDPOINT}?${params.toString()}`;
|
|
84
|
+
}
|
|
85
|
+
/** Path Google sends the login callback to. Routed under `/auth/google/callback`. */
|
|
86
|
+
loginRedirectUri() {
|
|
87
|
+
return this.config.loginRedirectUri ?? this.config.redirectUri;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Exchange an authorization code from the login flow for the user's
|
|
91
|
+
* profile. Does NOT require `refresh_token` (login is one-shot — the
|
|
92
|
+
* Braid server issues its own session afterwards, so Google's tokens
|
|
93
|
+
* are discarded after this call). Uses the userinfo endpoint instead
|
|
94
|
+
* of decoding the id_token, so we don't bring in a JWT verifier just
|
|
95
|
+
* to read 3 fields.
|
|
96
|
+
*/
|
|
97
|
+
async loginWithCode(input) {
|
|
98
|
+
const body = new URLSearchParams({
|
|
99
|
+
client_id: this.config.clientId,
|
|
100
|
+
client_secret: this.config.clientSecret,
|
|
101
|
+
redirect_uri: this.loginRedirectUri(),
|
|
102
|
+
grant_type: 'authorization_code',
|
|
103
|
+
code: input.code,
|
|
104
|
+
code_verifier: input.codeVerifier,
|
|
105
|
+
});
|
|
106
|
+
const tokenResponse = await fetch(TOKEN_ENDPOINT, {
|
|
107
|
+
method: 'POST',
|
|
108
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
109
|
+
body,
|
|
110
|
+
});
|
|
111
|
+
if (!tokenResponse.ok) {
|
|
112
|
+
const text = await tokenResponse.text().catch(() => '');
|
|
113
|
+
throw new Error(`Google login token exchange failed: ${tokenResponse.status} ${tokenResponse.statusText} ${text}`);
|
|
114
|
+
}
|
|
115
|
+
const { access_token } = await tokenResponse.json();
|
|
116
|
+
const userinfoResponse = await fetch(USERINFO_ENDPOINT, {
|
|
117
|
+
headers: { Authorization: `Bearer ${access_token}` },
|
|
118
|
+
});
|
|
119
|
+
if (!userinfoResponse.ok) {
|
|
120
|
+
const text = await userinfoResponse.text().catch(() => '');
|
|
121
|
+
throw new Error(`Google userinfo fetch failed: ${userinfoResponse.status} ${userinfoResponse.statusText} ${text}`);
|
|
122
|
+
}
|
|
123
|
+
const userinfo = await userinfoResponse.json();
|
|
124
|
+
return {
|
|
125
|
+
sub: userinfo.sub,
|
|
126
|
+
email: userinfo.email,
|
|
127
|
+
displayName: userinfo.name ?? userinfo.given_name ?? userinfo.email,
|
|
128
|
+
...(userinfo.email_verified !== undefined ? { emailVerified: userinfo.email_verified } : {}),
|
|
129
|
+
};
|
|
130
|
+
}
|
|
63
131
|
async refreshAccessToken(refreshToken) {
|
|
64
132
|
const body = new URLSearchParams({
|
|
65
133
|
client_id: this.config.clientId,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GoogleOAuth.js","sourceRoot":"","sources":["../../../src/infrastructure/oauth/GoogleOAuth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAErD,MAAM,aAAa,GAAG,8CAA8C,CAAA;AACpE,MAAM,cAAc,GAAG,qCAAqC,CAAA;
|
|
1
|
+
{"version":3,"file":"GoogleOAuth.js","sourceRoot":"","sources":["../../../src/infrastructure/oauth/GoogleOAuth.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { UserAuthor, UserDirectory } from '@braidhq/core';
|
|
2
|
+
import type { UserId } from '@braidhq/schema';
|
|
3
|
+
import type { UserRegistryFile } from './UserRegistryFile.js';
|
|
4
|
+
/**
|
|
5
|
+
* Adapt the file-backed user roster to the core `UserDirectory` port.
|
|
6
|
+
* Returns `null` for unknown ids (bootstrap / `braid-skill` etc.) so
|
|
7
|
+
* the git layer falls back to its synthetic author for those rows.
|
|
8
|
+
*/
|
|
9
|
+
export declare class UserDirectoryFromRegistry implements UserDirectory {
|
|
10
|
+
private readonly registry;
|
|
11
|
+
constructor(registry: UserRegistryFile);
|
|
12
|
+
resolve(userId: UserId): Promise<UserAuthor | null>;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=UserDirectoryFromRegistry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"UserDirectoryFromRegistry.d.ts","sourceRoot":"","sources":["../../../src/infrastructure/users/UserDirectoryFromRegistry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;AAC9D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAA;AAC7C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AAE7D;;;;GAIG;AACH,qBAAa,yBAA0B,YAAW,aAAa;IACjD,OAAO,CAAC,QAAQ,CAAC,QAAQ;gBAAR,QAAQ,EAAE,gBAAgB;IAEjD,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;CAS1D"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adapt the file-backed user roster to the core `UserDirectory` port.
|
|
3
|
+
* Returns `null` for unknown ids (bootstrap / `braid-skill` etc.) so
|
|
4
|
+
* the git layer falls back to its synthetic author for those rows.
|
|
5
|
+
*/
|
|
6
|
+
export class UserDirectoryFromRegistry {
|
|
7
|
+
registry;
|
|
8
|
+
constructor(registry) {
|
|
9
|
+
this.registry = registry;
|
|
10
|
+
}
|
|
11
|
+
async resolve(userId) {
|
|
12
|
+
const user = await this.registry.get(userId);
|
|
13
|
+
if (!user)
|
|
14
|
+
return null;
|
|
15
|
+
return {
|
|
16
|
+
displayName: user.displayName,
|
|
17
|
+
...(user.email ? { email: user.email } : {}),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=UserDirectoryFromRegistry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"UserDirectoryFromRegistry.js","sourceRoot":"","sources":["../../../src/infrastructure/users/UserDirectoryFromRegistry.ts"],"names":[],"mappings":"AAIA;;;;GAIG;AACH,MAAM,OAAO,yBAAyB;IACP;IAA7B,YAA6B,QAA0B;QAA1B,aAAQ,GAAR,QAAQ,CAAkB;IAAG,CAAC;IAE3D,KAAK,CAAC,OAAO,CAAC,MAAc;QAC1B,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;QAC5C,IAAI,CAAC,IAAI;YACP,OAAO,IAAI,CAAA;QACb,OAAO;YACL,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC7C,CAAA;IACH,CAAC;CACF"}
|