Skip to content
Merged

V3 #19

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 39 additions & 5 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -1,17 +1,50 @@
name: Node.js CI
name: Build

on:
push:
branches: [master, v3]
tags: ['*']
pull_request:

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
build:
quality:
name: Lint & format
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: 24.x
cache: npm

- name: Install dependencies
run: npm ci

- name: Check formatting
run: npm run format:check

- name: Lint
run: npm run lint

test:
name: Test (Node ${{ matrix.node-version }})
runs-on: ubuntu-latest
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
node-version: [lts/*]

node-version: [22.x, 24.x]
steps:
- name: Checkout repository
uses: actions/checkout@v4
Expand All @@ -20,9 +53,10 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: npm

- name: Install dependencies
run: npm install
run: npm ci

- name: Run tests
run: npm test
9 changes: 9 additions & 0 deletions .oxfmtrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"singleQuote": true,
"tabWidth": 4,
"printWidth": 80,
"semi": true,
"trailingComma": "all",
"arrowParens": "avoid",
"bracketSpacing": true
}
20 changes: 20 additions & 0 deletions .oxlintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"categories": {
"correctness": "error"
},
"rules": {
"no-debugger": "error",
"no-console": ["warn", { "allow": ["log", "warn", "error", "info"] }],
"no-unused-vars": "warn"
},
"ignorePatterns": [
"**/*.js",
"**/*.d.ts",
"docs/",
"coverage/",
".nyc_output/",
".agent-out/",
"node_modules/"
]
}
24 changes: 16 additions & 8 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,21 +24,29 @@
*/
import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';
import { VERSION, checkForUpdate } from './lib';
import { VERSION, checkForUpdate } from './lib/index.js';
import * as client from './src/client.js';
import * as completions from './src/completions.js';
import * as config from './src/config.js';
import * as service from './src/service.js';

(async () => {
const y = yargs(hideBin(process.argv));

await checkForUpdate();

await y.usage('IMQ Command Line Interface' +
`\nVersion: ${VERSION}` +
'\n\nUsage: $0 <command>')
await y
.usage(
'IMQ Command Line Interface' +
`\nVersion: ${VERSION}` +
'\n\nUsage: $0 <command>',
)
.version(VERSION)
.commandDir('src')
.command(client as any)
.command(completions as any)
.command(config as any)
.command(service as any)
.demandCommand()
.wrap(y.terminalWidth())
.help()
.argv
;
.help().argv;
})();
39 changes: 26 additions & 13 deletions lib/autoupdate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,41 +22,54 @@
* <support@imqueue.com> to get commercial licensing options.
*/
import { execSync } from 'child_process';
import inquirer, { QuestionCollection } from 'inquirer';
import inquirer, { type QuestionCollection } from 'inquirer';
import { createRequire } from 'node:module';
import * as semver from 'semver';

const require = createRequire(import.meta.url);

const pkg = require('../package.json');

// istanbul ignore next
/**
* Performs check if local version is latest
*/
export async function checkForUpdate() {
try {
// non-interactive runs (pipes, CI) must never hang on a prompt
if (!process.stdin.isTTY || !process.stdout.isTTY) {
return;
}

const remoteVersion = execSync(`npm show ${pkg.name} version`)
.toString('utf8').trim();
.toString('utf8')
.trim();
const localVersion = pkg.version.trim();

if (remoteVersion !== localVersion) {
// prompt only for a real upgrade, not when running a newer
// (e.g. not yet published) local version
if (semver.gt(remoteVersion, localVersion)) {
const answer: { update: boolean } = await inquirer.prompt<{
update: boolean;
}>([{
type: 'confirm',
name: 'update',
message: `New version ${remoteVersion} of ${
pkg.name} available. Would you like to update?`,
default: true,
}] as QuestionCollection);
}>([
{
type: 'confirm',
name: 'update',
message: `New version ${remoteVersion} of ${
pkg.name
} available. Would you like to update?`,
default: true,
},
] as QuestionCollection);

if (answer.update) {
update();
}
}
} catch (err) {
} catch {
/* ignore */
}
}

// istanbul ignore next
/**
* Executes update command for this package
*/
Expand Down
30 changes: 16 additions & 14 deletions lib/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@
import {
readFileSync as read,
writeFileSync as write,
existsSync as exists
existsSync as exists,
} from 'fs';
import { touch } from './fs';
import { CONFIG_PATH } from './constants';
import { touch } from './fs.js';
import { CONFIG_PATH } from './constants.js';

