great 0.0.4 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/.codeclimate.yml +7 -0
  2. package/.editorconfig +11 -0
  3. package/.nvmrc +1 -0
  4. package/.prettierrc.json +6 -0
  5. package/README.md +17 -107
  6. package/dist/accounts/index.d.ts +4 -0
  7. package/dist/accounts/index.js +28 -0
  8. package/dist/accounts/index.js.map +1 -0
  9. package/dist/index.d.ts +2 -0
  10. package/dist/index.js +18 -0
  11. package/dist/index.js.map +1 -0
  12. package/dist/now/index.d.ts +4 -0
  13. package/dist/now/index.js +9 -0
  14. package/dist/now/index.js.map +1 -0
  15. package/dist/now/index.test.d.ts +1 -0
  16. package/dist/now/index.test.js +18 -0
  17. package/dist/now/index.test.js.map +1 -0
  18. package/dist/projects/describe.d.ts +4 -0
  19. package/dist/projects/describe.js +44 -0
  20. package/dist/projects/describe.js.map +1 -0
  21. package/dist/projects/index.d.ts +4 -0
  22. package/dist/projects/index.js +39 -0
  23. package/dist/projects/index.js.map +1 -0
  24. package/dist/pull/index.d.ts +4 -0
  25. package/dist/pull/index.js +31 -0
  26. package/dist/pull/index.js.map +1 -0
  27. package/dist/run/index.d.ts +4 -0
  28. package/dist/run/index.js +55 -0
  29. package/dist/run/index.js.map +1 -0
  30. package/dist/utils/callApi.d.ts +5 -0
  31. package/dist/utils/callApi.js +20 -0
  32. package/dist/utils/callApi.js.map +1 -0
  33. package/dist/utils/fetchLocalProject.d.ts +1 -0
  34. package/dist/utils/fetchLocalProject.js +12 -0
  35. package/dist/utils/fetchLocalProject.js.map +1 -0
  36. package/dist/utils/saveProject.d.ts +2 -0
  37. package/dist/utils/saveProject.js +68 -0
  38. package/dist/utils/saveProject.js.map +1 -0
  39. package/dist/whoami/index.d.ts +4 -0
  40. package/dist/whoami/index.js +21 -0
  41. package/dist/whoami/index.js.map +1 -0
  42. package/eslint.config.js +27 -0
  43. package/package.json +46 -11
  44. package/src/accounts/index.ts +41 -0
  45. package/src/index.ts +22 -0
  46. package/src/now/index.test.ts +31 -0
  47. package/src/now/index.ts +15 -0
  48. package/src/projects/describe.ts +80 -0
  49. package/src/projects/index.ts +61 -0
  50. package/src/pull/index.ts +45 -0
  51. package/src/run/index.ts +81 -0
  52. package/src/utils/callApi.ts +29 -0
  53. package/src/utils/fetchLocalProject.ts +12 -0
  54. package/src/utils/saveProject.ts +106 -0
  55. package/src/whoami/index.ts +32 -0
  56. package/tsconfig.json +28 -0
  57. package/.npmignore +0 -6
  58. package/Greatfile.js +0 -30
  59. package/index.js +0 -166
