wgc
Version:
The official CLI tool to manage the GraphQL Federation Platform Cosmo
192 lines • 7.64 kB
JavaScript
import { readFile } from 'node:fs/promises';
import { existsSync } from 'node:fs';
import { arch, platform } from 'node:os';
import path from 'node:path';
import { EnumStatusCode } from '@wundergraph/cosmo-connect/dist/common/common_pb';
import { SubgraphType } from '@wundergraph/cosmo-connect/dist/platform/v1/platform_pb';
import { splitLabel } from '@wundergraph/cosmo-shared';
import { execa } from 'execa';
import { config, getBaseHeaders } from './config.js';
export function getDefaultPlatforms() {
const supportedPlatforms = ['linux/amd64', 'linux/arm64', 'darwin/amd64', 'darwin/arm64', 'windows/amd64'];
const defaultPlatforms = ['linux/amd64', 'linux/arm64'];
const currentPlatform = platform();
const currentArch = arch();
let dockerPlatform = null;
switch (currentPlatform) {
case 'linux': {
if (currentArch === 'x64') {
dockerPlatform = 'linux/amd64';
}
else if (currentArch === 'arm64') {
dockerPlatform = 'linux/arm64';
}
break;
}
case 'darwin': {
if (currentArch === 'x64') {
dockerPlatform = 'darwin/amd64';
}
else if (currentArch === 'arm64') {
dockerPlatform = 'darwin/arm64';
}
break;
}
case 'win32': {
if (currentArch === 'x64') {
dockerPlatform = 'windows/amd64';
}
break;
}
}
if (dockerPlatform && supportedPlatforms.includes(dockerPlatform) && !defaultPlatforms.includes(dockerPlatform)) {
defaultPlatforms.push(dockerPlatform);
}
return defaultPlatforms;
}
export const SUPPORTED_PLATFORMS = ['linux/amd64', 'linux/arm64', 'darwin/amd64', 'darwin/arm64', 'windows/amd64'];
/**
* Reads and validates the 5 required plugin files from a plugin directory.
* Throws on missing/empty files.
*/
export async function readPluginFiles(pluginDir) {
const schemaFile = path.join(pluginDir, 'src', 'schema.graphql');
const dockerFile = path.join(pluginDir, 'Dockerfile');
const protoSchemaFile = path.join(pluginDir, 'generated', 'service.proto');
const protoMappingFile = path.join(pluginDir, 'generated', 'mapping.json');
const protoLockFile = path.join(pluginDir, 'generated', 'service.proto.lock.json');
const requiredFiles = [schemaFile, dockerFile, protoSchemaFile, protoMappingFile, protoLockFile];
for (const f of requiredFiles) {
if (!existsSync(f)) {
throw new Error(`Required file does not exist: ${f}`);
}
}
async function readNonEmpty(filePath) {
const buffer = await readFile(filePath);
const content = new TextDecoder().decode(buffer);
if (content.trim().length === 0) {
throw new Error(`File is empty: ${filePath}`);
}
return content;
}
const [schema, protoSchema, protoMapping, protoLock] = await Promise.all([
readNonEmpty(schemaFile),
readNonEmpty(protoSchemaFile),
readNonEmpty(protoMappingFile),
readNonEmpty(protoLockFile),
]);
return { schema, dockerFile, protoSchema, protoMapping, protoLock };
}
/**
* Core plugin publish pipeline:
* 1. validateAndFetchPluginData (RPC)
* 2. Docker login → buildx build+push → logout
* 3. publishFederatedSubgraph (RPC)
*
* Returns a result object; never calls program.error() — the caller decides
* how to handle errors.
*/
export async function publishPluginPipeline(params) {
var _a, _b, _c, _d, _e, _f, _g, _h;
const { client, pluginName, pluginDir, namespace, labels, platforms, files, cancelSignal, onProcess } = params;
// Step 1: Validate and fetch plugin data
const pluginDataResponse = await client.platform.validateAndFetchPluginData({
name: pluginName,
namespace,
labels: labels.map((label) => splitLabel(label)),
}, {
headers: getBaseHeaders(),
});
if (((_a = pluginDataResponse.response) === null || _a === void 0 ? void 0 : _a.code) !== EnumStatusCode.OK) {
return { error: new Error((_c = (_b = pluginDataResponse.response) === null || _b === void 0 ? void 0 : _b.details) !== null && _c !== void 0 ? _c : 'Failed to validate plugin data') };
}
const { reference, newVersion, pushToken } = pluginDataResponse;
const imageTag = `${config.pluginRegistryURL}/${reference}:${newVersion}`;
const platformStr = platforms.join(',');
// Step 2: Docker operations
try {
const loginProc = execa('docker', ['login', config.pluginRegistryURL, '-u', 'x', '--password-stdin'], {
stdio: 'pipe',
input: pushToken,
...(cancelSignal ? { cancelSignal } : {}),
});
onProcess === null || onProcess === void 0 ? void 0 : onProcess(loginProc);
await loginProc;
const buildProc = execa('docker', [
'buildx',
'build',
'--sbom=false',
'--provenance=false',
'--push',
'--platform',
platformStr,
'-f',
files.dockerFile,
'-t',
imageTag,
pluginDir,
], {
stdio: 'pipe',
...(cancelSignal ? { cancelSignal } : {}),
});
onProcess === null || onProcess === void 0 ? void 0 : onProcess(buildProc);
await buildProc;
}
catch (error) {
return { error: new Error(`Docker operation failed: ${error instanceof Error ? error.message : String(error)}`) };
}
finally {
try {
const logoutProc = execa('docker', ['logout', config.pluginRegistryURL], { stdio: 'pipe' });
onProcess === null || onProcess === void 0 ? void 0 : onProcess(logoutProc);
await logoutProc;
}
catch {
// best-effort logout
}
}
// Step 3: Publish schema
const resp = await client.platform.publishFederatedSubgraph({
name: pluginName,
namespace,
schema: files.schema,
labels: labels.map((label) => splitLabel(label)),
type: SubgraphType.GRPC_PLUGIN,
proto: {
schema: files.protoSchema,
mappings: files.protoMapping,
lock: files.protoLock,
platforms,
version: newVersion,
},
}, {
headers: getBaseHeaders(),
});
const result = {
error: null,
response: {
code: (_d = resp.response) === null || _d === void 0 ? void 0 : _d.code,
details: (_e = resp.response) === null || _e === void 0 ? void 0 : _e.details,
hasChanged: resp.hasChanged,
compositionErrors: resp.compositionErrors,
deploymentErrors: resp.deploymentErrors,
compositionWarnings: resp.compositionWarnings,
proposalMatchMessage: resp.proposalMatchMessage,
},
};
switch ((_f = resp.response) === null || _f === void 0 ? void 0 : _f.code) {
case EnumStatusCode.OK:
case EnumStatusCode.ERR_SUBGRAPH_COMPOSITION_FAILED:
case EnumStatusCode.ERR_DEPLOYMENT_FAILED:
case EnumStatusCode.ERR_SCHEMA_MISMATCH_WITH_APPROVED_PROPOSAL: {
return result;
}
default: {
return {
...result,
error: new Error((_h = (_g = resp.response) === null || _g === void 0 ? void 0 : _g.details) !== null && _h !== void 0 ? _h : 'Failed to publish plugin subgraph'),
};
}
}
}
//# sourceMappingURL=plugin-publish.js.map