Skip to content

Commit 955c850

Browse files
committed
added 4 new commands login, bundle, bundle-upload, and version-bump
1 parent 29bf353 commit 955c850

File tree

3 files changed

+2183
-50
lines changed

3 files changed

+2183
-50
lines changed

index.js

+272
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,17 @@ const { prompt } = require('enquirer');
66
const fs = require('fs-extra');
77
const path = require('path');
88
const axios = require('axios');
9+
const tar = require('tar');
10+
const semver = require('semver');
11+
const glob = require('glob-promise');
12+
const FormData = require('form-data');
913
const _ = require('lodash');
1014
const packageJson = require('./package.json');
1115
const maxBuffer = 1024 * 1024 * 50; // 50MB
1216
const defaultRegistry = 'https://registry.fleetbase.io';
1317
const packageLookupApi = 'https://api.fleetbase.io/~registry/v1/lookup';
18+
const bundleUploadApi = 'http://localhost:8000/~registry/v1/bundle-upload';
19+
// const bundleUploadApi = 'https://api.fleetbase.io/~registry/v1/bundle-upload';
1420
const starterExtensionRepo = 'https://github.com/fleetbase/starter-extension.git';
1521

1622
function publishPackage (packagePath, registry, options = {}) {
@@ -513,6 +519,234 @@ function runCommand (command, workingDirectory) {
513519
});
514520
}
515521

