browser-git-ops 0.0.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.
@@ -0,0 +1,182 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.GitLabAdapter = void 0;
7
+ const crypto_1 = __importDefault(require("crypto"));
8
+ /**
9
+ *
10
+ */
11
+ class GitLabAdapter {
12
+ opts;
13
+ baseUrl;
14
+ headers;
15
+ pendingActions = null;
16
+ maxRetries = 3;
17
+ baseBackoff = 300;
18
+ /**
19
+ * GitLabAdapter を初期化します。
20
+ * @param {GLOpts} opts 設定オブジェクト
21
+ */
22
+ constructor(opts) {
23
+ this.opts = opts;
24
+ const host = opts.host || 'https://gitlab.com';
25
+ this.baseUrl = `${host}/api/v4/projects/${encodeURIComponent(opts.projectId)}`;
26
+ this.headers = { 'PRIVATE-TOKEN': opts.token, 'Content-Type': 'application/json' };
27
+ }
28
+ /**
29
+ * コンテンツから sha1 を算出します。
30
+ * @param {string} content コンテンツ
31
+ * @returns {string} sha1 ハッシュ
32
+ */
33
+ shaOf(content) {
34
+ return crypto_1.default.createHash('sha1').update(content, 'utf8').digest('hex');
35
+ }
36
+ /**
37
+ * 変更一覧から blob sha のマップを作成します(疑似実装)。
38
+ * @param {any[]} changes 変更一覧
39
+ * @returns {Promise<Record<string,string>>} path->sha マップ
40
+ */
41
+ async createBlobs(changes) {
42
+ const map = {};
43
+ for (const c of changes) {
44
+ if (c.type === 'create' || c.type === 'update')
45
+ map[c.path] = this.shaOf(c.content);
46
+ }
47
+ return map;
48
+ }
49
+ /**
50
+ * 互換用のツリー作成。実際には actions を保持しておき、マーカーを返します。
51
+ * @param {any[]} _changes 変更一覧
52
+ * @param {string} [_baseTreeSha] ベースツリー(未使用)
53
+ * @returns {Promise<string>} マーカー文字列
54
+ */
55
+ async createTree(_changes, _baseTreeSha) {
56
+ // Store actions for later commit; return marker token
57
+ const actions = (_changes || []).map((c) => {
58
+ if (c.type === 'delete')
59
+ return { action: 'delete', file_path: c.path };
60
+ if (c.type === 'create')
61
+ return { action: 'create', file_path: c.path, content: c.content };
62
+ return { action: 'update', file_path: c.path, content: c.content };
63
+ });
64
+ this.pendingActions = actions;
65
+ return `gitlab-tree-${Date.now()}-${Math.floor(Math.random() * 100000)}`;
66
+ }
67
+ /**
68
+ * createTree で保持した actions があればコミットし、なければ parentSha を返します。
69
+ * @param {string} message コミットメッセージ
70
+ * @param {string} parentSha 親コミット SHA
71
+ * @param {string} _treeSha ツリー SHA(未使用)
72
+ * @returns {Promise<string>} 新規コミット SHA または parentSha
73
+ */
74
+ async createCommit(message, parentSha, _treeSha) {
75
+ // If pendingActions exist (created via createTree), use commits API
76
+ if (this.pendingActions && this.pendingActions.length > 0) {
77
+ const branch = 'main';
78
+ const res = await this.createCommitWithActions(branch, message, this.pendingActions.map((a) => ({ type: a.action === 'delete' ? 'delete' : a.action === 'create' ? 'create' : 'update', path: a.file_path, content: a.content })));
79
+ this.pendingActions = null;
80
+ return res;
81
+ }
82
+ // Fallback: no-op commit (return parentSha)
83
+ return parentSha;
84
+ }
85
+ /**
86
+ * actions を用いて GitLab のコミット API を呼び出します。
87
+ * @param {string} branch ブランチ名
88
+ * @param {string} message コミットメッセージ
89
+ * @param {{type:string,path:string,content?:string}[]} changes 変更一覧
90
+ * @returns {Promise<any>} コミット応答(id など)
91
+ */
92
+ async createCommitWithActions(branch, message, changes) {
93
+ const url = `${this.baseUrl}/repository/commits`;
94
+ const actions = changes.map((c) => {
95
+ if (c.type === 'delete')
96
+ return { action: 'delete', file_path: c.path };
97
+ if (c.type === 'create')
98
+ return { action: 'create', file_path: c.path, content: c.content };
99
+ return { action: 'update', file_path: c.path, content: c.content };
100
+ });
101
+ /**
102
+ * GitLab のコミット API を呼び出します。
103
+ * @param {string} branch ブランチ名
104
+ * @param {string} message コミットメッセージ
105
+ * @param {{type:string,path:string,content?:string}[]} changes 変更一覧
106
+ * @returns {Promise<any>} 作成されたコミットの識別子
107
+ */
108
+ const body = JSON.stringify({ branch, commit_message: message, actions });
109
+ const res = await this.fetchWithRetry(url, { method: 'POST', headers: this.headers, body });
110
+ const text = await res.text().catch(() => '');
111
+ let j = null;
112
+ try {
113
+ j = text ? JSON.parse(text) : null;
114
+ }
115
+ catch (err) {
116
+ throw new Error(`GitLab commit invalid JSON response: ${text}`);
117
+ }
118
+ // validate expected fields (GitLab returns 'id' for commit id)
119
+ if (!j || (!j.id && !j.commit)) {
120
+ throw new Error(`GitLab commit unexpected response: ${JSON.stringify(j)}`);
121
+ }
122
+ return j.id || j.commit || j;
123
+ }
124
+ /**
125
+ * fetch をリトライ付きで実行します。
126
+ * @param {string} url リクエスト URL
127
+ * @param {RequestInit} opts fetch オプション
128
+ * @param {number} [retries] 最大リトライ回数
129
+ * @returns {Promise<Response>} レスポンス
130
+ */
131
+ async fetchWithRetry(url, opts, retries = this.maxRetries) {
132
+ for (let attempt = 1; attempt <= retries; attempt++) {
133
+ try {
134
+ const res = await fetch(url, opts);
135
+ if (!this.isRetryableStatus(res.status))
136
+ return res;
137
+ if (attempt === retries)
138
+ return res;
139
+ const wait = this.backoffMs(attempt);
140
+ await new Promise((r) => setTimeout(r, wait));
141
+ }
142
+ catch (err) {
143
+ if (attempt === retries)
144
+ throw err;
145
+ const wait = this.backoffMs(attempt);
146
+ await new Promise((r) => setTimeout(r, wait));
147
+ }
148
+ }
149
+ // should not reach here
150
+ throw new Error('fetchWithRetry: unexpected exit');
151
+ }
152
+ /**
153
+ * ステータスが再試行対象か判定します。
154
+ * @param {number} status ステータスコード
155
+ * @returns {boolean}
156
+ */
157
+ isRetryableStatus(status) {
158
+ return status === 429 || (status >= 500 && status < 600);
159
+ }
160
+ /**
161
+ * バックオフ時間を計算します。
162
+ * @param {number} attempt 試行回数(1..)
163
+ * @returns {number} ミリ秒
164
+ */
165
+ backoffMs(attempt) {
166
+ const base = this.baseBackoff * Math.pow(2, attempt - 1);
167
+ const jitter = Math.floor(Math.random() * base * 0.3);
168
+ return base + jitter;
169
+ }
170
+ /**
171
+ * リファレンス更新は不要なため noop 実装です。
172
+ * @param {string} _ref ref 名
173
+ * @param {string} _commitSha コミット SHA
174
+ * @param {boolean} [_force]
175
+ * @returns {Promise<void>}
176
+ */
177
+ async updateRef(_ref, _commitSha, _force = false) {
178
+ // Not required when using commits API
179
+ }
180
+ }
181
+ exports.GitLabAdapter = GitLabAdapter;
182
+ exports.default = GitLabAdapter;
@@ -0,0 +1,5 @@
1
+ export { default as VirtualFS } from './virtualfs/virtualfs';
2
+ export { default as GitHubAdapter } from './git/githubAdapter';
3
+ export { default as GitLabAdapter } from './git/gitlabAdapter';
4
+ export { default } from './virtualfs/virtualfs';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,uBAAuB,CAAA;AAC5D,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAC9D,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAC9D,OAAO,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.default = exports.GitLabAdapter = exports.GitHubAdapter = exports.VirtualFS = void 0;
7
+ var virtualfs_1 = require("./virtualfs/virtualfs");
8
+ Object.defineProperty(exports, "VirtualFS", { enumerable: true, get: function () { return __importDefault(virtualfs_1).default; } });
9
+ var githubAdapter_1 = require("./git/githubAdapter");
10
+ Object.defineProperty(exports, "GitHubAdapter", { enumerable: true, get: function () { return __importDefault(githubAdapter_1).default; } });
11
+ var gitlabAdapter_1 = require("./git/gitlabAdapter");
12
+ Object.defineProperty(exports, "GitLabAdapter", { enumerable: true, get: function () { return __importDefault(gitlabAdapter_1).default; } });
13
+ var virtualfs_2 = require("./virtualfs/virtualfs");
14
+ Object.defineProperty(exports, "default", { enumerable: true, get: function () { return __importDefault(virtualfs_2).default; } });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=github.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"github.spec.d.ts","sourceRoot":"","sources":["../../../test/e2e/github.spec.ts"],"names":[],"mappings":""}
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const test_1 = require("@playwright/test");
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ // runtime: load built library from dist (compiled tests live under dist/test/e2e)
10
+ // @ts-ignore: require of built JS module for e2e runtime
11
+ const { GitHubAdapter } = require('../../index.js');
12
+ const cfgPath = path_1.default.join(__dirname, 'github.config.json');
13
+ (0, test_1.test)('github adapter smoke (skips if config missing)', async () => {
14
+ if (!fs_1.default.existsSync(cfgPath)) {
15
+ test_1.test.skip(true, 'github.config.json not found — fill test/e2e/github.config.json based on example');
16
+ return;
17
+ }
18
+ const raw = fs_1.default.readFileSync(cfgPath, 'utf8');
19
+ const cfg = JSON.parse(raw);
20
+ if (!cfg.token || cfg.token.startsWith('ghp_replace')) {
21
+ test_1.test.skip(true, 'github token not set in config');
22
+ return;
23
+ }
24
+ const adapter = new GitHubAdapter({ owner: cfg.owner, repo: cfg.repo, token: cfg.token });
25
+ // simple smoke: create a small blob and then delete it via a simulated tree/commit flow
26
+ const tmpPath = `e2e-test-${Date.now()}.txt`;
27
+ const changes = [{ type: 'create', path: tmpPath, content: 'playwright test content' }];
28
+ const blobMap = await adapter.createBlobs(changes, 2);
29
+ (0, test_1.expect)(blobMap[tmpPath]).toBeTruthy();
30
+ // create tree and commit require a parent SHA — attempt to get default branch ref
31
+ // fetch default branch ref
32
+ const res = await fetch(`https://api.github.com/repos/${cfg.owner}/${cfg.repo}`, { headers: { Authorization: `token ${cfg.token}`, Accept: 'application/vnd.github+json' } });
33
+ (0, test_1.expect)(res.ok).toBeTruthy();
34
+ const repoInfo = await res.json();
35
+ const defaultBranch = repoInfo.default_branch || 'main';
36
+ // get commit sha of default branch
37
+ const refRes = await fetch(`https://api.github.com/repos/${cfg.owner}/${cfg.repo}/git/ref/heads/${defaultBranch}`, { headers: { Authorization: `token ${cfg.token}`, Accept: 'application/vnd.github+json' } });
38
+ (0, test_1.expect)(refRes.ok).toBeTruthy();
39
+ const refInfo = await refRes.json();
40
+ const parentSha = refInfo.object.sha;
41
+ const treeSha = await adapter.createTree([{ ...changes[0], blobSha: blobMap[tmpPath] }], undefined);
42
+ (0, test_1.expect)(treeSha).toBeTruthy();
43
+ const commitSha = await adapter.createCommit('playwright e2e test', parentSha, treeSha);
44
+ (0, test_1.expect)(commitSha).toBeTruthy();
45
+ // cleanup: attempt to update ref back to parentSha (do not force)
46
+ await adapter.updateRef(`heads/${defaultBranch}`, commitSha, false);
47
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=gitlab.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gitlab.spec.d.ts","sourceRoot":"","sources":["../../../test/e2e/gitlab.spec.ts"],"names":[],"mappings":""}
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const test_1 = require("@playwright/test");
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ // runtime: load built library from dist (compiled tests live under dist/test/e2e)
10
+ // @ts-ignore: require of built JS module for e2e runtime
11
+ const { GitLabAdapter } = require('../../index.js');
12
+ const cfgPath = path_1.default.join(__dirname, 'gitlab.config.json');
13
+ (0, test_1.test)('gitlab adapter smoke (skips if config missing)', async () => {
14
+ if (!fs_1.default.existsSync(cfgPath)) {
15
+ test_1.test.skip(true, 'gitlab.config.json not found — fill test/e2e/gitlab.config.json based on example');
16
+ return;
17
+ }
18
+ const raw = fs_1.default.readFileSync(cfgPath, 'utf8');
19
+ const cfg = JSON.parse(raw);
20
+ if (!cfg.token || cfg.token.startsWith('glpat_replace')) {
21
+ test_1.test.skip(true, 'gitlab token not set in config');
22
+ return;
23
+ }
24
+ const adapter = new GitLabAdapter({ projectId: cfg.projectId, token: cfg.token, host: cfg.host });
25
+ const tmpPath = `e2e-gitlab-${Date.now()}.txt`;
26
+ const changes = [{ type: 'create', path: tmpPath, content: 'gitlab e2e test content' }];
27
+ // create commit that adds the file
28
+ const branch = 'main'; // default; could be parameterized
29
+ const commit = await adapter.createCommitWithActions(branch, 'playwright gitlab e2e create', changes);
30
+ (0, test_1.expect)(commit).toBeTruthy();
31
+ // cleanup: delete the file in another commit
32
+ const del = await adapter.createCommitWithActions(branch, 'playwright gitlab e2e delete', [{ type: 'delete', path: tmpPath }]);
33
+ (0, test_1.expect)(del).toBeTruthy();
34
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=virtualfs.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"virtualfs.spec.d.ts","sourceRoot":"","sources":["../../../test/e2e/virtualfs.spec.ts"],"names":[],"mappings":""}