Skip to content
Merged
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
2 changes: 0 additions & 2 deletions .eslintignore

This file was deleted.

7 changes: 0 additions & 7 deletions .eslintrc

This file was deleted.

10 changes: 5 additions & 5 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,15 @@ jobs:

steps:
- name: Harden Runner
uses: step-security/harden-runner@1b05615854632b887b69ae1be8cbefe72d3ae423 # v2.6.0
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
with:
egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs
- name: Checkout repository
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@74483a38d39275f33fcff5f35b679b5ca4a26a99
uses: github/codeql-action/init@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
Expand All @@ -65,7 +65,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@74483a38d39275f33fcff5f35b679b5ca4a26a99
uses: github/codeql-action/autobuild@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0

# ℹ️ Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
Expand All @@ -78,6 +78,6 @@ jobs:
# ./location_of_script_within_repo/buildscript.sh

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@74483a38d39275f33fcff5f35b679b5ca4a26a99
uses: github/codeql-action/analyze@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0
with:
category: "/language:${{matrix.language}}"
16 changes: 9 additions & 7 deletions .github/workflows/node.js.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ name: Node.js CI

on:
push:
branches: [ "master" ]
branches:
- master
pull_request:
branches: [ "master" ]
branches:
- master

permissions:
contents: read
Expand All @@ -17,17 +19,17 @@ jobs:
runs-on: ${{matrix.os}}
strategy:
matrix:
node-version: [16.x, 18.x, 20.x]
os: [ubuntu-latest, windows-latest, macos-latest]
node-version: [22.x, 24.x]
os: [ubuntu-latest]
steps:
- name: Harden Runner
uses: step-security/harden-runner@1b05615854632b887b69ae1be8cbefe72d3ae423 # v2.6.0
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
with:
egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs

- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: ${{ matrix.node-version }}
- run: npm install
Expand Down
10 changes: 5 additions & 5 deletions .github/workflows/scorecard.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,17 @@ jobs:

steps:
- name: Harden Runner
uses: step-security/harden-runner@1b05615854632b887b69ae1be8cbefe72d3ae423 # v2.6.0
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
with:
egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs

- name: "Checkout code"
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false

- name: "Run analysis"
uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.3.1
uses: ossf/scorecard-action@05b42c624433fc40578a4040d5cf5e36ddca8cde # v2.4.2
with:
results_file: results.sarif
results_format: sarif
Expand All @@ -64,14 +64,14 @@ jobs:
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
# format to the repository Actions tab.
- name: "Upload artifact"
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: SARIF file
path: results.sarif
retention-days: 5

# Upload the results to GitHub's code scanning dashboard.
- name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@74483a38d39275f33fcff5f35b679b5ca4a26a99 # v2.22.5
uses: github/codeql-action/upload-sarif@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0
with:
sarif_file: results.sarif
3 changes: 1 addition & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -57,5 +57,4 @@ typings/
# dotenv environment variables file
.env

