├── .editorconfig
├── .eslintignore
├── .eslintrc
├── .github
└── dependabot.yml
├── .gitignore
├── .prettierrc.js
├── LICENSE
├── README.md
├── bin
├── run
└── run.cmd
├── package.json
├── renovate.json
├── src
├── commands
│ ├── config
│ │ ├── index.ts
│ │ ├── remove.ts
│ │ └── show.ts
│ ├── generate.ts
│ ├── gist.ts
│ ├── github.ts
│ └── imgur.ts
├── data
│ ├── constants.ts
│ ├── options.ts
│ └── questions.ts
├── index.ts
├── lib
│ ├── configValues.ts
│ ├── createImage.ts
│ └── truncateGist.ts
└── utils
│ ├── configFileExists.ts
│ ├── convertCodeIntoBase64.ts
│ ├── getConfigValues.ts
│ ├── getGistContent.ts
│ ├── getGitHubFileContent.ts
│ ├── uploadToImgur.ts
│ ├── verifyGistLink.ts
│ └── verifyGitHubLink.ts
└── tsconfig.json
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 2
6 | charset = utf-8
7 | trim_trailing_whitespace = true
8 | insert_final_newline = true
9 |
10 | [*.md]
11 | trim_trailing_whitespace = false
12 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | /dist
2 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "es2021": true
5 | },
6 | "parser": "@typescript-eslint/parser",
7 | "parserOptions": {
8 | "ecmaVersion": "latest",
9 | "sourceType": "module"
10 | },
11 | "plugins": ["@typescript-eslint"],
12 | "rules": {
13 | "indent": ["error", 2]
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: "npm"
4 | versioning-strategy: increase
5 | directory: "/"
6 | schedule:
7 | interval: "monthly"
8 | labels:
9 | - "dependencies"
10 | open-pull-requests-limit: 100
11 | pull-request-branch-name:
12 | separator: "-"
13 | ignore:
14 | - dependency-name: "fs-extra"
15 | - dependency-name: "*"
16 | update-types: ["version-update:semver-major"]
17 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *-debug.log
2 | *-debug.log
3 | *-error.log
4 | *-error.log
5 | /.nyc_output
6 | /dist
7 | /dist
8 | /lib
9 | /lib
10 | /package-lock.json
11 | /package-lock.json
12 | /tmp
13 | /tmp
14 | node_modules
15 | node_modules
16 | oclif.manifest.json
17 |
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | semi: true,
3 | arrowParens: "avoid",
4 | tabWidth: 2,
5 | singleQuote: false,
6 | };
7 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Kira
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 📸 rayli
2 |
3 | 
4 |
5 |
6 |
7 | 📸 A command-line tool to generate code images of your local code right away from the terminal
8 |
9 | - [Usage](#usage)
10 | - [Commands](#commands)
11 |
12 | # Usage
13 |
14 | ```sh-session
15 | $ npm install -g rayli
16 | $ rayli COMMAND
17 | running command...
18 | $ rayli (--version)
19 | rayli/0.0.1 win32-x64 node-v16.13.0
20 | $ rayli --help [COMMAND]
21 | USAGE
22 | $ rayli COMMAND
23 | ...
24 | ```
25 |
26 | # Commands
27 |
28 | - [`rayli config`](#rayli-config)
29 | - [`rayli config:remove`](#rayli-configremove)
30 | - [`rayli config:show`](#rayli-configshow)
31 | - [`rayli generate`](#rayli-generate)
32 | - [`rayli gist`](#rayli-gist)
33 | - [`rayli github`](#rayli-github)
34 | - [`rayli help [COMMAND]`](#rayli-help-command)
35 |
36 | ## `rayli config`
37 |
38 | 🔐 Configure the default values
39 |
40 | ```
41 | USAGE
42 | $ rayli config
43 |
44 | DESCRIPTION
45 | 🔐 Configure the default values
46 |
47 | EXAMPLES
48 | $ rayli config
49 | ```
50 |
51 | ## `rayli config:remove`
52 |
53 | 🚚 Remove the configured values
54 |
55 | ```
56 | USAGE
57 | $ rayli config:remove
58 |
59 | DESCRIPTION
60 | 🚚 Remove the configured values
61 |
62 | EXAMPLES
63 | $ rayli config:remove
64 | ```
65 |
66 | ## `rayli config:show`
67 |
68 | 👀 Check your configured values
69 |
70 | ```
71 | USAGE
72 | $ rayli config:show
73 |
74 | DESCRIPTION
75 | 👀 Check your configured values
76 |
77 | EXAMPLES
78 | $ rayli config:show
79 | ```
80 |
81 | ## `rayli generate`
82 |
83 | 📷 Generate a beautiful image of your code snippet
84 |
85 | ```
86 | USAGE
87 | $ rayli generate [-c]
88 |
89 | FLAGS
90 | -c, --[no-]config 🔐 Use the default configured values
91 |
92 | DESCRIPTION
93 | 📷 Generate a beautiful image of your code snippet
94 |
95 | EXAMPLES
96 | $ rayli generate --config
97 | ```
98 |
99 | ## `rayli gist`
100 |
101 | 🌌 Generate a beautiful image of your gist
102 |
103 | ```
104 | USAGE
105 | $ rayli gist -u [-c]
106 |
107 | FLAGS
108 | -c, --[no-]config 🔐 Use the default configured values
109 | -u, --url= (required) 🔗 Link of the gist
110 | -r, --range= 🔍 Range of the gist
111 |
112 | DESCRIPTION
113 | 🌌 Generate a beautiful image of your gist
114 |
115 | EXAMPLES
116 | $ rayli gist --url=https://gist.github.com/Kira272921/bfce776b3ad1145f764d89c296fec605
117 | ```
118 |
119 | ## `rayli github`
120 |
121 | 🐱 Generate a beautiful image of your code hosted on GitHub
122 |
123 | ```
124 | USAGE
125 | $ rayli github -u [-c]
126 |
127 | FLAGS
128 | -c, --[no-]config 🔐 Use the default configured values
129 | -u, --url= (required) 🔗 Link of the code
130 |
131 | DESCRIPTION
132 | 🐱 Generate a beautiful image of your code hosted on GitHub
133 |
134 | EXAMPLES
135 | $ rayli github --url=https://raw.githubusercontent.com/Kira272921/snipli/main/src/commands/download.ts
136 | ```
137 |
138 | ## `rayli help [COMMAND]`
139 |
140 | Display help for rayli.
141 |
142 | ```
143 | USAGE
144 | $ rayli help [COMMAND] [-n]
145 |
146 | ARGUMENTS
147 | COMMAND Command to show help for.
148 |
149 | FLAGS
150 | -n, --nested-commands Include all nested commands in the output.
151 |
152 | DESCRIPTION
153 | Display help for rayli.
154 | ```
155 |
156 | ## 🖍 Examples
157 |
158 | 
159 |
160 | 
161 |
162 | ## 🦸♂️ Authors
163 |
164 | ### Avneesh Agarwal
165 |
166 | - Website: https://www.avneesh.tech/
167 | - Twitter: [@avneesh0612](https://twitter.com/avneesh0612)
168 | - Github: [@avneesh0612](https://github.com/avneesh0612)
169 | - LinkedIn: [@avneesh0612](https://www.linkedin.com/in/avneesh0612)
170 |
171 | ### Kira
172 |
173 | - Website: https://kiradev.co
174 | - Twitter: [@kira_272921](https://twitter.com/kira_272921)
175 | - Discord: https://links.kiradev.co/discord
176 |
--------------------------------------------------------------------------------
/bin/run:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const oclif = require('@oclif/core')
4 |
5 | oclif.run().then(require('@oclif/core/flush')).catch(require('@oclif/core/handle'))
6 |
--------------------------------------------------------------------------------
/bin/run.cmd:
--------------------------------------------------------------------------------
1 | @echo off
2 |
3 | node "%~dp0\run" %*
4 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "rayli",
3 | "version": "1.0.8",
4 | "description": "📸 A command-line tool to generate code images of your local code right away from the terminal",
5 | "author": "Kira272921",
6 | "bin": {
7 | "rayli": "./bin/run"
8 | },
9 | "homepage": "https://github.com/buidler-hub/rayli",
10 | "license": "MIT",
11 | "main": "src/index.ts",
12 | "repository": "buidler-hub/rayli",
13 | "files": [
14 | "/bin",
15 | "/dist",
16 | "/npm-shrinkwrap.json",
17 | "/oclif.manifest.json"
18 | ],
19 | "dependencies": {
20 | "@lucasliet/imgur-ts": "^1.1.2",
21 | "@oclif/core": "^1",
22 | "@oclif/plugin-help": "^5",
23 | "@oclif/plugin-plugins": "^2.1.0",
24 | "@oclif/plugin-warn-if-update-available": "^2.0.4",
25 | "axios": "^0.27.2",
26 | "chalk": "4.1.2",
27 | "inquirer": "^8.2.4",
28 | "inquirer-search-list": "^1.2.6",
29 | "is-image": "^3.0.0",
30 | "js-base64": "^3.7.2",
31 | "ora": "6.1.0",
32 | "puppeteer": "^14.2.0",
33 | "shelljs": "^0.8.5"
34 | },
35 | "devDependencies": {
36 | "@types/inquirer": "8.2.1",
37 | "@types/node": "17.0.38",
38 | "@types/shelljs": "0.8.11",
39 | "eslint": "8.17.0",
40 | "eslint-config-oclif": "4.0.0",
41 | "eslint-config-oclif-typescript": "1.0.2",
42 | "globby": "13.1.1",
43 | "oclif": "3.0.1",
44 | "prettier": "2.6.2",
45 | "shx": "0.3.4",
46 | "ts-node": "10.8.1",
47 | "tslib": "2.4.0",
48 | "typescript": "4.7.3"
49 | },
50 | "oclif": {
51 | "bin": "rayli",
52 | "dirname": "rayli",
53 | "commands": "./dist/src/commands",
54 | "plugins": [
55 | "@oclif/plugin-help",
56 | "@oclif/plugin-plugins",
57 | "@oclif/plugin-warn-if-update-available"
58 | ],
59 | "warn-if-update-available": {
60 | "message": "Version <%= chalk.greenBright(latest) %> of rayli is available! You are currently using <%= chalk.greenBright(config.version) %> version of rayli. Run `npm i -g rayli` to update."
61 | }
62 | },
63 | "scripts": {
64 | "build": "shx rm -rf dist && tsc -b",
65 | "format": "prettier --write .",
66 | "lint": "eslint . --ext .ts --config .eslintrc",
67 | "postpack": "shx rm -f oclif.manifest.json",
68 | "posttest": "yarn lint",
69 | "prepack": "yarn build && oclif manifest && oclif readme",
70 | "version": "oclif readme && git add README.md"
71 | },
72 | "engines": {
73 | "node": ">=12.0.0"
74 | },
75 | "resolutions": {
76 | "@lucasliet/imgur-ts/**/@rmp135/imgur/**/axios": "^0.27.0"
77 | },
78 | "bugs": "https://github.com/buidler-hub/rayli/issues",
79 | "keywords": [
80 | "cli",
81 | "automation",
82 | "ray.so",
83 | "code-image"
84 | ],
85 | "types": "dist/index.d.ts"
86 | }
87 |
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "config:base"
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/src/commands/config/index.ts:
--------------------------------------------------------------------------------
1 | import { Command } from "@oclif/core";
2 | import inquirer from "inquirer";
3 | import ora from "ora";
4 | import chalk from "chalk";
5 |
6 | import configValues from "../../lib/configValues";
7 |
8 | import { configQuestions as questions } from "../../data/questions";
9 |
10 | export default class Config extends Command {
11 | static description = "🔐 Configure the default values";
12 |
13 | static examples = ["$ rayli config"];
14 |
15 | async run() {
16 | inquirer.prompt(questions).then(answers => {
17 | const spinner = ora("🔐 Configuring the default values").start();
18 |
19 | try {
20 | configValues(
21 | answers.color,
22 | answers.background,
23 | answers.darkMode,
24 | answers.padding
25 | );
26 |
27 | spinner.succeed(
28 | chalk.greenBright("Successfully configured the values")
29 | );
30 | } catch (err) {
31 | spinner.fail(
32 | chalk.redBright(
33 | "Something went wrong while configuring the default values"
34 | )
35 | );
36 | console.log(err);
37 | }
38 | });
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/commands/config/remove.ts:
--------------------------------------------------------------------------------
1 | import { Command } from "@oclif/core";
2 | import chalk from "chalk";
3 | import console from "console";
4 | import fs from "fs";
5 |
6 | import { CONFIG_FILE_PATH as configFilePath } from "../../data/constants";
7 |
8 | import configFileExists from "../../utils/configFileExists";
9 |
10 | export default class Show extends Command {
11 | static description = "🚚 Remove the configured values";
12 |
13 | static examples = ["$ rayli config:remove"];
14 |
15 | async run() {
16 | if (configFileExists()) {
17 | try {
18 | fs.unlinkSync(configFilePath);
19 | console.log(
20 | chalk.greenBright("🚚 Successfully removed the configured values.")
21 | );
22 | } catch (err) {
23 | console.log(
24 | chalk.redBright(
25 | "Something went wrong while removing the configured values."
26 | )
27 | );
28 | console.log(err);
29 | }
30 | } else {
31 | console.log(
32 | chalk.redBright(
33 | "🔐 Default configured values not found. Use `rayli config` to configure them."
34 | )
35 | );
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/commands/config/show.ts:
--------------------------------------------------------------------------------
1 | import { Command } from "@oclif/core";
2 | import chalk from "chalk";
3 |
4 | import configFileExists from "../../utils/configFileExists";
5 | import getConfigValues from "../../utils/getConfigValues";
6 |
7 | export default class Show extends Command {
8 | static description = "👀 Check your configured values";
9 |
10 | static examples = ["$ rayli config:show"];
11 |
12 | async run() {
13 | if (configFileExists()) {
14 | const configValues = getConfigValues();
15 |
16 | console.log(
17 | chalk.greenBright(
18 | `🔐 Your default configured values are:\n\n- 🎨 Color: ${configValues.color}\n- 🖼 Background: ${configValues.background}\n- 🌑 Dark Mode: ${configValues.darkMode}\n- 🖌 Padding: ${configValues.padding}`
19 | )
20 | );
21 | } else {
22 | console.log(
23 | chalk.redBright(
24 | "🔐 Default configured values not found. Use `rayli config` to configure them."
25 | )
26 | );
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/commands/generate.ts:
--------------------------------------------------------------------------------
1 | import { Command, Flags } from "@oclif/core";
2 | import inquirer from "inquirer";
3 | import fs from "fs";
4 | import chalk from "chalk";
5 |
6 | import createImage from "../lib/createImage";
7 |
8 | import configFileExists from "../utils/configFileExists";
9 | import getConfigValues from "../utils/getConfigValues";
10 |
11 | import {
12 | questions as questions,
13 | requiresUserInputQuestions,
14 | } from "../data/questions";
15 |
16 | export default class Generate extends Command {
17 | static description = "📷 Generate a beautiful image of your code snippet";
18 |
19 | static examples = ["$ rayli generate"];
20 |
21 | static flags = {
22 | config: Flags.boolean({
23 | char: "c",
24 | description: "🔐 Use the default configured values",
25 | allowNo: true,
26 | }),
27 | };
28 |
29 | async run() {
30 | const { flags } = await this.parse(Generate);
31 |
32 | let promptQuestions = questions;
33 |
34 | if (flags.config === true) {
35 | if (configFileExists()) {
36 | promptQuestions = requiresUserInputQuestions;
37 | } else {
38 | console.log(
39 | chalk.redBright(
40 | "Default configured values not found. Use `rayli config` to configure them."
41 | )
42 | );
43 | process.exit(1);
44 | }
45 | }
46 |
47 | inquirer.registerPrompt("search-list", require("inquirer-search-list"));
48 |
49 | inquirer.prompt(promptQuestions).then(answers => {
50 | let color = answers.color,
51 | background = answers.background,
52 | darkMode = answers.darkMode,
53 | padding = answers.padding;
54 |
55 | if (flags.config) {
56 | color = getConfigValues().color;
57 | background = getConfigValues().background;
58 | darkMode = getConfigValues().darkMode;
59 | padding = getConfigValues().padding;
60 | }
61 |
62 | createImage(
63 | color,
64 | background,
65 | darkMode,
66 | padding,
67 | fs.readFileSync(answers.code, "utf-8"),
68 | answers.language,
69 | answers.title,
70 | answers.download
71 | );
72 | });
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/commands/gist.ts:
--------------------------------------------------------------------------------
1 | import { Command, Flags } from "@oclif/core";
2 | import inquirer from "inquirer";
3 | import chalk from "chalk";
4 |
5 | import createImage from "../lib/createImage";
6 | import truncateGist from "../lib/truncateGist";
7 |
8 | import getGistContent from "../utils/getGistContent";
9 | import configFileExists from "../utils/configFileExists";
10 | import getConfigValues from "../utils/getConfigValues";
11 | import verifyGistLink from "../utils/verifyGistLink";
12 |
13 | import {
14 | gistQuestions as questions,
15 | gistRequiresUserInputQuestions,
16 | } from "../data/questions";
17 |
18 | export default class Gist extends Command {
19 | static description = "🌌 Generate a beautiful image of your gist";
20 |
21 | static examples = [
22 | "$ rayli gist --url=https://gist.github.com/tastatham/7305dc8f8ea5dc51da3c227fa92ed9d4 --range=1-10",
23 | ];
24 |
25 | static flags = {
26 | url: Flags.string({
27 | char: "u",
28 | description: "🔗 Link of the gist",
29 | required: true,
30 | }),
31 | config: Flags.boolean({
32 | char: "c",
33 | description: "🔐 Use the default configured values",
34 | allowNo: true,
35 | }),
36 | range: Flags.string({
37 | char: "r",
38 | description: "🔍 Range of the gist",
39 | }),
40 | };
41 |
42 | async run() {
43 | const { flags } = await this.parse(Gist);
44 |
45 | let gistId = flags.url.split("/").pop();
46 | let promptQuestions = questions;
47 | let content = await getGistContent(gistId as string).then(
48 | content => content
49 | );
50 |
51 | if ((await verifyGistLink(flags.url as string)) === false) {
52 | console.error(
53 | chalk.red(
54 | "🚫 The link you provided is not a valid Gist link. Please try again."
55 | )
56 | );
57 | return;
58 | }
59 |
60 | if (flags.config === true) {
61 | if (configFileExists()) {
62 | promptQuestions = gistRequiresUserInputQuestions;
63 | } else {
64 | console.log(
65 | chalk.redBright(
66 | "🔐 Default configured values not found. Use `rayli config` to configure them."
67 | )
68 | );
69 | process.exit(1);
70 | }
71 | }
72 |
73 | if (flags.range) {
74 | try {
75 | content = (await truncateGist(flags.range, gistId as string)) as string;
76 | } catch (err) {
77 | console.error(chalk.red(err));
78 | return;
79 | }
80 | }
81 |
82 | inquirer.registerPrompt("search-list", require("inquirer-search-list"));
83 |
84 | inquirer.prompt(promptQuestions).then(async answers => {
85 | let color = answers.color,
86 | background = answers.background,
87 | darkMode = answers.darkMode,
88 | padding = answers.padding;
89 |
90 | if (flags.config) {
91 | color = getConfigValues().color;
92 | background = getConfigValues().background;
93 | darkMode = getConfigValues().darkMode;
94 | padding = getConfigValues().padding;
95 | }
96 |
97 | createImage(
98 | color,
99 | background,
100 | darkMode,
101 | padding,
102 | content,
103 | answers.language,
104 | answers.title,
105 | answers.download
106 | );
107 | });
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/src/commands/github.ts:
--------------------------------------------------------------------------------
1 | import { Command, Flags } from "@oclif/core";
2 | import inquirer from "inquirer";
3 | import chalk from "chalk";
4 |
5 | import createImage from "../lib/createImage";
6 |
7 | import getGitHubFileContent from "../utils/getGitHubFileContent";
8 | import configFileExists from "../utils/configFileExists";
9 | import getConfigValues from "../utils/getConfigValues";
10 | import verifyGitHubLink from "../utils/verifyGitHubLink";
11 |
12 | import {
13 | gistQuestions,
14 | gistRequiresUserInputQuestions,
15 | } from "../data/questions";
16 |
17 | export default class Github extends Command {
18 | static description =
19 | "🐱 Generate a beautiful image of your code hosted on GitHub";
20 |
21 | static examples = [
22 | "$ rayli github --url=https://github.com/buidler-hub/projects/blob/main/README.md",
23 | ];
24 |
25 | static flags = {
26 | url: Flags.string({
27 | char: "u",
28 | description: "🔗 Link of the code",
29 | required: true,
30 | }),
31 | config: Flags.boolean({
32 | char: "c",
33 | description: "🔐 Use the default configured values",
34 | allowNo: true,
35 | }),
36 | };
37 |
38 | async run() {
39 | const { flags } = await this.parse(Github);
40 |
41 | let promptQuestions = gistQuestions;
42 |
43 | if ((await verifyGitHubLink(flags.url as string)) === false) {
44 | console.error(
45 | chalk.red(
46 | "🚫 The link you provided is not a valid GitHub link. Please try again."
47 | )
48 | );
49 | return;
50 | }
51 |
52 | if (flags.config === true) {
53 | if (configFileExists()) {
54 | promptQuestions = gistRequiresUserInputQuestions;
55 | } else {
56 | console.log(
57 | chalk.redBright(
58 | "Default configured values not found. Use `rayli config` to configure them."
59 | )
60 | );
61 | process.exit(1);
62 | }
63 | }
64 |
65 | inquirer.registerPrompt("search-list", require("inquirer-search-list"));
66 |
67 | inquirer.prompt(promptQuestions).then(async answers => {
68 | let color = answers.color,
69 | background = answers.background,
70 | darkMode = answers.darkMode,
71 | padding = answers.padding;
72 |
73 | if (flags.config) {
74 | color = getConfigValues().color;
75 | background = getConfigValues().background;
76 | darkMode = getConfigValues().darkMode;
77 | padding = getConfigValues().padding;
78 | }
79 |
80 | await getGitHubFileContent(flags.url).then(content =>
81 | createImage(
82 | color,
83 | background,
84 | darkMode,
85 | padding,
86 | content,
87 | answers.language,
88 | answers.title,
89 | answers.download
90 | )
91 | );
92 | });
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/src/commands/imgur.ts:
--------------------------------------------------------------------------------
1 | import { Command } from "@oclif/core";
2 | import inquirer from "inquirer";
3 |
4 | import uploadToImgur from "../utils/uploadToImgur";
5 |
6 | import { imgurQuestions as questions } from "../data/questions";
7 |
8 | export default class Imgur extends Command {
9 | static description = "✈ Upload an image to Imgur";
10 |
11 | static examples = ["$ rayli imgur"];
12 |
13 | async run() {
14 | inquirer.prompt(questions).then(answers => {
15 | uploadToImgur(answers.path);
16 | });
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/data/constants.ts:
--------------------------------------------------------------------------------
1 | import os from "os";
2 |
3 | const CONFIG_FILE_PATH = `${os.homedir()}/.config/.rayli/config.json`;
4 |
5 | export { CONFIG_FILE_PATH };
6 |
--------------------------------------------------------------------------------
/src/data/options.ts:
--------------------------------------------------------------------------------
1 | const colors = [
2 | "Breeze",
3 | "Candy",
4 | "Crimson",
5 | "Falcon",
6 | "Meadow",
7 | "Midnight",
8 | "Raindrop",
9 | "Sunset",
10 | ];
11 |
12 | const languages = [
13 | "Auto-Detect",
14 | "Bash",
15 | "C++",
16 | "C#",
17 | "Clojure",
18 | "CoffeeScript",
19 | "Crystal",
20 | "CSS",
21 | "D",
22 | "Dart",
23 | "Diff",
24 | "Docker",
25 | "Elm",
26 | "Erlang",
27 | "Fortran",
28 | "F#",
29 | "Gherkin",
30 | "Go",
31 | "Groovy",
32 | "Haskell",
33 | "HTTP",
34 | "HTML",
35 | "Java",
36 | "JavaScript",
37 | "JSON",
38 | "JSX",
39 | "Julia",
40 | "Kotlin",
41 | "LaTeX",
42 | "Lisp",
43 | "Lua",
44 | "Markdown",
45 | "Mathematica",
46 | "MATLAB/Octave",
47 | "NGINX",
48 | "Plaintext",
49 | "Objective C",
50 | "OCaml",
51 | "Perl",
52 | "PHP",
53 | "PowerShell",
54 | "Python",
55 | "R",
56 | "Ruby",
57 | "Rust",
58 | "Scala",
59 | "SCSS",
60 | "Smalltalk",
61 | "SQL",
62 | "Swift",
63 | "TOML",
64 | "TypeScript",
65 | "TSX",
66 | "Twig",
67 | "VB.NET",
68 | "Verilog",
69 | "VHDL",
70 | "XQuery",
71 | "YAML",
72 | ];
73 |
74 | export { colors, languages };
75 |
--------------------------------------------------------------------------------
/src/data/questions.ts:
--------------------------------------------------------------------------------
1 | import fs from "fs";
2 | import isImage from "is-image";
3 |
4 | import { colors, languages } from "./options";
5 |
6 | const questions = [
7 | {
8 | type: "search-list",
9 | name: "color",
10 | message: "What color style do you want to use?",
11 | choices: colors,
12 | },
13 | {
14 | type: "list",
15 | name: "background",
16 | message: "Do you want to have background?",
17 | choices: ["true", "false"],
18 | },
19 | {
20 | type: "list",
21 | name: "darkMode",
22 | message: "Do you want to use dark mode?",
23 | choices: ["true", "false"],
24 | },
25 | {
26 | type: "list",
27 | name: "padding",
28 | message: "Choose the amount of padding you want to use:",
29 | choices: ["16", "32", "64", "128"],
30 | },
31 | {
32 | type: "input",
33 | name: "code",
34 | message: "Enter the path of the file where the code of snippet is present:",
35 | validate: (path: string) => {
36 | if (path.length > 0) {
37 | if (fs.existsSync(path)) {
38 | return true;
39 | }
40 | return "The file doesn't exist";
41 | } else {
42 | return "Please enter a valid file path";
43 | }
44 | },
45 | },
46 | {
47 | type: "search-list",
48 | name: "language",
49 | message: "Choose the programming language of your code snippet:",
50 | choices: languages,
51 | },
52 | {
53 | type: "input",
54 | name: "title",
55 | message: "Enter the title of your snippet:",
56 | validate: async (value: string) => {
57 | if (value.length > 0) {
58 | return true;
59 | } else {
60 | return "Please enter a title for your snippet";
61 | }
62 | },
63 | },
64 | {
65 | type: "input",
66 | name: "download",
67 | message: "Enter the path where do you want to save the image:",
68 | validate: async (path: string) => {
69 | if (path.length > 0) {
70 | if (fs.existsSync(path)) {
71 | return true;
72 | }
73 | return "The provided path does not exist";
74 | } else {
75 | return "Please enter a valid path";
76 | }
77 | },
78 | },
79 | ];
80 |
81 | const configQuestions = questions.slice(0, 4);
82 |
83 | const requiresUserInputQuestions = questions.slice(-4);
84 |
85 | const newArr = [...questions];
86 |
87 | newArr.splice(4, 1);
88 |
89 | const gistQuestions = newArr;
90 |
91 | const gistRequiresUserInputQuestions = gistQuestions.slice(-3);
92 |
93 | const imgurQuestions = [
94 | {
95 | type: "input",
96 | name: "path",
97 | message: "Enter the path where the image exists:",
98 | validate: async (path: string) => {
99 | if (isImage(path)) {
100 | return true;
101 | } else {
102 | return "Please enter a valid image file's path";
103 | }
104 | },
105 | },
106 | ];
107 |
108 | export {
109 | questions,
110 | configQuestions,
111 | requiresUserInputQuestions,
112 | gistQuestions,
113 | gistRequiresUserInputQuestions,
114 | imgurQuestions,
115 | };
116 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | export { run } from "@oclif/core";
2 |
--------------------------------------------------------------------------------
/src/lib/configValues.ts:
--------------------------------------------------------------------------------
1 | import fs from "fs";
2 | import path from "path";
3 | import shelljs from "shelljs";
4 |
5 | import configFileExists from "../utils/configFileExists";
6 |
7 | import { CONFIG_FILE_PATH as configFilePath } from "../data/constants";
8 |
9 | const configValues = (
10 | color: string,
11 | background: boolean,
12 | darkMode: boolean,
13 | padding: string
14 | ) => {
15 | const config = JSON.stringify(
16 | {
17 | color: color,
18 | background: background,
19 | darkMode: darkMode,
20 | padding: padding,
21 | },
22 | null
23 | );
24 |
25 | if (configFileExists()) {
26 | fs.unlinkSync(configFilePath);
27 | }
28 |
29 | shelljs.mkdir("-p", path.dirname(configFilePath));
30 | fs.appendFileSync(configFilePath, config);
31 | };
32 |
33 | export default configValues;
34 |
--------------------------------------------------------------------------------
/src/lib/createImage.ts:
--------------------------------------------------------------------------------
1 | import puppeteer, { Browser } from "puppeteer";
2 | import path from "path";
3 | import chalk from "chalk";
4 | import ora from "ora";
5 |
6 | import convertCodeIntoBase64 from "../utils/convertCodeIntoBase64";
7 |
8 | const createImage = async (
9 | color: string,
10 | background: boolean,
11 | darkMode: boolean,
12 | padding: string,
13 | code: string,
14 | language: string,
15 | title: string,
16 | downloadPath: string
17 | ) => {
18 | const spinner = ora("📷 Generating image...").start();
19 | const browser = await puppeteer.launch();
20 |
21 | try {
22 | const page = await browser.newPage();
23 |
24 | const base64EncodedCode = convertCodeIntoBase64(code);
25 |
26 | await page.goto(
27 | `https://ray.so/?colors=${color.toLowerCase()}&background=${background}&darkMode=${darkMode}&padding=${padding}&title=${encodeURI(
28 | title
29 | )}&code=${base64EncodedCode}&language=${language.toLowerCase()}`
30 | );
31 |
32 | const resolvedDownloadPath = path.resolve(
33 | `${process.cwd()}/${downloadPath}`
34 | );
35 |
36 | await (page as any)._client.send("Page.setDownloadBehavior", {
37 | behavior: "allow",
38 | downloadPath: resolvedDownloadPath,
39 | });
40 |
41 | await page.click(".export");
42 |
43 | setTimeout(
44 | async () =>
45 | await browser
46 | .close()
47 | .then(() =>
48 | spinner.succeed(
49 | chalk.green(
50 | `📸 Image of your code has been saved at ${resolvedDownloadPath}`
51 | )
52 | )
53 | ),
54 | 2000
55 | );
56 | } catch (err) {
57 | spinner.fail();
58 | await browser.close();
59 | console.log(chalk.red("An error occurred while generating image"));
60 | console.log(chalk.red(err));
61 | }
62 | };
63 |
64 | export default createImage;
65 |
--------------------------------------------------------------------------------
/src/lib/truncateGist.ts:
--------------------------------------------------------------------------------
1 | import chalk from "chalk";
2 |
3 | import getGistContent from "../utils/getGistContent";
4 |
5 | const truncateGist = async (range: string, gistId: string) => {
6 | try {
7 | const [start, end] = range.split("-");
8 |
9 | const content = getGistContent(gistId);
10 | const contentArray = (await content).split("\n");
11 |
12 | const startIndex = parseInt(start, 10);
13 | const endIndex = parseInt(end, 10);
14 |
15 | if (typeof startIndex !== "number" && typeof endIndex !== "number") {
16 | throw new Error("Invalid range");
17 | }
18 |
19 | if (startIndex > endIndex) {
20 | throw chalk.red(
21 | "The start index cannot be greater than the end index. Please try again."
22 | );
23 | }
24 |
25 | if (startIndex < 0 || endIndex < 0) {
26 | throw chalk.red(
27 | "The start index and end index cannot be less than 0. Please try again."
28 | );
29 | }
30 |
31 | if (startIndex > contentArray.length || endIndex > contentArray.length) {
32 | throw chalk.red(
33 | "The start index and end index cannot be greater than the length of the Gist. Please try again."
34 | );
35 | }
36 |
37 | const truncatedContent = contentArray.slice(startIndex - 1, endIndex);
38 |
39 | return truncatedContent.join("\n");
40 | } catch (err) {
41 | throw chalk.red(err);
42 | }
43 | };
44 |
45 | export default truncateGist;
46 |
--------------------------------------------------------------------------------
/src/utils/configFileExists.ts:
--------------------------------------------------------------------------------
1 | import fs from "fs";
2 |
3 | import { CONFIG_FILE_PATH as configFilePath } from "../data/constants";
4 |
5 | const configFileExists = () => {
6 | try {
7 | fs.accessSync(configFilePath, fs.constants.F_OK);
8 | return true;
9 | } catch (err) {
10 | return false;
11 | }
12 | };
13 |
14 | export default configFileExists;
15 |
--------------------------------------------------------------------------------
/src/utils/convertCodeIntoBase64.ts:
--------------------------------------------------------------------------------
1 | import { Base64 } from "js-base64";
2 |
3 | const convertCodeIntoBase64 = (code: string) => {
4 | return Base64.encodeURI(code);
5 | };
6 |
7 | export default convertCodeIntoBase64;
8 |
--------------------------------------------------------------------------------
/src/utils/getConfigValues.ts:
--------------------------------------------------------------------------------
1 | import fs from "fs";
2 |
3 | import { CONFIG_FILE_PATH as configFilePath } from "../data/constants";
4 |
5 | const getConfigValues = () => {
6 | try {
7 | const config = fs.readFileSync(configFilePath, "utf-8");
8 | return JSON.parse(config);
9 | } catch (err) {
10 | return null;
11 | }
12 | };
13 |
14 | export default getConfigValues;
15 |
--------------------------------------------------------------------------------
/src/utils/getGistContent.ts:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 |
3 | const getGistContent = async (gistId: string): Promise => {
4 | const response = await axios
5 | .get(`https://api.github.com/gists/${gistId}`)
6 | .then(res => res.data);
7 |
8 | const file = Object.keys(response.files)[0];
9 | const content = response.files[file].content;
10 | return content;
11 | };
12 |
13 | export default getGistContent;
14 |
--------------------------------------------------------------------------------
/src/utils/getGitHubFileContent.ts:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 |
3 | const getGitHubFileContent = async (githubFileLink: string) => {
4 | const rawContentLink = githubFileLink
5 | .replace("github.com", "raw.githubusercontent.com")
6 | .replace("/blob/", "/");
7 | const response = await axios.get(rawContentLink).then(res => res.data);
8 | return response;
9 | };
10 |
11 | export default getGitHubFileContent;
12 |
--------------------------------------------------------------------------------
/src/utils/uploadToImgur.ts:
--------------------------------------------------------------------------------
1 | import fs from "fs";
2 | import upload from "@lucasliet/imgur-ts";
3 | import ora from "ora";
4 |
5 | const uploadToImgur = async (filePath: string) => {
6 | const spinner = ora("Uploading to Imgur").start();
7 | try {
8 | const link = await upload(filePath);
9 | spinner.succeed(`Uploaded to Imgur: ${link}`);
10 | } catch (error) {
11 | spinner.fail(`Failed to upload to Imgur: ${error}`);
12 | }
13 | };
14 |
15 | export default uploadToImgur;
16 |
--------------------------------------------------------------------------------
/src/utils/verifyGistLink.ts:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 |
3 | const verifyGistLink = async (gistLink: string) => {
4 | let gistId = gistLink.split("/").pop();
5 | try {
6 | const response = await axios.get(`https://api.github.com/gists/${gistId}`);
7 | if (response.status === 200) {
8 | return true;
9 | } else {
10 | return false;
11 | }
12 | } catch {
13 | return false;
14 | }
15 | };
16 |
17 | export default verifyGistLink;
18 |
--------------------------------------------------------------------------------
/src/utils/verifyGitHubLink.ts:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 |
3 | const verifyGitHubLink = async (githubLink: string) => {
4 | const rawContentLink = githubLink
5 | .replace("github.com", "raw.githubusercontent.com")
6 | .replace("/blob/", "/");
7 |
8 | try {
9 | const response = await axios.get(rawContentLink);
10 | if (response.status === 200) {
11 | return true;
12 | } else {
13 | return false;
14 | }
15 | } catch {
16 | return false;
17 | }
18 | };
19 |
20 | export default verifyGitHubLink;
21 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "declaration": true,
4 | "importHelpers": true,
5 | "module": "commonjs",
6 | "outDir": "dist",
7 | "rootDir": ".",
8 | "strict": true,
9 | "target": "es2019",
10 | "esModuleInterop": true,
11 | "resolveJsonModule": true
12 | },
13 | "include": ["src/**/*"]
14 | }
15 |
--------------------------------------------------------------------------------