underpost 3.1.2 → 3.2.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/.env.example +0 -2
- package/.github/workflows/ghpkg.ci.yml +4 -4
- package/.github/workflows/npmpkg.ci.yml +38 -7
- package/.github/workflows/pwa-microservices-template-page.cd.yml +3 -4
- package/.github/workflows/pwa-microservices-template-test.ci.yml +3 -3
- package/.github/workflows/release.cd.yml +4 -4
- package/CHANGELOG.md +365 -1
- package/CLI-HELP.md +55 -3
- package/README.md +7 -3
- package/bin/build.js +18 -12
- package/bin/deploy.js +205 -225
- package/bin/file.js +3 -0
- package/conf.js +4 -10
- package/jsdoc.json +1 -1
- package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +1 -1
- package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +1 -1
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
- package/manifests/deployment/dd-test-development/deployment.yaml +72 -50
- package/manifests/deployment/dd-test-development/proxy.yaml +13 -4
- package/manifests/deployment/playwright/deployment.yaml +1 -1
- package/nodemon.json +1 -1
- package/package.json +21 -14
- package/scripts/ports-ls.sh +2 -0
- package/scripts/rhel-grpc-setup.sh +56 -0
- package/src/api/file/file.ref.json +18 -0
- package/src/api/user/user.service.js +8 -7
- package/src/cli/cluster.js +7 -7
- package/src/cli/db.js +76 -242
- package/src/cli/deploy.js +104 -65
- package/src/cli/env.js +1 -0
- package/src/cli/fs.js +2 -1
- package/src/cli/index.js +50 -1
- package/src/cli/kubectl.js +211 -0
- package/src/cli/release.js +284 -0
- package/src/cli/repository.js +328 -112
- package/src/cli/run.js +283 -69
- package/src/cli/test.js +3 -3
- package/src/client/Default.index.js +3 -4
- package/src/client/components/core/Alert.js +2 -2
- package/src/client/components/core/AppStore.js +69 -0
- package/src/client/components/core/CalendarCore.js +2 -2
- package/src/client/components/core/Docs.js +9 -2
- package/src/client/components/core/DropDown.js +129 -17
- package/src/client/components/core/Keyboard.js +2 -2
- package/src/client/components/core/LogIn.js +2 -2
- package/src/client/components/core/LogOut.js +2 -2
- package/src/client/components/core/Modal.js +0 -1
- package/src/client/components/core/Panel.js +0 -1
- package/src/client/components/core/PanelForm.js +19 -19
- package/src/client/components/core/RichText.js +1 -2
- package/src/client/components/core/SocketIo.js +82 -29
- package/src/client/components/core/SocketIoHandler.js +75 -0
- package/src/client/components/core/Stream.js +143 -95
- package/src/client/components/core/Webhook.js +40 -7
- package/src/client/components/default/AppStoreDefault.js +5 -0
- package/src/client/components/default/LogInDefault.js +3 -3
- package/src/client/components/default/LogOutDefault.js +2 -2
- package/src/client/components/default/MenuDefault.js +5 -5
- package/src/client/components/default/SocketIoDefault.js +3 -51
- package/src/client/services/core/core.service.js +20 -8
- package/src/client/services/user/user.management.js +2 -2
- package/src/client/ssr/body/404.js +15 -11
- package/src/client/ssr/body/500.js +15 -11
- package/src/client/ssr/body/SwaggerDarkMode.js +285 -0
- package/src/client/ssr/offline/NoNetworkConnection.js +11 -10
- package/src/client/ssr/pages/Test.js +11 -10
- package/src/index.js +24 -1
- package/src/runtime/express/Express.js +26 -9
- package/src/runtime/lampp/Dockerfile +9 -2
- package/src/runtime/lampp/Lampp.js +4 -3
- package/src/runtime/wp/Dockerfile +64 -0
- package/src/runtime/wp/Wp.js +497 -0
- package/src/server/auth.js +30 -6
- package/src/server/backup.js +19 -1
- package/src/server/client-build-docs.js +51 -110
- package/src/server/client-build.js +55 -64
- package/src/server/client-formatted.js +109 -57
- package/src/server/conf.js +19 -15
- package/src/server/ipfs-client.js +24 -1
- package/src/server/peer.js +8 -0
- package/src/server/runtime.js +25 -1
- package/src/server/start.js +21 -8
- package/src/ws/IoInterface.js +1 -10
- package/src/ws/IoServer.js +14 -33
- package/src/ws/core/channels/core.ws.chat.js +65 -20
- package/src/ws/core/channels/core.ws.mailer.js +113 -32
- package/src/ws/core/channels/core.ws.stream.js +90 -31
- package/src/ws/core/core.ws.connection.js +12 -33
- package/src/ws/core/core.ws.emit.js +10 -26
- package/src/ws/core/core.ws.server.js +25 -58
- package/src/ws/default/channels/default.ws.main.js +53 -12
- package/src/ws/default/default.ws.connection.js +26 -13
- package/src/ws/default/default.ws.server.js +30 -12
- package/src/client/components/default/ElementsDefault.js +0 -38
- package/src/ws/core/management/core.ws.chat.js +0 -8
- package/src/ws/core/management/core.ws.mailer.js +0 -16
- package/src/ws/core/management/core.ws.stream.js +0 -8
- package/src/ws/default/management/default.ws.main.js +0 -8
package/src/cli/repository.js
CHANGED
|
@@ -97,6 +97,7 @@ class UnderpostRepository {
|
|
|
97
97
|
* @param {boolean} [options.cached=false] - If true, commits only staged changes.
|
|
98
98
|
* @param {number} [options.log=0] - If greater than 0, shows the last N commits with diffs.
|
|
99
99
|
* @param {boolean} [options.lastMsg=0] - If greater than 0, copies or show the last last single n commit message to clipboard.
|
|
100
|
+
* @param {boolean} [options.unpush=false] - If true with --log, automatically detects unpushed commits ahead of remote and uses that count.
|
|
100
101
|
* @param {string} [options.deployId=''] - An optional deploy ID to include in the commit message.
|
|
101
102
|
* @param {string} [options.hashes=''] - If provided with diff option, shows the diff between two hashes.
|
|
102
103
|
* @param {string} [options.extension=''] - If provided with diff option, filters the diff by this file extension.
|
|
@@ -127,11 +128,51 @@ class UnderpostRepository {
|
|
|
127
128
|
changelogBuild: false,
|
|
128
129
|
changelogMinVersion: '',
|
|
129
130
|
changelogNoHash: false,
|
|
131
|
+
unpush: false,
|
|
130
132
|
b: false,
|
|
133
|
+
p: undefined,
|
|
134
|
+
bc: '',
|
|
135
|
+
isRemoteRepo: '',
|
|
131
136
|
},
|
|
132
137
|
) {
|
|
133
138
|
if (!repoPath) repoPath = '.';
|
|
134
139
|
|
|
140
|
+
if (options.isRemoteRepo) {
|
|
141
|
+
const accessible = Underpost.repo.isRemoteRepo(options.isRemoteRepo);
|
|
142
|
+
console.log(accessible);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (options.bc) {
|
|
147
|
+
console.log(
|
|
148
|
+
shellExec(`cd ${repoPath} && git for-each-ref --contains ${options.bc} --format='%(refname:short)'`, {
|
|
149
|
+
stdout: true,
|
|
150
|
+
silent: true,
|
|
151
|
+
disableLog: true,
|
|
152
|
+
}).trim(),
|
|
153
|
+
);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (options.p !== undefined) {
|
|
158
|
+
const branch =
|
|
159
|
+
options.p === true
|
|
160
|
+
? shellExec(`cd ${repoPath} && git branch --show-current`, {
|
|
161
|
+
stdout: true,
|
|
162
|
+
silent: true,
|
|
163
|
+
disableLog: true,
|
|
164
|
+
}).trim()
|
|
165
|
+
: options.p;
|
|
166
|
+
console.log(
|
|
167
|
+
shellExec(`cd ${repoPath} && git --no-pager reflog show refs/heads/${branch}`, {
|
|
168
|
+
stdout: true,
|
|
169
|
+
silent: true,
|
|
170
|
+
disableLog: true,
|
|
171
|
+
}).trim(),
|
|
172
|
+
);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
|
|
135
176
|
if (options.b) {
|
|
136
177
|
const currentBranch = shellExec(`cd ${repoPath} && git branch --show-current`, {
|
|
137
178
|
stdout: true,
|
|
@@ -144,8 +185,6 @@ class UnderpostRepository {
|
|
|
144
185
|
}
|
|
145
186
|
|
|
146
187
|
if (options.changelog !== undefined || options.changelogBuild) {
|
|
147
|
-
const ciIntegrationPrefix = 'ci(package-pwa-microservices-';
|
|
148
|
-
const ciIntegrationVersionPrefix = 'New release v:';
|
|
149
188
|
const releaseMatch = 'New release v:';
|
|
150
189
|
// Helper: parse [<tag>] commits into grouped sections
|
|
151
190
|
const buildSectionChangelog = (commits) => {
|
|
@@ -273,39 +312,13 @@ class UnderpostRepository {
|
|
|
273
312
|
fs.writeFileSync(changelogPath, `# Changelog\n\n${changelog}`);
|
|
274
313
|
logger.info('CHANGELOG.md built at', changelogPath);
|
|
275
314
|
} else {
|
|
276
|
-
// --changelog [latest-n]: print changelog of last N commits
|
|
315
|
+
// --changelog [latest-n]: print changelog of last N commits (default: 1)
|
|
277
316
|
const hasExplicitCount =
|
|
278
317
|
options.changelog !== undefined && options.changelog !== true && !isNaN(parseInt(options.changelog));
|
|
279
|
-
const scanLimit = hasExplicitCount ? parseInt(options.changelog) :
|
|
318
|
+
const scanLimit = hasExplicitCount ? parseInt(options.changelog) : 1;
|
|
280
319
|
const allCommits = fetchHistory(scanLimit);
|
|
281
320
|
|
|
282
|
-
|
|
283
|
-
if (!hasExplicitCount) {
|
|
284
|
-
let boundaryIndex = -1;
|
|
285
|
-
|
|
286
|
-
// New boundary: deploy hash stored in config via `underpost config set LAST_CI_DEPLOY_HASH`
|
|
287
|
-
const lastDeployHash = shellExec('underpost config get LAST_CI_DEPLOY_HASH --plain', {
|
|
288
|
-
stdout: true,
|
|
289
|
-
silent: true,
|
|
290
|
-
disableLog: true,
|
|
291
|
-
}).trim();
|
|
292
|
-
if (lastDeployHash && lastDeployHash !== 'undefined' && lastDeployHash !== 'null') {
|
|
293
|
-
boundaryIndex = allCommits.findIndex((c) => c.fullHash === lastDeployHash || c.hash === lastDeployHash);
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
// Fallback: old CI integration commit boundary
|
|
297
|
-
if (boundaryIndex < 0) {
|
|
298
|
-
boundaryIndex = allCommits.findIndex(
|
|
299
|
-
(c) => c.message.startsWith(ciIntegrationPrefix) && !c.message.match(ciIntegrationVersionPrefix),
|
|
300
|
-
);
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
commits = boundaryIndex >= 0 ? allCommits.slice(0, boundaryIndex) : allCommits;
|
|
304
|
-
} else {
|
|
305
|
-
commits = allCommits;
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
const sections = buildVersionSections(commits);
|
|
321
|
+
const sections = buildVersionSections(allCommits);
|
|
309
322
|
let changelog = renderSections(sections);
|
|
310
323
|
console.log(changelog || `No changelog entries found.\n`);
|
|
311
324
|
}
|
|
@@ -332,17 +345,25 @@ class UnderpostRepository {
|
|
|
332
345
|
else console.log('Diff command:', _diffCmd);
|
|
333
346
|
return;
|
|
334
347
|
}
|
|
335
|
-
if (options.log) {
|
|
336
|
-
|
|
348
|
+
if (options.log || options.unpush) {
|
|
349
|
+
if (options.unpush) {
|
|
350
|
+
const { count, hasUnpushed } = Underpost.repo.getUnpushedCount(repoPath);
|
|
351
|
+
if (!hasUnpushed) {
|
|
352
|
+
logger.warn('No unpushed commits found');
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
options.log = count;
|
|
356
|
+
}
|
|
357
|
+
const history = Underpost.repo.getHistory(options.log, repoPath);
|
|
337
358
|
const chainCmd = history
|
|
338
359
|
.reverse()
|
|
339
|
-
.map((commitData, i) => `${i === 0 ? '' : ' && '}git ${diffCmd} ${commitData.hash}`)
|
|
360
|
+
.map((commitData, i) => `${i === 0 ? '' : ' && '}git -C ${repoPath} ${diffCmd} ${commitData.hash}`)
|
|
340
361
|
.join('');
|
|
341
362
|
if (history[0]) {
|
|
342
363
|
let index = history.length;
|
|
343
364
|
for (const commit of history) {
|
|
344
365
|
console.log(
|
|
345
|
-
shellExec(`git show -s --format=%ci ${commit.hash}`, {
|
|
366
|
+
shellExec(`cd ${repoPath} && git show -s --format=%ci ${commit.hash}`, {
|
|
346
367
|
stdout: true,
|
|
347
368
|
silent: true,
|
|
348
369
|
disableLog: true,
|
|
@@ -351,7 +372,7 @@ class UnderpostRepository {
|
|
|
351
372
|
console.log(`${index}`.magenta, commit.hash.yellow, commit.message);
|
|
352
373
|
index--;
|
|
353
374
|
console.log(
|
|
354
|
-
shellExec(`git show --name-status --pretty="" ${commit.hash}`, {
|
|
375
|
+
shellExec(`cd ${repoPath} && git show --name-status --pretty="" ${commit.hash}`, {
|
|
355
376
|
stdout: true,
|
|
356
377
|
silent: true,
|
|
357
378
|
disableLog: true,
|
|
@@ -575,14 +596,9 @@ class UnderpostRepository {
|
|
|
575
596
|
logger.info(`Created repository directory: ${repo.path}`);
|
|
576
597
|
}
|
|
577
598
|
|
|
578
|
-
// Initialize git repository
|
|
579
|
-
shellExec(`cd ${repo.path} && git init`, { disableLog: false });
|
|
580
|
-
logger.info(`Initialized git repository in: ${repo.path}`);
|
|
581
|
-
|
|
582
|
-
// Add remote origin
|
|
583
599
|
const remoteUrl = `https://${token ? `${token}@` : ''}github.com/${username}/${repo.name}.git`;
|
|
584
|
-
|
|
585
|
-
logger.info(`
|
|
600
|
+
UnderpostRepository.API.initLocalRepo({ path: repo.path, origin: remoteUrl });
|
|
601
|
+
logger.info(`Initialized git repository with remote: ${repo.name}`);
|
|
586
602
|
}
|
|
587
603
|
}
|
|
588
604
|
return resolve(true);
|
|
@@ -601,7 +617,8 @@ class UnderpostRepository {
|
|
|
601
617
|
fs.readFileSync(`${underpostRoot}/.dockerignore`, 'utf8'),
|
|
602
618
|
'utf8',
|
|
603
619
|
);
|
|
604
|
-
|
|
620
|
+
UnderpostRepository.API.initLocalRepo({ path: destFolder });
|
|
621
|
+
shellExec(`cd ${destFolder} && git add . && git commit -m "Base template implementation"`);
|
|
605
622
|
}
|
|
606
623
|
shellExec(`cd ${destFolder} && npm run build`);
|
|
607
624
|
shellExec(`cd ${destFolder} && npm run dev`);
|
|
@@ -624,6 +641,9 @@ class UnderpostRepository {
|
|
|
624
641
|
* @param {object} [options] - Build options.
|
|
625
642
|
* @param {boolean} [options.syncEnvPort=false] - If true, syncs environment port assignments across all deploy IDs.
|
|
626
643
|
* @param {boolean} [options.singleReplica=false] - If true, builds single replica folders instead of full client.
|
|
644
|
+
* @param {boolean} [options.buildZip=false] - If true, creates zip files of the builds.
|
|
645
|
+
* @param {boolean} [options.liteBuild=false] - If true, skips full build (default is full build).
|
|
646
|
+
* @param {boolean} [options.iconsBuild=false] - If true, builds icons.
|
|
627
647
|
* @returns {Promise<boolean>} A promise that resolves when the build is complete.
|
|
628
648
|
* @memberof UnderpostRepository
|
|
629
649
|
*/
|
|
@@ -635,69 +655,14 @@ class UnderpostRepository {
|
|
|
635
655
|
options = {
|
|
636
656
|
syncEnvPort: false,
|
|
637
657
|
singleReplica: false,
|
|
658
|
+
buildZip: false,
|
|
659
|
+
liteBuild: false,
|
|
660
|
+
iconsBuild: false,
|
|
638
661
|
},
|
|
639
662
|
) {
|
|
640
663
|
return new Promise(async (resolve, reject) => {
|
|
641
664
|
try {
|
|
642
|
-
// Handle syncEnvPort
|
|
643
|
-
if (options.syncEnvPort) {
|
|
644
|
-
const dataDeploy = await getDataDeploy({ disableSyncEnvPort: true });
|
|
645
|
-
const dataEnv = [
|
|
646
|
-
{ env: 'production', port: 3000 },
|
|
647
|
-
{ env: 'development', port: 4000 },
|
|
648
|
-
{ env: 'test', port: 5000 },
|
|
649
|
-
];
|
|
650
|
-
let portOffset = 0;
|
|
651
|
-
const singleReplicaPortOffsets = {};
|
|
652
|
-
for (const deployIdObj of dataDeploy) {
|
|
653
|
-
const { deployId } = deployIdObj;
|
|
654
|
-
const baseConfPath = fs.existsSync(`./engine-private/replica/${deployId}`)
|
|
655
|
-
? `./engine-private/replica`
|
|
656
|
-
: `./engine-private/conf`;
|
|
657
|
-
|
|
658
|
-
const effectivePortOffset =
|
|
659
|
-
singleReplicaPortOffsets[deployId] !== undefined ? singleReplicaPortOffsets[deployId] : portOffset;
|
|
660
|
-
|
|
661
|
-
for (const envInstanceObj of dataEnv) {
|
|
662
|
-
const envPath = `${baseConfPath}/${deployId}/.env.${envInstanceObj.env}`;
|
|
663
|
-
const envObj = dotenv.parse(fs.readFileSync(envPath, 'utf8'));
|
|
664
|
-
envObj.PORT = `${envInstanceObj.port + effectivePortOffset}`;
|
|
665
|
-
writeEnv(envPath, envObj);
|
|
666
|
-
}
|
|
667
|
-
|
|
668
|
-
if (singleReplicaPortOffsets[deployId] !== undefined) continue;
|
|
669
|
-
|
|
670
|
-
const serverConf = loadReplicas(
|
|
671
|
-
deployId,
|
|
672
|
-
loadConfServerJson(`${baseConfPath}/${deployId}/conf.server.json`),
|
|
673
|
-
);
|
|
674
|
-
for (const host of Object.keys(serverConf)) {
|
|
675
|
-
let deferredSingleReplicaSlots = [];
|
|
676
|
-
for (const path of Object.keys(serverConf[host])) {
|
|
677
|
-
if (serverConf[host][path].singleReplica && serverConf[host][path].replicas) {
|
|
678
|
-
deferredSingleReplicaSlots.push({
|
|
679
|
-
replicas: serverConf[host][path].replicas,
|
|
680
|
-
peer: !!serverConf[host][path].peer,
|
|
681
|
-
});
|
|
682
|
-
continue;
|
|
683
|
-
}
|
|
684
|
-
portOffset++;
|
|
685
|
-
if (serverConf[host][path].peer) portOffset++;
|
|
686
|
-
}
|
|
687
|
-
for (const slot of deferredSingleReplicaSlots) {
|
|
688
|
-
for (const replica of slot.replicas) {
|
|
689
|
-
const replicaDeployId = buildReplicaId({ deployId, replica });
|
|
690
|
-
singleReplicaPortOffsets[replicaDeployId] = portOffset;
|
|
691
|
-
portOffset++;
|
|
692
|
-
if (slot.peer) portOffset++;
|
|
693
|
-
}
|
|
694
|
-
}
|
|
695
|
-
}
|
|
696
|
-
}
|
|
697
|
-
return resolve(true);
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
// Handle singleReplica operation
|
|
665
|
+
// Handle singleReplica operation (must run before syncEnvPort to ensure replica dirs exist)
|
|
701
666
|
if (options.singleReplica) {
|
|
702
667
|
const replicaPath = path;
|
|
703
668
|
if (!deployId || !host || !replicaPath) {
|
|
@@ -763,6 +728,71 @@ class UnderpostRepository {
|
|
|
763
728
|
}
|
|
764
729
|
}
|
|
765
730
|
}
|
|
731
|
+
if (!options.syncEnvPort) return resolve(true);
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
// Handle syncEnvPort operation
|
|
735
|
+
if (options.syncEnvPort) {
|
|
736
|
+
const dataDeploy = await getDataDeploy({ disableSyncEnvPort: true });
|
|
737
|
+
const dataEnv = [
|
|
738
|
+
{ env: 'production', port: 3000 },
|
|
739
|
+
{ env: 'development', port: 4000 },
|
|
740
|
+
{ env: 'test', port: 5000 },
|
|
741
|
+
];
|
|
742
|
+
let portOffset = 0;
|
|
743
|
+
const singleReplicaPortOffsets = {};
|
|
744
|
+
for (const deployIdObj of dataDeploy) {
|
|
745
|
+
const { deployId } = deployIdObj;
|
|
746
|
+
const baseConfPath = fs.existsSync(`./engine-private/replica/${deployId}`)
|
|
747
|
+
? `./engine-private/replica`
|
|
748
|
+
: `./engine-private/conf`;
|
|
749
|
+
|
|
750
|
+
const effectivePortOffset =
|
|
751
|
+
singleReplicaPortOffsets[deployId] !== undefined ? singleReplicaPortOffsets[deployId] : portOffset;
|
|
752
|
+
|
|
753
|
+
let skipDeploy = false;
|
|
754
|
+
for (const envInstanceObj of dataEnv) {
|
|
755
|
+
const envPath = `${baseConfPath}/${deployId}/.env.${envInstanceObj.env}`;
|
|
756
|
+
if (!fs.existsSync(envPath)) {
|
|
757
|
+
logger.warn(`Skipping ${deployId}: ${envPath} not found`);
|
|
758
|
+
skipDeploy = true;
|
|
759
|
+
break;
|
|
760
|
+
}
|
|
761
|
+
const envObj = dotenv.parse(fs.readFileSync(envPath, 'utf8'));
|
|
762
|
+
envObj.PORT = `${envInstanceObj.port + effectivePortOffset}`;
|
|
763
|
+
writeEnv(envPath, envObj);
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
if (skipDeploy) continue;
|
|
767
|
+
if (singleReplicaPortOffsets[deployId] !== undefined) continue;
|
|
768
|
+
|
|
769
|
+
const serverConf = loadReplicas(
|
|
770
|
+
deployId,
|
|
771
|
+
loadConfServerJson(`${baseConfPath}/${deployId}/conf.server.json`),
|
|
772
|
+
);
|
|
773
|
+
for (const host of Object.keys(serverConf)) {
|
|
774
|
+
let deferredSingleReplicaSlots = [];
|
|
775
|
+
for (const path of Object.keys(serverConf[host])) {
|
|
776
|
+
if (serverConf[host][path].singleReplica && serverConf[host][path].replicas) {
|
|
777
|
+
deferredSingleReplicaSlots.push({
|
|
778
|
+
replicas: serverConf[host][path].replicas,
|
|
779
|
+
peer: !!serverConf[host][path].peer,
|
|
780
|
+
});
|
|
781
|
+
continue;
|
|
782
|
+
}
|
|
783
|
+
portOffset++;
|
|
784
|
+
if (serverConf[host][path].peer) portOffset++;
|
|
785
|
+
}
|
|
786
|
+
for (const slot of deferredSingleReplicaSlots) {
|
|
787
|
+
for (const replica of slot.replicas) {
|
|
788
|
+
const replicaDeployId = buildReplicaId({ deployId, replica });
|
|
789
|
+
singleReplicaPortOffsets[replicaDeployId] = portOffset;
|
|
790
|
+
portOffset++;
|
|
791
|
+
if (slot.peer) portOffset++;
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
}
|
|
766
796
|
return resolve(true);
|
|
767
797
|
}
|
|
768
798
|
|
|
@@ -784,8 +814,6 @@ class UnderpostRepository {
|
|
|
784
814
|
if (argHost.length && argPath.length && (!argHost.includes(host) || !argPath.includes(path))) {
|
|
785
815
|
delete serverConf[host][path];
|
|
786
816
|
} else {
|
|
787
|
-
serverConf[host][path].liteBuild = false;
|
|
788
|
-
serverConf[host][path].minifyBuild = process.env.NODE_ENV === 'production' ? true : false;
|
|
789
817
|
if (serverConf[host][path].singleReplica && serverConf[host][path].replicas) {
|
|
790
818
|
singleReplicaHosts.push({ host, path });
|
|
791
819
|
deployIdSingleReplicas = deployIdSingleReplicas.concat(
|
|
@@ -797,10 +825,11 @@ class UnderpostRepository {
|
|
|
797
825
|
}
|
|
798
826
|
}
|
|
799
827
|
}
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
828
|
+
await buildClient({
|
|
829
|
+
buildZip: options.buildZip || false,
|
|
830
|
+
fullBuild: options.liteBuild ? false : true,
|
|
831
|
+
iconsBuild: options.iconsBuild || false,
|
|
832
|
+
});
|
|
804
833
|
for (const replicaDeployId of deployIdSingleReplicas) await Underpost.repo.client(replicaDeployId);
|
|
805
834
|
|
|
806
835
|
return resolve(true);
|
|
@@ -891,8 +920,8 @@ Prevent build private config repo.`,
|
|
|
891
920
|
* @returns {Array<{hash: string, message: string, files: string}>} An array of commit objects with hash, message, and files.
|
|
892
921
|
* @memberof UnderpostRepository
|
|
893
922
|
*/
|
|
894
|
-
getHistory(sinceCommit = 1) {
|
|
895
|
-
return shellExec(`git log -1 --pretty=format:"%h %s" -n ${sinceCommit}`, {
|
|
923
|
+
getHistory(sinceCommit = 1, repoPath = '.') {
|
|
924
|
+
return shellExec(`cd ${repoPath} && git log -1 --pretty=format:"%h %s" -n ${sinceCommit}`, {
|
|
896
925
|
stdout: true,
|
|
897
926
|
silent: true,
|
|
898
927
|
disableLog: true,
|
|
@@ -907,7 +936,7 @@ Prevent build private config repo.`,
|
|
|
907
936
|
})
|
|
908
937
|
.filter((line) => line.hash)
|
|
909
938
|
.map((line) => {
|
|
910
|
-
line.files = shellExec(`git show --name-status --pretty="" ${line.hash}`, {
|
|
939
|
+
line.files = shellExec(`cd ${repoPath} && git show --name-status --pretty="" ${line.hash}`, {
|
|
911
940
|
stdout: true,
|
|
912
941
|
silent: true,
|
|
913
942
|
disableLog: true,
|
|
@@ -1207,6 +1236,193 @@ Prevent build private config repo.`,
|
|
|
1207
1236
|
}
|
|
1208
1237
|
logger.info('Dispatched workflow', `${repo} -> ${workflowFile}`, inputs.job ? `(job: ${inputs.job})` : '');
|
|
1209
1238
|
},
|
|
1239
|
+
|
|
1240
|
+
/**
|
|
1241
|
+
* Returns metadata about unpushed commits in a git repository.
|
|
1242
|
+
* Fetches from origin, then counts commits ahead of the remote branch.
|
|
1243
|
+
* @param {string} [repoPath='.'] - Path to the git repository.
|
|
1244
|
+
* @param {number} [fallback=1] - Value to return as `count` when no unpushed commits are detected.
|
|
1245
|
+
* @returns {{ count: number, branch: string, hasUnpushed: boolean }} Unpush metadata.
|
|
1246
|
+
* @memberof UnderpostRepository
|
|
1247
|
+
*/
|
|
1248
|
+
/**
|
|
1249
|
+
* Checks whether a remote Git repository URL is reachable.
|
|
1250
|
+
* Uses `git ls-remote` with `|| true` so the process always exits 0.
|
|
1251
|
+
* Injects `GITHUB_TOKEN` into GitHub HTTPS URLs when available.
|
|
1252
|
+
* @param {string} url - Full HTTPS clone URL to test (e.g. "https://github.com/org/repo.git").
|
|
1253
|
+
* @returns {boolean} `true` when the remote responded with at least one ref hash.
|
|
1254
|
+
* @memberof UnderpostRepository
|
|
1255
|
+
*/
|
|
1256
|
+
resolveAuthUrl(url) {
|
|
1257
|
+
if (!url) return url;
|
|
1258
|
+
// Normalize short form "owner/repo" → full GitHub HTTPS URL
|
|
1259
|
+
let normalized = url;
|
|
1260
|
+
if (!url.startsWith('http://') && !url.startsWith('https://') && !url.startsWith('git@')) {
|
|
1261
|
+
normalized = `https://github.com/${url}`;
|
|
1262
|
+
}
|
|
1263
|
+
if (process.env.GITHUB_TOKEN && normalized.startsWith('https://github.com/')) {
|
|
1264
|
+
return normalized.replace(
|
|
1265
|
+
'https://github.com/',
|
|
1266
|
+
`https://x-access-token:${process.env.GITHUB_TOKEN}@github.com/`,
|
|
1267
|
+
);
|
|
1268
|
+
}
|
|
1269
|
+
return normalized;
|
|
1270
|
+
},
|
|
1271
|
+
|
|
1272
|
+
isRemoteRepo(url) {
|
|
1273
|
+
if (!url) return false;
|
|
1274
|
+
const authUrl = Underpost.repo.resolveAuthUrl(url);
|
|
1275
|
+
// GIT_TERMINAL_PROMPT=0 prevents git from hanging on credential prompts inside containers.
|
|
1276
|
+
const raw = shellExec(`GIT_TERMINAL_PROMPT=0 git ls-remote "${authUrl}" HEAD 2>&1 || true`, {
|
|
1277
|
+
stdout: true,
|
|
1278
|
+
silent: true,
|
|
1279
|
+
disableLog: true,
|
|
1280
|
+
});
|
|
1281
|
+
logger.info('isRemoteRepo', { url, raw: (raw || '').slice(0, 120) });
|
|
1282
|
+
return typeof raw === 'string' && /^[0-9a-f]{40}\t/m.test(raw);
|
|
1283
|
+
},
|
|
1284
|
+
|
|
1285
|
+
getUnpushedCount(repoPath = '.', fallback = 1) {
|
|
1286
|
+
const branch = shellExec(`cd ${repoPath} && git branch --show-current`, {
|
|
1287
|
+
stdout: true,
|
|
1288
|
+
silent: true,
|
|
1289
|
+
disableLog: true,
|
|
1290
|
+
}).trim();
|
|
1291
|
+
shellExec(`cd ${repoPath} && git fetch origin 2>/dev/null`, { silent: true, disableLog: true });
|
|
1292
|
+
const raw = shellExec(`cd ${repoPath} && git rev-list --count origin/${branch}..HEAD 2>/dev/null`, {
|
|
1293
|
+
stdout: true,
|
|
1294
|
+
silent: true,
|
|
1295
|
+
disableLog: true,
|
|
1296
|
+
}).trim();
|
|
1297
|
+
const count = parseInt(raw);
|
|
1298
|
+
const hasUnpushed = !isNaN(count) && count > 0;
|
|
1299
|
+
return { count: hasUnpushed ? count : fallback, branch, hasUnpushed };
|
|
1300
|
+
},
|
|
1301
|
+
|
|
1302
|
+
/**
|
|
1303
|
+
* Sanitizes a markdown changelog string into a compact message format.
|
|
1304
|
+
* Strips date headers, converts section tags to `[tag]` prefixes, removes bullet markers and special characters.
|
|
1305
|
+
* @param {string} message - The raw markdown changelog output.
|
|
1306
|
+
* @returns {string} The sanitized single-line or multi-line compact message.
|
|
1307
|
+
* @memberof UnderpostRepository
|
|
1308
|
+
*/
|
|
1309
|
+
sanitizeChangelogMessage(message) {
|
|
1310
|
+
if (!message) return '';
|
|
1311
|
+
return message
|
|
1312
|
+
.replace(/^##\s+\d{4}-\d{2}-\d{2}\s*/gm, '')
|
|
1313
|
+
.replace(/^###\s+(\S+)\s*/gm, '[$1] ')
|
|
1314
|
+
.replace(/^- /gm, '')
|
|
1315
|
+
.replaceAll('"', '')
|
|
1316
|
+
.replaceAll('`', '')
|
|
1317
|
+
.split('\n')
|
|
1318
|
+
.map((l) => l.trim())
|
|
1319
|
+
.filter(Boolean)
|
|
1320
|
+
.join('\n')
|
|
1321
|
+
.trim()
|
|
1322
|
+
.replaceAll('] - ', '] ');
|
|
1323
|
+
},
|
|
1324
|
+
|
|
1325
|
+
/**
|
|
1326
|
+
* Manages a cron-backup Git repository: clone, pull, commit, or push.
|
|
1327
|
+
* Resolves the repository path as `../<repoName>` relative to the CWD.
|
|
1328
|
+
* Requires the `GITHUB_USERNAME` environment variable to be set.
|
|
1329
|
+
* @param {object} params
|
|
1330
|
+
* @param {string} params.repoName - Repository name (e.g. `engine-cyberia-cron-backups`).
|
|
1331
|
+
* @param {'clone'|'pull'|'commit'|'push'} params.operation - Git operation to perform.
|
|
1332
|
+
* @param {string} [params.message=''] - Commit message (used by the `commit` operation).
|
|
1333
|
+
* @param {boolean} [params.forceClone=false] - Remove existing clone before re-cloning.
|
|
1334
|
+
* @returns {boolean} `true` on success, `false` if GITHUB_USERNAME is unset or on error.
|
|
1335
|
+
* @memberof UnderpostRepository
|
|
1336
|
+
*/
|
|
1337
|
+
/**
|
|
1338
|
+
* Initializes a git repository at the given path and configures user identity
|
|
1339
|
+
* from environment variables (`GITHUB_USERNAME` / `GITHUB_EMAIL`).
|
|
1340
|
+
* Safe to call on an already-initialized repo — only runs `git init` when
|
|
1341
|
+
* `.git` is absent and always ensures user.name / user.email are set.
|
|
1342
|
+
* @param {object} opts
|
|
1343
|
+
* @param {string} opts.path - Absolute or relative path to the repository.
|
|
1344
|
+
* @param {string} [opts.origin] - If provided, sets or updates git remote `origin`.
|
|
1345
|
+
* @memberof UnderpostRepository
|
|
1346
|
+
*/
|
|
1347
|
+
initLocalRepo({ path: repoPath, origin }) {
|
|
1348
|
+
const gitUsername = process.env.GITHUB_USERNAME || 'underpostnet';
|
|
1349
|
+
const gitEmail = process.env.GITHUB_EMAIL || `development@underpost.net`;
|
|
1350
|
+
|
|
1351
|
+
if (!fs.existsSync(`${repoPath}/.git`)) {
|
|
1352
|
+
shellExec(`cd "${repoPath}" && git init`);
|
|
1353
|
+
}
|
|
1354
|
+
shellExec(`cd "${repoPath}" && git config user.name '${gitUsername}'`);
|
|
1355
|
+
shellExec(`cd "${repoPath}" && git config user.email '${gitEmail}'`);
|
|
1356
|
+
|
|
1357
|
+
if (origin) {
|
|
1358
|
+
const currentRemote = shellExec(`cd "${repoPath}" && git remote get-url origin 2>/dev/null || true`, {
|
|
1359
|
+
stdout: true,
|
|
1360
|
+
silent: true,
|
|
1361
|
+
}).trim();
|
|
1362
|
+
if (!currentRemote) {
|
|
1363
|
+
shellExec(`cd "${repoPath}" && git remote add origin "${origin}"`);
|
|
1364
|
+
} else if (currentRemote !== origin) {
|
|
1365
|
+
shellExec(`cd "${repoPath}" && git remote set-url origin "${origin}"`);
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
},
|
|
1369
|
+
|
|
1370
|
+
manageBackupRepo({ repoName, operation, message = '', forceClone = false }) {
|
|
1371
|
+
try {
|
|
1372
|
+
const username = process.env.GITHUB_USERNAME;
|
|
1373
|
+
if (!username) {
|
|
1374
|
+
logger.error('GITHUB_USERNAME environment variable not set');
|
|
1375
|
+
return false;
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
const repoPath = `../${repoName}`;
|
|
1379
|
+
|
|
1380
|
+
switch (operation) {
|
|
1381
|
+
case 'clone':
|
|
1382
|
+
if (forceClone && fs.existsSync(repoPath)) {
|
|
1383
|
+
logger.info(`Force clone: removing existing repository: ${repoName}`);
|
|
1384
|
+
fs.removeSync(repoPath);
|
|
1385
|
+
}
|
|
1386
|
+
if (!fs.existsSync(repoPath)) {
|
|
1387
|
+
shellExec(`cd .. && underpost clone ${username}/${repoName}`);
|
|
1388
|
+
logger.info(`Cloned repository: ${repoName}`);
|
|
1389
|
+
}
|
|
1390
|
+
break;
|
|
1391
|
+
|
|
1392
|
+
case 'pull':
|
|
1393
|
+
if (fs.existsSync(repoPath)) {
|
|
1394
|
+
shellExec(`cd ${repoPath} && git checkout . && git clean -f -d`);
|
|
1395
|
+
shellExec(`cd ${repoPath} && underpost pull . ${username}/${repoName}`, { silent: true });
|
|
1396
|
+
logger.info(`Pulled repository: ${repoName}`);
|
|
1397
|
+
}
|
|
1398
|
+
break;
|
|
1399
|
+
|
|
1400
|
+
case 'commit':
|
|
1401
|
+
if (fs.existsSync(repoPath)) {
|
|
1402
|
+
shellExec(`cd ${repoPath} && git add .`);
|
|
1403
|
+
shellExec(`underpost cmt ${repoPath} backup '' '${message}'`);
|
|
1404
|
+
logger.info(`Committed to repository: ${repoName}`, { message });
|
|
1405
|
+
}
|
|
1406
|
+
break;
|
|
1407
|
+
|
|
1408
|
+
case 'push':
|
|
1409
|
+
if (fs.existsSync(repoPath)) {
|
|
1410
|
+
shellExec(`cd ${repoPath} && underpost push . ${username}/${repoName}`, { silent: true });
|
|
1411
|
+
logger.info(`Pushed repository: ${repoName}`);
|
|
1412
|
+
}
|
|
1413
|
+
break;
|
|
1414
|
+
|
|
1415
|
+
default:
|
|
1416
|
+
logger.warn(`Unknown git operation: ${operation}`);
|
|
1417
|
+
return false;
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
return true;
|
|
1421
|
+
} catch (error) {
|
|
1422
|
+
logger.error(`Git operation failed`, { repoName, operation, error: error.message });
|
|
1423
|
+
return false;
|
|
1424
|
+
}
|
|
1425
|
+
},
|
|
1210
1426
|
};
|
|
1211
1427
|
}
|
|
1212
1428
|
|