mirror of
https://github.com/Lifeforge-app/lifeforge.git
synced 2026-06-27 22:36:06 +00:00
feat(cli): add view and whoami commands, remove dependencies to npm
This commit is contained in:
@@ -1,3 +1,3 @@
|
||||
[install]
|
||||
[install.scopes]
|
||||
"@lifeforge" = "https://registry.lifeforge.dev/"
|
||||
"@lifeforge" = "https://registry.lifeforge.dev/:_authToken=${FORGISTRY_TOKEN}"
|
||||
|
||||
@@ -11,6 +11,7 @@ import dev from '@/commands/dev'
|
||||
import locales from '@/commands/locales'
|
||||
import modules from '@/commands/modules'
|
||||
import project from '@/commands/project'
|
||||
import whoami from '@/commands/whoami'
|
||||
|
||||
type CommandSetup = (program: Command) => void
|
||||
|
||||
@@ -20,5 +21,6 @@ export const commands: CommandSetup[] = [
|
||||
dev,
|
||||
locales,
|
||||
modules,
|
||||
project
|
||||
project,
|
||||
whoami
|
||||
]
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { LOG_LEVELS } from '@lifeforge/log'
|
||||
import chalk from 'chalk'
|
||||
import { Command, program } from 'commander'
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
@@ -57,7 +58,7 @@ export function setupCLI(): void {
|
||||
cmd = cmd.parent
|
||||
}
|
||||
|
||||
logger.debug(`Executing command "${commandPath.join(' ')}"`)
|
||||
logger.info(`Executing command "${chalk.green(commandPath.join(' '))}"`)
|
||||
})
|
||||
|
||||
setupCommands(program)
|
||||
|
||||
@@ -8,13 +8,11 @@ import executeCommand from '@/utils/commands'
|
||||
import logger from '@/utils/logger'
|
||||
import normalizePackage from '@/utils/normalizePackage'
|
||||
|
||||
import { checkNPM, getRegistryUrl } from '../../../utils/registry'
|
||||
import { getRegistryUrl } from '../../../utils/registry'
|
||||
import validateLocalesAuthor from '../functions/validateLocalesAuthor'
|
||||
import { validateLocaleStructureHandler } from './validateLocaleStructure'
|
||||
|
||||
export async function publishLocaleHandler(langCode: string): Promise<void> {
|
||||
checkNPM()
|
||||
|
||||
const { fullName, targetDir } = normalizePackage(langCode, 'locale')
|
||||
|
||||
if (!fs.existsSync(targetDir)) {
|
||||
@@ -38,7 +36,7 @@ export async function publishLocaleHandler(langCode: string): Promise<void> {
|
||||
logger.info(`Publishing ${chalk.blue(fullName)}...`)
|
||||
|
||||
try {
|
||||
executeCommand(`npm publish --registry ${getRegistryUrl()}`, {
|
||||
executeCommand(`bun publish --registry ${getRegistryUrl()}`, {
|
||||
cwd: targetDir
|
||||
})
|
||||
|
||||
|
||||
@@ -104,7 +104,7 @@ function compareDirectories(localDir: string, registryDir: string): FileDiff {
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads and extracts an npm package tarball.
|
||||
* Downloads and extracts a package tarball.
|
||||
*/
|
||||
async function downloadAndExtractTarball(
|
||||
tarballUrl: string,
|
||||
|
||||
@@ -9,7 +9,7 @@ import logger from '@/utils/logger'
|
||||
import bumpPackageVersion, {
|
||||
revertPackageVersion
|
||||
} from '../../../utils/bumpPackageVersion'
|
||||
import { checkNPM, getRegistryUrl } from '../../../utils/registry'
|
||||
import { getRegistryUrl } from '../../../utils/registry'
|
||||
import validateModuleAuthor from '../functions/validateModuleAuthor'
|
||||
import validateModuleStructure from '../functions/validateModuleStructure'
|
||||
|
||||
@@ -52,13 +52,11 @@ function restoreGitignoreAfterPublish(modulePath: string): void {
|
||||
* 2. Validates author permissions
|
||||
* 3. Bumps version in package.json
|
||||
* 4. Renames .gitignore to gitignore (npm excludes .gitignore)
|
||||
* 5. Publishes to npm registry
|
||||
* 5. Publishes to LifeForge registry
|
||||
* 6. Restores gitignore to .gitignore
|
||||
* 7. Reverts version on failure
|
||||
*/
|
||||
export async function publishModuleHandler(moduleName: string): Promise<void> {
|
||||
checkNPM()
|
||||
|
||||
const modulePath = path.join(ROOT_DIR, 'apps', moduleName)
|
||||
|
||||
if (!fs.existsSync(modulePath)) {
|
||||
@@ -84,7 +82,7 @@ export async function publishModuleHandler(moduleName: string): Promise<void> {
|
||||
logger.debug(`Publishing ${chalk.blue(moduleName)}...`)
|
||||
|
||||
try {
|
||||
executeCommand(`npm publish --registry ${getRegistryUrl()}`, {
|
||||
executeCommand(`bun publish --registry ${getRegistryUrl()}`, {
|
||||
cwd: modulePath
|
||||
})
|
||||
|
||||
|
||||
88
tools/src/commands/modules/handlers/viewModuleHandler.ts
Normal file
88
tools/src/commands/modules/handlers/viewModuleHandler.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import chalk from 'chalk'
|
||||
import _ from 'lodash'
|
||||
|
||||
import logger from '@/utils/logger'
|
||||
import normalizePackage from '@/utils/normalizePackage'
|
||||
import { getPackageMetadata } from '@/utils/registry'
|
||||
|
||||
/**
|
||||
* Views package information from the registry.
|
||||
*
|
||||
* @param moduleName - The module name to view (e.g., calendar or @lifeforge/lifeforge--calendar)
|
||||
*/
|
||||
export async function viewModuleHandler(moduleName: string): Promise<void> {
|
||||
const { fullName } = normalizePackage(moduleName)
|
||||
|
||||
const data = await getPackageMetadata(fullName)
|
||||
|
||||
if (!data) {
|
||||
logger.error(`Package ${chalk.blue(fullName)} not found in registry`)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const latestVersion = data['dist-tags']?.latest
|
||||
|
||||
const latest = latestVersion ? data.versions?.[latestVersion] : undefined
|
||||
|
||||
const info = {
|
||||
version: latestVersion,
|
||||
versions: data.versions ? Object.keys(data.versions) : [],
|
||||
deps: latest?.dependencies,
|
||||
author:
|
||||
typeof latest?.author === 'string' ? latest.author : latest?.author?.name,
|
||||
repo:
|
||||
typeof latest?.repository === 'string'
|
||||
? latest.repository
|
||||
: latest?.repository?.url,
|
||||
displayName: latest?.displayName,
|
||||
description: latest?.description,
|
||||
homepage: latest?.homepage,
|
||||
license: latest?.license
|
||||
}
|
||||
|
||||
logger.print('')
|
||||
logger.print(
|
||||
info.displayName
|
||||
? `${chalk.bold.blue(info.displayName)} ${chalk.dim(`(${data.name || fullName})`)}`
|
||||
: chalk.bold.blue(data.name || fullName)
|
||||
)
|
||||
|
||||
logger.print(info.description)
|
||||
logger.print('')
|
||||
|
||||
const fields = [
|
||||
['version', info.version ? chalk.green(info.version) : undefined],
|
||||
['license', info.license],
|
||||
['author', info.author],
|
||||
['homepage', info.homepage ? chalk.cyan(info.homepage) : undefined],
|
||||
['repository', info.repo ? chalk.cyan(info.repo) : undefined]
|
||||
] as const
|
||||
|
||||
fields
|
||||
.filter(([_, value]) => value)
|
||||
.forEach(([label, value]) => {
|
||||
logger.print(`${chalk.dim(label.padEnd(12))} ${value}`)
|
||||
})
|
||||
|
||||
if (info.deps && Object.keys(info.deps).length > 0) {
|
||||
logger.print('')
|
||||
logger.print(chalk.dim('dependencies'))
|
||||
|
||||
Object.entries(info.deps).forEach(([dep, version]) => {
|
||||
logger.print(` ${dep} ${chalk.dim(version)}`)
|
||||
})
|
||||
}
|
||||
|
||||
if (info.versions.length > 1) {
|
||||
logger.print('')
|
||||
logger.print(chalk.dim(`versions (${info.versions.length})`))
|
||||
|
||||
const chunkedVersions = _.chunk(info.versions, 8)
|
||||
|
||||
chunkedVersions.forEach((chunk: string[]) => {
|
||||
logger.print(` ${chunk.join(chalk.dim(' · '))}`)
|
||||
})
|
||||
}
|
||||
|
||||
logger.print('')
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import { listModulesHandler } from './handlers/listModuleHandler'
|
||||
import { publishModuleHandler } from './handlers/publishModuleHandler'
|
||||
import { uninstallModuleHandler } from './handlers/uninstallModuleHandler'
|
||||
import { upgradeModuleHandler } from './handlers/upgradeModuleHandler'
|
||||
import { viewModuleHandler } from './handlers/viewModuleHandler'
|
||||
|
||||
export default function setup(program: Command): void {
|
||||
const command = program
|
||||
@@ -85,4 +86,15 @@ export default function setup(program: Command): void {
|
||||
.description('Compare local module content with registry version')
|
||||
.argument('[module]', 'Module to compare (optional, checks all if omitted)')
|
||||
.action(compareModuleHandler)
|
||||
|
||||
command
|
||||
.command('view')
|
||||
.alias('v')
|
||||
.alias('info')
|
||||
.description('View package info from the registry')
|
||||
.argument(
|
||||
'<module>',
|
||||
'Module to view, e.g., calendar or @lifeforge/lifeforge--calendar'
|
||||
)
|
||||
.action(viewModuleHandler)
|
||||
}
|
||||
|
||||
22
tools/src/commands/whoami/index.ts
Normal file
22
tools/src/commands/whoami/index.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import chalk from 'chalk'
|
||||
import type { Command } from 'commander'
|
||||
|
||||
import logger from '@/utils/logger'
|
||||
import { checkAuth, getRegistryUrl } from '@/utils/registry'
|
||||
|
||||
export default function setup(program: Command): void {
|
||||
program
|
||||
.command('whoami')
|
||||
.description('Show the currently authenticated user on the registry')
|
||||
.action(whoamiHandler)
|
||||
}
|
||||
|
||||
async function whoamiHandler() {
|
||||
const registry = getRegistryUrl()
|
||||
|
||||
logger.debug(`Registry: ${chalk.blue(registry)}`)
|
||||
|
||||
const { username } = await checkAuth()
|
||||
|
||||
logger.info(`Logged in as: ${chalk.blue(username)}`)
|
||||
}
|
||||
@@ -4,22 +4,7 @@ import path from 'path'
|
||||
import { ROOT_DIR } from '@/constants/constants'
|
||||
import logger from '@/utils/logger'
|
||||
|
||||
import executeCommand from './commands'
|
||||
|
||||
export function checkNPM(): void {
|
||||
try {
|
||||
executeCommand('npm --version', { cwd: ROOT_DIR })
|
||||
} catch (err) {
|
||||
const errorMsg = err instanceof Error ? err.message : String(err)
|
||||
|
||||
if (errorMsg.includes('not found')) {
|
||||
logger.error(
|
||||
'npm not found. Please make sure npm is installed and accessible.'
|
||||
)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
import { getEnvVar } from './helpers'
|
||||
|
||||
/**
|
||||
* Gets the registry URL for the @lifeforge scope from bunfig.toml.
|
||||
@@ -51,17 +36,16 @@ export function getRegistryUrl(): string {
|
||||
export async function checkPackageExists(
|
||||
packageName: string
|
||||
): Promise<boolean> {
|
||||
checkNPM()
|
||||
|
||||
const registry = getRegistryUrl()
|
||||
|
||||
try {
|
||||
executeCommand(`npm view ${packageName} --registry ${registry}`, {
|
||||
cwd: ROOT_DIR,
|
||||
exitOnError: false
|
||||
})
|
||||
const targetURL = new URL(registry)
|
||||
|
||||
return true
|
||||
targetURL.pathname = packageName
|
||||
|
||||
const response = await fetch(targetURL.toString())
|
||||
|
||||
return response.ok
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
@@ -77,23 +61,29 @@ export async function checkAuth(): Promise<{
|
||||
authenticated: boolean
|
||||
username?: string
|
||||
}> {
|
||||
checkNPM()
|
||||
|
||||
const registry = getRegistryUrl()
|
||||
|
||||
const token = getEnvVar('FORGISTRY_AUTH_TOKEN')
|
||||
|
||||
try {
|
||||
const result = executeCommand(
|
||||
`npm whoami --registry ${registry} 2>/dev/null`,
|
||||
{
|
||||
cwd: ROOT_DIR,
|
||||
exitOnError: false
|
||||
const targetURL = new URL(registry)
|
||||
|
||||
targetURL.pathname = '/-/whoami'
|
||||
|
||||
const response = await fetch(targetURL.toString(), {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
const username = result?.toString().trim()
|
||||
if (!response.ok) {
|
||||
throw new Error('Not authenticated')
|
||||
}
|
||||
|
||||
if (username) {
|
||||
return { authenticated: true, username }
|
||||
const data = (await response.json()) as { username?: string }
|
||||
|
||||
if (data.username) {
|
||||
return { authenticated: true, username: data.username }
|
||||
}
|
||||
|
||||
throw new Error('Not authenticated')
|
||||
@@ -103,9 +93,21 @@ export async function checkAuth(): Promise<{
|
||||
}
|
||||
}
|
||||
|
||||
interface PackageMetadata {
|
||||
export interface PackageVersionData {
|
||||
displayName?: string
|
||||
description?: string
|
||||
author?: string | { name?: string }
|
||||
license?: string
|
||||
homepage?: string
|
||||
repository?: { url?: string } | string
|
||||
dependencies?: Record<string, string>
|
||||
dist?: { tarball?: string }
|
||||
}
|
||||
|
||||
export interface PackageMetadata {
|
||||
name?: string
|
||||
'dist-tags'?: { latest?: string }
|
||||
versions?: Record<string, { dist?: { tarball?: string } }>
|
||||
versions?: Record<string, PackageVersionData>
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -114,7 +116,7 @@ interface PackageMetadata {
|
||||
* @param packageName - The full package name to fetch metadata for
|
||||
* @returns The package metadata, or null if not found
|
||||
*/
|
||||
async function getPackageMetadata(
|
||||
export async function getPackageMetadata(
|
||||
packageName: string
|
||||
): Promise<PackageMetadata | null> {
|
||||
const registry = getRegistryUrl()
|
||||
|
||||
Reference in New Issue
Block a user