diff --git a/.cherry.cjs b/.cherry.cjs index cabde7d5..a9ae5bed 100644 --- a/.cherry.cjs +++ b/.cherry.cjs @@ -1,6 +1,3 @@ -const JS_FILES = '**/*.{js,jsx}' -const TS_FILES = '**/*.{ts,tsx}' - module.exports = { project_name: 'cherrypush/cherry-cli', plugins: { diff --git a/bin/commands/init.ts b/bin/commands/init.ts index 6f97a672..32c23970 100755 --- a/bin/commands/init.ts +++ b/bin/commands/init.ts @@ -1,11 +1,10 @@ #! /usr/bin/env node -import * as git from '../../src/git.js' - import { createConfigurationFile, createWorkflowFile, getConfigFile, workflowExists } from '../../src/configuration.js' import { Command } from 'commander' -import prompt from 'prompt' +import { gitProjectRoot, gitRemoteUrl } from '../../src/git.js' +import { guessRepositoryInfo } from '../../src/repository.js' export default function (program: Command) { program.command('init').action(async () => { @@ -16,24 +15,16 @@ export default function (program: Command) { process.exit(1) } - prompt.message = '' - prompt.start() - - const remoteUrl = await git.gitRemoteUrl() - let projectName = git.guessProjectName(remoteUrl) - - if (projectName === null) { - const { repo } = await prompt.get({ - properties: { repo: { message: 'Enter your project name', required: true } }, - }) - if (typeof repo === 'string') projectName = repo - } - - if (!projectName) throw new Error('Project name is required') + const remoteUrl = await gitRemoteUrl() + const projectRoot = await gitProjectRoot() + const repositoryInfo = await guessRepositoryInfo({ remoteUrl, configFile: null, projectRoot }) - createConfigurationFile(projectName) + if (!repositoryInfo.host || !repositoryInfo.owner || !repositoryInfo.name) + throw new Error('Could not guess repository info. Please setup your config file manually.') + createConfigurationFile(repositoryInfo) if (!workflowExists()) createWorkflowFile() - console.log('Your initial setup is done! Now try the command `cherry run` to see your first metrics.') + + console.log('Your initial setup is complete! Run `cherry run` to view your metrics.') }) } diff --git a/package-lock.json b/package-lock.json index 07496e53..19ea2f2f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,7 +20,6 @@ "madge": "^8.0.0", "minimatch": "^6.1.6", "p-limit": "^4.0.0", - "prompt": "^1.3.0", "semver": "^7.6.3", "spinnies": "^0.5.1", "true-case-path": "^2.2.1" @@ -103,14 +102,6 @@ "node": ">=6.9.0" } }, - "node_modules/@colors/colors": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", - "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", - "engines": { - "node": ">=0.1.90" - } - }, "node_modules/@dependents/detective-less": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/@dependents/detective-less/-/detective-less-5.0.0.tgz", @@ -1526,11 +1517,6 @@ "node": ">=18" } }, - "node_modules/async": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.3.tgz", - "integrity": "sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==" - }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -1884,14 +1870,6 @@ "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", "dev": true }, - "node_modules/colors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", - "integrity": "sha512-pFGrxThWcWQ2MsAz6RtgeWe4NK2kUE1WfsrvvlctdII745EW9I0yflqhe7++M5LEc7bV2c/9/5zc8sFcpL0Drw==", - "engines": { - "node": ">=0.1.90" - } - }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -1935,14 +1913,6 @@ "node": ">= 8" } }, - "node_modules/cycle": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/cycle/-/cycle-1.0.3.tgz", - "integrity": "sha512-TVF6svNzeQCOpjCqsy0/CSy8VgObG3wXusJ73xW2GbG5rGx7lC8zxDSURicsXI2UsGdi2L0QNRCi745/wUDvsA==", - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/debug": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", @@ -2748,14 +2718,6 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/eyes": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", - "integrity": "sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ==", - "engines": { - "node": "> 0.1.90" - } - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -3370,11 +3332,6 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, - "node_modules/isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==" - }, "node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -3985,11 +3942,6 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, - "node_modules/mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==" - }, "node_modules/nanoid": { "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", @@ -4484,21 +4436,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/prompt": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/prompt/-/prompt-1.3.0.tgz", - "integrity": "sha512-ZkaRWtaLBZl7KKAKndKYUL8WqNT+cQHKRZnT4RYYms48jQkFw3rrBL+/N5K/KtdEveHkxs982MX2BkDKub2ZMg==", - "dependencies": { - "@colors/colors": "1.5.0", - "async": "3.2.3", - "read": "1.0.x", - "revalidator": "0.1.x", - "winston": "2.x" - }, - "engines": { - "node": ">= 6.0.0" - } - }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -4564,17 +4501,6 @@ "node": ">=0.10.0" } }, - "node_modules/read": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", - "integrity": "sha512-rSOKNYUmaxy0om1BNjMN4ezNT6VKK+2xF4GBhc81mkH7L60i6dp8qPYrkndNLT3QPphoII3maL9PVC9XmhHwVQ==", - "dependencies": { - "mute-stream": "~0.0.4" - }, - "engines": { - "node": ">=0.8" - } - }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -4685,14 +4611,6 @@ "node": ">=0.10.0" } }, - "node_modules/revalidator": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/revalidator/-/revalidator-0.1.8.tgz", - "integrity": "sha512-xcBILK2pA9oh4SiinPEZfhP8HfrB/ha+a2fTMyl7Om2WjlDVrOQy99N2MXXlUHqGJz4qEu2duXxHJjDWuK/0xg==", - "engines": { - "node": ">= 0.4.0" - } - }, "node_modules/rfdc": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", @@ -5058,14 +4976,6 @@ "node": ">=6" } }, - "node_modules/stack-trace": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", - "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", - "engines": { - "node": "*" - } - }, "node_modules/stackback": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", @@ -6454,30 +6364,6 @@ "node": ">=8" } }, - "node_modules/winston": { - "version": "2.4.7", - "resolved": "https://registry.npmjs.org/winston/-/winston-2.4.7.tgz", - "integrity": "sha512-vLB4BqzCKDnnZH9PHGoS2ycawueX4HLqENXQitvFHczhgW2vFpSOn31LZtVr1KU8YTw7DS4tM+cqyovxo8taVg==", - "dependencies": { - "async": "^2.6.4", - "colors": "1.0.x", - "cycle": "1.0.x", - "eyes": "0.1.x", - "isstream": "0.1.x", - "stack-trace": "0.0.x" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/winston/node_modules/async": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", - "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", - "dependencies": { - "lodash": "^4.17.14" - } - }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", diff --git a/package.json b/package.json index 4fb48e63..2c0567c2 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "dist" ], "scripts": { + "install:global": "npm install -g .", "build": "tsc", "cherry": "tsx ./bin/cherry.ts", "prepare": "husky install && npm run build", @@ -49,7 +50,6 @@ "madge": "^8.0.0", "minimatch": "^6.1.6", "p-limit": "^4.0.0", - "prompt": "^1.3.0", "semver": "^7.6.3", "spinnies": "^0.5.1", "true-case-path": "^2.2.1" diff --git a/src/configuration.ts b/src/configuration.ts index e8b85ed4..ae57473b 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -1,11 +1,10 @@ import { gitProjectRoot, gitRemoteUrl } from './git.js' import fs from 'fs' -import { dirname } from 'path' -import { fileURLToPath } from 'url' import buildAndImport from './build-and-import.cjs' import { guessRepositoryInfo } from './repository.js' -import { Configuration } from './types.js' +import { getConfigTemplate, getWorkflowTemplate } from './templates.js' +import { Configuration, Repository } from './types.js' export const CONFIG_FILE_LOCAL_PATHS = ['.cherry.js', '.cherry.cjs', '.cherry.ts'] export const WORKFLOW_FILE_LOCAL_PATH = '.github/workflows/cherry_push.yml' @@ -13,18 +12,15 @@ export const WORKFLOW_FILE_LOCAL_PATH = '.github/workflows/cherry_push.yml' export const CONFIG_FILE_FULL_PATHS = CONFIG_FILE_LOCAL_PATHS.map((filePath) => `${process.cwd()}/${filePath}`) export const WORKFLOW_FILE_FULL_PATH = `${process.cwd()}/${WORKFLOW_FILE_LOCAL_PATH}` -const CONFIG_TEMPLATE_PATH = dirname(fileURLToPath(import.meta.url)) + '/templates/.cherry.js.template' -const WORKFLOW_TEMPLATE_PATH = dirname(fileURLToPath(import.meta.url)) + '/templates/.cherry_push.yml.template' - -export const createConfigurationFile = (projectName: string) => - fs.writeFileSync( - CONFIG_FILE_FULL_PATHS[0], - fs.readFileSync(CONFIG_TEMPLATE_PATH).toString().replace('PROJECT_NAME', projectName) - ) +export const createConfigurationFile = (repositoryInfo: Repository) => { + const filePath = CONFIG_FILE_FULL_PATHS[0] + console.log('Creating configuration file at:', filePath) + fs.writeFileSync(filePath, getConfigTemplate(repositoryInfo)) +} export const createWorkflowFile = () => { fs.mkdirSync(`${process.cwd()}/.github/workflows`, { recursive: true }) - fs.writeFileSync(WORKFLOW_FILE_FULL_PATH, fs.readFileSync(WORKFLOW_TEMPLATE_PATH).toString()) + fs.writeFileSync(WORKFLOW_FILE_FULL_PATH, getWorkflowTemplate()) } export const getConfigFile = () => CONFIG_FILE_FULL_PATHS.find((filePath) => fs.existsSync(filePath)) ?? null diff --git a/src/repository.ts b/src/repository.ts index 5ce1eb4c..9e78ebf9 100644 --- a/src/repository.ts +++ b/src/repository.ts @@ -1,5 +1,5 @@ import path from 'path' -import { PermalinkFn, Repository } from './types.js' +import { Host, PermalinkFn, Repository } from './types.js' export const buildRepoURL = (repository: Repository) => `https://${repository.host}/${repository.owner}/${repository.name}` @@ -34,7 +34,7 @@ export async function guessRepositoryInfo({ remoteUrl: string | null configFile: string | null projectRoot: string -}) { +}): Promise { if (remoteUrl === null) { throw new Error('Could not guess repository info: no remote URL found') } @@ -42,7 +42,7 @@ export async function guessRepositoryInfo({ // For github ssh remotes such as git@github.com:cherrypush/cherry-cli.git if (remoteUrl.includes('git@github.com')) { return { - host: 'github.com', + host: Host.Github, owner: remoteUrl.split(':')[1].split('/')[0], name: remoteUrl.split('/')[1].replace('.git', ''), subdir: guessRepositorySubdir({ configFile, projectRoot }), @@ -52,7 +52,7 @@ export async function guessRepositoryInfo({ // For github https remotes such as https://github.com/cherrypush/cherry-cli.git if (remoteUrl.includes('https://github.com')) { return { - host: 'github.com', + host: Host.Github, owner: remoteUrl.split('/')[3], name: remoteUrl.split('/')[4].replace('.git', ''), subdir: guessRepositorySubdir({ configFile, projectRoot }), diff --git a/src/templates.ts b/src/templates.ts new file mode 100644 index 00000000..2b546691 --- /dev/null +++ b/src/templates.ts @@ -0,0 +1,47 @@ +import { Repository } from './types.js' + +export function getConfigTemplate(repositoryInfo: Repository) { + return `// For detailed configuration options, see the documentation: +// https://www.cherrypush.com/docs +// +// In this configuration file, you can set up your repository information, +// enable plugins, and define custom metrics for your codebase. + +export default { + repository: { + host: '${repositoryInfo.host}', + owner: '${repositoryInfo.owner}', + name: '${repositoryInfo.name}', + subdir: '${repositoryInfo.subdir}', + }, + plugins: { loc: {} }, + metrics: [ + { + name: 'TODO/FIXME', + pattern: /(TODO|FIXME):/i, + }, + ], +}` +} + +export function getWorkflowTemplate() { + return `name: cherry push + +on: + push: + branches: + - \${{ github.event.repository.default_branch }} + +jobs: + cherry_push: + runs-on: ubuntu-latest + steps: + - name: Checkout project + uses: actions/checkout@v4 + + - name: Install dependencies + run: npm i -g cherrypush + + - name: Push metrics to Cherry + run: cherry push --quiet --api-key=\${{ secrets.CHERRY_API_KEY }}` +} diff --git a/src/templates/.cherry.js.template b/src/templates/.cherry.js.template deleted file mode 100644 index 296fa4c6..00000000 --- a/src/templates/.cherry.js.template +++ /dev/null @@ -1,12 +0,0 @@ -// Refer to the docs here: https://www.cherrypush.com/docs - -module.exports = { - project_name: 'PROJECT_NAME', - plugins: ['loc'], - metrics: [ - { - name: 'TODO/FIXME', - pattern: /(TODO|FIXME):/i, // the i flag makes the regex case insensitive - }, - ], -} diff --git a/src/templates/.cherry_push.yml.template b/src/templates/.cherry_push.yml.template deleted file mode 100644 index 59e4995d..00000000 --- a/src/templates/.cherry_push.yml.template +++ /dev/null @@ -1,19 +0,0 @@ -name: cherry push - -on: - push: - branches: - - ${{ github.event.repository.default_branch }} - -jobs: - cherry_push: - runs-on: ubuntu-latest - steps: - - name: Checkout project - uses: actions/checkout@v4 - - - name: Install dependencies - run: npm i -g cherrypush - - - name: Push metrics to Cherry - run: cherry push --quiet --api-key=${{ secrets.CHERRY_API_KEY }} diff --git a/test/cherry_init.test.js b/test/cherry_init.test.js deleted file mode 100644 index 1f9df2db..00000000 --- a/test/cherry_init.test.js +++ /dev/null @@ -1,30 +0,0 @@ -import { afterAll, beforeAll, describe, expect, it } from 'vitest' - -import fs from 'fs' -import path from 'path' -import { execAsync } from './helpers' - -const originalCwd = process.cwd() -const fixturesPath = path.join(originalCwd, 'test/fixtures/empty-project-source') - -describe('cherry init', () => { - beforeAll(() => process.chdir(fixturesPath)) - afterAll(() => process.chdir(originalCwd)) - - it('creates a config file', async () => { - // Remove .cherry.js file if it exists - if (fs.existsSync('.cherry.js')) fs.unlinkSync('.cherry.js') - - // The cherry init command prompts for a project name. Feed it with: "cherrypush/cherry-cli" - await execAsync('echo "cherrypush/cherry-cli" | tsx ./../../../bin/cherry.js init') - expect(fs.existsSync('.cherry.js')).toBe(true) - - const cherryConfig = await import(path.join(process.cwd(), '.cherry.js')) - expect(cherryConfig.project_name).toBe('cherrypush/cherry-cli') - - // Remove .cherry.js file - if (fs.existsSync('.cherry.js')) fs.unlinkSync('.cherry.js') - // Remove .github folder - if (fs.existsSync('.github')) fs.rmSync('.github', { recursive: true }) - }) -})