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.
- package/.codeclimate.yml +7 -0
- package/.editorconfig +11 -0
- package/.nvmrc +1 -0
- package/.prettierrc.json +6 -0
- package/README.md +17 -107
- package/dist/accounts/index.d.ts +4 -0
- package/dist/accounts/index.js +28 -0
- package/dist/accounts/index.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/now/index.d.ts +4 -0
- package/dist/now/index.js +9 -0
- package/dist/now/index.js.map +1 -0
- package/dist/now/index.test.d.ts +1 -0
- package/dist/now/index.test.js +18 -0
- package/dist/now/index.test.js.map +1 -0
- package/dist/projects/describe.d.ts +4 -0
- package/dist/projects/describe.js +44 -0
- package/dist/projects/describe.js.map +1 -0
- package/dist/projects/index.d.ts +4 -0
- package/dist/projects/index.js +39 -0
- package/dist/projects/index.js.map +1 -0
- package/dist/pull/index.d.ts +4 -0
- package/dist/pull/index.js +31 -0
- package/dist/pull/index.js.map +1 -0
- package/dist/run/index.d.ts +4 -0
- package/dist/run/index.js +55 -0
- package/dist/run/index.js.map +1 -0
- package/dist/utils/callApi.d.ts +5 -0
- package/dist/utils/callApi.js +20 -0
- package/dist/utils/callApi.js.map +1 -0
- package/dist/utils/fetchLocalProject.d.ts +1 -0
- package/dist/utils/fetchLocalProject.js +12 -0
- package/dist/utils/fetchLocalProject.js.map +1 -0
- package/dist/utils/saveProject.d.ts +2 -0
- package/dist/utils/saveProject.js +68 -0
- package/dist/utils/saveProject.js.map +1 -0
- package/dist/whoami/index.d.ts +4 -0
- package/dist/whoami/index.js +21 -0
- package/dist/whoami/index.js.map +1 -0
- package/eslint.config.js +27 -0
- package/package.json +46 -11
- package/src/accounts/index.ts +41 -0
- package/src/index.ts +22 -0
- package/src/now/index.test.ts +31 -0
- package/src/now/index.ts +15 -0
- package/src/projects/describe.ts +80 -0
- package/src/projects/index.ts +61 -0
- package/src/pull/index.ts +45 -0
- package/src/run/index.ts +81 -0
- package/src/utils/callApi.ts +29 -0
- package/src/utils/fetchLocalProject.ts +12 -0
- package/src/utils/saveProject.ts +106 -0
- package/src/whoami/index.ts +32 -0
- package/tsconfig.json +28 -0
- package/.npmignore +0 -6
- package/Greatfile.js +0 -30
- 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"}
|
package/eslint.config.js
ADDED
|
@@ -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.
|
|
4
|
-
"description": "
|
|
5
|
-
"
|
|
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
|
|
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
|
-
"
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
|
|
16
|
-
|
|
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
|
+
})
|
package/src/now/index.ts
ADDED
|
@@ -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))
|
package/src/run/index.ts
ADDED
|
@@ -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))
|