522+
// Function to bundle the extension
523+
async function bundleExtension (options) {
524+
const extensionPath = options.path || '.';
525+
const upload = options.upload;
526+
try {
527+
// Check if extension.json exists in the specified directory
528+
const extensionJsonPath = path.join(extensionPath, 'extension.json');
529+
if (!(await fs.pathExists(extensionJsonPath))) {
530+
console.error(`extension.json not found in ${extensionPath}`);
531+
process.exit(1);
532+
}
533+
// Read extension.json
534+
const extensionJson = await fs.readJson(extensionJsonPath);
535+
const name = extensionJson.name;
536+
const version = extensionJson.version;
537+
538+
if (!name || !version) {
539+
console.error('Name or version not specified in extension.json');
540+
process.exit(1);
541+
}
542+
// Build the bundle filename
543+
const nameDasherized = _.kebabCase(name.replace('@', ''));
544+
const bundleFilename = `${nameDasherized}-v${version}-bundle.tar.gz`;
545+
const bundlePath = path.join(extensionPath, bundleFilename);
546+
547+
// Exclude directories
548+
const excludeDirs = ['node_modules', 'server_vendor'];
549+
550+
console.log(`Creating bundle ${bundleFilename}...`);
551+
552+
await tar.c(
553+
{
554+
gzip: true,
555+
file: bundlePath,
556+
cwd: extensionPath,
557+
filter: (filePath, stat) => {
558+
// Exclude specified directories and the bundle file itself
559+
const relativePath = path.relative(extensionPath, filePath);
560+
561+
// Exclude directories
562+
if (excludeDirs.some(dir => relativePath.startsWith(dir + path.sep))) {
563+
return false; // exclude
564+
}
565+
566+
// Exclude the bundle file
567+
if (relativePath === bundleFilename) {
568+
return false; // exclude
569+
}
570+
571+
// Exclude any existing bundle files matching the pattern
572+
if (relativePath.match(/-v\d+\.\d+\.\d+(-[\w\.]+)?-bundle\.tar\.gz$/)) {
573+
return false; // exclude
574+
}
575+
576+
return true; // include
577+
},
578+
},
579+
['.']
580+
);
581+
582+
console.log(`Bundle created at ${bundlePath}`);
583+
584+
if (upload) {
585+
// Call upload function with the bundle path
586+
await uploadBundle(bundlePath, options);
587+
}
588+
} catch (error) {
589+
console.error(`Error bundling extension: ${error.message}`);
590+
process.exit(1);
591+
}
592+
}
593+
594+
// Function to upload the bundle
595+
async function uploadBundle (bundlePath, options) {
596+
const registry = options.registry || defaultRegistry;
597+
const uploadUrl = bundleUploadApi;
598+
599+
let authToken = options.authToken;
600+
if (!authToken) {
601+
// Try to get auth token from ~/.npmrc
602+
authToken = await getAuthToken(registry);
603+
if (!authToken) {
604+
console.error(`Auth token not found for registry ${registry}. Please provide an auth token using the --auth-token option.`);
605+
process.exit(1);
606+
}
607+
}
608+
609+
try {
610+
const form = new FormData();
611+
form.append('bundle', fs.createReadStream(bundlePath));
612+
613+
const response = await axios.post(uploadUrl, form, {
614+
headers: {
615+
...form.getHeaders(),
616+
Authorization: `Bearer ${authToken}`,
617+
},
618+
maxContentLength: Infinity,
619+
maxBodyLength: Infinity,
620+
});
621+
622+
console.log(`Bundle uploaded successfully: ${response.data.message}`);
623+
} catch (error) {
624+
console.log(error.response.data);
625+
console.error(`Error uploading bundle: ${error.response.data?.error ?? error.message}`);
626+
process.exit(1);
627+
}
628+
}
629+
630+
// Function to get the auth token from .npmrc
631+
async function getAuthToken (registryUrl) {
632+
const npmrcPath = path.join(require('os').homedir(), '.npmrc');
633+
if (!(await fs.pathExists(npmrcPath))) {
634+
return null;
635+
}
636+
637+
const npmrcContent = await fs.readFile(npmrcPath, 'utf-8');
638+
const lines = npmrcContent.split('\n');
639+
640+
const registryHost = new URL(registryUrl).host;
641+
642+
// Look for line matching //registry.fleetbase.io/:_authToken=...
643+
for (const line of lines) {
644+
const match = line.match(new RegExp(`^//${registryHost}/:_authToken=(.*)$`));
645+
if (match) {
646+
return match[1].replace(/^"|"$/g, ''); // Remove quotes if present
647+
}
648+
}
649+
650+
return null;
651+
}
652+
653+
// Function to find the latest bundle
654+
async function findLatestBundle (directory) {
655+
const pattern = '*-v*-bundle.tar.gz';
656+
const files = await glob(pattern, { cwd: directory });
657+
if (files.length === 0) {
658+
return null;
659+
}
660+
// Extract version numbers and sort
661+
const bundles = files
662+
.map(file => {
663+
const match = file.match(/-v(\d+\.\d+\.\d+(-[\w\.]+)?)-bundle\.tar\.gz$/);
664+
if (match) {
665+
const version = match[1];
666+
return { file, version };
667+
}
668+
return null;
669+
})
670+
.filter(Boolean);
671+
672+
if (bundles.length === 0) {
673+
return null;
674+
}
675+
676+
// Sort by version
677+
bundles.sort((a, b) => semver.compare(b.version, a.version));
678+
return bundles[0].file;
679+
}
680+
681+
// Command to handle the upload
682+
async function uploadCommand (bundleFile, options) {
683+
const directory = options.path || '.';
684+
const registry = options.registry || defaultRegistry;
685+
const authToken = options.authToken;
686+
687+
if (!bundleFile) {
688+
bundleFile = await findLatestBundle(directory);
689+
if (!bundleFile) {
690+
console.error('No bundle file found in the current directory.');
691+
process.exit(1);
692+
}
693+
}
694+
695+
const bundlePath = path.join(directory, bundleFile);
696+
697+
await uploadBundle(bundlePath, { registry, authToken });
698+
}
699+
700+
// Function to bump the version
701+
async function versionBump (options) {
702+
const extensionPath = options.path || '.';
703+
const releaseType = options.major ? 'major' : options.minor ? 'minor' : options.patch ? 'patch' : 'patch';
704+
const preRelease = options.preRelease;
705+
706+
const files = ['extension.json', 'package.json', 'composer.json'];
707+
for (const file of files) {
708+
const filePath = path.join(extensionPath, file);
709+
if (await fs.pathExists(filePath)) {
710+
const content = await fs.readJson(filePath);
711+
if (content.version) {
712+
let newVersion = semver.inc(content.version, releaseType, preRelease);
713+
if (!newVersion) {
714+
console.error(`Invalid version in ${file}: ${content.version}`);
715+
continue;
716+
}
717+
content.version = newVersion;
718+
await fs.writeJson(filePath, content, { spaces: 4 });
719+
console.log(`Updated ${file} to version ${newVersion}`);
720+
}
721+
}
722+
}
723+
}
724+
725+
// Command to handle login
726+
function loginCommand (options) {
727+
const npmLogin = require('npm-cli-login');
728+
const username = options.username;
729+
const password = options.password;
730+
const email = options.email;
731+
const registry = options.registry || defaultRegistry;
732+
const scope = options.scope || '';
733+
const quotes = options.quotes || '';
734+
const configPath = options.configPath || '';
735+
736+
if (!username || !password || !email) {
737+
console.error('Username, password, and email are required for login.');
738+
process.exit(1);
739+
}
740+
741+
try {
742+
npmLogin(username, password, email, registry, scope, quotes, configPath);
743+
console.log(`Logged in to registry ${registry}`);
744+
} catch (error) {
745+
console.error(`Error during login: ${error.message}`);
746+
process.exit(1);
747+
}
748+
}
749+
516750
program.name('flb').description('CLI tool for managing Fleetbase Extensions').version(`${packageJson.name} ${packageJson.version}`, '-v, --version', 'Output the current version');
517751
program.option('-r, --registry [url]', 'Specify a fleetbase extension repository', defaultRegistry);
518752

