underpost 3.2.12 → 3.2.14
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/.github/workflows/ghpkg.ci.yml +1 -0
- package/.github/workflows/npmpkg.ci.yml +9 -5
- package/CHANGELOG.md +58 -1
- package/CLI-HELP.md +906 -1130
- package/README.md +47 -41
- package/bin/build.js +88 -137
- package/bin/build.template.js +23 -179
- package/bin/deploy.js +4 -1
- package/bin/index.js +2 -2
- package/conf.js +11 -37
- package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +2 -2
- package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +1 -1
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
- package/package.json +9 -14
- package/src/cli/deploy.js +0 -228
- package/src/cli/image.js +58 -4
- package/src/cli/monitor.js +190 -6
- package/src/cli/release.js +5 -5
- package/src/cli/repository.js +80 -3
- package/src/cli/run.js +115 -69
- package/src/index.js +1 -1
- package/src/runtime/wp/Dockerfile +3 -3
- package/src/server/catalog-underpost.js +61 -0
- package/src/server/catalog.js +77 -0
- package/src/server/conf.js +334 -49
- package/src/server/start.js +7 -3
- package/test/deploy-monitor.test.js +188 -0
- package/manifests/deployment/dd-test-development/deployment.yaml +0 -256
- package/manifests/deployment/dd-test-development/proxy.yaml +0 -102
package/src/cli/monitor.js
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
etcHostFactory,
|
|
13
13
|
} from '../server/conf.js';
|
|
14
14
|
import { loggerFactory } from '../server/logger.js';
|
|
15
|
+
import { timer } from '../client/components/core/CommonJs.js';
|
|
15
16
|
import axios from 'axios';
|
|
16
17
|
import fs from 'fs-extra';
|
|
17
18
|
import { shellExec } from '../server/process.js';
|
|
@@ -93,13 +94,13 @@ class UnderpostMonitor {
|
|
|
93
94
|
}
|
|
94
95
|
|
|
95
96
|
if (options.readyDeployment) {
|
|
96
|
-
|
|
97
|
-
(async () => {
|
|
98
|
-
await Underpost.
|
|
97
|
+
await Promise.all(
|
|
98
|
+
options.versions.split(',').map(async (version) => {
|
|
99
|
+
await Underpost.monitor.monitorReadyRunner(deployId, env, version, [], options.namespace);
|
|
99
100
|
if (options.promote)
|
|
100
101
|
Underpost.deploy.switchTraffic(deployId, env, version, options.replicas, options.namespace, options);
|
|
101
|
-
})
|
|
102
|
-
|
|
102
|
+
}),
|
|
103
|
+
);
|
|
103
104
|
return;
|
|
104
105
|
}
|
|
105
106
|
|
|
@@ -227,7 +228,7 @@ class UnderpostMonitor {
|
|
|
227
228
|
monitorPodName = undefined;
|
|
228
229
|
}
|
|
229
230
|
const checkDeploymentReadyStatus = async () => {
|
|
230
|
-
const { ready, notReadyPods, readyPods } = await Underpost.
|
|
231
|
+
const { ready, notReadyPods, readyPods } = await Underpost.monitor.checkDeploymentReadyStatus(
|
|
231
232
|
deployId,
|
|
232
233
|
env,
|
|
233
234
|
traffic,
|
|
@@ -272,6 +273,189 @@ class UnderpostMonitor {
|
|
|
272
273
|
};
|
|
273
274
|
return new Promise((...args) => monitorCallBack(...args));
|
|
274
275
|
},
|
|
276
|
+
/**
|
|
277
|
+
* Checks the status of a deployment.
|
|
278
|
+
* @param {string} deployId - Deployment ID for which the status is being checked.
|
|
279
|
+
* @param {string} env - Environment for which the status is being checked.
|
|
280
|
+
* @param {string} traffic - Current traffic status for the deployment.
|
|
281
|
+
* @param {Array<string>} ignoresNames - List of pod names to ignore.
|
|
282
|
+
* @param {string} [namespace='default'] - Kubernetes namespace for the deployment.
|
|
283
|
+
* @returns {object} - Object containing the status of the deployment.
|
|
284
|
+
* @memberof UnderpostMonitor
|
|
285
|
+
*/
|
|
286
|
+
async checkDeploymentReadyStatus(deployId, env, traffic, ignoresNames = [], namespace = 'default') {
|
|
287
|
+
const pods = Underpost.kubectl.get(`${deployId}-${env}-${traffic}`, 'pods', namespace);
|
|
288
|
+
const readyPods = [];
|
|
289
|
+
const notReadyPods = [];
|
|
290
|
+
|
|
291
|
+
// Readiness signal: the pod's Kubernetes `Ready` condition driven by the
|
|
292
|
+
// container's readinessProbe (TCP socket, HTTP get, or exec). Set by kubelet
|
|
293
|
+
// when the probe passes. A failed or crashing runtime never becomes Ready —
|
|
294
|
+
// kubelet surfaces CrashLoopBackOff and this gate stays closed.
|
|
295
|
+
for (const pod of pods) {
|
|
296
|
+
const { NAME } = pod;
|
|
297
|
+
if (ignoresNames && ignoresNames.find((t) => NAME.trim().toLowerCase().match(t.trim().toLowerCase()))) continue;
|
|
298
|
+
|
|
299
|
+
let podJson = null;
|
|
300
|
+
try {
|
|
301
|
+
// Pod may not exist yet (between deployment apply and pod
|
|
302
|
+
// scheduling). silentOnError lets the monitor loop continue
|
|
303
|
+
// instead of aborting on the transient NotFound exit.
|
|
304
|
+
const raw = shellExec(`sudo kubectl get pod ${NAME} -n ${namespace} -o json`, {
|
|
305
|
+
silent: true,
|
|
306
|
+
disableLog: true,
|
|
307
|
+
stdout: true,
|
|
308
|
+
silentOnError: true,
|
|
309
|
+
});
|
|
310
|
+
podJson = raw ? JSON.parse(raw) : null;
|
|
311
|
+
} catch (_) {
|
|
312
|
+
podJson = null;
|
|
313
|
+
}
|
|
314
|
+
const conditions = podJson?.status?.conditions || [];
|
|
315
|
+
const readyCondition = conditions.find((c) => c.type === 'Ready');
|
|
316
|
+
const k8sReady = readyCondition?.status === 'True';
|
|
317
|
+
|
|
318
|
+
pod.out = JSON.stringify({ k8sReady, condition: readyCondition ?? null });
|
|
319
|
+
|
|
320
|
+
if (k8sReady) readyPods.push(pod);
|
|
321
|
+
else notReadyPods.push(pod);
|
|
322
|
+
}
|
|
323
|
+
const consideredCount = readyPods.length + notReadyPods.length;
|
|
324
|
+
return {
|
|
325
|
+
ready: consideredCount > 0 && notReadyPods.length === 0,
|
|
326
|
+
notReadyPods,
|
|
327
|
+
readyPods,
|
|
328
|
+
};
|
|
329
|
+
},
|
|
330
|
+
/**
|
|
331
|
+
* Monitors the ready status of a deployment.
|
|
332
|
+
*
|
|
333
|
+
* Ready signal:
|
|
334
|
+
* The orchestrator gate is the Kubernetes pod Ready condition. When the
|
|
335
|
+
* container's `readinessProbe` succeeds, kubelet flips
|
|
336
|
+
* `status.conditions[Ready]` to True and `checkDeploymentReadyStatus`
|
|
337
|
+
* returns the pod in `readyPods`. This is the only required signal — see
|
|
338
|
+
* `src/client/public/nexodev/docs/references/Deploy custom instance to K8S.md`.
|
|
339
|
+
*
|
|
340
|
+
* Container-status:
|
|
341
|
+
* `underpost config get container-status` is read from each pod for both
|
|
342
|
+
* the display column and as a second ready gate alongside the K8S Ready
|
|
343
|
+
* condition. Both must be satisfied before the monitor exits:
|
|
344
|
+
* 1. K8S readinessProbe (TCP socket) — ensures the port is bound.
|
|
345
|
+
* 2. container-status == `<deploy>-<env>-running-deployment` — ensures
|
|
346
|
+
* the application has completed its own startup sequence.
|
|
347
|
+
* Early-abort on `error` container-status remains in effect: a failing
|
|
348
|
+
* runtime keeps its pod alive (not Ready) with `container-status=error`,
|
|
349
|
+
* so this `exec`-read surfaces the failure and the monitor aborts —
|
|
350
|
+
* failing the CD runner instead of waiting out the full timeout.
|
|
351
|
+
*
|
|
352
|
+
* @param {string} deployId - Deployment ID for which the ready status is being monitored.
|
|
353
|
+
* @param {string} env - Environment for which the ready status is being monitored.
|
|
354
|
+
* @param {string} targetTraffic - Target traffic status for the deployment.
|
|
355
|
+
* @param {Array<string>} ignorePods - List of pod names to ignore.
|
|
356
|
+
* @param {string} [namespace='default'] - Kubernetes namespace for the deployment.
|
|
357
|
+
* @returns {object} - Object containing the ready status of the deployment.
|
|
358
|
+
* @memberof UnderpostMonitor
|
|
359
|
+
*/
|
|
360
|
+
async monitorReadyRunner(deployId, env, targetTraffic, ignorePods = [], namespace = 'default') {
|
|
361
|
+
const delayMs = 1000;
|
|
362
|
+
const maxIterations = 3000;
|
|
363
|
+
const deploymentId = `${deployId}-${env}-${targetTraffic}`;
|
|
364
|
+
const expectedContainerStatus = `${deployId}-${env}-running-deployment`;
|
|
365
|
+
const tag = `[${deploymentId}]`;
|
|
366
|
+
const containerStatusDefault = 'waiting for status';
|
|
367
|
+
|
|
368
|
+
logger.info('Deployment init', { deployId, env, targetTraffic, namespace });
|
|
369
|
+
|
|
370
|
+
const podStatusCache = new Map();
|
|
371
|
+
const advancedPods = new Set();
|
|
372
|
+
|
|
373
|
+
const readContainerStatus = (podName) => {
|
|
374
|
+
try {
|
|
375
|
+
const raw = shellExec(
|
|
376
|
+
`sudo kubectl exec ${podName} -n ${namespace} -- sh -c 'underpost config get container-status --plain'`,
|
|
377
|
+
{ silent: true, disableLog: true, stdout: true, silentOnError: true },
|
|
378
|
+
);
|
|
379
|
+
const val = raw ? raw.toString().trim() : '';
|
|
380
|
+
return val && val !== 'undefined' ? val : containerStatusDefault;
|
|
381
|
+
} catch (_) {
|
|
382
|
+
// exec failed (e.g. pod not yet running) — preserve last known value
|
|
383
|
+
return podStatusCache.get(podName) || containerStatusDefault;
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
|
|
387
|
+
for (let i = 0; i < maxIterations; i++) {
|
|
388
|
+
const result = await Underpost.monitor.checkDeploymentReadyStatus(
|
|
389
|
+
deployId,
|
|
390
|
+
env,
|
|
391
|
+
targetTraffic,
|
|
392
|
+
ignorePods,
|
|
393
|
+
namespace,
|
|
394
|
+
);
|
|
395
|
+
|
|
396
|
+
const allPods = [...result.readyPods, ...result.notReadyPods];
|
|
397
|
+
|
|
398
|
+
for (const pod of allPods) {
|
|
399
|
+
if (!pod?.NAME) continue;
|
|
400
|
+
const podStatus = (pod.STATUS || '').toLowerCase().trim();
|
|
401
|
+
if (
|
|
402
|
+
['error', 'crashloopbackoff', 'oomkilled', 'imagepullbackoff', 'errimagepull'].find((s) =>
|
|
403
|
+
podStatus.match(s),
|
|
404
|
+
)
|
|
405
|
+
)
|
|
406
|
+
throw new Error(`Pod ${pod.NAME} has error pod status: ${pod.STATUS}`);
|
|
407
|
+
const status = readContainerStatus(pod.NAME);
|
|
408
|
+
if (status === 'error') throw new Error(`Pod ${pod.NAME} has error container-status`);
|
|
409
|
+
if (advancedPods.has(pod.NAME) && status === containerStatusDefault)
|
|
410
|
+
throw new Error(`Pod ${pod.NAME} container-status regressed to default — pod likely restarted`);
|
|
411
|
+
if (status !== containerStatusDefault) advancedPods.add(pod.NAME);
|
|
412
|
+
podStatusCache.set(pod.NAME, status);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
const allPodsK8sReady = allPods.length > 0 && result.notReadyPods.length === 0;
|
|
416
|
+
|
|
417
|
+
const allPodsStatusReady =
|
|
418
|
+
allPods.length > 0 && allPods.every((pod) => podStatusCache.get(pod.NAME) === expectedContainerStatus);
|
|
419
|
+
|
|
420
|
+
// Print snapshot for every pod — annotate when container-status hasn't caught
|
|
421
|
+
// up to the K8S Ready condition yet.
|
|
422
|
+
for (const pod of allPods) {
|
|
423
|
+
const status = podStatusCache.get(pod.NAME) || containerStatusDefault;
|
|
424
|
+
const podStatus = pod.STATUS || 'Unknown';
|
|
425
|
+
const statusMatchesExpected = status === expectedContainerStatus;
|
|
426
|
+
const statusDisplay = statusMatchesExpected ? status : `${status} (pending)`;
|
|
427
|
+
|
|
428
|
+
console.log(
|
|
429
|
+
'Target pod:',
|
|
430
|
+
pod.NAME[pod.NAME.includes('green') ? 'bgGreen' : 'bgBlue'].bold.black,
|
|
431
|
+
'| Pod status:',
|
|
432
|
+
podStatus.bold.yellow,
|
|
433
|
+
'| Runtime status:',
|
|
434
|
+
statusDisplay.bold.cyan,
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Both K8S readinessProbe AND container-status must be satisfied before
|
|
439
|
+
// declaring the deployment ready. The TCP probe ensures the port is bound;
|
|
440
|
+
// container-status == running-deployment ensures the application has
|
|
441
|
+
// completed its own startup sequence so traffic is not switched prematurely.
|
|
442
|
+
if (allPodsK8sReady && allPodsStatusReady) {
|
|
443
|
+
logger.info(`${tag} | All pods Ready (K8S readinessProbe satisfied)`);
|
|
444
|
+
return result;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
await timer(delayMs);
|
|
448
|
+
|
|
449
|
+
if ((i + 1) % 10 === 0) {
|
|
450
|
+
logger.info(`${tag} | In progress... iteration ${i + 1}`);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
logger.error(`${tag} | Deployment timeout after ${maxIterations} iterations`);
|
|
455
|
+
throw new Error(
|
|
456
|
+
`monitorReadyRunner timeout: ${deploymentId} did not become Ready within ${maxIterations}*${delayMs}ms`,
|
|
457
|
+
);
|
|
458
|
+
},
|
|
275
459
|
};
|
|
276
460
|
}
|
|
277
461
|
|
package/src/cli/release.js
CHANGED
|
@@ -268,7 +268,7 @@ async function buildAndTestTemplate() {
|
|
|
268
268
|
killDevServers();
|
|
269
269
|
Underpost.repo.clean({ paths: ['/home/dd/engine', '/home/dd/engine/engine-private '] });
|
|
270
270
|
shellExec(`node bin pull . ${process.env.GITHUB_USERNAME}/engine`);
|
|
271
|
-
shellExec(`npm run
|
|
271
|
+
shellExec(`npm run build:template`);
|
|
272
272
|
shellExec(`node bin run shared-dir ${TEMPLATE_PATH}`);
|
|
273
273
|
|
|
274
274
|
const dhcpHostIp = Dns.getLocalIPv4Address();
|
|
@@ -392,7 +392,7 @@ class UnderpostRelease {
|
|
|
392
392
|
shellExec(`node bin/build dd`);
|
|
393
393
|
shellExec(`node bin deploy --build-manifest --sync --info-router --replicas 1 dd production`);
|
|
394
394
|
shellExec(`node bin deploy --build-manifest --sync --info-router --replicas 1 dd development`);
|
|
395
|
-
shellExec(`node bin
|
|
395
|
+
shellExec(`node bin new --default-conf --conf-workflow-id template`);
|
|
396
396
|
shellExec(`sudo rm -rf ./engine-private/conf/dd-default`);
|
|
397
397
|
shellExec(`node bin new --deploy-id dd-default`);
|
|
398
398
|
console.log(fs.existsSync(`./engine-private/conf/dd-default`));
|
|
@@ -460,7 +460,7 @@ class UnderpostRelease {
|
|
|
460
460
|
* Runs the pwa-microservices-template update and push flow locally.
|
|
461
461
|
*
|
|
462
462
|
* Always removes and re-clones pwa-microservices-template, then:
|
|
463
|
-
* 1. Runs
|
|
463
|
+
* 1. Runs build:template (node bin/build.template) to sync engine sources.
|
|
464
464
|
* 2. Installs dependencies and builds the template.
|
|
465
465
|
* 3. Commits and pushes to the pwa-microservices-template remote repository.
|
|
466
466
|
*
|
|
@@ -488,7 +488,7 @@ class UnderpostRelease {
|
|
|
488
488
|
shellExec(`sudo rm -rf /home/dd/pwa-microservices-template`);
|
|
489
489
|
shellExec(`node engine/bin clone ${githubOrg}/pwa-microservices-template`);
|
|
490
490
|
shellCd('/home/dd/engine');
|
|
491
|
-
shellExec(`npm run
|
|
491
|
+
shellExec(`npm run build:template`);
|
|
492
492
|
shellExec(`cd ../pwa-microservices-template && npm install && npm run build`);
|
|
493
493
|
shellCd('/home/dd/pwa-microservices-template');
|
|
494
494
|
shellExec(`git add .`);
|
|
@@ -520,7 +520,7 @@ class UnderpostRelease {
|
|
|
520
520
|
shellExec(
|
|
521
521
|
`node bin secret underpost --create-from-file /home/dd/engine/engine-private/conf/dd-cron/.env.production`,
|
|
522
522
|
);
|
|
523
|
-
shellExec(`node bin/build dd conf`);
|
|
523
|
+
shellExec(`node bin/build dd --conf`);
|
|
524
524
|
shellExec(`git add . && cd ./engine-private && git add .`);
|
|
525
525
|
shellExec(`node bin cmt . ci package-pwa-microservices-template 'New release v:${version}'`);
|
|
526
526
|
shellExec(`node bin cmt ./engine-private ci package-pwa-microservices-template`);
|
package/src/cli/repository.js
CHANGED
|
@@ -556,7 +556,7 @@ class UnderpostRepository {
|
|
|
556
556
|
// Handle sync-conf operation
|
|
557
557
|
if (options.syncConf) {
|
|
558
558
|
logger.info(`Syncing configuration for deploy ID: ${deployId}`);
|
|
559
|
-
shellExec(`node bin/build ${deployId} conf`);
|
|
559
|
+
shellExec(`node bin/build ${deployId} --conf`);
|
|
560
560
|
logger.info('Configuration synced successfully');
|
|
561
561
|
return resolve(true);
|
|
562
562
|
}
|
|
@@ -852,6 +852,7 @@ class UnderpostRepository {
|
|
|
852
852
|
}
|
|
853
853
|
}
|
|
854
854
|
await buildClient({
|
|
855
|
+
deployId: resolvedDeployId,
|
|
855
856
|
buildZip: options.buildZip || false,
|
|
856
857
|
split: options.split || '',
|
|
857
858
|
fullBuild: options.liteBuild ? false : true,
|
|
@@ -862,7 +863,12 @@ class UnderpostRepository {
|
|
|
862
863
|
logger.warn('Skip replica client build: replica folder not found', { replicaDeployId });
|
|
863
864
|
continue;
|
|
864
865
|
}
|
|
865
|
-
await Underpost.repo.client(replicaDeployId
|
|
866
|
+
await Underpost.repo.client(replicaDeployId, '', '', '', {
|
|
867
|
+
buildZip: options.buildZip || false,
|
|
868
|
+
split: options.split || '',
|
|
869
|
+
liteBuild: options.liteBuild || false,
|
|
870
|
+
iconsBuild: options.iconsBuild || false,
|
|
871
|
+
});
|
|
866
872
|
}
|
|
867
873
|
|
|
868
874
|
return resolve(true);
|
|
@@ -937,7 +943,7 @@ Prevent build private config repo.`,
|
|
|
937
943
|
deployVersion: packageJsonDeploy.version,
|
|
938
944
|
};
|
|
939
945
|
}
|
|
940
|
-
shellExec(`node bin/build ${deployId} conf`);
|
|
946
|
+
shellExec(`node bin/build ${deployId} --conf`);
|
|
941
947
|
return {
|
|
942
948
|
validVersion: true,
|
|
943
949
|
engineVersion: packageJsonEngine.version,
|
|
@@ -1653,6 +1659,77 @@ Prevent build private config repo.`,
|
|
|
1653
1659
|
}
|
|
1654
1660
|
return fallback;
|
|
1655
1661
|
},
|
|
1662
|
+
|
|
1663
|
+
/**
|
|
1664
|
+
* Performs a shallow sparse Git checkout of a single subdirectory from any
|
|
1665
|
+
* GitHub repository into a local target directory.
|
|
1666
|
+
*
|
|
1667
|
+
* Uses `--depth 1 --no-checkout` + `git sparse-checkout` so only the
|
|
1668
|
+
* requested path is fetched — no full clone of the remote repo.
|
|
1669
|
+
* Skips the clone entirely when `<targetDir>/<subPath>` already exists on
|
|
1670
|
+
* disk (idempotent).
|
|
1671
|
+
*
|
|
1672
|
+
* Requires `GITHUB_TOKEN` to be set in the environment for authenticated
|
|
1673
|
+
* access to private repositories.
|
|
1674
|
+
*
|
|
1675
|
+
* @param {string} subPath - The subdirectory path within the remote repo to
|
|
1676
|
+
* check out (e.g. `'conf/dd-prototype'`, `'src/api/payments'`).
|
|
1677
|
+
* @param {object} [options]
|
|
1678
|
+
* @param {string} [options.repoOwner='underpostnet'] - GitHub organisation or
|
|
1679
|
+
* user that owns the repository.
|
|
1680
|
+
* @param {string} [options.repoName='engine-private'] - Name of the
|
|
1681
|
+
* repository on GitHub.
|
|
1682
|
+
* @param {string} [options.targetDir='./engine-private'] - Local directory
|
|
1683
|
+
* where the repo will be cloned.
|
|
1684
|
+
* @returns {boolean} `true` when the checkout was performed, `false` when it
|
|
1685
|
+
* was skipped because the target path already existed.
|
|
1686
|
+
* @memberof UnderpostRepository
|
|
1687
|
+
*/
|
|
1688
|
+
sparseCheckoutDirectory(
|
|
1689
|
+
subPath,
|
|
1690
|
+
options = { repoOwner: 'underpostnet', repoName: 'engine-private', targetDir: './engine-private' },
|
|
1691
|
+
) {
|
|
1692
|
+
const { repoOwner = 'underpostnet', repoName = 'engine-private', targetDir = './engine-private' } = options;
|
|
1693
|
+
const localPath = `${targetDir}/${subPath}`;
|
|
1694
|
+
if (fs.existsSync(localPath)) {
|
|
1695
|
+
logger.info('[sparseCheckoutDirectory] path already present, skipping', localPath);
|
|
1696
|
+
return false;
|
|
1697
|
+
}
|
|
1698
|
+
const authUrl = `https://${process.env.GITHUB_TOKEN}@github.com/${repoOwner}/${repoName}.git`;
|
|
1699
|
+
shellExec(`git clone --depth 1 --no-checkout ${authUrl} ${targetDir}`, { disableLog: true });
|
|
1700
|
+
shellExec(`cd ${targetDir} && git sparse-checkout set ${subPath} && git checkout`, { disableLog: true });
|
|
1701
|
+
logger.info('[sparseCheckoutDirectory] sparse checkout complete', localPath);
|
|
1702
|
+
return true;
|
|
1703
|
+
},
|
|
1704
|
+
|
|
1705
|
+
/**
|
|
1706
|
+
* Ensures a deploy's public source repo (e.g. `engine-prototype`) is present
|
|
1707
|
+
* next to the engine and reset to a pristine HEAD, so catalog `sourceMoves`
|
|
1708
|
+
* can (re)pull custom sources even after a previous build moved them out of
|
|
1709
|
+
* the source tree.
|
|
1710
|
+
*
|
|
1711
|
+
* Clones `../<repoName>` when missing; otherwise restores a clean checkout
|
|
1712
|
+
* (`git checkout .` brings back any moved-out tracked files) and pulls latest.
|
|
1713
|
+
* Mirrors the sibling-repo handling used by `syncPrivateConf`.
|
|
1714
|
+
*
|
|
1715
|
+
* @param {string} repoName - Public source repo name (e.g. `engine-prototype`).
|
|
1716
|
+
* @returns {boolean} `true` when the repo is available on disk.
|
|
1717
|
+
* @memberof UnderpostRepository
|
|
1718
|
+
*/
|
|
1719
|
+
pullSourceRepo(repoName) {
|
|
1720
|
+
const username = process.env.GITHUB_USERNAME;
|
|
1721
|
+
if (!username || !repoName) return false;
|
|
1722
|
+
const repoPath = `../${repoName}`;
|
|
1723
|
+
const gitUri = `${username}/${repoName}`;
|
|
1724
|
+
if (!fs.existsSync(repoPath)) {
|
|
1725
|
+
shellExec(`cd .. && underpost clone ${gitUri}`, { silent: true });
|
|
1726
|
+
} else {
|
|
1727
|
+
shellExec(`cd ${repoPath} && git checkout . && git clean -f -d && underpost pull . ${gitUri}`, {
|
|
1728
|
+
silent: true,
|
|
1729
|
+
});
|
|
1730
|
+
}
|
|
1731
|
+
return fs.existsSync(repoPath);
|
|
1732
|
+
},
|
|
1656
1733
|
};
|
|
1657
1734
|
}
|
|
1658
1735
|
|