Skip to content

Commit

Permalink
Merge pull request #53 from arkahna/FB-73-ability-to-auth-as-a-user-f…
Browse files Browse the repository at this point in the history
…rom-the-command-line-2

Fb 73 ability to auth as a user from the command line 2
  • Loading branch information
JakeGinnivan authored Nov 2, 2023
2 parents c2c9b37 + de22e78 commit 74012b4
Show file tree
Hide file tree
Showing 54 changed files with 1,320 additions and 1,057 deletions.
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,9 @@ Thumbs.db

tsconfig.tsbuildinfo

.nx/cache
.nx/cache

**/src/**/*.js
**/src/**/*.d.ts
**/src/**/*.js.map
!**/src/**/schema.d.ts
2 changes: 2 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,5 @@ To run the CLI locally run the following command (with the arguments you want to
```bash
pnpm cli
```

`pnpm cli login` will login to the dev app registration by default. If you want to login to production FeatureBoard
5 changes: 4 additions & 1 deletion apps/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@
"@commander-js/extra-typings": "^11.1.0",
"@featureboard/code-generator": "workspace:*",
"ejs": "^3.1.9",
"prompts": "^2.4.2"
"prompts": "^2.4.2",
"zod": "^3.22.4",
"open": "^9.1.0",
"@types/open": "^6.2.1"
},
"type": "module",
"bin": "./dist/main.js",
Expand Down
9 changes: 8 additions & 1 deletion apps/cli/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,21 @@
"sourceRoot": "apps/cli/src",
"projectType": "application",
"targets": {
"copy-templates": {
"executor": "nx:run-commands",
"options": {
"command": "copyfiles -u 4 \"./libs/code-generator/src/lib/templates/**/*\" ./apps/cli/dist"
}
},
"build": {
"executor": "@nx/esbuild:esbuild",
"outputs": ["{options.outputPath}"],
"defaultConfiguration": "production",
"dependsOn": ["copy-templates"],
"options": {
"platform": "node",
"outputPath": "apps/cli/dist",
"assets": [],
"deleteOutputPath": false,
"format": ["esm"],
"bundle": true,
"skipTypeCheck": true,
Expand Down
66 changes: 66 additions & 0 deletions apps/cli/src/commands/account.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { Command } from '@commander-js/extra-typings'
import prompts from 'prompts'
import { actionRunner } from '../lib/action-runner'
import { readCurrentOrganization } from '../lib/current-organization'
import { getValidToken } from '../lib/get-valid-token'

export function accountCommand() {
return new Command('account')
.description(
`Shows the account information of the currently logged in user`,
)
.option(
'-v, --verbose',
'Verbose output, show additional logging and tracing',
false,
)
.option(
'-n, --nonInteractive',
"Don't prompt for missing options",
!!process.env['CI'],
)
.action(
actionRunner(async function codeGen(options) {
prompts.override(options)

let bearerToken: string | undefined
if (!options.nonInteractive) {
const token = await getValidToken()
if (!token) {
return
}
bearerToken = token
}

if (!bearerToken) {
console.log('Not logged in')
return
}

const decodedToken = parseJwt(bearerToken)

console.log(
JSON.stringify(
{
id: decodedToken.oid,
name: decodedToken.name,
upn: decodedToken.upn,
tenantId: decodedToken.tid,
unique_name: decodedToken.unique_name,
currentOrganization: await readCurrentOrganization(
options.verbose,
),
},
null,
2,
),
)
}),
)
}

function parseJwt(token: string) {
return JSON.parse(
Buffer.from(token.split('.')[1], 'base64').toString(),
) as Record<string, string>
}
90 changes: 53 additions & 37 deletions apps/cli/src/commands/code-gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ import fsAsync from 'fs/promises'
import path from 'node:path'
import prompts from 'prompts'
import { actionRunner } from '../lib/action-runner'
import { API_ENDPOINT } from '../lib/config'
import { readCurrentOrganization } from '../lib/current-organization'
import { getValidToken } from '../lib/get-valid-token'
import { promptForOrganization } from '../lib/prompt-for-organization'
import { titleText } from '../lib/title-text'

// Code Gen
Expand All @@ -18,18 +22,20 @@ const templateChoices: Template[] = ['dotnet-api']
export function codeGenCommand() {
return new Command('code-gen')
.description(`A Code generator for FeatureBoard`)
.option('-p, --output-path <path>', 'Output path')
.option('-o, --output <path>', 'Output path')
.option('-g, --organizationId <id>', 'The Orgnization Id')
.addOption(
new Option(
'-t, --template <template>',
'Select the template ',
).choices(templateChoices),
)
.option(
'-k, --featureBoardKey <key>',
'-k, --featureBoardApiKey <key>',
'FeatureBoard API key',
process.env.FEATUREBOARD_API_KEY,
)
.option('-p, --project <name>', 'FeatureBoard project name')
.option('-d, --dryRun', 'Dry run show what files have changed', false)
.option('-q, --quiet', "Don't show file changes", false)
.option(
Expand Down Expand Up @@ -69,66 +75,76 @@ export function codeGenCommand() {
options.template = promptResult.template
}

if (!options.outputPath && !options.nonInteractive) {
if (!options.output && !options.nonInteractive) {
const promptResult = await prompts({
type: 'text',
name: 'outputPath',
name: 'output',
message: `Enter the output path for the generated code.`,
validate: (x) => !!x,
})

if (!('outputPath' in promptResult)) {
if (!('output' in promptResult)) {
return
}

options.outputPath = promptResult.outputPath
options.output = promptResult.output
}
const outputAbsolutePath = path.join(
process.cwd(),
options.output!,
)
try {
await fsAsync.access(outputAbsolutePath)
} catch {
throw new Error(
`Output path doesn't exist: ${outputAbsolutePath}`,
)
}

let bearerToken: string | undefined
if (!options.featureBoardKey && !options.nonInteractive) {
const promptResult = await prompts({
type: 'password',
name: 'bearerToken',
message: `Enter your featureboard bearer token:`,
validate: (x) => !!x,
})

if (!('bearerToken' in promptResult)) {
if (!options.featureBoardApiKey && !options.nonInteractive) {
const token = await getValidToken()
if (!token) {
return
}

bearerToken = promptResult.bearerToken
bearerToken = token
}

const sanitisedOutputPath = (options.outputPath ?? '').replace(
'../',
'./',
)

const outputPath = path.join(process.cwd(), sanitisedOutputPath)
try {
await fsAsync.access(outputPath)
} catch {
throw new Error(`Output path doesn't exist: ${outputPath}`)
let currentOrganization =
options.organizationId ??
(await readCurrentOrganization(options.verbose))

if (
bearerToken &&
!currentOrganization &&
!options.nonInteractive
) {
currentOrganization =
await promptForOrganization(bearerToken)
}

if (!options.template) throw new Error('Template is not set')
if (!options.featureBoardKey && !bearerToken) {
throw new Error(
options.nonInteractive
? 'FeatureBoard Key is not set'
: 'Bearer token is not set',
)
if (!currentOrganization) {
throw new Error("Organization isn't set")
}

const tree = new FsTree(process.cwd(), options.verbose)
await codeGenerator({
template: options.template as Template,
tree: tree,
relativeFilePath: sanitisedOutputPath,
featureBoardKey: options.featureBoardKey,
featureBoardBearerToken: bearerToken,
relativeFilePath: options.output!,
featureBoardProjectName: options.project,
auth: bearerToken
? {
featureBoardBearerToken: bearerToken,
organizationId: currentOrganization,
}
: {
featureBoardApiKey: options.featureBoardApiKey,
organizationId: currentOrganization,
},

interactive: !options.nonInteractive,
apiEndpoint: API_ENDPOINT,
})

const changes = tree.listChanges()
Expand Down
Loading

0 comments on commit 74012b4

Please sign in to comment.