mirror of
https://github.com/ragestudio/comty.git
synced 2025-06-09 10:34:17 +00:00
merge
This commit is contained in:
parent
536d7b82d5
commit
8cd34fafcb
28
packages/cli/package.json
Normal file
28
packages/cli/package.json
Normal file
@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "@ragestudio/comty-cli",
|
||||
"description": "Command line interface for Comty Services",
|
||||
"version": "0.1.2",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"main": "./src/index.js",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"comty-cli": "./src/index.js"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node ./src/index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"7zip-min": "^2.0.0",
|
||||
"@inquirer/prompts": "^7.4.0",
|
||||
"axios": "^1.8.3",
|
||||
"commander": "^13.1.0",
|
||||
"comty.js": "^0.60.7",
|
||||
"form-data": "^4.0.2",
|
||||
"formdata-node": "^6.0.3",
|
||||
"glob": "^11.0.1",
|
||||
"yocto-spinner": "^0.2.1"
|
||||
}
|
||||
}
|
30
packages/cli/src/classes/cache.js
Normal file
30
packages/cli/src/classes/cache.js
Normal file
@ -0,0 +1,30 @@
|
||||
import path from "node:path"
|
||||
import fs from "node:fs"
|
||||
|
||||
import Config from "./config.js"
|
||||
|
||||
export default class Cache {
|
||||
static cachePath = path.resolve(Config.appWorkdir, "cache")
|
||||
|
||||
static async initialize() {
|
||||
if (!fs.existsSync(this.cachePath)) {
|
||||
await fs.promises.mkdir(this.cachePath, { recursive: true })
|
||||
}
|
||||
}
|
||||
|
||||
static async destroyTemporalDir(tempDirId) {
|
||||
const tempDir = path.resolve(this.cachePath, tempDirId)
|
||||
|
||||
await fs.promises.rm(tempDir, {
|
||||
recursive: true,
|
||||
})
|
||||
}
|
||||
|
||||
static async createTemporalDir() {
|
||||
const tempDir = path.join(this.cachePath, `temp-${Date.now()}`)
|
||||
|
||||
await fs.promises.mkdir(tempDir, { recursive: true })
|
||||
|
||||
return [tempDir, async () => await Cache.destroyTemporalDir(tempDir)]
|
||||
}
|
||||
}
|
51
packages/cli/src/classes/config.js
Normal file
51
packages/cli/src/classes/config.js
Normal file
@ -0,0 +1,51 @@
|
||||
import fs from "node:fs"
|
||||
import path from "node:path"
|
||||
import os from "node:os"
|
||||
|
||||
export default class Config {
|
||||
static appWorkdir = path.resolve(os.homedir(), ".comty-cli")
|
||||
static configFilePath = path.resolve(Config.appWorkdir, "config.json")
|
||||
|
||||
data = {}
|
||||
|
||||
async initialize() {
|
||||
if (!fs.existsSync(path.dirname(Config.configFilePath))) {
|
||||
fs.mkdirSync(path.dirname(Config.configFilePath), {
|
||||
recursive: true,
|
||||
})
|
||||
}
|
||||
|
||||
if (!fs.existsSync(Config.configFilePath)) {
|
||||
fs.writeFileSync(Config.configFilePath, JSON.stringify({}))
|
||||
}
|
||||
|
||||
this.data = JSON.parse(
|
||||
await fs.promises.readFile(Config.configFilePath, "utf8"),
|
||||
)
|
||||
}
|
||||
|
||||
async write() {
|
||||
await fs.promises.writeFile(
|
||||
Config.configFilePath,
|
||||
JSON.stringify(this.data),
|
||||
)
|
||||
}
|
||||
|
||||
get(key) {
|
||||
return this.data[key]
|
||||
}
|
||||
|
||||
async set(key, value) {
|
||||
this.data[key] = value
|
||||
|
||||
await this.write()
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
async delete(key) {
|
||||
delete this.data[key]
|
||||
|
||||
await this.write()
|
||||
}
|
||||
}
|
8
packages/cli/src/commands/auth/index.js
Normal file
8
packages/cli/src/commands/auth/index.js
Normal file
@ -0,0 +1,8 @@
|
||||
import authorizeAccount from "../../utils/authorizeAccount.js"
|
||||
|
||||
export default {
|
||||
cmd: "auth",
|
||||
fn: async () => {
|
||||
await authorizeAccount()
|
||||
},
|
||||
}
|
100
packages/cli/src/commands/publish/index.js
Normal file
100
packages/cli/src/commands/publish/index.js
Normal file
@ -0,0 +1,100 @@
|
||||
import fs from "node:fs"
|
||||
import path from "node:path"
|
||||
import yoctoSpinner from "yocto-spinner"
|
||||
import { fileFromPath } from "formdata-node/file-from-path"
|
||||
import FormData from "form-data"
|
||||
|
||||
import Cache from "../../classes/cache.js"
|
||||
import getUploadsPaths from "../../utils/getUploadsPaths.js"
|
||||
import compressFiles from "../../utils/compressFiles.js"
|
||||
|
||||
import Request from "comty.js/dist/request.js"
|
||||
|
||||
export default {
|
||||
cmd: "publish",
|
||||
arguments: [
|
||||
{
|
||||
argument: "<cwd>",
|
||||
description: "Set the current working directory",
|
||||
},
|
||||
],
|
||||
fn: async (customCwd) => {
|
||||
const token = global.config.get("auth").token
|
||||
const projectFolder = customCwd ?? process.cwd()
|
||||
const pkgJsonPath = path.join(projectFolder, "package.json")
|
||||
|
||||
let pkgJSON = null
|
||||
|
||||
if (!fs.existsSync(pkgJsonPath)) {
|
||||
console.error("package.json not found")
|
||||
return 1
|
||||
}
|
||||
|
||||
pkgJSON = JSON.parse(fs.readFileSync(pkgJsonPath, "utf8"))
|
||||
console.log(`⚙️ Publishing ${pkgJSON.name}@${pkgJSON.version}`)
|
||||
|
||||
const [temporalDir, destroyTemporalDir] =
|
||||
await Cache.createTemporalDir()
|
||||
|
||||
const spinner = yoctoSpinner({ text: "Loading…" }).start()
|
||||
|
||||
try {
|
||||
spinner.text = "Reading files..."
|
||||
const paths = await getUploadsPaths(pkgJsonPath)
|
||||
|
||||
const originPath = path.join(temporalDir, "origin")
|
||||
const bundlePath = path.join(temporalDir, "bundle.7z")
|
||||
|
||||
// copy files and dirs to origin path
|
||||
spinner.text = "Copying files and directories"
|
||||
for await (const file of paths) {
|
||||
await fs.promises.cp(
|
||||
file,
|
||||
path.join(originPath, path.basename(file)),
|
||||
{ recursive: true },
|
||||
)
|
||||
}
|
||||
|
||||
spinner.text = "Compressing files"
|
||||
await compressFiles(`${originPath}/*`, bundlePath)
|
||||
|
||||
// PUT to registry
|
||||
const bodyData = new FormData()
|
||||
|
||||
//bodyData.append("pkg", JSON.stringify(pkgJSON))
|
||||
bodyData.append("bundle", fs.createReadStream(bundlePath))
|
||||
|
||||
spinner.text = "Publishing extension"
|
||||
const response = await Request.default({
|
||||
method: "PUT",
|
||||
url: "/extensions/publish",
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
"Content-Type": "multipart/form-data",
|
||||
pkg: JSON.stringify(pkgJSON),
|
||||
},
|
||||
data: bodyData,
|
||||
}).catch((error) => {
|
||||
throw new Error(
|
||||
`Failed to publish extension: ${error.response?.data?.error ?? error.message}`,
|
||||
)
|
||||
})
|
||||
|
||||
// cleanup
|
||||
spinner.text = "Cleaning up"
|
||||
await destroyTemporalDir()
|
||||
|
||||
spinner.success(`Ok!`)
|
||||
|
||||
console.log(response.data)
|
||||
return 0
|
||||
} catch (error) {
|
||||
await destroyTemporalDir()
|
||||
|
||||
spinner.error(`${error.message}`)
|
||||
console.error(error)
|
||||
|
||||
return 1
|
||||
}
|
||||
},
|
||||
}
|
3
packages/cli/src/commands/template/index.js
Normal file
3
packages/cli/src/commands/template/index.js
Normal file
@ -0,0 +1,3 @@
|
||||
export default {
|
||||
cmd: "template",
|
||||
}
|
63
packages/cli/src/index.js
Executable file
63
packages/cli/src/index.js
Executable file
@ -0,0 +1,63 @@
|
||||
#!/usr/bin/env node
|
||||
import fs from "node:fs"
|
||||
import path from "node:path"
|
||||
import { Command } from "commander"
|
||||
import { createClient } from "comty.js"
|
||||
import SessionModel from "comty.js/dist/models/session/index.js"
|
||||
|
||||
import Cache from "./classes/cache.js"
|
||||
import Config from "./classes/config.js"
|
||||
|
||||
import readCommandsFiles from "./utils/readCommandsFiles.js"
|
||||
import importDefaults from "./utils/importDefaults.js"
|
||||
import buildCommands from "./utils/buildCommands.js"
|
||||
import authorizeAccount from "./utils/authorizeAccount.js"
|
||||
|
||||
const commandsPath = path.resolve(import.meta.dirname, "commands")
|
||||
const packageJsonPath = path.resolve(import.meta.dirname, "../package.json")
|
||||
|
||||
// only for development
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = 0
|
||||
}
|
||||
|
||||
async function main() {
|
||||
let packageJson = await fs.promises.readFile(packageJsonPath, "utf8")
|
||||
packageJson = JSON.parse(packageJson)
|
||||
|
||||
global.config = new Config()
|
||||
global.comtyClient = createClient({
|
||||
origin:
|
||||
process.env.NODE_ENV === "production"
|
||||
? "https://api.comty.app"
|
||||
: "https://indev.comty.app/api",
|
||||
})
|
||||
|
||||
await global.config.initialize()
|
||||
await Cache.initialize()
|
||||
|
||||
if (!global.config.get("auth")) {
|
||||
console.log("No auth found, authentication required...")
|
||||
|
||||
await authorizeAccount()
|
||||
}
|
||||
|
||||
SessionModel.default.token = global.config.get("auth").token
|
||||
|
||||
let program = new Command()
|
||||
|
||||
program
|
||||
.name(packageJson.name)
|
||||
.description(packageJson.description)
|
||||
.version(packageJson.version)
|
||||
|
||||
let commands = await readCommandsFiles(commandsPath)
|
||||
commands = await importDefaults(commands)
|
||||
program = await buildCommands(commands, program)
|
||||
|
||||
program.parse()
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
main()
|
25
packages/cli/src/utils/authorizeAccount.js
Normal file
25
packages/cli/src/utils/authorizeAccount.js
Normal file
@ -0,0 +1,25 @@
|
||||
import * as prompts from "@inquirer/prompts"
|
||||
import AuthModel from "comty.js/dist/models/auth/index.js"
|
||||
|
||||
export default function authorizeAccount() {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const username = await prompts.input({
|
||||
message: "username or email >",
|
||||
})
|
||||
|
||||
const password = await prompts.password({
|
||||
message: "password >",
|
||||
})
|
||||
|
||||
try {
|
||||
const result = await AuthModel.default.login({ username, password })
|
||||
console.log("✅ Logged in successfully")
|
||||
|
||||
await global.config.set("auth", result)
|
||||
resolve(result)
|
||||
} catch (error) {
|
||||
console.error(`⛔ Failed to login: ${error.response.data.error}`)
|
||||
authorizeAccount()
|
||||
}
|
||||
})
|
||||
}
|
30
packages/cli/src/utils/buildCommands.js
Normal file
30
packages/cli/src/utils/buildCommands.js
Normal file
@ -0,0 +1,30 @@
|
||||
export default async function buildCommands(commands, program) {
|
||||
for await (const command of commands) {
|
||||
if (typeof command.fn !== "function") {
|
||||
continue
|
||||
}
|
||||
|
||||
const commandInstance = program.command(command.cmd).action(command.fn)
|
||||
|
||||
if (command.description) {
|
||||
commandInstance.description(command.description)
|
||||
}
|
||||
|
||||
if (command.options) {
|
||||
for (const option of command.options) {
|
||||
commandInstance.option(option.option, option.description)
|
||||
}
|
||||
}
|
||||
|
||||
if (command.arguments) {
|
||||
for (const argument of command.arguments) {
|
||||
commandInstance.argument(
|
||||
argument.argument,
|
||||
argument.description,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return program
|
||||
}
|
21
packages/cli/src/utils/compressFiles.js
Normal file
21
packages/cli/src/utils/compressFiles.js
Normal file
@ -0,0 +1,21 @@
|
||||
import fs from "node:fs"
|
||||
import sevenzip from "7zip-min"
|
||||
|
||||
export default async function compressFiles(origin, output) {
|
||||
// check if out file exists
|
||||
if (fs.existsSync(output)) {
|
||||
fs.unlinkSync(output)
|
||||
}
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
sevenzip.pack(origin, output, (err) => {
|
||||
if (err) {
|
||||
return reject(err)
|
||||
}
|
||||
|
||||
return resolve(output)
|
||||
})
|
||||
})
|
||||
|
||||
return output
|
||||
}
|
23
packages/cli/src/utils/getUploadsPaths.js
Normal file
23
packages/cli/src/utils/getUploadsPaths.js
Normal file
@ -0,0 +1,23 @@
|
||||
import fs from "node:fs"
|
||||
import path from "node:path"
|
||||
import { glob } from "glob"
|
||||
|
||||
export default async function getUploadsPaths(pkgJsonPath) {
|
||||
const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, "utf8"))
|
||||
const projectFolder = path.dirname(pkgJsonPath)
|
||||
//const gitIgnore = await readGitIgnore(projectFolder)
|
||||
|
||||
let globs = []
|
||||
|
||||
if (Array.isArray(pkgJson.files)) {
|
||||
globs.push(...pkgJson.files)
|
||||
}
|
||||
|
||||
globs = globs.map((glob) => path.resolve(projectFolder, glob))
|
||||
|
||||
globs = await glob(globs, { cwd: projectFolder })
|
||||
|
||||
globs.push(pkgJsonPath)
|
||||
|
||||
return globs
|
||||
}
|
10
packages/cli/src/utils/importDefaults.js
Normal file
10
packages/cli/src/utils/importDefaults.js
Normal file
@ -0,0 +1,10 @@
|
||||
export default async function importDefaults(commands) {
|
||||
const result = []
|
||||
|
||||
for await (const command of commands) {
|
||||
const commandModule = await import(command)
|
||||
result.push(commandModule.default)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
18
packages/cli/src/utils/readCommandsFiles.js
Normal file
18
packages/cli/src/utils/readCommandsFiles.js
Normal file
@ -0,0 +1,18 @@
|
||||
import fs from "node:fs"
|
||||
import path from "node:path"
|
||||
|
||||
export default async function readCommandsFiles(from) {
|
||||
const result = []
|
||||
let files = await fs.promises.readdir(from)
|
||||
|
||||
for (const file of files) {
|
||||
const filePath = path.join(from, file)
|
||||
const stat = await fs.promises.stat(filePath)
|
||||
|
||||
if (stat.isDirectory()) {
|
||||
result.push(path.resolve(from, file, "index.js"))
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
12
packages/cli/src/utils/readGitIgnore.js
Normal file
12
packages/cli/src/utils/readGitIgnore.js
Normal file
@ -0,0 +1,12 @@
|
||||
import fs from "node:fs"
|
||||
import path from "node:path"
|
||||
|
||||
export default async function readGitIgnore(projectFolder) {
|
||||
const gitIgnorePath = path.join(projectFolder, ".gitignore")
|
||||
|
||||
if (!fs.existsSync(gitIgnorePath)) {
|
||||
return []
|
||||
}
|
||||
|
||||
return fs.readFileSync(gitIgnorePath, "utf8").split("\n").filter(Boolean)
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user