@@ -0,0 +1,21 @@
1
+ import chalk from 'chalk';
2
+ import callApi from '../utils/callApi.js';
3
+ export const action = (log, apiToken) => async function whoami() {
4
+ const response = await callApi('https://api.integreat.io/whoami', apiToken);
5
+ if (response.error) {
6
+ log(chalk.red(response.error));
7
+ return;
8
+ }
9
+ const username = response.user?.id;
10
+ if (username) {
11
+ log(chalk.white(username));
12
+ }
13
+ else {
14
+ log(chalk.red('You are not logged in to Integreat.'));
15
+ }
16
+ };
17
+ export default (program, apiToken) => program
18
+ .command('whoami')
19
+ .description('display your Integreat user name')
20
+ .action(action(console.log, apiToken));
21
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/whoami/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AACzB,OAAO,OAA0B,MAAM,qBAAqB,CAAA;AAO5D,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,GAA8B,EAAE,QAAiB,EAAE,EAAE,CAC1E,KAAK,UAAU,MAAM;IACnB,MAAM,QAAQ,GAAG,MAAM,OAAO,CAC5B,iCAAiC,EACjC,QAAQ,CACT,CAAA;IACD,IAAI,QAAQ,CAAC,KAAK,EAAE,CAAC;QACnB,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAA;QAC9B,OAAM;IACR,CAAC;IAED,MAAM,QAAQ,GAAG,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAA;IAClC,IAAI,QAAQ,EAAE,CAAC;QACb,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAA;IAC5B,CAAC;SAAM,CAAC;QACN,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,qCAAqC,CAAC,CAAC,CAAA;IACvD,CAAC;AACH,CAAC,CAAA;AAEH,eAAe,CAAC,OAAgB,EAAE,QAAiB,EAAE,EAAE,CACrD,OAAO;KACJ,OAAO,CAAC,QAAQ,CAAC;KACjB,WAAW,CAAC,kCAAkC,CAAC;KAC/C,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAA"}
@@ -0,0 +1,27 @@
1
+ import pluginPromise from 'eslint-plugin-promise'
2
+ import pluginSecurity from 'eslint-plugin-security'
3
+ import eslintConfigPrettier from 'eslint-config-prettier'
4
+
5
+ import eslint from '@eslint/js'
6
+ import tseslint from 'typescript-eslint'
7
+
8
+ export default tseslint.config(
9
+ eslint.configs.recommended,
10
+ tseslint.configs.strict,
11
+ tseslint.configs.stylistic,
12
+ eslintConfigPrettier,
13
+ pluginPromise.configs['flat/recommended'],
14
+ pluginSecurity.configs.recommended,
15
+ {
16
+ rules: {
17
+ 'no-unused-vars': 0,
18
+ '@typescript-eslint/no-unused-vars': [
19
+ 'error',
20
+ { argsIgnorePattern: '^_', ignoreRestSiblings: true },
21
+ ],
22
+ },
23
+ linterOptions: {
24
+ reportUnusedDisableDirectives: 'error',
25
+ },
26
+ },
27
+ )
package/package.json CHANGED
@@ -1,17 +1,52 @@
1
1
  {
2
2
  "name": "great",
3
- "version": "0.0.4",
4
- "description": "Another task runner with js",
5
- "main": "index.js",
3
+ "version": "0.3.2",
4
+ "description": "A CLI for Integreat",
5
+ "author": "Kjell-Morten Bratsberg Thorsen <kjellmorten@integreat.io>",
6
+ "license": "UNLICENSED",
7
+ "type": "module",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ }
13
+ },
14
+ "bin": {
15
+ "great": "dist/index.js"
16
+ },
17
+ "scripts": {
18
+ "test": "node --import tsx --no-deprecation --test --enable-source-maps --experimental-test-coverage --test-reporter node-test-reporter \"src/**/*.test.ts\"",
19
+ "test:watch": "npm run dev",
20
+ "dev": "node --import tsx --no-deprecation --test --enable-source-maps --test-reporter node-test-reporter --watch \"src/**/*.test.ts\" || exit 0",
21
+ "build": "tsc",
22
+ "prepublishOnly": "npm run build",
23
+ "coverage": "c8 report",
24
+ "lint": "eslint --ext .ts src",
25
+ "typecheck": "tsc --noEmit --strict",
26
+ "verify": "npm run lint && npm run typecheck && npm test"
27
+ },
6
28
  "repository": {
7
29
  "type": "git",
8
- "url": "git@github.com:fritx/great.git"
30
+ "url": "git+https://github.com/integreat-io/great.git"
31
+ },
32
+ "bugs": {
33
+ "url": "https://github.com/integreat-io/great/issues"
34
+ },
35
+ "homepage": "https://github.com/integreat-io/great#readme",
36
+ "dependencies": {
37
+ "@inquirer/prompts": "^7.4.0",
38
+ "chalk": "^5.4.1",
39
+ "columnify": "^1.6.0",
40
+ "commander": "^13.1.0",
41
+ "ky": "^1.7.5",
42
+ "ora": "^8.2.0"
9
43
  },