@@ -616,4 +850,42 @@ program
616850
console.log(`${packageJson.name} ${packageJson.version}`);
617851
});
618852

853+
program
854+
.command('bundle')
855+
.description('Bundle the Fleetbase extension into a tar.gz file')
856+
.option('-p, --path <path>', 'Path of the Fleetbase extension to bundle', '.')
857+
.option('-u, --upload', 'Upload the created bundle after bundling')
858+
.option('--auth-token <token>', 'Auth token for uploading the bundle')
859+
.action(bundleExtension);
860+
861+
program
862+
.command('bundle-upload [bundleFile]')
863+
.alias('upload-bundle')
864+
.description('Upload a Fleetbase extension bundle')
865+
.option('-p, --path <path>', 'Path where the bundle is located', '.')
866+
.option('--auth-token <token>', 'Auth token for uploading the bundle')
867+
.action(uploadCommand);
868+
869+
program
870+
.command('version-bump')
871+
.description('Bump the version of the Fleetbase extension')
872+
.option('-p, --path <path>', 'Path of the Fleetbase extension', '.')
873+
.option('--major', 'Bump major version')
874+
.option('--minor', 'Bump minor version')
875+
.option('--patch', 'Bump patch version')
876+
.option('--pre-release [identifier]', 'Add pre-release identifier')
877+
.action(versionBump);
878+
879+
program
880+
.command('login')
881+
.description('Log in to the Fleetbase registry')
882+
.option('-u, --username <username>', 'Username for the registry')
883+
.option('-p, --password <password>', 'Password for the registry')
884+
.option('-e, --email <email>', 'Email associated with your account')
885+
.option('-r, --registry <registry>', 'Registry URL', defaultRegistry)
886+
.option('--scope <scope>', 'Scope for the registry')
887+
.option('--quotes <quotes>', 'Quotes option for npm-cli-login')
888+
.option('--config-path <configPath>', 'Path to the npm config file')
889+
.action(loginCommand);
890+
619891
program.parse(process.argv);

0 commit comments

Comments
 (0)