@nfps.dev/cli
Version:
CLI for NFP development, inspection, and manipulation
232 lines • 10.8 kB
JavaScript
import { readFile } from 'node:fs/promises';
import path from 'node:path';
import zlib from 'node:zlib';
import { buffer_to_base64, buffer_to_base93, buffer_to_text } from '@blake.regalia/belt';
import { query_contract_infer } from '@solar-republic/neutrino';
import kleur from 'kleur';
import mime from 'mime-types';
import prettyBytes from 'pretty-bytes';
import { cli_exec_contract, define_command, exit, load, print, result } from '../common.js';
const bytes = (nb_bytes) => prettyBytes(nb_bytes, {
minimumFractionDigits: 2,
});
export const H_CMDS_PACKAGE = {
'package <cmd>': define_command({
info: 'manage a package',
commands: {
'info <package_id>': define_command({
info: 'view version info for a given package',
pos: {
package_id: {
type: 'string',
},
},
async handler(g_argv) {
const { sh_vk, k_contract, k_service, } = await load(g_argv, ['vk']);
// query contract
// const [g_package, xc_code, s_error] = await query_contract_infer<{
// info: {
// index: number;
// tags: string[];
// metadata: object;
// access: string;
// }[];
// version_count: number;
// }>(k_contract, 'package_info', {
// package_id: g_argv.package_id!,
// }, sh_vk);
const [g_package, xc_code, s_error] = await k_service.query('package_info', {
package_id: g_argv.package_id,
}, sh_vk);
// query failed
if (xc_code)
return exit(s_error);
// print
const h_nested = {};
const h_tags = {};
for (const g_version of (g_package?.info || []).reverse()) {
h_nested[g_version.index + ''] = [
g_version.access,
JSON.stringify(g_version.metadata || {}),
g_version.tags.map(s => h_tags[s] ? `@${s}` : h_tags[s] = kleur.cyan(`@${s}`)).join(', '),
].join(' ');
}
print('Package info', h_nested);
print('Raw response:');
result(JSON.stringify(g_package));
},
}),
'download <package_id>': define_command({
info: 'download a package from chain',
opts: {
index: {
alias: 'i',
type: 'string',
desc: 'select which package version by index',
conflicts: ['tag'],
},
tag: {
alias: 't',
type: 'string',
desc: 'select which package version by tag',
conflicts: ['index'],
},
out: {
alias: 'o',
type: 'string',
desc: 'path of file to write to',
},
},
pos: {
package_id: {
type: 'string',
desc: 'id of the package to download',
},
},
async handler(g_argv) {
const { sh_vk, k_contract, k_wallet, } = await load(g_argv, ['vk']);
// prep args
const h_args = {
package_id: g_argv.package_id,
};
// by index
if (g_argv.index) {
h_args.index = g_argv.index;
}
// by tag
else if (g_argv.tag) {
h_args.tag = g_argv.tag;
}
// query contract
const [g_package, xc_code, s_error] = await query_contract_infer(k_contract, 'package_version', h_args, [sh_vk, k_wallet.addr]);
// query failed
if (xc_code)
return exit(s_error);
// print
print('Package contents');
result(JSON.stringify(g_package));
},
}),
'upload <file>': define_command({
info: 'upload a new package',
opts: {
'id': {
alias: 'i',
type: 'string',
desc: 'sets the id of the package on chain (defaults to name of file)',
},
'access': {
alias: 'a',
type: 'string',
choices: ['public', 'owners', 'cleared'],
default: 'public',
desc: 'set access control for who is able to view the package. public: no access-control | owners: only accounts that own a token can access | cleared: only accounts that have been added to allowlist can access',
},
'tags': {
alias: 't',
type: 'string',
array: true,
desc: 'adds tags to the package',
},
'content-type': {
alias: 'c',
type: 'string',
desc: 'content-type (MIME) of the file. defaults to guessing based on file extension',
},
'raw': {
type: 'boolean',
desc: 'bypass compression and encoding by directly uploading the raw bytes of the given file (must already be base64-encoded)',
},
'content-encoding': {
type: 'string',
desc: 'explicitly sets the content-encoding (must be used with the --raw option)',
implies: ['raw'],
},
},
pos: {
file: {
type: 'string',
},
},
async handler(g_argv) {
const { sh_vk, k_contract, k_wallet, } = await load(g_argv, ['vk']);
const a_auth = [sh_vk, k_wallet.addr];
// positional path arg
const sr_path = g_argv.file;
// determine content-type
const si_content_type = g_argv.contentType || {
'.js': 'application/ecmascript',
}[path.extname(sr_path)] || mime.lookup(path.extname(sr_path)) || 'application/octet-stream';
// options
const si_access = g_argv.access;
const a_tags = g_argv.tags || [];
// read file contents
const atu8_contents = await readFile(sr_path);
// package name
let si_package = g_argv.id;
if (!si_package)
si_package = path.basename(sr_path);
// compress using gzip
const atu8_compressed = zlib.gzipSync(atu8_contents, {
level: zlib.constants.Z_BEST_COMPRESSION,
});
//
const sb64_compressed = buffer_to_base64(atu8_compressed);
const sb93_compressed = buffer_to_base93(atu8_compressed);
// const sx_raw = Array.from(atu8_compressed).map(x => String.fromCharCode(x)).join('');
const sx_upload = g_argv.raw ? buffer_to_text(atu8_contents) : sb64_compressed;
// verbose
print('Gzip compression results:', {
'file': sr_path,
'before': bytes(atu8_contents.byteLength),
' after': bytes(atu8_compressed.byteLength),
'base64': bytes(sb64_compressed.length),
'base93': bytes(sb93_compressed.length),
// ' raw': `${sx_raw.length} chars`,
});
// tx fee (40k if package exists, +4k if first creation)
// const XG_BASE = 59262;
let xg_limit = 40000n + (19n * BigInt(Math.ceil(1.05 * sx_upload.length)));
// check for existing package
{
const [g_info, xc_code_info, s_err_info] = await query_contract_infer(k_contract, 'package_info', {
package_id: si_package,
}, a_auth);
// query failed
if (xc_code_info) {
return exit(`Failed to check for existing package: ${s_err_info}`);
}
// no package
else if (0 === g_info?.['version_count']) {
xg_limit += 4000n;
}
}
print('Ready to upload:', {
package_id: si_package,
access: si_access,
tags: a_tags.join(', '),
content_type: si_content_type,
size: prettyBytes(sx_upload.length),
});
// upload package
const [xc_code, s_res, g_tx] = await cli_exec_contract(g_argv, {
upload_package_version: {
package_id: si_package,
access: si_access,
tags: a_tags,
data: {
bytes: sx_upload,
content_type: si_content_type,
content_encoding: g_argv.raw ? g_argv.contentEncoding || 'identity' : 'gzip',
},
},
}, xg_limit);
// result
print('Result:');
result(s_res);
},
}),
},
}),
};
//# sourceMappingURL=package.js.map