@dev-mainsequence/codex-plugin 0.0.0-stage → 0.4.2
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/.codex-plugin/plugin.json +22 -0
- package/.mcp.json +16 -0
- package/LICENSE +21 -0
- package/NOTICE +13 -0
- package/README.md +155 -2
- package/assets/mainsequence-icon.png +0 -0
- package/assets/mainsequence-icon.svg +454 -0
- package/build-info.json +10 -0
- package/dist/hosts/codex.js +62 -0
- package/dist/hosts/codexPresentation.js +165 -0
- package/dist/hosts/discovery.js +138 -0
- package/dist/hosts/panel/panel.html +165 -0
- package/dist/package.json +1 -0
- package/dist/panel-server.js +135 -0
- package/dist/runner.js +71 -0
- package/dist/vendor/devtools/LICENSE +21 -0
- package/dist/vendor/devtools/NOTICE +12 -0
- package/dist/vendor/devtools/index.js +72 -0
- package/dist/vendor/devtools/package.json +5 -0
- package/dist/vendor/devtools/shared/accountScope.js +19 -0
- package/dist/vendor/devtools/shared/actions.js +164 -0
- package/dist/vendor/devtools/shared/agentReadiness.js +71 -0
- package/dist/vendor/devtools/shared/agents.js +240 -0
- package/dist/vendor/devtools/shared/arguments.js +142 -0
- package/dist/vendor/devtools/shared/cli.js +144 -0
- package/dist/vendor/devtools/shared/config.js +64 -0
- package/dist/vendor/devtools/shared/devcontainerJsonc.js +153 -0
- package/dist/vendor/devtools/shared/envfile.js +128 -0
- package/dist/vendor/devtools/shared/environments.js +38 -0
- package/dist/vendor/devtools/shared/hostSession.js +324 -0
- package/dist/vendor/devtools/shared/machineSession.js +338 -0
- package/dist/vendor/devtools/shared/mcp.js +421 -0
- package/dist/vendor/devtools/shared/mcpTransport.js +177 -0
- package/dist/vendor/devtools/shared/models.js +12 -0
- package/dist/vendor/devtools/shared/organizationProjectWorkspace.js +300 -0
- package/dist/vendor/devtools/shared/organizationProjects.js +134 -0
- package/dist/vendor/devtools/shared/process.js +61 -0
- package/dist/vendor/devtools/shared/projectOperations.js +279 -0
- package/dist/vendor/devtools/shared/projects.js +390 -0
- package/dist/vendor/devtools/shared/pythonProjectStatus.js +86 -0
- package/dist/vendor/devtools/shared/pythonSdkStatus.js +26 -0
- package/dist/vendor/devtools/shared/repositories.js +215 -0
- package/dist/vendor/devtools/shared/repositoryClient.js +108 -0
- package/dist/vendor/devtools/shared/repositoryIdentity.js +116 -0
- package/dist/vendor/devtools/shared/repositoryModels.js +72 -0
- package/dist/vendor/devtools/shared/session.js +532 -0
- package/docs/adr/0002-conversational-project-mcp-client.md +59 -0
- package/docs/agents-tools-panel.md +63 -0
- package/docs/codex-verification.md +161 -0
- package/docs/conversational-mcp.md +54 -0
- package/hooks/hooks.json +17 -0
- package/package.json +50 -4
- package/skills/mainsequence-mcp/SKILL.md +40 -0
- package/skills/ms/SKILL.md +8 -0
- package/skills/ms/agents/openai.yaml +7 -0
- package/skills/ms/assets/mainsequence-icon.png +0 -0
- package/skills/ms/assets/mainsequence-icon.svg +454 -0
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.readProjectEnv = readProjectEnv;
|
|
37
|
+
exports.resolveBackend = resolveBackend;
|
|
38
|
+
exports.readWorkspacePreferences = readWorkspacePreferences;
|
|
39
|
+
exports.git = git;
|
|
40
|
+
exports.projectContext = projectContext;
|
|
41
|
+
exports.detectProjectType = detectProjectType;
|
|
42
|
+
exports.listRepositories = listRepositories;
|
|
43
|
+
exports.resolveRepository = resolveRepository;
|
|
44
|
+
exports.repositorySetupInfo = repositorySetupInfo;
|
|
45
|
+
exports.safeSlug = safeSlug;
|
|
46
|
+
exports.canonicalRepositoryDir = canonicalRepositoryDir;
|
|
47
|
+
// Git identity and canonical layout extracted from the VS Code extension.
|
|
48
|
+
const fs = __importStar(require("fs/promises"));
|
|
49
|
+
const path = __importStar(require("path"));
|
|
50
|
+
const os = __importStar(require("os"));
|
|
51
|
+
const models_1 = require("./models.js");
|
|
52
|
+
const session_1 = require("./session.js");
|
|
53
|
+
const repositoryIdentity_1 = require("./repositoryIdentity.js");
|
|
54
|
+
const repositoryClient_1 = require("./repositoryClient.js");
|
|
55
|
+
const repositoryModels_1 = require("./repositoryModels.js");
|
|
56
|
+
async function readProjectEnv(root) {
|
|
57
|
+
let text;
|
|
58
|
+
try {
|
|
59
|
+
text = await fs.readFile(path.join(root, '.env'), 'utf8');
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
if (error.code === 'ENOENT')
|
|
63
|
+
return {};
|
|
64
|
+
throw new models_1.ToolingError('INVALID_REQUEST', 'The project .env could not be read.');
|
|
65
|
+
}
|
|
66
|
+
const values = {};
|
|
67
|
+
for (const line of text.split(/\r?\n/)) {
|
|
68
|
+
const match = line.match(/^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*?)\s*$/);
|
|
69
|
+
if (!match)
|
|
70
|
+
continue;
|
|
71
|
+
let value = match[2];
|
|
72
|
+
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'")))
|
|
73
|
+
value = value.slice(1, -1);
|
|
74
|
+
else
|
|
75
|
+
value = value.replace(/\s+#.*$/, '').trim();
|
|
76
|
+
values[match[1]] = value;
|
|
77
|
+
}
|
|
78
|
+
return values;
|
|
79
|
+
}
|
|
80
|
+
async function resolveBackend(explicit, root, env = process.env) {
|
|
81
|
+
if (explicit)
|
|
82
|
+
return (0, session_1.requireBackend)(explicit);
|
|
83
|
+
if (env.MAINSEQUENCE_ENDPOINT)
|
|
84
|
+
return (0, session_1.requireBackend)(env.MAINSEQUENCE_ENDPOINT);
|
|
85
|
+
if (root) {
|
|
86
|
+
const projectEnv = await readProjectEnv(root);
|
|
87
|
+
if (projectEnv.MAINSEQUENCE_ENDPOINT)
|
|
88
|
+
return (0, session_1.requireBackend)(projectEnv.MAINSEQUENCE_ENDPOINT);
|
|
89
|
+
}
|
|
90
|
+
return (await readWorkspacePreferences({ env })).backend;
|
|
91
|
+
}
|
|
92
|
+
/** Resolve non-secret workspace settings without requiring a session or writing configuration. */
|
|
93
|
+
async function readWorkspacePreferences(options = {}) {
|
|
94
|
+
const env = options.env ?? process.env, home = options.homeDir ?? os.homedir();
|
|
95
|
+
const directory = process.platform === 'darwin' ? path.join(home, 'Library', 'Application Support', 'MainSequenceCLI')
|
|
96
|
+
: process.platform === 'win32' ? path.join(env.APPDATA || home, 'MainSequenceCLI') : path.join(home, '.config', 'mainsequence');
|
|
97
|
+
const config = await fs.readFile(options.configPath ?? path.join(directory, 'config.json'), 'utf8')
|
|
98
|
+
.then(text => JSON.parse(text)).catch(() => ({}));
|
|
99
|
+
const folder = config?.mainsequence_path;
|
|
100
|
+
return { backend: (0, session_1.requireBackend)(env.MAINSEQUENCE_ENDPOINT || (typeof config?.backend_url === 'string' && config.backend_url.trim()) || 'https://api.main-sequence.app'),
|
|
101
|
+
baseDir: typeof folder === 'string' && path.isAbsolute(folder) && !/[\0\r\n]/.test(folder) ? folder : path.join(home, 'mainsequence') };
|
|
102
|
+
}
|
|
103
|
+
async function git(host, cwd, args, signal) {
|
|
104
|
+
const result = await host.runProcess({ executable: 'git', args, cwd, signal, timeoutMs: 15_000 });
|
|
105
|
+
if (result.cancelled)
|
|
106
|
+
throw new models_1.ToolingError('CANCELLED', 'The action was cancelled.');
|
|
107
|
+
if (result.exitCode !== 0)
|
|
108
|
+
throw new models_1.ToolingError('INVALID_GIT_CONTEXT', 'Git could not resolve the required worktree, attached branch, commit, or origin.');
|
|
109
|
+
return result.stdout.trim();
|
|
110
|
+
}
|
|
111
|
+
async function projectContext(host, root, backend, projectType, env, signal) {
|
|
112
|
+
let requested;
|
|
113
|
+
try {
|
|
114
|
+
requested = await fs.realpath(path.resolve(root));
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
throw new models_1.ToolingError('PROJECT_NOT_FOUND', 'The project directory does not exist.');
|
|
118
|
+
}
|
|
119
|
+
const resolved = await fs.realpath(await git(host, requested, ['rev-parse', '--show-toplevel'], signal));
|
|
120
|
+
const relative = path.relative(resolved, requested);
|
|
121
|
+
if (relative === '..' || relative.startsWith('..' + path.sep) || path.isAbsolute(relative))
|
|
122
|
+
throw new models_1.ToolingError('INVALID_GIT_CONTEXT', 'Git resolved a worktree outside the selected project directory. Check inherited Git environment overrides.');
|
|
123
|
+
const ref = await git(host, resolved, ['symbolic-ref', '--quiet', 'HEAD'], signal);
|
|
124
|
+
if (!ref.startsWith('refs/heads/'))
|
|
125
|
+
throw new models_1.ToolingError('INVALID_GIT_CONTEXT', 'An attached Git branch is required.');
|
|
126
|
+
const branch = ref.slice('refs/heads/'.length);
|
|
127
|
+
const commit = (await git(host, resolved, ['rev-parse', '--verify', 'HEAD^{commit}'], signal)).toLowerCase();
|
|
128
|
+
if (!/^[0-9a-f]{40}$/.test(commit))
|
|
129
|
+
throw new models_1.ToolingError('INVALID_GIT_CONTEXT', 'A full Git commit SHA is required.');
|
|
130
|
+
let remote;
|
|
131
|
+
try {
|
|
132
|
+
remote = await git(host, resolved, ['config', '--get', `branch.${branch}.remote`], signal);
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
remote = '';
|
|
136
|
+
}
|
|
137
|
+
if (!remote || remote === '.') {
|
|
138
|
+
const remotes = (await git(host, resolved, ['remote'], signal)).split(/\r?\n/).filter(Boolean);
|
|
139
|
+
remote = remotes.includes('origin') ? 'origin' : remotes.length === 1 ? remotes[0] : '';
|
|
140
|
+
}
|
|
141
|
+
if (!remote)
|
|
142
|
+
throw new models_1.ToolingError('INVALID_GIT_CONTEXT', 'The project Git remote is ambiguous.');
|
|
143
|
+
const origin = await git(host, resolved, ['remote', 'get-url', remote], signal);
|
|
144
|
+
let identity;
|
|
145
|
+
try {
|
|
146
|
+
identity = (0, repositoryIdentity_1.repositorySshKeyIdentity)(origin).identity;
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
throw new models_1.ToolingError('INVALID_GIT_CONTEXT', 'The Git origin is not a supported repository URL.');
|
|
150
|
+
}
|
|
151
|
+
const common = await git(host, resolved, ['rev-parse', '--git-common-dir'], signal);
|
|
152
|
+
const backendUrl = await resolveBackend(backend, resolved, env);
|
|
153
|
+
const type = projectType ?? await detectProjectType(resolved);
|
|
154
|
+
return { root: resolved, projectType: type, backend: backendUrl, origin: safeOrigin(origin), repositoryIdentity: identity, branch, ref, commit, gitCommonDir: path.resolve(resolved, common) };
|
|
155
|
+
}
|
|
156
|
+
function safeOrigin(origin) {
|
|
157
|
+
if (!origin.includes('://'))
|
|
158
|
+
return origin;
|
|
159
|
+
const parsed = new URL(origin);
|
|
160
|
+
parsed.username = '';
|
|
161
|
+
parsed.password = '';
|
|
162
|
+
parsed.search = '';
|
|
163
|
+
parsed.hash = '';
|
|
164
|
+
return parsed.toString();
|
|
165
|
+
}
|
|
166
|
+
async function detectProjectType(root) {
|
|
167
|
+
const python = await fs.stat(path.join(root, 'pyproject.toml')).then(s => s.isFile()).catch(() => false);
|
|
168
|
+
const node = await fs.readFile(path.join(root, 'package.json'), 'utf8').then(text => {
|
|
169
|
+
const pkg = JSON.parse(text);
|
|
170
|
+
return Boolean(pkg.dependencies?.['@dev-mainsequence/command-center-sdk'] || pkg.devDependencies?.['@dev-mainsequence/command-center-sdk']);
|
|
171
|
+
}).catch(() => false);
|
|
172
|
+
return python === node ? undefined : python ? 'python' : 'vite_react';
|
|
173
|
+
}
|
|
174
|
+
async function listRepositories(session, backend) {
|
|
175
|
+
const payload = await session.request(backend, 'GET', '/api/v1/code-repositories/');
|
|
176
|
+
const values = Array.isArray(payload) ? payload : payload?.results;
|
|
177
|
+
if (!Array.isArray(values))
|
|
178
|
+
throw new models_1.ToolingError('API_ERROR', 'The backend repository list has an invalid shape.');
|
|
179
|
+
return values.map((value, index) => (0, repositoryModels_1.codeRepositoryFromResponse)(value, `CodeRepository ${index}`));
|
|
180
|
+
}
|
|
181
|
+
async function resolveRepository(session, context) {
|
|
182
|
+
return new repositoryClient_1.RepositoryClient(session, context.backend).resolveCodeRepositoryGitContext({ repositoryIdentity: context.repositoryIdentity, repositoryBranch: context.branch, commitSha: context.commit });
|
|
183
|
+
}
|
|
184
|
+
async function repositorySetupInfo(session, backend, uid, branchName) {
|
|
185
|
+
const repository = (0, repositoryModels_1.codeRepositoryFromResponse)(await session.request(backend, 'GET', `/api/v1/code-repositories/${encodeURIComponent(uid)}/`), 'CodeRepository');
|
|
186
|
+
if (repository.uid !== uid)
|
|
187
|
+
throw new models_1.ToolingError('API_ERROR', 'The backend returned another CodeRepository.');
|
|
188
|
+
const summary = repository.branches.find(branch => branch.repository_branch === branchName);
|
|
189
|
+
if (!summary)
|
|
190
|
+
throw new models_1.ToolingError('INVALID_REQUEST', 'The requested branch is not registered for this CodeRepository.');
|
|
191
|
+
const branch = (0, repositoryModels_1.codeRepositoryBranchFromResponse)(await session.request(backend, 'GET', `/api/v1/code-repository-branches/${encodeURIComponent(summary.uid)}/`), 'CodeRepositoryBranch');
|
|
192
|
+
if (branch.uid !== summary.uid || branch.code_repository_uid !== uid || branch.repository_branch !== branchName)
|
|
193
|
+
throw new models_1.ToolingError('API_ERROR', 'The backend returned another repository branch.');
|
|
194
|
+
if (!branch.is_initialized)
|
|
195
|
+
throw new models_1.ToolingError('INVALID_REQUEST', 'This CodeRepositoryBranch has not finished initializing.');
|
|
196
|
+
const bindingUid = branch.github_repository_binding_uid || repository.github_repository_binding_uid;
|
|
197
|
+
if (!bindingUid)
|
|
198
|
+
throw new models_1.ToolingError('API_ERROR', 'This repository has no GitHub binding.');
|
|
199
|
+
const binding = (0, repositoryModels_1.gitHubRepositoryBindingFromResponse)(await session.request(backend, 'GET', `/api/v1/github-repository-bindings/${encodeURIComponent(bindingUid)}/`), 'GitHub binding');
|
|
200
|
+
if (binding.code_repository_uid !== uid || binding.uid !== bindingUid)
|
|
201
|
+
throw new models_1.ToolingError('API_ERROR', 'The GitHub binding belongs to another repository.');
|
|
202
|
+
const profile = await session.request(backend, 'GET', '/api/v1/users/me/');
|
|
203
|
+
const user = profile?.user || profile;
|
|
204
|
+
const organization = user?.organization || profile?.organization || {};
|
|
205
|
+
const orgName = typeof organization === 'string' ? organization : organization.name || organization.slug || organization.uid || 'default';
|
|
206
|
+
return { repository, branch, binding, orgSlug: safeSlug(orgName) };
|
|
207
|
+
}
|
|
208
|
+
function safeSlug(value) {
|
|
209
|
+
return String(value || 'code-repository').toLowerCase().replace(/[^a-z0-9-_]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 64) || 'code-repository';
|
|
210
|
+
}
|
|
211
|
+
function canonicalRepositoryDir(base, org, uid, name) {
|
|
212
|
+
if (!/^[A-Za-z0-9._-]+$/.test(uid) || uid === '.' || uid === '..')
|
|
213
|
+
throw new models_1.ToolingError('INVALID_REQUEST', 'The CodeRepository UID must be a filesystem-safe segment.');
|
|
214
|
+
return path.join(path.resolve(base), safeSlug(org), 'projects', `${safeSlug(name)}-${uid}`);
|
|
215
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RepositoryClient = void 0;
|
|
4
|
+
const models_1 = require("./models.js");
|
|
5
|
+
const repositoryModels_1 = require("./repositoryModels.js");
|
|
6
|
+
const AUTH_PATHS = { userDetails: '/api/v1/users/me/' };
|
|
7
|
+
const ApiError = Error;
|
|
8
|
+
function requiredObject(value, label) {
|
|
9
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
10
|
+
throw new models_1.ToolingError('API_ERROR', `${label} must be an object.`);
|
|
11
|
+
return value;
|
|
12
|
+
}
|
|
13
|
+
function requiredSingleLine(value, label) {
|
|
14
|
+
if (typeof value !== 'string' || !value.trim() || /[\r\n\0]/.test(value))
|
|
15
|
+
throw new models_1.ToolingError('INVALID_REQUEST', `${label} is required.`);
|
|
16
|
+
return value.trim();
|
|
17
|
+
}
|
|
18
|
+
class RepositoryClient {
|
|
19
|
+
session;
|
|
20
|
+
backend;
|
|
21
|
+
constructor(session, backend) {
|
|
22
|
+
this.session = session;
|
|
23
|
+
this.backend = backend;
|
|
24
|
+
}
|
|
25
|
+
async authed(method, apiPath, body) {
|
|
26
|
+
const retrySafe = method === 'GET' || apiPath === '/api/v1/code-repository-branches/resolve-git-context/';
|
|
27
|
+
return { data: await this.session.request(this.backend, method, apiPath, body, retrySafe) };
|
|
28
|
+
}
|
|
29
|
+
async getCurrentUserProfile() {
|
|
30
|
+
const details = await this.authed('GET', AUTH_PATHS.userDetails);
|
|
31
|
+
const detailsData = details.data || {};
|
|
32
|
+
const detailsUser = detailsData.user || detailsData;
|
|
33
|
+
const userUid = typeof detailsUser?.uid === 'string' ? detailsUser.uid.trim() : '';
|
|
34
|
+
const detailsOrg = detailsUser?.organization || detailsData.organization || {};
|
|
35
|
+
const org = detailsUser?.organization || detailsOrg || {};
|
|
36
|
+
const orgName = typeof org === 'string'
|
|
37
|
+
? org
|
|
38
|
+
: (org?.name || org?.slug || detailsUser?.organization_name || detailsData.organization_name || '');
|
|
39
|
+
const organizationUid = typeof org === 'object' && org
|
|
40
|
+
? (org?.uid || detailsOrg?.uid || '')
|
|
41
|
+
: '';
|
|
42
|
+
return {
|
|
43
|
+
username: detailsUser?.username || '',
|
|
44
|
+
organization: orgName,
|
|
45
|
+
uid: userUid,
|
|
46
|
+
organizationUid
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
async getCodeRepositories() {
|
|
50
|
+
const r = await this.authed('GET', '/api/v1/code-repositories/');
|
|
51
|
+
const data = r.data || {};
|
|
52
|
+
const results = Array.isArray(data) ? data : data.results || [];
|
|
53
|
+
if (!Array.isArray(results))
|
|
54
|
+
throw new ApiError('CodeRepository list returned an unexpected payload.');
|
|
55
|
+
return results.map((value, index) => (0, repositoryModels_1.codeRepositoryFromResponse)(value, `CodeRepository list item ${index}`));
|
|
56
|
+
}
|
|
57
|
+
async getCodeRepository(codeRepositoryUid) {
|
|
58
|
+
const uid = requiredSingleLine(codeRepositoryUid, 'CodeRepository UID');
|
|
59
|
+
const r = await this.authed('GET', `/api/v1/code-repositories/${encodeURIComponent(uid)}/`);
|
|
60
|
+
return (0, repositoryModels_1.codeRepositoryFromResponse)(r.data, 'CodeRepository response');
|
|
61
|
+
}
|
|
62
|
+
async getCodeRepositoryBranch(codeRepositoryBranchUid) {
|
|
63
|
+
const uid = requiredSingleLine(codeRepositoryBranchUid, 'CodeRepositoryBranch UID');
|
|
64
|
+
const r = await this.authed('GET', `/api/v1/code-repository-branches/${encodeURIComponent(uid)}/`);
|
|
65
|
+
return (0, repositoryModels_1.codeRepositoryBranchFromResponse)(r.data, 'CodeRepositoryBranch response');
|
|
66
|
+
}
|
|
67
|
+
async getGitHubRepositoryBinding(repositoryUid) {
|
|
68
|
+
const uid = requiredSingleLine(repositoryUid, 'GitHubRepositoryBinding UID');
|
|
69
|
+
const r = await this.authed('GET', `/api/v1/github-repository-bindings/${encodeURIComponent(uid)}/`);
|
|
70
|
+
return (0, repositoryModels_1.gitHubRepositoryBindingFromResponse)(r.data, 'GitHubRepositoryBinding response');
|
|
71
|
+
}
|
|
72
|
+
async addCodeRepositoryDeployKey(codeRepositoryUid, keyTitle, publicKey) {
|
|
73
|
+
const uid = requiredSingleLine(codeRepositoryUid, 'CodeRepository UID');
|
|
74
|
+
const title = requiredSingleLine(keyTitle, 'Deploy-key title');
|
|
75
|
+
const key = requiredSingleLine(publicKey, 'Deploy-key public key');
|
|
76
|
+
await this.authed('POST', `/api/v1/code-repositories/${encodeURIComponent(uid)}/add-deploy-key/`, {
|
|
77
|
+
key_title: title, public_key: key
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
async resolveCodeRepositoryGitContext(input) {
|
|
81
|
+
const repositoryIdentity = requiredSingleLine(input.repositoryIdentity, 'Git repository identity');
|
|
82
|
+
const repositoryBranch = requiredSingleLine(input.repositoryBranch, 'Git branch');
|
|
83
|
+
const commitSha = requiredSingleLine(input.commitSha, 'Git commit').toLowerCase();
|
|
84
|
+
if (!/^[0-9a-f]{40}$/.test(commitSha))
|
|
85
|
+
throw new ApiError('Git commit must be a canonical full SHA.');
|
|
86
|
+
const r = await this.authed('POST', '/api/v1/code-repository-branches/resolve-git-context/', {
|
|
87
|
+
repository_identity: repositoryIdentity,
|
|
88
|
+
repository_branch: repositoryBranch,
|
|
89
|
+
commit_sha: commitSha
|
|
90
|
+
});
|
|
91
|
+
const payload = requiredObject(r.data, 'Git-context resolution');
|
|
92
|
+
const branch = (0, repositoryModels_1.codeRepositoryBranchFromResponse)(payload.code_repository_branch, 'Resolved CodeRepositoryBranch');
|
|
93
|
+
if (payload.canonical_repository_identity !== repositoryIdentity) {
|
|
94
|
+
throw new ApiError('Git-context resolution returned another canonical repository identity.');
|
|
95
|
+
}
|
|
96
|
+
if (payload.repository_branch !== repositoryBranch || branch.repository_branch !== repositoryBranch) {
|
|
97
|
+
throw new ApiError('Git-context resolution returned another repository branch.');
|
|
98
|
+
}
|
|
99
|
+
if (payload.repository_ref !== `refs/heads/${repositoryBranch}`) {
|
|
100
|
+
throw new ApiError('Git-context resolution returned another attached Git ref.');
|
|
101
|
+
}
|
|
102
|
+
if (String(payload.commit_sha || '').toLowerCase() !== commitSha) {
|
|
103
|
+
throw new ApiError('Git-context resolution returned another Git commit.');
|
|
104
|
+
}
|
|
105
|
+
return { ...payload, code_repository_branch: branch };
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
exports.RepositoryClient = RepositoryClient;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.repositorySshKeyIdentity = repositorySshKeyIdentity;
|
|
37
|
+
exports.repositorySshKeyName = repositorySshKeyName;
|
|
38
|
+
exports.repositorySshKeyPaths = repositorySshKeyPaths;
|
|
39
|
+
const crypto = __importStar(require("crypto"));
|
|
40
|
+
const os = __importStar(require("os"));
|
|
41
|
+
const path = __importStar(require("path"));
|
|
42
|
+
const SUPPORTED_GIT_ORIGIN_SCHEMES = new Set(['git', 'git+ssh', 'http', 'https', 'ssh']);
|
|
43
|
+
const SSH_GIT_ORIGIN_SCHEMES = new Set(['git+ssh', 'ssh']);
|
|
44
|
+
const DEFAULT_GIT_ORIGIN_PORTS = {
|
|
45
|
+
http: '80',
|
|
46
|
+
https: '443',
|
|
47
|
+
ssh: '22',
|
|
48
|
+
'git+ssh': '22'
|
|
49
|
+
};
|
|
50
|
+
const SCP_GIT_ORIGIN_PATTERN = /^(?:[^@/\s]+@)?(?<host>[^:/\s]+):(?<path>.+)$/;
|
|
51
|
+
function repositorySshKeyIdentity(repoUrl) {
|
|
52
|
+
const candidate = String(repoUrl || '').trim().replace(/[?#].*$/, '');
|
|
53
|
+
if (!candidate)
|
|
54
|
+
throw new Error('Git origin must be non-empty.');
|
|
55
|
+
if (/[\r\n]/.test(candidate))
|
|
56
|
+
throw new Error('Git origin must contain one non-empty line.');
|
|
57
|
+
const schemeMatch = candidate.match(/^(?<scheme>[A-Za-z][A-Za-z0-9+.-]*):\/\//);
|
|
58
|
+
if (schemeMatch?.groups) {
|
|
59
|
+
const scheme = schemeMatch.groups.scheme.toLowerCase();
|
|
60
|
+
if (!SUPPORTED_GIT_ORIGIN_SCHEMES.has(scheme)) {
|
|
61
|
+
throw new Error(`Unsupported Git origin scheme: ${scheme}.`);
|
|
62
|
+
}
|
|
63
|
+
let parsed;
|
|
64
|
+
try {
|
|
65
|
+
parsed = new URL(candidate);
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
throw new Error(`Invalid Git origin: ${repoUrl}.`);
|
|
69
|
+
}
|
|
70
|
+
const host = parsed.hostname.toLowerCase().replace(/\.$/, '');
|
|
71
|
+
if (!host)
|
|
72
|
+
throw new Error('Git origin must include a hostname.');
|
|
73
|
+
const repositoryPath = cleanGitRepositoryPath(parsed.pathname);
|
|
74
|
+
const port = parsed.port;
|
|
75
|
+
const defaultPort = DEFAULT_GIT_ORIGIN_PORTS[scheme];
|
|
76
|
+
const hostIdentity = !port || port === defaultPort ? host : `${host}:${port}`;
|
|
77
|
+
return {
|
|
78
|
+
identity: `${hostIdentity}/${repositoryPath}`,
|
|
79
|
+
usesSsh: SSH_GIT_ORIGIN_SCHEMES.has(scheme)
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
const scpMatch = candidate.match(SCP_GIT_ORIGIN_PATTERN);
|
|
83
|
+
if (!scpMatch?.groups) {
|
|
84
|
+
throw new Error('Git origin must be an SSH, Git, HTTP, or HTTPS repository URL.');
|
|
85
|
+
}
|
|
86
|
+
const host = scpMatch.groups.host.toLowerCase().replace(/\.$/, '');
|
|
87
|
+
const repositoryPath = cleanGitRepositoryPath(scpMatch.groups.path);
|
|
88
|
+
return { identity: `${host}/${repositoryPath}`, usesSsh: true };
|
|
89
|
+
}
|
|
90
|
+
function repositorySshKeyName(repoUrl) {
|
|
91
|
+
const { identity } = repositorySshKeyIdentity(repoUrl);
|
|
92
|
+
const repositoryName = identity.split('/').pop() || '';
|
|
93
|
+
const slug = repositoryName
|
|
94
|
+
.replace(/[^A-Za-z0-9._-]+/g, '-')
|
|
95
|
+
.replace(/^[._-]+|[._-]+$/g, '')
|
|
96
|
+
.toLowerCase()
|
|
97
|
+
.slice(0, 48)
|
|
98
|
+
.replace(/[._-]+$/g, '') || 'repository';
|
|
99
|
+
const digest = crypto.createHash('sha256').update(identity, 'utf8').digest('hex').slice(0, 16);
|
|
100
|
+
return `mainsequence-${slug}-${digest}`;
|
|
101
|
+
}
|
|
102
|
+
function repositorySshKeyPaths(repoUrl) {
|
|
103
|
+
const keyPath = path.join(os.homedir(), '.ssh', repositorySshKeyName(repoUrl));
|
|
104
|
+
return { keyPath, pubPath: `${keyPath}.pub` };
|
|
105
|
+
}
|
|
106
|
+
function cleanGitRepositoryPath(value) {
|
|
107
|
+
if (value.includes('\\'))
|
|
108
|
+
throw new Error('Git origin repository paths must use forward slashes.');
|
|
109
|
+
let repositoryPath = value.replace(/\/{2,}/g, '/').replace(/^\/+|\/+$/g, '');
|
|
110
|
+
if (repositoryPath.toLowerCase().endsWith('.git'))
|
|
111
|
+
repositoryPath = repositoryPath.slice(0, -4);
|
|
112
|
+
repositoryPath = repositoryPath.replace(/\/+$/g, '');
|
|
113
|
+
if (!repositoryPath)
|
|
114
|
+
throw new Error('Git origin must include a repository path.');
|
|
115
|
+
return repositoryPath;
|
|
116
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.codeRepositoryFromResponse = codeRepositoryFromResponse;
|
|
4
|
+
exports.codeRepositoryBranchFromResponse = codeRepositoryBranchFromResponse;
|
|
5
|
+
exports.gitHubRepositoryBindingFromResponse = gitHubRepositoryBindingFromResponse;
|
|
6
|
+
const models_1 = require("./models.js");
|
|
7
|
+
class ApiError extends models_1.ToolingError {
|
|
8
|
+
constructor(message) { super('API_ERROR', message); }
|
|
9
|
+
}
|
|
10
|
+
function requiredSingleLine(value, label) {
|
|
11
|
+
const normalized = typeof value === 'string' ? value.trim() : '';
|
|
12
|
+
if (!normalized || /[\r\n]/.test(normalized))
|
|
13
|
+
throw new ApiError(`${label} is required.`);
|
|
14
|
+
return normalized;
|
|
15
|
+
}
|
|
16
|
+
function requiredObject(value, label) {
|
|
17
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
18
|
+
throw new ApiError(`${label} must be an object.`);
|
|
19
|
+
}
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
22
|
+
function optionalSingleLine(value, label) {
|
|
23
|
+
if (value == null)
|
|
24
|
+
return value;
|
|
25
|
+
return requiredSingleLine(value, label);
|
|
26
|
+
}
|
|
27
|
+
function codeRepositoryFromResponse(value, label) {
|
|
28
|
+
const payload = requiredObject(value, label);
|
|
29
|
+
if (!Array.isArray(payload.branches))
|
|
30
|
+
throw new ApiError(`${label}.branches must be an array.`);
|
|
31
|
+
return {
|
|
32
|
+
uid: requiredSingleLine(payload.uid, `${label} UID`),
|
|
33
|
+
code_repository_name: requiredSingleLine(payload.code_repository_name, `${label} name`),
|
|
34
|
+
code_repository_type: requiredSingleLine(payload.code_repository_type, `${label} type`),
|
|
35
|
+
primary_language: requiredSingleLine(payload.primary_language, `${label} primary language`),
|
|
36
|
+
framework: requiredSingleLine(payload.framework, `${label} framework`),
|
|
37
|
+
github_repository_binding_uid: optionalSingleLine(payload.github_repository_binding_uid, `${label} GitHubRepositoryBinding UID`),
|
|
38
|
+
branches: payload.branches.map((branch, index) => {
|
|
39
|
+
const item = requiredObject(branch, `${label}.branches[${index}]`);
|
|
40
|
+
return {
|
|
41
|
+
uid: requiredSingleLine(item.uid, `${label}.branches[${index}] UID`),
|
|
42
|
+
repository_branch: requiredSingleLine(item.repository_branch, `${label}.branches[${index}] repository branch`)
|
|
43
|
+
};
|
|
44
|
+
})
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function codeRepositoryBranchFromResponse(value, label) {
|
|
48
|
+
const payload = requiredObject(value, label);
|
|
49
|
+
if (typeof payload.is_initialized !== 'boolean') {
|
|
50
|
+
throw new ApiError(`${label}.is_initialized must be a boolean.`);
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
uid: requiredSingleLine(payload.uid, `${label} UID`),
|
|
54
|
+
code_repository_uid: requiredSingleLine(payload.code_repository_uid, `${label} CodeRepository UID`),
|
|
55
|
+
code_repository_name: requiredSingleLine(payload.code_repository_name, `${label} CodeRepository name`),
|
|
56
|
+
repository_branch: requiredSingleLine(payload.repository_branch, `${label} repository branch`),
|
|
57
|
+
github_repository_binding_uid: optionalSingleLine(payload.github_repository_binding_uid, `${label} GitHubRepositoryBinding UID`),
|
|
58
|
+
is_initialized: payload.is_initialized
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function gitHubRepositoryBindingFromResponse(value, label) {
|
|
62
|
+
const payload = requiredObject(value, label);
|
|
63
|
+
return {
|
|
64
|
+
uid: requiredSingleLine(payload.uid, `${label} UID`),
|
|
65
|
+
code_repository_uid: requiredSingleLine(payload.code_repository_uid, `${label} CodeRepository UID`),
|
|
66
|
+
code_repository_name: requiredSingleLine(payload.code_repository_name, `${label} CodeRepository name`),
|
|
67
|
+
repo_name: requiredSingleLine(payload.repo_name, `${label} repository name`),
|
|
68
|
+
github_full_name: requiredSingleLine(payload.github_full_name, `${label} GitHub full name`),
|
|
69
|
+
git_ssh_url: optionalSingleLine(payload.git_ssh_url, `${label} SSH URL`),
|
|
70
|
+
git_repo_url: optionalSingleLine(payload.git_repo_url, `${label} Git URL`)
|
|
71
|
+
};
|
|
72
|
+
}
|