Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 18b976abab | |||
| 9841953e8f | |||
| 7d8606daef | |||
| 9ef20a7667 | |||
| 8228d17a30 | |||
| 057d7926be | |||
| 72a10dcb4b | |||
| 5dbf81ca0f | |||
| 80f5e33f36 | |||
| 8301255dd6 | |||
| dbb2c8891b | |||
| 1fc25ff1d8 | |||
| fc34eb502d | |||
| d2febe2b18 |
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"featureCode": "manual",
|
"featureCode": "manual",
|
||||||
"syncDate": "2026-04-24T17:41:52.985Z",
|
"syncDate": "2026-04-24T23:45:06.978Z",
|
||||||
"filesSynced": 12
|
"filesSynced": 12
|
||||||
}
|
}
|
||||||
@@ -30,15 +30,29 @@ function buildSyncBranchName(featureCode) {
|
|||||||
|
|
||||||
function mapAppsScriptFileToRepoPath(file) {
|
function mapAppsScriptFileToRepoPath(file) {
|
||||||
if (!file || !file.name || !file.type) return null;
|
if (!file || !file.name || !file.type) return null;
|
||||||
|
|
||||||
if (file.name === 'appsscript' && file.type === 'JSON') {
|
if (file.name === 'appsscript' && file.type === 'JSON') {
|
||||||
return 'appsscript.json';
|
return 'appsscript.json';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const appFiles = ['Code', 'Index', 'styles', 'scripts'];
|
||||||
|
const docsFiles = ['Readme'];
|
||||||
|
|
||||||
|
let prefix = 'workflow/';
|
||||||
|
if (appFiles.includes(file.name)) {
|
||||||
|
prefix = 'app/';
|
||||||
|
} else if (docsFiles.includes(file.name)) {
|
||||||
|
prefix = 'docs/';
|
||||||
|
}
|
||||||
|
|
||||||
if (file.type === 'SERVER_JS') {
|
if (file.type === 'SERVER_JS') {
|
||||||
return 'src/' + file.name + '.gs';
|
return prefix + file.name + '.gs';
|
||||||
}
|
}
|
||||||
if (file.type === 'HTML') {
|
if (file.type === 'HTML') {
|
||||||
return 'src/' + file.name + '.html';
|
return prefix + file.name + '.html';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Logger.log('Unknown file type for mapping: ' + file.name + ' (' + file.type + ')');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,6 +252,47 @@ function buildSyncManifest(featureCode, mappedFiles) {
|
|||||||
return JSON.stringify(manifest, null, 2);
|
return JSON.stringify(manifest, null, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function checkGiteaPullRequestExists(baseBranch, headBranch) {
|
||||||
|
const config = getSyncConfig();
|
||||||
|
const path = '/repos/' + config.owner + '/' + config.repo + '/pulls?state=open';
|
||||||
|
const res = giteaSyncRequest('GET', path, null);
|
||||||
|
if (res.ok && res.data && Array.isArray(res.data)) {
|
||||||
|
const existing = res.data.find(function(pr) {
|
||||||
|
return pr.base && pr.head && pr.base.ref === baseBranch && pr.head.ref === headBranch;
|
||||||
|
});
|
||||||
|
if (existing) {
|
||||||
|
return { ok: true, existed: true, data: existing };
|
||||||
|
}
|
||||||
|
return { ok: true, existed: false };
|
||||||
|
}
|
||||||
|
return { ok: false, existed: false, message: res.message || 'Failed to check existing PRs' };
|
||||||
|
}
|
||||||
|
|
||||||
|
function createGiteaPullRequest(title, body, baseBranch, headBranch) {
|
||||||
|
const config = getSyncConfig();
|
||||||
|
|
||||||
|
const checkRes = checkGiteaPullRequestExists(baseBranch, headBranch);
|
||||||
|
if (checkRes.existed) {
|
||||||
|
return { ok: true, existed: true, data: checkRes.data, message: 'Pull request already exists' };
|
||||||
|
}
|
||||||
|
if (!checkRes.ok) {
|
||||||
|
return checkRes;
|
||||||
|
}
|
||||||
|
|
||||||
|
const path = '/repos/' + config.owner + '/' + config.repo + '/pulls';
|
||||||
|
const payload = {
|
||||||
|
title: title,
|
||||||
|
body: body,
|
||||||
|
base: baseBranch,
|
||||||
|
head: headBranch
|
||||||
|
};
|
||||||
|
const res = giteaSyncRequest('POST', path, payload);
|
||||||
|
if (res.ok) {
|
||||||
|
res.existed = false;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
function ensureGiteaBranch(baseBranch, newBranch) {
|
function ensureGiteaBranch(baseBranch, newBranch) {
|
||||||
const config = getSyncConfig();
|
const config = getSyncConfig();
|
||||||
const checkRes = giteaSyncRequest('GET', '/repos/' + config.owner + '/' + config.repo + '/branches/' + newBranch, null);
|
const checkRes = giteaSyncRequest('GET', '/repos/' + config.owner + '/' + config.repo + '/branches/' + newBranch, null);
|
||||||
@@ -303,18 +358,45 @@ function syncAppsScriptToGiteaBranch(featureCode) {
|
|||||||
results.errors++;
|
results.errors++;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("Sync Summary: " + results.created + " created, " + results.updated + " updated, " + results.errors + " errors.");
|
const prTitle = 'Sync Feature: ' + featureCode;
|
||||||
|
const prBody = 'Automated sync from Google Apps Script for feature: ' + featureCode + '.\n\nFiles synced:\n' + payload.files.map(function(f) { return '- ' + f.repoPath; }).join('\n');
|
||||||
|
const prRes = createGiteaPullRequest(prTitle, prBody, payload.baseBranch, payload.syncBranch);
|
||||||
|
|
||||||
return { ok: true, branch: payload.syncBranch, summary: results };
|
let prInfo = null;
|
||||||
|
if (prRes.ok) {
|
||||||
|
prInfo = {
|
||||||
|
url: prRes.data.html_url || '',
|
||||||
|
existed: prRes.existed,
|
||||||
|
number: prRes.data.number
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Sync Summary: " + results.created + " created, " + results.updated + " updated, " + results.errors + " errors.");
|
||||||
|
if (prInfo) {
|
||||||
|
console.log("Pull Request: " + prInfo.url + (prInfo.existed ? " (Existed)" : " (Created)"));
|
||||||
|
} else {
|
||||||
|
console.log("Pull Request Failed: " + prRes.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { ok: true, branch: payload.syncBranch, summary: results, pullRequest: prInfo };
|
||||||
}
|
}
|
||||||
|
|
||||||
function runManualSyncToGitea() {
|
function runManualSyncToGitea() {
|
||||||
const res = syncAppsScriptToGiteaBranch('manual');
|
const res = syncAppsScriptToGiteaBranch('manual');
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
console.log('Sync successful to branch: ' + res.branch);
|
console.log('=== Sync Summary ===');
|
||||||
console.log(JSON.stringify(res.summary, null, 2));
|
console.log('Branch Sync: ' + res.branch);
|
||||||
|
console.log('Files: ' + res.summary.created + ' created, ' + res.summary.updated + ' updated, ' + res.summary.errors + ' errors.');
|
||||||
|
console.log('Catatan: File baru akan menggunakan path app/, docs/, dan workflow/. File lama dengan folder src/ mungkin masih tertinggal di repositori (Gitea) dan harus dihapus secara manual.');
|
||||||
|
if (res.pullRequest) {
|
||||||
|
console.log('Pull Request Status: ' + (res.pullRequest.existed ? 'Existing' : 'Created'));
|
||||||
|
console.log('Pull Request URL: ' + res.pullRequest.url);
|
||||||
|
} else {
|
||||||
|
console.log('Pull Request Status: Not available');
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
console.error('Sync failed: ' + res.message);
|
console.error('Sync failed: ' + res.message);
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -472,9 +472,11 @@ function testGiteaPathMapping() {
|
|||||||
|
|
||||||
const testCases = [
|
const testCases = [
|
||||||
{ input: { name: 'appsscript', type: 'JSON' }, expected: 'appsscript.json' },
|
{ input: { name: 'appsscript', type: 'JSON' }, expected: 'appsscript.json' },
|
||||||
{ input: { name: 'Code', type: 'SERVER_JS' }, expected: 'src/Code.gs' },
|
{ input: { name: 'Code', type: 'SERVER_JS' }, expected: 'app/Code.gs' },
|
||||||
{ input: { name: 'Index', type: 'HTML' }, expected: 'src/Index.html' },
|
{ input: { name: 'Index', type: 'HTML' }, expected: 'app/Index.html' },
|
||||||
{ input: { name: 'styles', type: 'HTML' }, expected: 'src/styles.html' },
|
{ input: { name: 'styles', type: 'HTML' }, expected: 'app/styles.html' },
|
||||||
|
{ input: { name: 'Readme', type: 'SERVER_JS' }, expected: 'docs/Readme.gs' },
|
||||||
|
{ input: { name: 'WorkflowConfig', type: 'SERVER_JS' }, expected: 'workflow/WorkflowConfig.gs' },
|
||||||
{ input: { name: 'Unknown', type: 'OTHER' }, expected: null }
|
{ input: { name: 'Unknown', type: 'OTHER' }, expected: null }
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -554,7 +556,7 @@ function testPrepareGitSyncPayload() {
|
|||||||
{ name: 'Code', type: 'SERVER_JS', source: 'function test() {}' },
|
{ name: 'Code', type: 'SERVER_JS', source: 'function test() {}' },
|
||||||
{ name: 'Index', type: 'HTML', source: '<html></html>' },
|
{ name: 'Index', type: 'HTML', source: '<html></html>' },
|
||||||
{ name: 'InvalidFile', type: 'UNKNOWN', source: 'junk' },
|
{ name: 'InvalidFile', type: 'UNKNOWN', source: 'junk' },
|
||||||
{ name: 'MissingSource', type: 'SERVER_JS' }, // valid file type mapping, missing source resolves to empty string
|
{ name: 'MissingSource', type: 'SERVER_JS' },
|
||||||
null
|
null
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -566,13 +568,14 @@ function testPrepareGitSyncPayload() {
|
|||||||
|
|
||||||
assertEqual_('Valid files mapped correctly', payload.files.length, 4);
|
assertEqual_('Valid files mapped correctly', payload.files.length, 4);
|
||||||
assertEqual_('JSON mapped to appsscript.json', payload.files[0].repoPath, 'appsscript.json');
|
assertEqual_('JSON mapped to appsscript.json', payload.files[0].repoPath, 'appsscript.json');
|
||||||
assertEqual_('SERVER_JS mapped to src/Code.gs', payload.files[1].repoPath, 'src/Code.gs');
|
assertEqual_('SERVER_JS mapped to app/Code.gs', payload.files[1].repoPath, 'app/Code.gs');
|
||||||
assertEqual_('HTML mapped to src/Index.html', payload.files[2].repoPath, 'src/Index.html');
|
assertEqual_('HTML mapped to app/Index.html', payload.files[2].repoPath, 'app/Index.html');
|
||||||
assertEqual_('SERVER_JS missing source mapped to src/MissingSource.gs', payload.files[3].repoPath, 'src/MissingSource.gs');
|
assertEqual_('SERVER_JS missing source mapped to workflow/MissingSource.gs', payload.files[3].repoPath, 'workflow/MissingSource.gs');
|
||||||
|
|
||||||
console.log('--- Test Passed: Prepare Git Sync Payload ---');
|
console.log('--- Test Passed: Prepare Git Sync Payload ---');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function testGiteaSyncHelpers() {
|
function testGiteaSyncHelpers() {
|
||||||
console.log('--- Starting Test: Gitea Sync Helpers ---');
|
console.log('--- Starting Test: Gitea Sync Helpers ---');
|
||||||
|
|
||||||
@@ -612,7 +615,7 @@ function testSyncSummaryFormat() {
|
|||||||
updated: 1,
|
updated: 1,
|
||||||
errors: 0,
|
errors: 0,
|
||||||
details: [
|
details: [
|
||||||
{ file: "src/Code.gs", ok: true, message: "" },
|
{ file: "app/Code.gs", ok: true, message: "" },
|
||||||
{ file: "appsscript.json", ok: true, message: "" },
|
{ file: "appsscript.json", ok: true, message: "" },
|
||||||
{ file: "sync-manifest.json", ok: true, message: "" }
|
{ file: "sync-manifest.json", ok: true, message: "" }
|
||||||
]
|
]
|
||||||
@@ -623,8 +626,22 @@ function testSyncSummaryFormat() {
|
|||||||
assertTrue_('Summary has errors count', mockResults.errors === 0);
|
assertTrue_('Summary has errors count', mockResults.errors === 0);
|
||||||
assertEqual_('Summary details length is correct', mockResults.details.length, 3);
|
assertEqual_('Summary details length is correct', mockResults.details.length, 3);
|
||||||
|
|
||||||
|
const mockFinalResult = {
|
||||||
|
ok: true,
|
||||||
|
branch: 'sync/manual',
|
||||||
|
summary: mockResults,
|
||||||
|
pullRequest: { url: 'https://gitea.example.com/pr/1', existed: true }
|
||||||
|
};
|
||||||
|
|
||||||
|
assertTrue_('Final result has branch info', !!mockFinalResult.branch);
|
||||||
|
assertTrue_('Final result has pullRequest info', !!mockFinalResult.pullRequest);
|
||||||
|
assertTrue_('Final result pullRequest has url', !!mockFinalResult.pullRequest.url);
|
||||||
|
assertEqual_('Final result pullRequest existed flag is true', mockFinalResult.pullRequest.existed, true);
|
||||||
|
|
||||||
console.log('--- Test Passed: Sync Summary Format ---');
|
console.log('--- Test Passed: Sync Summary Format ---');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function testGetAppsScriptProjectContentErrorFormat() {
|
function testGetAppsScriptProjectContentErrorFormat() {
|
||||||
console.log('--- Starting Test: Get Apps Script Project Content Error Format ---');
|
console.log('--- Starting Test: Get Apps Script Project Content Error Format ---');
|
||||||
|
|
||||||
@@ -668,6 +685,50 @@ function testScriptIdFallback() {
|
|||||||
console.log('--- Test Passed: Script ID Fallback ---');
|
console.log('--- Test Passed: Script ID Fallback ---');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function testGiteaPullRequestPayload() {
|
||||||
|
console.log('--- Starting Test: Gitea Pull Request Payload ---');
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
title: 'Sync Feature: test-feat',
|
||||||
|
body: 'Auto sync',
|
||||||
|
base: 'main',
|
||||||
|
head: 'sync/test-feat'
|
||||||
|
};
|
||||||
|
|
||||||
|
assertEqual_('PR title is set', payload.title, 'Sync Feature: test-feat');
|
||||||
|
assertEqual_('PR base is main', payload.base, 'main');
|
||||||
|
assertEqual_('PR head is correct', payload.head, 'sync/test-feat');
|
||||||
|
|
||||||
|
console.log('--- Test Passed: Gitea Pull Request Payload ---');
|
||||||
|
}
|
||||||
|
|
||||||
|
function testCheckGiteaPullRequestExistsLogic() {
|
||||||
|
console.log('--- Starting Test: Check Gitea PR Exists Logic ---');
|
||||||
|
|
||||||
|
const mockPrs = [
|
||||||
|
{ base: { ref: 'main' }, head: { ref: 'sync/feat-1' } },
|
||||||
|
{ base: { ref: 'main' }, head: { ref: 'sync/feat-2' } }
|
||||||
|
];
|
||||||
|
|
||||||
|
const baseBranch = 'main';
|
||||||
|
const headBranch = 'sync/feat-2';
|
||||||
|
|
||||||
|
const existing = mockPrs.find(function(pr) {
|
||||||
|
return pr.base && pr.head && pr.base.ref === baseBranch && pr.head.ref === headBranch;
|
||||||
|
});
|
||||||
|
|
||||||
|
assertTrue_('Finds existing PR', !!existing);
|
||||||
|
assertEqual_('Matches correct head', existing.head.ref, 'sync/feat-2');
|
||||||
|
|
||||||
|
const notExisting = mockPrs.find(function(pr) {
|
||||||
|
return pr.base && pr.head && pr.base.ref === baseBranch && pr.head.ref === 'sync/feat-3';
|
||||||
|
});
|
||||||
|
|
||||||
|
assertTrue_('Does not find missing PR', !notExisting);
|
||||||
|
|
||||||
|
console.log('--- Test Passed: Check Gitea PR Exists Logic ---');
|
||||||
|
}
|
||||||
|
|
||||||
function testEmptyRepoErrorDetection() {
|
function testEmptyRepoErrorDetection() {
|
||||||
console.log('--- Starting Test: Empty Repo Error Detection ---');
|
console.log('--- Starting Test: Empty Repo Error Detection ---');
|
||||||
|
|
||||||
Reference in New Issue
Block a user