jsdoc/
docs/
.temp
2 changes: 0 additions & 2 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -1,3 +1 @@
package-lock=false
tag-version-prefix=""
message="chore(release): %s"
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2018 TopCli
Copyright (c) 2018-2025 TopCli

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
21 changes: 10 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/TopCli/Pretty-JSON/badge?style=for-the-badge)](https://api.securityscorecards.dev/projects/github.com/TopCli/Pretty-JSON)
![build](https://img.shields.io/github/actions/workflow/status/TopCli/Pretty-JSON/node.js.yml?style=for-the-badge)

Stdout JSON in your terminal with colors. This package has been created to stdout clean and beautiful JSON in the SlimIO CLI.
Pretty-print JSON to the terminal with syntax highlighting and structure-aware formatting.

## Requirements
- [Node.js](https://nodejs.org/en/) v16 or higher
- [Node.js](https://nodejs.org/en/) v20 or higher

## Getting Started

Expand All @@ -25,9 +25,9 @@ $ yarn add @topcli/pretty-json
import prettyJSON from "@topcli/pretty-json";

prettyJSON({
foo: "bar",
hello: "world!",
arr: [1, 2, 3]
foo: "bar",
hello: "world!",
arr: [1, 2, 3]
});
```

Expand All @@ -38,14 +38,13 @@ It will produce the following stdout:
## API

### prettyJSON(obj: object): void
Stdout a given JSON Object (Plain Object, Objects Prototype of Object or Array).

## Dependencies
Prints a JSON-compatible object or array to the terminal with syntax highlighting and structured indentation.

|Name|Refactoring|Security Risk|Usage|
|---|---|---|---|
|[@slimio/is](https://github.com/SlimIO/is)|Minor|Low|Type checker|
|[kleur](https://github.com/lukeed/kleur)|Minor|Low|TTY color|
- Supports plain objects, arrays, and nested structures.
- Skips functions and symbols.
- Color-codes data types (strings, numbers, booleans, etc.).
- Outputs readable, formatted JSON — ideal for CLI inspection.

## License
MIT
11 changes: 11 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Import Third-party Dependencies
import { ESLintConfig } from "@openally/config.eslint";

export default [
...ESLintConfig,
{
languageOptions: {
sourceType: "module"
}
}
];
107 changes: 58 additions & 49 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,120 +1,129 @@
// Require Third-party Dependencies
import kleur from "kleur";
import is from "@slimio/is";
// Import Node.js Dependencies
import { EOL } from "node:os";
import { styleText } from "node:util";

// Import Internal Dependencies
import * as utils from "./src/utils.js";

// Require Internal Dependencies
import { maxKeyLength, primeColor } from "./src/utils.js";
function logArray(arr, options = {}) {
const {
depth = 1,
disableEndLine = false
} = options;

// CONSTANTS
const EOL = "\n";

/**
* @function logArray
* @param {!Array<any>} arr
* @param {number} [depth=1]
* @param {boolean} [disableEndLine=false]
* @returns {void}
*/
function logArray(arr, depth = 1, disableEndLine = false) {
const backStart = depth === 2 ? " " : " ".repeat(depth - 1);
const startSpace = depth === 1 ? " " : " ".repeat(depth);
const lenMax = arr.length - 1;
const returnLine = arr.length > 10;
const firstIsObject = arr.length > 0 && is.plainObject(arr[0]);
const returnLine = utils.shouldArrayReturnLine(arr, { depth });
const firstIsObject = arr.length > 0 && utils.isPlainObject(arr[0]);
let forceNext = false;

if (arr.length === 0) {
process.stdout.write(`${kleur.gray("[]")}${disableEndLine ? "" : EOL}`);
process.stdout.write(`${styleText("gray", "[]")}${disableEndLine ? "" : EOL}`);

return;
}

process.stdout.write(`${kleur.gray("[")}${EOL}${firstIsObject ? "" : startSpace}`);
if (depth === 1) {
process.stdout.write(EOL);
}

const shouldReturn = utils.isPlainObject(arr[0]) === false;
process.stdout.write(`${styleText("gray", "[")}${shouldReturn ? EOL : ""}${firstIsObject ? "" : startSpace}`);
for (let id = 0; id < arr.length; id++) {
const value = arr[id];

if (is.array(value)) {
if (Array.isArray(value)) {
if (id !== 0) {
process.stdout.write(startSpace);
}
logArray(value, depth + 1, true);
logArray(value, { depth: depth + 1, disableEndLine: true });
forceNext = true;
if (id !== lenMax) {
process.stdout.write(kleur.gray(", "));
process.stdout.write(styleText("gray", ", "));
process.stdout.write(EOL);
}
}
else if (is.plainObject(value)) {
logObject(value, depth, true);
else if (utils.isPlainObject(value)) {
logObject(value, {
depth,
disableEndLine: utils.shouldObjectReturnLine(value, { depth }),
inArray: true
});
forceNext = true;
}
else {
// eslint-disable-next-line
if ((returnLine && id > 0) || forceNext) {
if ((returnLine && id > 0) || forceNext) {
process.stdout.write(startSpace);
forceNext = false;
}

process.stdout.write(primeColor(value)(is.string(value) ? `'${value}'` : String(value)));
const isString = typeof value === "string";
process.stdout.write(utils.primeColor(value)(isString ? `'${value}'` : String(value)));
if (id !== lenMax) {
process.stdout.write(kleur.gray(", "));
process.stdout.write(styleText("gray", ", "));
if (returnLine) {
process.stdout.write(EOL);
}
}
}
}
process.stdout.write(`${EOL}${backStart}${kleur.gray("]")}${disableEndLine ? "" : EOL}`);
process.stdout.write(`${EOL}${backStart}${styleText("gray", "]")}${disableEndLine ? "" : EOL}`);
}

/**
* @function logObject
* @param {!object} obj
* @param {number} [depth=1]
* @param {boolean} [disableEndLine=false]
* @returns {void}
*/
function logObject(obj, depth = 1, disableEndLine = false) {
const betweenSpace = maxKeyLength(obj, 4);
function logObject(obj, options = {}) {
const {
depth = 1,
disableEndLine = false,
inArray = false
} = options;
const betweenSpace = utils.maxKeyLength(obj, 4);
const startSpace = depth === 1 ? " " : " ".repeat(depth);
const entries = Object.entries(obj);

if (entries.length === 0) {
process.stdout.write(`${kleur.gray("{}")}${disableEndLine ? "" : EOL}`);
process.stdout.write(`${styleText("gray", "{}")}${disableEndLine ? "" : EOL}`);

return;
}

for (let id = 0; id < entries.length; id++) {
const [key, value] = entries[id];
if (is.func(value) || is.symbol(value)) {
if (["function", "symbol"].includes(typeof value)) {
continue;
}

if (id === 0) {
process.stdout.write(EOL);
}
process.stdout.write(kleur.bold(kleur.white(`${startSpace}${key}: ${" ".repeat(betweenSpace - key.length)}`)));
if (is.object(value)) {
(Array.isArray(value) ? logArray : logObject)(value, depth + 1);
process.stdout.write(styleText(["bold", "white"], `${startSpace}${key}: ${" ".repeat(betweenSpace - key.length)}`));
if (utils.isPlainObject(value)) {
logObject(value, { depth: depth + 1 });
continue;
}
if (Array.isArray(value)) {
logArray(value, { depth: depth + 1 });
continue;
}

process.stdout.write(primeColor(value)(is.string(value) ? `'${value}'` : String(value)));
if (!disableEndLine) {
process.stdout.write(utils.primeColor(value)(typeof value === "string" ? `'${value}'` : String(value)));
if (!disableEndLine && !(inArray && id === entries.length - 1)) {
process.stdout.write(EOL);
}
}
}

export default function stdoutJSON(obj) {
if (is.object(obj)) {
(Array.isArray(obj) ? logArray : logObject)(obj);
process.stdout.write(EOL);
if (utils.isPlainObject(obj)) {
logObject(obj);
}
else if (Array.isArray(obj)) {
logArray(obj);
}
else {
throw new Error(`${obj} should be object or array.`);
}

process.stdout.write(EOL);
}

20 changes: 0 additions & 20 deletions jsdoc.json

This file was deleted.

Loading