10
- "keywords": [
11
- "task",
12
- "runner",
13
- "build",
14
- "flow"
15
- ],
16
- "license": "MIT"
44
+ "devDependencies": {
45
+ "@integreat/ts-dev-setup": "^8.1.2",
46
+ "@types/columnify": "^1.5.4",
47
+ "@types/node": "^22.13.10",
48
+ "@types/sinon": "^17.0.4",
49
+ "integreat": "^1.6.1",
50
+ "sinon": "^19.0.2"
51
+ }
17
52
  }
@@ -0,0 +1,41 @@
1
+ import chalk from 'chalk'
2
+ import columnify from 'columnify'
3
+ import callApi, { type Response } from '../utils/callApi.js'
4
+ import type { Command } from 'commander'
5
+
6
+ interface AccountsResponse extends Response {
7
+ accounts?: [{ id: string; name: string }]
8
+ }
9
+
10
+ export const action = (log: (message: string) => void, apiToken?: string) =>
11
+ async function accountsList() {
12
+ const response = await callApi<AccountsResponse>(
13
+ 'https://api.integreat.io/accounts',
14
+ apiToken,
15
+ )
16
+ if (response.error) {
17
+ log(chalk.red(response.error))
18
+ return
19
+ }
20
+
21
+ const accounts = response.accounts
22
+ if (Array.isArray(accounts) && accounts.length > 0) {
23
+ log(columnify(accounts, { columns: ['id', 'name'] }))
24
+ } else {
25
+ log(chalk.white('No accounts.'))
26
+ }
27
+ }
28
+
29
+ export default (program: Command, apiToken?: string) => {
30
+ const projects = program
31
+ .command('accounts')
32
+ .description(
33
+ 'commands for accounts, run `great accounts --help` to see options. `great accounts` is also a shortcut for `great accounts list`',
34
+ )
35
+ .action(action(console.log, apiToken))
36
+
37
+ projects
38
+ .command('list')
39
+ .description('list all accounts you have access to')
40
+ .action(action(console.log, apiToken))
41
+ }
package/src/index.ts ADDED
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { program } from 'commander'
4
+ import accounts from './accounts/index.js'
5
+ import now from './now/index.js'
6
+ import projects from './projects/index.js'
7
+ import pull from './pull/index.js'
8
+ import run from './run/index.js'
9
+ import whoami from './whoami/index.js'
10
+
11
+ const apiToken = process.env.GREAT_TOKEN
12
+
13
+ program.version('0.3.2', '-v, --version').description('Integreat CLI')
14
+
15
+ accounts(program, apiToken)
16
+ now(program)
17
+ projects(program, apiToken)
18
+ pull(program, apiToken)
19
+ run(program, apiToken)
20
+ whoami(program, apiToken)
21
+
22
+ program.parse(process.argv)
@@ -0,0 +1,31 @@
1
+ import test from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+ import sinon from 'sinon'
4
+
5
+ import { action } from './index.js'
6
+
7
+ // Tests
8
+
9
+ test('should return timestamp', () => {
10
+ const log = sinon.stub()
11
+
12
+ const before = Date.now()
13
+ action(log)()
14
+ const after = Date.now()
15
+
16
+ assert.equal(log.callCount, 1)
17
+ const message = log.args[0][0]
18
+ assert.equal(typeof message, 'string')
19
+ const ms = Number.parseInt(message.slice(5, 18)) // We extract the number within the formatting
20
+ assert.equal(typeof ms, 'number')
21
+ assert.equal(
22
+ ms >= before,
23
+ true,
24
+ 'Milliseconds should be greater than or equal the before timestamp',
25
+ )
26
+ assert.equal(
27
+ ms <= after,
28
+ true,
29
+ 'Milliseconds should be less than or equal the after timestamp',
30
+ )
31
+ })
@@ -0,0 +1,15 @@
1
+ import chalk from 'chalk'
2
+ import type { Command } from 'commander'
3
+
4
+ export const action = (log: (message: string) => void) =>
5
+ function now() {
6
+ log(chalk.white(Date.now()))
7
+ }
8
+
9
+ export default (program: Command) =>
10
+ program
11
+ .command('now')
12
+ .description(
13
+ 'display current time as a Unix time stamp (milliseconds since 1970-01-01',
14
+ )
15
+ .action(action(console.log))
@@ -0,0 +1,80 @@
1
+ import chalk from 'chalk'
2
+ import columnify from 'columnify'
3
+ import callApi, { type Response } from '../utils/callApi.js'
4
+ import type { Command } from 'commander'
5
+
6
+ interface Project {
7
+ id: string
8
+ name: string
9
+ status: string
10
+ shared?: boolean
11
+ createdAt: string
12
+ updatedAt: string
13
+ values: Record<string, string>
14
+ }
15
+
16
+ interface ProjectResponse extends Response {
17
+ project?: Project
18
+ }
19
+
20
+ const prepareDetails = ({
21
+ id,
22
+ name,
23
+ status,
24
+ shared,
25
+ createdAt,
26
+ updatedAt,
27
+ }: Project) => ({
28
+ id,
29
+ name,
30
+ status,
31
+ shared: shared ?? false,
32
+ createdAt,
33
+ updatedAt,
34
+ })
35
+
36
+ const prepareValues = (values: Record<string, string>) =>
37
+ Object.fromEntries(
38
+ Object.entries(values).map(([key, value]) =>
39
+ key.endsWith('$secret')
40
+ ? [key.slice(0, key.length - 7), `🔒 ${value}`]
41
+ : [key, value],
42
+ ),
43
+ )
44
+
45
+ export const action = (log: (message: string) => void, apiToken?: string) =>
46
+ async function projectList(projectId?: string) {
47
+ const url = `https://api.integreat.io/projects/${projectId}`
48
+
49
+ const response = await callApi<ProjectResponse>(url, apiToken)
50
+ if (response.error) {
51
+ log(chalk.red(response.error))
52
+ return
53
+ }
54
+
55
+ const project = response.project
56
+ if (project) {
57
+ const details = prepareDetails(project)
58
+ log(columnify(details, { columns: ['key', 'value'] }))
59
+
60
+ if (project.values) {
61
+ log(chalk.yellow('\nValues:'))
62
+ const values = prepareValues(project.values)
63
+ log(columnify(values, { columns: ['key', 'value'] }))
64
+ } else {
65
+ log('\nNo values')
66
+ }
67
+ } else {
68
+ log(chalk.white('No project.'))
69
+ }
70
+ }
71
+
72
+ export default (program: Command, apiToken?: string) =>
73
+ program
74
+ .command('describe')
75
+ .description('show project details')
76
+ .argument(
77
+ '[project]',
78
+ 'id of the project. if not given, the id of the local project is used',
79
+ )
80
+ .action(action(console.log, apiToken))
@@ -0,0 +1,61 @@
1
+ import chalk from 'chalk'
2
+ import columnify from 'columnify'
3
+ import callApi, { type Response } from '../utils/callApi.js'
4
+ import describe from './describe.js'
5
+ import type { Command } from 'commander'
6
+
7
+ interface ProjectsResponse extends Response {
8
+ projects?: [{ id: string; name: string }]
9
+ meta?: {
10
+ totalCount: number
11
+ }
12
+ }
13
+
14
+ export const action = (log: (message: string) => void, apiToken?: string) =>
15
+ async function projectList(accountId?: string) {
16
+ const url = accountId
17
+ ? `https://api.integreat.io/accounts/${accountId}/projects?pageSize=50`
18
+ : 'https://api.integreat.io/projects?pageSize=50'
19
+
20
+ const response = await callApi<ProjectsResponse>(url, apiToken)
21
+ if (response.error) {
22
+ log(chalk.red(response.error))
23
+ return
24
+ }
25
+
26
+ const projects = response.projects
27
+ if (Array.isArray(projects) && projects.length > 0) {
28
+ log(columnify(projects, { columns: ['id', 'name'] }))
29
+
30
+ const count = response.meta?.totalCount
31
+ if (count) {
32
+ log(`\n${count} projects`)
33
+ }
34
+ } else {
35
+ log(chalk.white('No projects.'))
36
+ }
37
+ }
38
+
39
+ export default (program: Command, apiToken?: string) => {
40
+ const projects = program
41
+ .command('projects')
42
+ .description(
43
+ 'commands for projects, run `great projects --help` to see options. `great projects` is also a shortcut for `great projects list`',
44
+ )
45
+ .argument(
46
+ '[account]',
47
+ 'id of an account to fetch projects for, or leave it out to fetch all projects',
48
+ )
49
+ .action(action(console.log, apiToken))
50
+
51
+ projects
52
+ .command('list')
53
+ .description('list all projects you have access to')
54
+ .argument(
55
+ '[account]',
56
+ 'id of an account to fetch projects for, or leave it out to fetch all projects',
57
+ )
58
+ .action(action(console.log, apiToken))
59
+
60
+ describe(projects, apiToken)
61
+ }
@@ -0,0 +1,45 @@
1
+ import chalk from 'chalk'
2
+ import callApi, { type Response } from '../utils/callApi.js'
3
+ import saveProject from '../utils/saveProject.js'
4
+ import { fetchIdFromIndex } from '../utils/fetchLocalProject.js'
5
+ import type { Command } from 'commander'
6
+ import type { Definitions } from 'integreat'
7
+
8
+ interface ProjectResponse extends Response {
9
+ project?: { id: string; name: string; definitions?: Definitions }
10
+ }
11
+
12
+ export const action = (log: (message: string) => void, apiToken?: string) =>
13
+ async function pullProject(projectId?: string) {
14
+ const id = projectId ?? fetchIdFromIndex()
15
+ if (!id) {
16
+ log(chalk.red('No project argument and no local project'))
17
+ return
18
+ }
19
+
20
+ const url = `https://api.integreat.io/projects/${id}`
21
+
22
+ const response = await callApi<ProjectResponse>(url, apiToken)
23
+ if (response.error) {
24
+ log(chalk.red(response.error))
25
+ return
26
+ }
27
+
28
+ const defs = response.project?.definitions
29
+ if (defs) {
30
+ const root = projectId ? projectId : undefined // Use the projectId as root folder if provided as arg
31
+ saveProject(id, defs, root)
32
+ } else {
33
+ log(chalk.red('Project has no definitions'))
34
+ }
35
+ }
36
+
37
+ export default (program: Command, apiToken?: string) =>
38
+ program
39
+ .command('pull')
40
+ .description('pull a project config to a folder structure')
41
+ .argument(
42
+ '[project]',
43
+ 'id of the project to pull, will override any id in the local index file',
44
+ )
45
+ .action(action(console.log, apiToken))
@@ -0,0 +1,81 @@
1
+ import chalk from 'chalk'
2
+ import ora from 'ora'
3
+ import { setTimeout } from 'timers/promises'
4
+ import { select } from '@inquirer/prompts'
5
+ import callApi, { type Response } from '../utils/callApi.js'
6
+ import { fetchIdFromIndex } from '../utils/fetchLocalProject.js'
7
+ import type { Command } from 'commander'
8
+
9
+ interface JobsResponse extends Response {
10
+ jobs?: [
11
+ {
12
+ id: string
13
+ name: string
14
+ description?: string
15
+ watch?: boolean
16
+ sort?: number
17
+ allowManualDispatch?: boolean
18
+ },
19
+ ]
20
+ }
21
+
22
+ export const action = (log: (message: string) => void, apiToken?: string) =>
23
+ async function projectList(projectId?: string) {
24
+ const id = projectId ?? fetchIdFromIndex()
25
+ if (!id) {
26
+ log(chalk.red('No project id given and none found in local project'))
27
+ return
28
+ }
29
+ const url = `https://api.integreat.io/projects/${id}/jobs`
30
+
31
+ const response = await callApi<JobsResponse>(url, apiToken)
32
+ if (response.error) {
33
+ log(chalk.red(response.error))
34
+ return
35
+ }
36
+
37
+ const jobs = response.jobs
38
+ if (Array.isArray(jobs) && jobs.length > 0) {
39
+ const hasWatchJobs = jobs.some((job) => job.watch)
40
+ const listJobs = (
41
+ hasWatchJobs ? jobs.filter((job) => job.watch) : jobs
42
+ ).sort((a, b) => (a.sort ?? 0) - (b.sort ?? 0))
43
+
44
+ let jobId
45
+ try {
46
+ jobId = await select({
47
+ message: 'Select a job to run',
48
+ choices: listJobs.map((job) => ({
49
+ name: job.name,
50
+ value: job.id,
51
+ description: job.description,
52
+ })),
53
+ pageSize: 20,
54
+ loop: false,
55
+ })
56
+ } catch (error) {
57
+ console.error('Job failed', error)
58
+ }
59
+
60
+ if (jobId) {
61
+ const spinner = ora().start()
62
+ await setTimeout(1000)
63
+ spinner.stop()
64
+ log('Job would have run if we had implemented the run command ...')
65
+ }
66
+ } else {
67
+ log(chalk.white('No jobs for this project.'))
68
+ }
69
+ }
70
+
71
+ export default (program: Command, apiToken?: string) =>
72
+ program
73
+ .command('run')
74
+ .description(
75
+ 'run a job in the current project or the project with the provided id',
76
+ )
77
+ .argument(
78
+ '[project]',
79
+ 'id of the project to run jobs for, will override any id in the local index file',
80
+ )
81
+ .action(action(console.log, apiToken))
@@ -0,0 +1,29 @@
1
+ import ky from 'ky'
2
+ import ora from 'ora'
3
+
4
+ export interface Response {
5
+ status?: string
6
+ error?: string
7
+ }
8
+
9
+ export default async function callApi<T extends Response>(
10
+ uri: string,
11
+ token?: string,
12
+ ): Promise<T> {
13
+ if (!token) {
14
+ return {
15
+ status: 'noaccess',
16
+ error: 'You are not logged in to Integreat.',
17
+ } as T
18
+ }
19
+
20
+ const spinner = ora().start()
21
+ const response = await ky
22
+ .get(uri, {
23
+ headers: { Authorization: `Bearer ${token}` },
24
+ throwHttpErrors: false,
25
+ })
26
+ .json<T>()
27
+ spinner.stop()
28
+ return response
29
+ }
@@ -0,0 +1,12 @@
1
+ import fs from 'node:fs'
2
+
3
+ export function fetchIdFromIndex() {
4
+ try {
5
+ const content = fs.readFileSync('index.json', 'utf8')
6
+ const index = JSON.parse(content)
7
+ return index.id
8
+ } catch {
9
+ // TODO: Do proper error handling
10
+ return undefined
11
+ }
12
+ }
@@ -0,0 +1,106 @@
1
+ /* eslint-disable security/detect-non-literal-fs-filename */
2
+ import fs from 'node:fs'
3
+ import type { Definitions } from 'integreat'
4
+
5
+ const normalizeFolderName = (folder: string) => folder.replace(':', '_')
6
+
7
+ function createFolderIfMissing(root?: string) {
8
+ const rootPath = root ? `${root}/` : ''
9
+ return (name: string) => {
10
+ const path = `${rootPath}${name}`
11
+
12
+ if (!fs.existsSync(path)) {
13
+ fs.mkdirSync(path)
14
+ }
15
+ }
16
+ }
17
+
18
+ function writeJsonFile(root?: string) {
19
+ const rootPath = root ? `${root}/` : ''
20
+ return (folder: string | null, name: string, content: unknown) => {
21
+ const path = folder
22
+ ? `${rootPath}${folder}/${name}.json`
23
+ : `${rootPath}${name}.json`
24
+ fs.writeFileSync(path, JSON.stringify(content, undefined, 2))
25
+ }
26
+ }
27
+
28
+ export default function saveProject(
29
+ id: string,
30
+ defs: Definitions,
31
+ root?: string,
32
+ ) {
33
+ const {
34
+ identConfig,
35
+ nonvalues,
36
+ queueService,
37
+ flags,
38
+ schemas,
39
+ services,
40
+ mutations,
41
+ auths,
42
+ dictionaries,
43
+ jobs,
44
+ } = defs
45
+
46
+ const rootFolder = root ? normalizeFolderName(root) : undefined
47
+ const createFolder = createFolderIfMissing(rootFolder)
48
+ const writeFile = writeJsonFile(rootFolder)
49
+
50
+ // Set up root folder
51
+ if (rootFolder) {
52
+ createFolderIfMissing()(rootFolder)
53
+ }
54
+
55
+ // Index
56
+ const index = { id, flags, identConfig, nonvalues, queueService }
57
+ writeFile(null, 'index', index)
58
+
59
+ // Schemas
60
+ createFolder('schemas')
61
+ if (schemas) {
62
+ schemas.forEach((schema) => {
63
+ writeFile('schemas', schema.id, schema)
64
+ })
65
+ }
66
+
67
+ // Services
68
+ createFolder('services')
69
+ if (services) {
70
+ services.forEach((service) => {
71
+ writeFile('services', service.id, service)
72
+ })
73
+ }
74
+
75
+ // Mutations
76
+ createFolder('mutations')
77
+ if (mutations) {
78
+ Object.entries(mutations).forEach(([id, mutation]) => {
79
+ writeFile('mutations', id, { id, pipeline: mutation })
80
+ })
81
+ }
82
+
83
+ // Auths
84
+ createFolder('auths')
85
+ if (auths) {
86
+ auths.forEach((auth) => {
87
+ writeFile('auths', auth.id, auth)
88
+ })
89
+ }
90
+
91
+ // Dictionaries
92
+ createFolder('dictionaries')
93
+ if (dictionaries) {
94
+ Object.entries(dictionaries).forEach(([id, dictionary]) => {
95
+ writeFile('dictionaries', id, dictionary)
96
+ })
97
+ }
98
+
99
+ // Jobs
100
+ createFolder('jobs')
101
+ if (jobs) {
102
+ jobs.forEach((job, index) => {
103
+ writeFile('jobs', job.id ?? `job${index}`, job) // id is required in practice, but not in the typing ...
104
+ })
105
+ }
106
+ }
@@ -0,0 +1,32 @@
1
+ import chalk from 'chalk'
2
+ import callApi, { type Response } from '../utils/callApi.js'
3
+ import type { Command } from 'commander'
4
+
5
+ interface UserResponse extends Response {
6
+ user?: { id: string }
7
+ }
8
+
9
+ export const action = (log: (message: string) => void, apiToken?: string) =>
10
+ async function whoami() {
11
+ const response = await callApi<UserResponse>(
12
+ 'https://api.integreat.io/whoami',
13
+ apiToken,
14
+ )
15
+ if (response.error) {
16
+ log(chalk.red(response.error))
17
+ return
18
+ }
19
+
20
+ const username = response.user?.id
21
+ if (username) {
22
+ log(chalk.white(username))
23
+ } else {
24
+ log(chalk.red('You are not logged in to Integreat.'))
25
+ }
26
+ }
27
+
28
+ export default (program: Command, apiToken?: string) =>
29
+ program
30
+ .command('whoami')
31
+ .description('display your Integreat user name')
32
+ .action(action(console.log, apiToken))