mirror of
https://gitee.com/pnoker/iot-dc3.git
synced 2026-10-31 18:21:58 +08:00
232 lines
7.1 KiB
JavaScript
Executable File
232 lines
7.1 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
|
/*
|
|
* Copyright 2016-present the IoT DC3 original author or authors.
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License as
|
|
* published by the Free Software Foundation, either version 3 of the
|
|
* License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
import { createInterface } from 'node:readline';
|
|
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
import { spawn } from 'node:child_process';
|
|
import { resolve } from 'node:path';
|
|
|
|
const repositoryRoot = resolve(import.meta.dirname, '..', '..');
|
|
const environmentFile = process.env.DC3_ENV_FILE ?? 'dc3/env/dev.env';
|
|
|
|
const services = {
|
|
auth: { module: 'dc3-center/dc3-center-auth', artifact: 'dc3-center-auth' },
|
|
gateway: { module: 'dc3-gateway', artifact: 'dc3-gateway' },
|
|
data: { module: 'dc3-center/dc3-center-data', artifact: 'dc3-center-data' },
|
|
manager: { module: 'dc3-center/dc3-center-manager', artifact: 'dc3-center-manager' },
|
|
agentic: { module: 'dc3-center/dc3-center-agentic', artifact: 'dc3-center-agentic' },
|
|
};
|
|
|
|
const serviceOrder = ['auth', 'gateway', 'data', 'manager', 'agentic'];
|
|
|
|
function printUsage() {
|
|
process.stderr.write(
|
|
`Usage: make dev | make dev-<service>\n\nServices: ${serviceOrder.join(', ')}\nEnvironment: DC3_ENV_FILE=path/to/env make dev-auth\n`,
|
|
);
|
|
}
|
|
|
|
function parseEnvironmentFile(filePath) {
|
|
const absolutePath = resolve(repositoryRoot, filePath);
|
|
if (!existsSync(absolutePath)) {
|
|
throw new Error(`Environment file not found: ${filePath}`);
|
|
}
|
|
|
|
const environment = {};
|
|
const lines = readFileSync(absolutePath, 'utf8').split(/\r?\n/);
|
|
for (const [index, line] of lines.entries()) {
|
|
const trimmed = line.trim();
|
|
if (!trimmed || trimmed.startsWith('#')) {
|
|
continue;
|
|
}
|
|
|
|
const assignment = trimmed.match(/^(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)=(.*)$/);
|
|
if (!assignment) {
|
|
throw new Error(`Invalid environment assignment at ${filePath}:${index + 1}`);
|
|
}
|
|
|
|
let value = assignment[2].trim();
|
|
if (
|
|
(value.startsWith('"') && value.endsWith('"')) ||
|
|
(value.startsWith("'") && value.endsWith("'"))
|
|
) {
|
|
value = value.slice(1, -1);
|
|
}
|
|
environment[assignment[1]] = value;
|
|
}
|
|
return environment;
|
|
}
|
|
|
|
function prefixOutput(stream, service, childStream) {
|
|
const reader = createInterface({ input: childStream });
|
|
reader.on('line', (line) => {
|
|
stream.write(`[${service}] ${line}\n`);
|
|
});
|
|
}
|
|
|
|
function stopChildren(children, signal) {
|
|
for (const child of children.values()) {
|
|
if (child.exitCode === null && !child.killed) {
|
|
child.kill(signal);
|
|
}
|
|
}
|
|
}
|
|
|
|
function findBootJar(service) {
|
|
const targetDirectory = resolve(repositoryRoot, services[service].module, 'target');
|
|
const artifact = services[service].artifact;
|
|
const jar = readdirSync(targetDirectory)
|
|
.filter(
|
|
(file) =>
|
|
(file === `${artifact}.jar` || file.startsWith(`${artifact}-`)) &&
|
|
file.endsWith('.jar') &&
|
|
!file.startsWith('original-'),
|
|
)
|
|
.sort((left, right) =>
|
|
left === `${artifact}.jar` ? -1 : right === `${artifact}.jar` ? 1 : left.localeCompare(right),
|
|
)[0];
|
|
if (!jar) {
|
|
throw new Error(`Spring Boot jar not found for ${service}: ${targetDirectory}`);
|
|
}
|
|
return resolve(targetDirectory, jar);
|
|
}
|
|
|
|
function buildServices(selectedServices, environment) {
|
|
const maven = process.env.MAVEN_CMD ?? (process.platform === 'win32' ? 'mvn.cmd' : 'mvn');
|
|
const modules = selectedServices.map((service) => services[service].module).join(',');
|
|
return new Promise((resolveBuild, rejectBuild) => {
|
|
const build = spawn(
|
|
maven,
|
|
[
|
|
'-s',
|
|
resolve(repositoryRoot, '.mvn/settings.xml'),
|
|
'-pl',
|
|
modules,
|
|
'-am',
|
|
'package',
|
|
'-DskipTests',
|
|
],
|
|
{
|
|
cwd: repositoryRoot,
|
|
env: environment,
|
|
stdio: ['inherit', 'pipe', 'pipe'],
|
|
},
|
|
);
|
|
prefixOutput(process.stdout, 'build', build.stdout);
|
|
prefixOutput(process.stderr, 'build', build.stderr);
|
|
build.on('error', rejectBuild);
|
|
build.on('close', (code) => {
|
|
if (code === 0) {
|
|
resolveBuild();
|
|
} else {
|
|
rejectBuild(new Error(`Maven package failed with exit code ${code ?? 'unknown'}`));
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
async function runServices(selectedServices) {
|
|
const environment = {
|
|
...parseEnvironmentFile(environmentFile),
|
|
...process.env,
|
|
};
|
|
const children = new Map();
|
|
const results = new Map();
|
|
let stopping = false;
|
|
let interrupted = false;
|
|
let failureCode = null;
|
|
|
|
const stop = (signal) => {
|
|
if (stopping) {
|
|
return;
|
|
}
|
|
stopping = true;
|
|
stopChildren(children, signal);
|
|
};
|
|
|
|
await buildServices(selectedServices, environment);
|
|
|
|
const completion = new Promise((resolveCompletion) => {
|
|
for (const service of selectedServices) {
|
|
const java = process.env.JAVA_CMD ?? (process.platform === 'win32' ? 'java.exe' : 'java');
|
|
const child = spawn(java, ['-jar', findBootJar(service)], {
|
|
cwd: repositoryRoot,
|
|
env: environment,
|
|
stdio: ['inherit', 'pipe', 'pipe'],
|
|
});
|
|
children.set(service, child);
|
|
prefixOutput(process.stdout, service, child.stdout);
|
|
prefixOutput(process.stderr, service, child.stderr);
|
|
child.on('error', (error) => {
|
|
process.stderr.write(`[${service}] ${error.message}\n`);
|
|
});
|
|
child.on('close', (code, signal) => {
|
|
results.set(service, { code, signal });
|
|
if (!stopping) {
|
|
if (code !== 0) {
|
|
failureCode = code !== null && code > 0 ? code : 1;
|
|
}
|
|
stop(signal ?? 'SIGTERM');
|
|
}
|
|
if (results.size === selectedServices.length) {
|
|
resolveCompletion();
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
const handleSignal = (signal) => {
|
|
interrupted = true;
|
|
stop(signal);
|
|
};
|
|
process.once('SIGINT', () => handleSignal('SIGINT'));
|
|
process.once('SIGTERM', () => handleSignal('SIGTERM'));
|
|
await completion;
|
|
|
|
if (interrupted) {
|
|
return 130;
|
|
}
|
|
return failureCode ?? 0;
|
|
}
|
|
|
|
async function main() {
|
|
const argumentsList = process.argv.slice(2);
|
|
const requestedService = argumentsList[0] ?? 'all';
|
|
if (argumentsList.includes('--help') || argumentsList.includes('-h')) {
|
|
printUsage();
|
|
return 0;
|
|
}
|
|
|
|
const selectedServices = requestedService === 'all' ? serviceOrder : [requestedService];
|
|
if (selectedServices.some((service) => !services[service])) {
|
|
printUsage();
|
|
return 1;
|
|
}
|
|
|
|
process.stdout.write(`Starting ${selectedServices.join(', ')} using ${environmentFile}\n`);
|
|
return runServices(selectedServices);
|
|
}
|
|
|
|
main()
|
|
.then((exitCode) => {
|
|
process.exitCode = exitCode;
|
|
})
|
|
.catch((error) => {
|
|
process.stderr.write(`${error.message}\n`);
|
|
process.exitCode = 1;
|
|
});
|