export interface IMQCLIConfig {
[options: string]: any;
Expand Down Expand Up @@ -73,13 +73,13 @@ export function saveConfig(config: IMQCLIConfig) {
* @return {boolean}
*/
export function configEmpty() {
if (!exists(CONFIG_PATH)) {
return true;
}
if (!exists(CONFIG_PATH)) {
return true;
}

const config = loadConfig();
const config = loadConfig();

return !(config && Object.keys(config).length);
return !(config && Object.keys(config).length);
}

/**
Expand All @@ -93,14 +93,16 @@ export function configEmpty() {
export function prepareConfigValue(value: any) {
if (typeof value === 'string') {
switch (value) {
case 'true': return true;
case 'false': return false;
case 'null': return null;
case 'undefined': return undefined;
case 'true':
return true;
case 'false':
return false;
case 'null':
return null;
case 'undefined':
return undefined;
}

// istanbul ignore else
// noinspection RegExpSingleCharAlternation,RegExpRedundantEscape
if (/^\s*(\[|\{)/.test(value)) {
return JSON.parse(value);
}
Expand Down
17 changes: 13 additions & 4 deletions lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,23 @@
* <support@imqueue.com> to get commercial licensing options.
*/
import * as os from 'os';
import { resolve } from './path';
import { resolve } from './path.js';
import { createRequire } from 'node:module';

export const OS_HOME: string = os.homedir() || String(process.env['HOME']);
const require = createRequire(import.meta.url);

// IMQ_CLI_HOME allows tests (or callers) to sandbox all cli file locations
export const OS_HOME: string =
process.env.IMQ_CLI_HOME || os.homedir() || String(process.env['HOME']);
export const IMQ_HOME = '~/.imq';
export const TPL_REPO = 'git@github.com:imqueue/templates.git';
export const TPL_HOME = resolve(IMQ_HOME, 'templates');
export const CUSTOM_TPL_HOME = resolve(IMQ_HOME, 'custom-templates');
export const CONFIG_FILENAME = 'config.json';
export const CONFIG_PATH = resolve(IMQ_HOME, CONFIG_FILENAME);
export const IS_ZSH = Object.keys(process.env).some(key => /^ZSH/.test(key));
export const VERSION = require(`${__dirname}/../package.json`).version;
export const IS_ZSH = Object.keys(process.env).some(key =>
key.startsWith('ZSH'),
);
export const VERSION = require(
`${import.meta.dirname}/../package.json`,
).version;
17 changes: 9 additions & 8 deletions lib/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,9 @@
* purchase a proprietary commercial license. Please contact us at
* <support@imqueue.com> to get commercial licensing options.
*/
import chalk from 'chalk';
import { styleText } from 'node:util';

// that is just a printing function, no need to do specific tests
// istanbul ignore next
/**
* Prints error message to standard error output
*
Expand All @@ -38,15 +37,17 @@ export function printError(err: Error, withStackTrace: boolean = false) {
let obj = JSON.parse(message);

if (obj.message && obj.errors) {
message = `${obj.message}: ${
obj.errors.map((err: any) => err.message).join('\n')
}`;
message = `${obj.message}: ${obj.errors
.map((err: any) => err.message)
.join('\n')}`;
}
} catch (err) { /* ignore */ }
} catch {
/* ignore */
}

process.stderr.write(chalk.bold.red(message) + '\n');
process.stderr.write(styleText(['bold', 'red'], message) + '\n');

if (withStackTrace && err.stack) {
process.stderr.write(chalk.cyan(err.stack) + '\n');
process.stderr.write(styleText('cyan', err.stack) + '\n');
}
}
22 changes: 6 additions & 16 deletions lib/fs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
*/
import * as fs from 'fs';
import * as p from 'path';
import { resolve } from '.';
import { resolve } from './index.js';

/**
* Copy contents from source directory to destination directory
Expand All @@ -46,9 +46,7 @@ export function cpr(from: string, to: string) {

if (fs.statSync(fromPath).isDirectory()) {
cpr(fromPath, toPath);
}

else {
} else {
fs.copyFileSync(fromPath, toPath);
}
});
Expand All @@ -62,8 +60,7 @@ export function cpr(from: string, to: string) {
export function rmdir(path: string) {
let files = [];

// istanbul ignore else
if( fs.existsSync(path) ) {
if (fs.existsSync(path)) {
files = fs.readdirSync(path);

files.forEach(file => {
Expand All @@ -88,15 +85,12 @@ export function rmdir(path: string) {
export function mkdirp(path: string): void {
try {
fs.mkdirSync(path);
}

catch (err) {
if (err.code === 'ENOENT') {
} catch (err) {
if ((err as NodeJS.ErrnoException).code === 'ENOENT') {
mkdirp(p.dirname(path));
return mkdirp(path);
}

// istanbul ignore if
if (!fs.statSync(path).isDirectory()) {
throw err;
}
Expand All @@ -110,11 +104,7 @@ export function mkdirp(path: string): void {
* @param {string} path - path to file to create
* @param {string} [content] - file contents to put in
*/
export function touch(
path: string,
// istanbul ignore next
content: string = ''
): void {
export function touch(path: string, content: string = ''): void {
if (!fs.existsSync(path)) {
mkdirp(p.dirname(path));
fs.writeFileSync(path, content);
Expand Down
Loading
Loading