@carthooks/arcubase-cli 0.1.0
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/bundle/arcubase-admin.mjs +9875 -0
- package/bundle/arcubase.mjs +9875 -0
- package/dist/bin/arcubase-admin.d.ts +2 -0
- package/dist/bin/arcubase-admin.d.ts.map +1 -0
- package/dist/bin/arcubase-admin.js +22 -0
- package/dist/bin/arcubase.d.ts +2 -0
- package/dist/bin/arcubase.d.ts.map +1 -0
- package/dist/bin/arcubase.js +22 -0
- package/dist/generated/command_registry.generated.d.ts +3072 -0
- package/dist/generated/command_registry.generated.d.ts.map +1 -0
- package/dist/generated/command_registry.generated.js +4587 -0
- package/dist/generated/type_index.generated.d.ts +496 -0
- package/dist/generated/type_index.generated.d.ts.map +1 -0
- package/dist/generated/type_index.generated.js +502 -0
- package/dist/generated/zod_registry.generated.d.ts +356 -0
- package/dist/generated/zod_registry.generated.d.ts.map +1 -0
- package/dist/generated/zod_registry.generated.js +483 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/runtime/argv.d.ts +8 -0
- package/dist/runtime/argv.d.ts.map +1 -0
- package/dist/runtime/argv.js +31 -0
- package/dist/runtime/body_loader.d.ts +3 -0
- package/dist/runtime/body_loader.d.ts.map +1 -0
- package/dist/runtime/body_loader.js +26 -0
- package/dist/runtime/command_registry.d.ts +9 -0
- package/dist/runtime/command_registry.d.ts.map +1 -0
- package/dist/runtime/command_registry.js +14 -0
- package/dist/runtime/env.d.ts +14 -0
- package/dist/runtime/env.d.ts.map +1 -0
- package/dist/runtime/env.js +42 -0
- package/dist/runtime/errors.d.ts +23 -0
- package/dist/runtime/errors.d.ts.map +1 -0
- package/dist/runtime/errors.js +18 -0
- package/dist/runtime/execute.d.ts +17 -0
- package/dist/runtime/execute.d.ts.map +1 -0
- package/dist/runtime/execute.js +183 -0
- package/dist/runtime/http.d.ts +4 -0
- package/dist/runtime/http.d.ts.map +1 -0
- package/dist/runtime/http.js +21 -0
- package/dist/runtime/output.d.ts +2 -0
- package/dist/runtime/output.d.ts.map +1 -0
- package/dist/runtime/output.js +3 -0
- package/dist/runtime/paths.d.ts +4 -0
- package/dist/runtime/paths.d.ts.map +1 -0
- package/dist/runtime/paths.js +8 -0
- package/dist/runtime/zod_registry.d.ts +10 -0
- package/dist/runtime/zod_registry.d.ts.map +1 -0
- package/dist/runtime/zod_registry.js +50 -0
- package/dist/tests/bootstrap.test.d.ts +2 -0
- package/dist/tests/bootstrap.test.d.ts.map +1 -0
- package/dist/tests/bootstrap.test.js +59 -0
- package/dist/tests/command_registry.test.d.ts +2 -0
- package/dist/tests/command_registry.test.d.ts.map +1 -0
- package/dist/tests/command_registry.test.js +22 -0
- package/dist/tests/execute_validation.test.d.ts +2 -0
- package/dist/tests/execute_validation.test.d.ts.map +1 -0
- package/dist/tests/execute_validation.test.js +41 -0
- package/dist/tests/help.test.d.ts +2 -0
- package/dist/tests/help.test.d.ts.map +1 -0
- package/dist/tests/help.test.js +22 -0
- package/dist/tests/zod_registry.test.d.ts +2 -0
- package/dist/tests/zod_registry.test.d.ts.map +1 -0
- package/dist/tests/zod_registry.test.js +26 -0
- package/package.json +46 -0
- package/sdk-dist/api/admin/app-entity-share.ts +52 -0
- package/sdk-dist/api/admin/app.ts +187 -0
- package/sdk-dist/api/admin/automate-rule.ts +59 -0
- package/sdk-dist/api/admin/business-hooks.ts +17 -0
- package/sdk-dist/api/admin/config.ts +12 -0
- package/sdk-dist/api/admin/dashboard.ts +88 -0
- package/sdk-dist/api/admin/dataset.ts +133 -0
- package/sdk-dist/api/admin/departments.ts +223 -0
- package/sdk-dist/api/admin/developer.ts +126 -0
- package/sdk-dist/api/admin/entity-tags.ts +71 -0
- package/sdk-dist/api/admin/entity.ts +188 -0
- package/sdk-dist/api/admin/extension.ts +19 -0
- package/sdk-dist/api/admin/index.ts +21 -0
- package/sdk-dist/api/admin/ingress.ts +117 -0
- package/sdk-dist/api/admin/integration.ts +64 -0
- package/sdk-dist/api/admin/kiosk-flow.ts +67 -0
- package/sdk-dist/api/admin/print-template-background.ts +43 -0
- package/sdk-dist/api/admin/print-templates.ts +60 -0
- package/sdk-dist/api/admin/tenant.ts +231 -0
- package/sdk-dist/api/admin/workflow.ts +51 -0
- package/sdk-dist/api/kiosk/index.ts +4 -0
- package/sdk-dist/api/kiosk/starter.ts +34 -0
- package/sdk-dist/api/kiosk/ui.ts +79 -0
- package/sdk-dist/api/open/api.ts +11 -0
- package/sdk-dist/api/open/index.ts +3 -0
- package/sdk-dist/api/shared-link/form.ts +103 -0
- package/sdk-dist/api/shared-link/index.ts +3 -0
- package/sdk-dist/api/user/common.ts +159 -0
- package/sdk-dist/api/user/context.ts +10 -0
- package/sdk-dist/api/user/copilot.ts +20 -0
- package/sdk-dist/api/user/dashboard.ts +43 -0
- package/sdk-dist/api/user/entity.ts +213 -0
- package/sdk-dist/api/user/flow.ts +20 -0
- package/sdk-dist/api/user/global-action.ts +128 -0
- package/sdk-dist/api/user/index.ts +12 -0
- package/sdk-dist/api/user/kiosk.ts +12 -0
- package/sdk-dist/api/user/profile.ts +57 -0
- package/sdk-dist/api/user/workflow.ts +186 -0
- package/sdk-dist/generated/command_registry.generated.ts +4603 -0
- package/sdk-dist/generated/type_index.generated.ts +503 -0
- package/sdk-dist/generated/zod_registry.generated.ts +713 -0
- package/sdk-dist/types/app-user.ts +0 -0
- package/sdk-dist/types/app.ts +182 -0
- package/sdk-dist/types/auth.ts +174 -0
- package/sdk-dist/types/automate-rule.ts +100 -0
- package/sdk-dist/types/business-hooks.ts +37 -0
- package/sdk-dist/types/common.ts +676 -0
- package/sdk-dist/types/copilot.ts +34 -0
- package/sdk-dist/types/dashboard.ts +122 -0
- package/sdk-dist/types/dataset.ts +168 -0
- package/sdk-dist/types/departments.ts +351 -0
- package/sdk-dist/types/developer.ts +190 -0
- package/sdk-dist/types/entity.ts +400 -0
- package/sdk-dist/types/extension.ts +15 -0
- package/sdk-dist/types/global-action.ts +206 -0
- package/sdk-dist/types/index.ts +26 -0
- package/sdk-dist/types/ingress.ts +173 -0
- package/sdk-dist/types/kiosk-starter.ts +46 -0
- package/sdk-dist/types/kiosk-ui.ts +94 -0
- package/sdk-dist/types/oauth-authorize.ts +52 -0
- package/sdk-dist/types/oauth.ts +33 -0
- package/sdk-dist/types/openapi.ts +24 -0
- package/sdk-dist/types/platform-setup.ts +26 -0
- package/sdk-dist/types/shared-link.ts +41 -0
- package/sdk-dist/types/tenant.ts +340 -0
- package/sdk-dist/types/upload.ts +0 -0
- package/sdk-dist/types/user-action.ts +871 -0
- package/sdk-dist/types/user-flow.ts +90 -0
- package/sdk-dist/types/widgets.ts +115 -0
- package/sdk-dist/types/workflow.ts +162 -0
- package/src/bin/arcubase-admin.ts +23 -0
- package/src/bin/arcubase.ts +23 -0
- package/src/generated/command_registry.generated.ts +4603 -0
- package/src/generated/type_index.generated.ts +503 -0
- package/src/generated/zod_registry.generated.ts +713 -0
- package/src/index.ts +4 -0
- package/src/runtime/argv.ts +39 -0
- package/src/runtime/body_loader.ts +26 -0
- package/src/runtime/command_registry.ts +22 -0
- package/src/runtime/env.ts +60 -0
- package/src/runtime/errors.ts +41 -0
- package/src/runtime/execute.ts +206 -0
- package/src/runtime/http.ts +23 -0
- package/src/runtime/output.ts +3 -0
- package/src/runtime/paths.ts +8 -0
- package/src/runtime/zod_registry.ts +53 -0
- package/src/tests/bootstrap.test.ts +67 -0
- package/src/tests/command_registry.test.ts +27 -0
- package/src/tests/execute_validation.test.ts +50 -0
- package/src/tests/help.test.ts +25 -0
- package/src/tests/zod_registry.test.ts +33 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { CLIError } from './errors.js'
|
|
2
|
+
|
|
3
|
+
export type ParsedCLIInput = {
|
|
4
|
+
commandTokens: string[]
|
|
5
|
+
flags: Record<string, string | boolean>
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function parseCLIArgs(argv: string[]): ParsedCLIInput {
|
|
9
|
+
const commandTokens: string[] = []
|
|
10
|
+
const flags: Record<string, string | boolean> = {}
|
|
11
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
12
|
+
const token = argv[i]
|
|
13
|
+
if (!token.startsWith('--')) {
|
|
14
|
+
commandTokens.push(token)
|
|
15
|
+
continue
|
|
16
|
+
}
|
|
17
|
+
const name = token.slice(2)
|
|
18
|
+
if (!name) {
|
|
19
|
+
throw new CLIError('INVALID_FLAG', 'flag name is required', 2)
|
|
20
|
+
}
|
|
21
|
+
const next = argv[i + 1]
|
|
22
|
+
if (next && !next.startsWith('--')) {
|
|
23
|
+
flags[name] = next
|
|
24
|
+
i += 1
|
|
25
|
+
continue
|
|
26
|
+
}
|
|
27
|
+
flags[name] = true
|
|
28
|
+
}
|
|
29
|
+
return { commandTokens, flags }
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function flagValue(flags: Record<string, string | boolean>, name: string): string | undefined {
|
|
33
|
+
const value = flags[name]
|
|
34
|
+
return typeof value === 'string' ? value : undefined
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function hasFlag(flags: Record<string, string | boolean>, name: string): boolean {
|
|
38
|
+
return Boolean(flags[name])
|
|
39
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import { CLIError } from './errors.js'
|
|
3
|
+
|
|
4
|
+
function parseJSONFile(filePath: string, label: string): unknown {
|
|
5
|
+
try {
|
|
6
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'))
|
|
7
|
+
} catch (error) {
|
|
8
|
+
throw new CLIError('INVALID_JSON_FILE', `${label} file is invalid: ${filePath}`, 2)
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function loadBodyFromFlags(flags: Record<string, string | boolean>): unknown | undefined {
|
|
13
|
+
const bodyFile = typeof flags['body-file'] === 'string' ? flags['body-file'] : undefined
|
|
14
|
+
if (!bodyFile) return undefined
|
|
15
|
+
return parseJSONFile(bodyFile, 'body')
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function loadQueryFromFlags(flags: Record<string, string | boolean>): Record<string, any> | undefined {
|
|
19
|
+
const queryFile = typeof flags['query-file'] === 'string' ? flags['query-file'] : undefined
|
|
20
|
+
if (!queryFile) return undefined
|
|
21
|
+
const parsed = parseJSONFile(queryFile, 'query')
|
|
22
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
23
|
+
throw new CLIError('INVALID_QUERY_FILE', 'query file must contain a JSON object', 2)
|
|
24
|
+
}
|
|
25
|
+
return parsed as Record<string, any>
|
|
26
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { adminCommands, userCommands } from '../generated/command_registry.generated.js'
|
|
2
|
+
|
|
3
|
+
export { adminCommands, userCommands }
|
|
4
|
+
|
|
5
|
+
export type CommandScope = 'admin' | 'user'
|
|
6
|
+
export type CommandDef = (typeof adminCommands)[number] | (typeof userCommands)[number]
|
|
7
|
+
|
|
8
|
+
export function listCommands(scope: CommandScope): readonly CommandDef[] {
|
|
9
|
+
return scope === 'admin' ? adminCommands : userCommands
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function findCommand(scope: CommandScope, moduleName: string, commandName: string): CommandDef | null {
|
|
13
|
+
return listCommands(scope).find((item) => item.commandPath[0] === moduleName && item.commandPath[1] === commandName) ?? null
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function listModules(scope: CommandScope): string[] {
|
|
17
|
+
return [...new Set(listCommands(scope).map((item) => item.commandPath[0]))].sort()
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function listModuleCommands(scope: CommandScope, moduleName: string): readonly CommandDef[] {
|
|
21
|
+
return listCommands(scope).filter((item) => item.commandPath[0] === moduleName)
|
|
22
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
export type RuntimeEnv = {
|
|
2
|
+
ARCUBASE_BASE_URL: string
|
|
3
|
+
ARCUBASE_TENANT_ID: string
|
|
4
|
+
ARCUBASE_ACCESS_TOKEN: string
|
|
5
|
+
ARCUBASE_TRACE_ID?: string
|
|
6
|
+
ARCUBASE_SUBJECT_TYPE?: string
|
|
7
|
+
ARCUBASE_SUBJECT_ID?: string
|
|
8
|
+
ARCUBASE_TOKEN_EXPIRES_AT?: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type RuntimeEnvScope = 'admin' | 'user'
|
|
12
|
+
|
|
13
|
+
type EnvInput = Record<string, string | undefined>
|
|
14
|
+
|
|
15
|
+
const PREFIX_BY_SCOPE: Record<RuntimeEnvScope, string> = {
|
|
16
|
+
admin: 'ARCUBASE_ADMIN_',
|
|
17
|
+
user: 'ARCUBASE_USER_',
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function envValue(env: EnvInput, scope: RuntimeEnvScope, key: keyof RuntimeEnv): string | undefined {
|
|
21
|
+
const direct = env[key]
|
|
22
|
+
if (direct && direct.trim() !== '') {
|
|
23
|
+
return direct.trim()
|
|
24
|
+
}
|
|
25
|
+
const suffix = String(key).replace(/^ARCUBASE_/, '')
|
|
26
|
+
const scopedKey = `${PREFIX_BY_SCOPE[scope]}${suffix}`
|
|
27
|
+
const scopedValue = env[scopedKey]
|
|
28
|
+
if (!scopedValue || scopedValue.trim() === '') {
|
|
29
|
+
return undefined
|
|
30
|
+
}
|
|
31
|
+
return scopedValue.trim()
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function requiredValue(env: EnvInput, scope: RuntimeEnvScope, key: keyof RuntimeEnv): string {
|
|
35
|
+
const value = envValue(env, scope, key)
|
|
36
|
+
if (!value || value.trim() === '') {
|
|
37
|
+
throw new Error(`${key} is required`)
|
|
38
|
+
}
|
|
39
|
+
return value
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function optionalValue(env: EnvInput, scope: RuntimeEnvScope, key: keyof RuntimeEnv): string | undefined {
|
|
43
|
+
const value = envValue(env, scope, key)
|
|
44
|
+
if (!value || value.trim() === '') {
|
|
45
|
+
return undefined
|
|
46
|
+
}
|
|
47
|
+
return value
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function loadRuntimeEnv(scope: RuntimeEnvScope, env: EnvInput = process.env): RuntimeEnv {
|
|
51
|
+
return {
|
|
52
|
+
ARCUBASE_BASE_URL: requiredValue(env, scope, 'ARCUBASE_BASE_URL'),
|
|
53
|
+
ARCUBASE_TENANT_ID: requiredValue(env, scope, 'ARCUBASE_TENANT_ID'),
|
|
54
|
+
ARCUBASE_ACCESS_TOKEN: requiredValue(env, scope, 'ARCUBASE_ACCESS_TOKEN'),
|
|
55
|
+
ARCUBASE_TRACE_ID: optionalValue(env, scope, 'ARCUBASE_TRACE_ID'),
|
|
56
|
+
ARCUBASE_SUBJECT_TYPE: optionalValue(env, scope, 'ARCUBASE_SUBJECT_TYPE'),
|
|
57
|
+
ARCUBASE_SUBJECT_ID: optionalValue(env, scope, 'ARCUBASE_SUBJECT_ID'),
|
|
58
|
+
ARCUBASE_TOKEN_EXPIRES_AT: optionalValue(env, scope, 'ARCUBASE_TOKEN_EXPIRES_AT')
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export type CLIValidationIssue = {
|
|
2
|
+
path: string
|
|
3
|
+
message: string
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export type CLITSTypeHint = {
|
|
7
|
+
symbol: string
|
|
8
|
+
file: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type CLIErrorDetails = {
|
|
12
|
+
operation?: string
|
|
13
|
+
requestType?: string
|
|
14
|
+
issues?: CLIValidationIssue[]
|
|
15
|
+
tsHints?: CLITSTypeHint[]
|
|
16
|
+
reason?: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class CLIError extends Error {
|
|
20
|
+
readonly code: string
|
|
21
|
+
readonly exitCode: number
|
|
22
|
+
readonly details?: CLIErrorDetails
|
|
23
|
+
|
|
24
|
+
constructor(code: string, message: string, exitCode = 1, details?: CLIErrorDetails) {
|
|
25
|
+
super(message)
|
|
26
|
+
this.name = 'CLIError'
|
|
27
|
+
this.code = code
|
|
28
|
+
this.exitCode = exitCode
|
|
29
|
+
this.details = details
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
toJSON(): Record<string, unknown> {
|
|
33
|
+
return {
|
|
34
|
+
error: {
|
|
35
|
+
code: this.code,
|
|
36
|
+
message: this.message,
|
|
37
|
+
...(this.details ? this.details : {}),
|
|
38
|
+
},
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { parseCLIArgs, hasFlag, flagValue } from './argv.js'
|
|
2
|
+
import { loadRuntimeEnv, type RuntimeEnv } from './env.js'
|
|
3
|
+
import { CLIError } from './errors.js'
|
|
4
|
+
import { buildRequestHeaders, buildURL } from './http.js'
|
|
5
|
+
import { listModules, listModuleCommands, findCommand, type CommandScope } from './command_registry.js'
|
|
6
|
+
import { loadBodyFromFlags, loadQueryFromFlags } from './body_loader.js'
|
|
7
|
+
import { getBodySchema, getTypeHints, getUnsupportedBodySchemaReason } from './zod_registry.js'
|
|
8
|
+
|
|
9
|
+
export type CommandExecutionSummary = {
|
|
10
|
+
scope: CommandScope
|
|
11
|
+
commandPath: readonly [string, string]
|
|
12
|
+
method: string
|
|
13
|
+
endpoint: string
|
|
14
|
+
requestType: string | null
|
|
15
|
+
pathFlags: readonly string[]
|
|
16
|
+
scalarFlags: readonly string[]
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function renderRootHelp(scope: CommandScope): string {
|
|
20
|
+
const binary = scope === 'admin' ? 'arcubase-admin' : 'arcubase'
|
|
21
|
+
return [
|
|
22
|
+
`${binary} <module> <command> [flags]`,
|
|
23
|
+
'',
|
|
24
|
+
'modules:',
|
|
25
|
+
...listModules(scope).map((item) => ` - ${item}`),
|
|
26
|
+
].join('\n')
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function renderModuleHelp(scope: CommandScope, moduleName: string): string {
|
|
30
|
+
const items = listModuleCommands(scope, moduleName)
|
|
31
|
+
if (items.length === 0) {
|
|
32
|
+
throw new CLIError('UNKNOWN_MODULE', `unknown module: ${moduleName}`, 2)
|
|
33
|
+
}
|
|
34
|
+
return [
|
|
35
|
+
`${scope === 'admin' ? 'arcubase-admin' : 'arcubase'} ${moduleName} <command> [flags]`,
|
|
36
|
+
'',
|
|
37
|
+
'commands:',
|
|
38
|
+
...items.map((item) => ` - ${item.commandPath[1]}`),
|
|
39
|
+
].join('\n')
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function renderCommandHelp(scope: CommandScope, moduleName: string, commandName: string): string {
|
|
43
|
+
const command = findCommand(scope, moduleName, commandName)
|
|
44
|
+
if (!command) {
|
|
45
|
+
throw new CLIError('UNKNOWN_COMMAND', `unknown command: ${moduleName} ${commandName}`, 2)
|
|
46
|
+
}
|
|
47
|
+
return [
|
|
48
|
+
`${scope === 'admin' ? 'arcubase-admin' : 'arcubase'} ${moduleName} ${commandName}`,
|
|
49
|
+
`method: ${command.method}`,
|
|
50
|
+
`endpoint: ${command.endpoint}`,
|
|
51
|
+
command.requestType ? `body: ${command.requestType} via --body-file` : 'body: none',
|
|
52
|
+
command.endpointParams.length ? `path flags: ${command.endpointParams.map((item) => `--${item.replace(/_/g, '-')}`).join(', ')}` : 'path flags: none',
|
|
53
|
+
command.scalarParams.length ? `scalar flags: ${command.scalarParams.map((item) => `--${item.name.replace(/([A-Z])/g, '-$1').toLowerCase()}`).join(', ')}` : 'scalar flags: none',
|
|
54
|
+
].join('\n')
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function coerceScalar(value: string, typeText: string): any {
|
|
58
|
+
const normalized = typeText.trim()
|
|
59
|
+
if (normalized === 'number' || normalized === 'string | number') {
|
|
60
|
+
const parsed = Number(value)
|
|
61
|
+
if (Number.isNaN(parsed)) {
|
|
62
|
+
throw new CLIError('INVALID_FLAG_VALUE', `expected number, got ${value}`, 2)
|
|
63
|
+
}
|
|
64
|
+
return parsed
|
|
65
|
+
}
|
|
66
|
+
if (normalized === 'boolean') {
|
|
67
|
+
if (value === 'true') return true
|
|
68
|
+
if (value === 'false') return false
|
|
69
|
+
throw new CLIError('INVALID_FLAG_VALUE', `expected boolean, got ${value}`, 2)
|
|
70
|
+
}
|
|
71
|
+
return value
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function resolveEndpoint(endpoint: string, flags: Record<string, string | boolean>): string {
|
|
75
|
+
let resolved = endpoint
|
|
76
|
+
for (const placeholder of [...endpoint.matchAll(/:([A-Za-z0-9_]+)/g)].map((match) => match[1])) {
|
|
77
|
+
const flagName = placeholder.replace(/_/g, '-')
|
|
78
|
+
const value = flagValue(flags, flagName)
|
|
79
|
+
if (!value) {
|
|
80
|
+
throw new CLIError('MISSING_PATH_FLAG', `missing required flag --${flagName}`, 2)
|
|
81
|
+
}
|
|
82
|
+
resolved = resolved.replace(`:${placeholder}`, encodeURIComponent(value))
|
|
83
|
+
}
|
|
84
|
+
return resolved
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function buildScalarQuery(command: ReturnType<typeof findCommand>, flags: Record<string, string | boolean>): Record<string, any> | undefined {
|
|
88
|
+
if (!command) {
|
|
89
|
+
return undefined
|
|
90
|
+
}
|
|
91
|
+
const out: Record<string, any> = {}
|
|
92
|
+
for (const scalar of command.scalarParams) {
|
|
93
|
+
const flagName = scalar.name.replace(/([A-Z])/g, '-$1').toLowerCase()
|
|
94
|
+
const value = flagValue(flags, flagName)
|
|
95
|
+
if (value === undefined) continue
|
|
96
|
+
out[scalar.name] = coerceScalar(value, scalar.type)
|
|
97
|
+
}
|
|
98
|
+
return Object.keys(out).length > 0 ? out : undefined
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function summarizeCommand(scope: CommandScope, moduleName: string, commandName: string): CommandExecutionSummary {
|
|
102
|
+
const command = findCommand(scope, moduleName, commandName)
|
|
103
|
+
if (!command) {
|
|
104
|
+
throw new CLIError('UNKNOWN_COMMAND', `unknown command: ${moduleName} ${commandName}`, 2)
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
scope,
|
|
108
|
+
commandPath: command.commandPath,
|
|
109
|
+
method: command.method,
|
|
110
|
+
endpoint: command.endpoint,
|
|
111
|
+
requestType: command.requestType,
|
|
112
|
+
pathFlags: command.endpointParams,
|
|
113
|
+
scalarFlags: command.scalarParams.map((item) => item.name.replace(/([A-Z])/g, '-$1').toLowerCase()),
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export async function executeCLI(scope: CommandScope, argv: string[], env: RuntimeEnv = loadRuntimeEnv(scope), fetchImpl: typeof fetch = fetch): Promise<any> {
|
|
118
|
+
const parsed = parseCLIArgs(argv)
|
|
119
|
+
if (parsed.commandTokens.length === 0) {
|
|
120
|
+
return { kind: 'help', text: renderRootHelp(scope) }
|
|
121
|
+
}
|
|
122
|
+
const [moduleName, commandName] = parsed.commandTokens
|
|
123
|
+
if (!moduleName) {
|
|
124
|
+
return { kind: 'help', text: renderRootHelp(scope) }
|
|
125
|
+
}
|
|
126
|
+
if (!commandName) {
|
|
127
|
+
return { kind: 'help', text: renderModuleHelp(scope, moduleName) }
|
|
128
|
+
}
|
|
129
|
+
if (hasFlag(parsed.flags, 'help')) {
|
|
130
|
+
return { kind: 'help', text: renderCommandHelp(scope, moduleName, commandName) }
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const command = findCommand(scope, moduleName, commandName)
|
|
134
|
+
if (!command) {
|
|
135
|
+
throw new CLIError('UNKNOWN_COMMAND', `unknown command: ${moduleName} ${commandName}`, 2)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const endpoint = resolveEndpoint(command.endpoint, parsed.flags)
|
|
139
|
+
const scalarQuery = buildScalarQuery(command, parsed.flags)
|
|
140
|
+
const fileQuery = loadQueryFromFlags(parsed.flags)
|
|
141
|
+
const query = { ...(scalarQuery ?? {}), ...(fileQuery ?? {}) }
|
|
142
|
+
const body = loadBodyFromFlags(parsed.flags)
|
|
143
|
+
|
|
144
|
+
if (command.requestType) {
|
|
145
|
+
if (body === undefined) {
|
|
146
|
+
throw new CLIError('MISSING_BODY_FILE', `command requires --body-file for ${command.requestType}`, 2)
|
|
147
|
+
}
|
|
148
|
+
const schema = getBodySchema(command.requestType)
|
|
149
|
+
if (schema) {
|
|
150
|
+
const parsedBody = schema.safeParse(body)
|
|
151
|
+
if (!parsedBody.success) {
|
|
152
|
+
throw new CLIError(
|
|
153
|
+
'BODY_VALIDATION_FAILED',
|
|
154
|
+
parsedBody.error.issues.map((issue) => `${issue.path.join('.')}: ${issue.message}`).join('; '),
|
|
155
|
+
2,
|
|
156
|
+
{
|
|
157
|
+
operation: `${scope}.${command.module}.${command.functionName}`,
|
|
158
|
+
requestType: command.requestType,
|
|
159
|
+
issues: parsedBody.error.issues.map((issue) => ({
|
|
160
|
+
path: issue.path.length > 0 ? `body.${issue.path.join('.')}` : 'body',
|
|
161
|
+
message: issue.message,
|
|
162
|
+
})),
|
|
163
|
+
tsHints: getTypeHints(command.requestType),
|
|
164
|
+
}
|
|
165
|
+
)
|
|
166
|
+
}
|
|
167
|
+
} else {
|
|
168
|
+
const unsupportedReason = getUnsupportedBodySchemaReason(command.requestType)
|
|
169
|
+
if (unsupportedReason) {
|
|
170
|
+
throw new CLIError('UNSUPPORTED_BODY_SCHEMA', `${command.requestType}: ${unsupportedReason}`, 2, {
|
|
171
|
+
operation: `${scope}.${command.module}.${command.functionName}`,
|
|
172
|
+
requestType: command.requestType,
|
|
173
|
+
reason: unsupportedReason,
|
|
174
|
+
tsHints: getTypeHints(command.requestType),
|
|
175
|
+
})
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const headers = buildRequestHeaders(env)
|
|
181
|
+
const response = await fetchImpl(buildURL(env.ARCUBASE_BASE_URL, endpoint, Object.keys(query).length > 0 ? query : undefined), {
|
|
182
|
+
method: command.method,
|
|
183
|
+
headers: {
|
|
184
|
+
...headers,
|
|
185
|
+
'Content-Type': 'application/json',
|
|
186
|
+
},
|
|
187
|
+
body: body === undefined || command.method === 'GET' ? undefined : JSON.stringify(body),
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
const raw = await response.text()
|
|
191
|
+
let parsedResponse: any = raw
|
|
192
|
+
try {
|
|
193
|
+
parsedResponse = raw ? JSON.parse(raw) : null
|
|
194
|
+
} catch {
|
|
195
|
+
// keep raw text
|
|
196
|
+
}
|
|
197
|
+
if (!response.ok) {
|
|
198
|
+
throw new CLIError('UPSTREAM_REQUEST_FAILED', typeof parsedResponse === 'string' ? parsedResponse : JSON.stringify(parsedResponse), 1)
|
|
199
|
+
}
|
|
200
|
+
return {
|
|
201
|
+
kind: 'result',
|
|
202
|
+
commandPath: command.commandPath,
|
|
203
|
+
status: response.status,
|
|
204
|
+
data: parsedResponse,
|
|
205
|
+
}
|
|
206
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { RuntimeEnv } from './env.js'
|
|
2
|
+
|
|
3
|
+
export function buildRequestHeaders(env: Pick<RuntimeEnv, 'ARCUBASE_ACCESS_TOKEN' | 'ARCUBASE_TENANT_ID' | 'ARCUBASE_TRACE_ID'>): Record<string, string> {
|
|
4
|
+
const headers: Record<string, string> = {
|
|
5
|
+
Authorization: `Bearer ${env.ARCUBASE_ACCESS_TOKEN}`,
|
|
6
|
+
'X-Tenant-Id': env.ARCUBASE_TENANT_ID
|
|
7
|
+
}
|
|
8
|
+
if (env.ARCUBASE_TRACE_ID) {
|
|
9
|
+
headers['X-Trace-Id'] = env.ARCUBASE_TRACE_ID
|
|
10
|
+
}
|
|
11
|
+
return headers
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function buildURL(baseURL: string, endpoint: string, query?: Record<string, any>): string {
|
|
15
|
+
const url = new URL(endpoint, baseURL.endsWith('/') ? baseURL : `${baseURL}/`)
|
|
16
|
+
if (query) {
|
|
17
|
+
for (const [key, value] of Object.entries(query)) {
|
|
18
|
+
if (value === undefined || value === null) continue
|
|
19
|
+
url.searchParams.set(key, typeof value === 'string' ? value : JSON.stringify(value))
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return url.toString()
|
|
23
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export const DEFAULT_ARCUBASE_CLI_ROOT = '/opt/arcubase-cli'
|
|
2
|
+
export const DEFAULT_ARCUBASE_SDK_ROOT = '/opt/arcubase-sdk'
|
|
3
|
+
|
|
4
|
+
export function resolveArcubaseSDKRoot(env: Record<string, string | undefined> = process.env): string {
|
|
5
|
+
const value = env.ARCUBASE_SDK_ROOT?.trim()
|
|
6
|
+
if (value) return value
|
|
7
|
+
return DEFAULT_ARCUBASE_SDK_ROOT
|
|
8
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { ZodTypeAny } from 'zod'
|
|
2
|
+
import { bodySchemas, unsupportedBodySchemas } from '../generated/zod_registry.generated.js'
|
|
3
|
+
import { typeIndex } from '../generated/type_index.generated.js'
|
|
4
|
+
|
|
5
|
+
export function getBodySchema(typeName: string): ZodTypeAny | null {
|
|
6
|
+
const name = String(typeName || '').trim()
|
|
7
|
+
if (!name) return null
|
|
8
|
+
return (bodySchemas as Record<string, ZodTypeAny | undefined>)[name] ?? null
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function getUnsupportedBodySchemaReason(typeName: string): string | null {
|
|
12
|
+
const name = String(typeName || '').trim()
|
|
13
|
+
if (!name) return null
|
|
14
|
+
return (unsupportedBodySchemas as Record<string, string | undefined>)[name] ?? null
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function requireBodySchema(typeName: string): ZodTypeAny {
|
|
18
|
+
const schema = getBodySchema(typeName)
|
|
19
|
+
if (schema) return schema
|
|
20
|
+
const reason = getUnsupportedBodySchemaReason(typeName)
|
|
21
|
+
if (reason) {
|
|
22
|
+
throw new Error(`runtime validation for ${typeName} is not supported: ${reason}`)
|
|
23
|
+
}
|
|
24
|
+
throw new Error(`runtime validation schema not found for ${typeName}`)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export type RuntimeTypeHint = {
|
|
28
|
+
symbol: string
|
|
29
|
+
file: string
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function getTypeHints(typeName: string): RuntimeTypeHint[] {
|
|
33
|
+
const name = String(typeName || '').trim()
|
|
34
|
+
if (!name) return []
|
|
35
|
+
const entry = (typeIndex as Record<string, { file: string; dependencies?: ReadonlyArray<RuntimeTypeHint> } | undefined>)[name]
|
|
36
|
+
if (!entry?.file) return []
|
|
37
|
+
const seen = new Set<string>()
|
|
38
|
+
const out: RuntimeTypeHint[] = []
|
|
39
|
+
const append = (symbol: string, file: string) => {
|
|
40
|
+
symbol = String(symbol || '').trim()
|
|
41
|
+
file = String(file || '').trim()
|
|
42
|
+
if (!symbol || !file) return
|
|
43
|
+
const key = `${symbol}@@${file}`
|
|
44
|
+
if (seen.has(key)) return
|
|
45
|
+
seen.add(key)
|
|
46
|
+
out.push({ symbol, file })
|
|
47
|
+
}
|
|
48
|
+
append(name, entry.file)
|
|
49
|
+
for (const dep of entry.dependencies ?? []) {
|
|
50
|
+
append(dep.symbol, dep.file)
|
|
51
|
+
}
|
|
52
|
+
return out
|
|
53
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import test from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
|
|
4
|
+
import { loadRuntimeEnv } from '../runtime/env.js'
|
|
5
|
+
|
|
6
|
+
test('loadRuntimeEnv requires base url, tenant id and access token', () => {
|
|
7
|
+
assert.throws(() => loadRuntimeEnv('admin', {}), /ARCUBASE_BASE_URL is required/)
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
test('loadRuntimeEnv returns normalized runtime env', () => {
|
|
11
|
+
const env = loadRuntimeEnv('admin', {
|
|
12
|
+
ARCUBASE_BASE_URL: ' http://example.com ',
|
|
13
|
+
ARCUBASE_TENANT_ID: ' tenant-1 ',
|
|
14
|
+
ARCUBASE_ACCESS_TOKEN: ' token-1 ',
|
|
15
|
+
ARCUBASE_TRACE_ID: ' trace-1 '
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
assert.deepEqual(env, {
|
|
19
|
+
ARCUBASE_BASE_URL: 'http://example.com',
|
|
20
|
+
ARCUBASE_TENANT_ID: 'tenant-1',
|
|
21
|
+
ARCUBASE_ACCESS_TOKEN: 'token-1',
|
|
22
|
+
ARCUBASE_TRACE_ID: 'trace-1',
|
|
23
|
+
ARCUBASE_SUBJECT_TYPE: undefined,
|
|
24
|
+
ARCUBASE_SUBJECT_ID: undefined,
|
|
25
|
+
ARCUBASE_TOKEN_EXPIRES_AT: undefined
|
|
26
|
+
})
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
test('loadRuntimeEnv accepts admin-prefixed runtime env', () => {
|
|
30
|
+
const env = loadRuntimeEnv('admin', {
|
|
31
|
+
ARCUBASE_ADMIN_BASE_URL: ' https://admin.example.com ',
|
|
32
|
+
ARCUBASE_ADMIN_TENANT_ID: ' tenant-admin ',
|
|
33
|
+
ARCUBASE_ADMIN_ACCESS_TOKEN: ' token-admin ',
|
|
34
|
+
ARCUBASE_ADMIN_SUBJECT_TYPE: ' system_admin ',
|
|
35
|
+
ARCUBASE_ADMIN_SUBJECT_ID: ' user-admin ',
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
assert.deepEqual(env, {
|
|
39
|
+
ARCUBASE_BASE_URL: 'https://admin.example.com',
|
|
40
|
+
ARCUBASE_TENANT_ID: 'tenant-admin',
|
|
41
|
+
ARCUBASE_ACCESS_TOKEN: 'token-admin',
|
|
42
|
+
ARCUBASE_TRACE_ID: undefined,
|
|
43
|
+
ARCUBASE_SUBJECT_TYPE: 'system_admin',
|
|
44
|
+
ARCUBASE_SUBJECT_ID: 'user-admin',
|
|
45
|
+
ARCUBASE_TOKEN_EXPIRES_AT: undefined
|
|
46
|
+
})
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
test('loadRuntimeEnv accepts user-prefixed runtime env', () => {
|
|
50
|
+
const env = loadRuntimeEnv('user', {
|
|
51
|
+
ARCUBASE_USER_BASE_URL: ' https://user.example.com ',
|
|
52
|
+
ARCUBASE_USER_TENANT_ID: ' tenant-user ',
|
|
53
|
+
ARCUBASE_USER_ACCESS_TOKEN: ' token-user ',
|
|
54
|
+
ARCUBASE_USER_SUBJECT_TYPE: ' service_account ',
|
|
55
|
+
ARCUBASE_USER_SUBJECT_ID: ' sa-user ',
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
assert.deepEqual(env, {
|
|
59
|
+
ARCUBASE_BASE_URL: 'https://user.example.com',
|
|
60
|
+
ARCUBASE_TENANT_ID: 'tenant-user',
|
|
61
|
+
ARCUBASE_ACCESS_TOKEN: 'token-user',
|
|
62
|
+
ARCUBASE_TRACE_ID: undefined,
|
|
63
|
+
ARCUBASE_SUBJECT_TYPE: 'service_account',
|
|
64
|
+
ARCUBASE_SUBJECT_ID: 'sa-user',
|
|
65
|
+
ARCUBASE_TOKEN_EXPIRES_AT: undefined
|
|
66
|
+
})
|
|
67
|
+
})
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import test from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
|
|
4
|
+
import { adminCommands, userCommands } from '../generated/command_registry.generated.js'
|
|
5
|
+
|
|
6
|
+
test('admin app create-entity command exists', () => {
|
|
7
|
+
assert.ok(adminCommands.some((c) => c.commandPath.join(' ') === 'app create-entity'))
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
test('user workflow query-entity command exists', () => {
|
|
11
|
+
assert.ok(userCommands.some((c) => c.commandPath.join(' ') === 'workflow query-entity'))
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
test('inline request type keeps full object text instead of truncated brace', () => {
|
|
15
|
+
const command = adminCommands.find((c) => c.commandPath.join(' ') === 'dashboard clone-dashboard')
|
|
16
|
+
assert.ok(command)
|
|
17
|
+
const typed = command!
|
|
18
|
+
assert.match(String(typed.requestType), /^\{\s*name:\s*string\s*\}$/)
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
test('tag commands no longer emit truncated inline request type', () => {
|
|
22
|
+
const command = adminCommands.find((c) => c.commandPath.join(' ') === 'entity-tags batch-add-entity-tags')
|
|
23
|
+
assert.ok(command)
|
|
24
|
+
const typed = command!
|
|
25
|
+
assert.match(String(typed.requestType), /^\{\s*policy_id:\s*string.*selection:\s*any.*tag_names:\s*string\[\]\s*\}$/)
|
|
26
|
+
assert.equal(typed.scalarParams.length, 0)
|
|
27
|
+
})
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import test from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
import fs from 'fs'
|
|
4
|
+
import os from 'os'
|
|
5
|
+
import path from 'path'
|
|
6
|
+
import { executeCLI } from '../runtime/execute.js'
|
|
7
|
+
import { CLIError } from '../runtime/errors.js'
|
|
8
|
+
|
|
9
|
+
const env = {
|
|
10
|
+
ARCUBASE_BASE_URL: 'https://arcubase.example.com/api/',
|
|
11
|
+
ARCUBASE_TENANT_ID: 'tenant_1',
|
|
12
|
+
ARCUBASE_ACCESS_TOKEN: 'tok',
|
|
13
|
+
ARCUBASE_TRACE_ID: '',
|
|
14
|
+
ARCUBASE_SUBJECT_TYPE: '',
|
|
15
|
+
ARCUBASE_SUBJECT_ID: '',
|
|
16
|
+
ARCUBASE_TOKEN_EXPIRES_AT: '',
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function tempJSONFile(payload: unknown): string {
|
|
20
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'arcubase-cli-'))
|
|
21
|
+
const filePath = path.join(dir, 'payload.json')
|
|
22
|
+
fs.writeFileSync(filePath, JSON.stringify(payload, null, 2))
|
|
23
|
+
return filePath
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
test('admin save entity rejects invalid body file', async () => {
|
|
27
|
+
const invalidFile = tempJSONFile({ name: '订单', layout: 'bad-layout' })
|
|
28
|
+
await assert.rejects(async () => {
|
|
29
|
+
await executeCLI('admin', ['entity', 'admin-save-entity', '--app-id', 'app_1', '--entity-id', 'entity_1', '--body-file', invalidFile], env as any, async () => new Response('{}'))
|
|
30
|
+
}, (error: unknown) => {
|
|
31
|
+
assert.ok(error instanceof CLIError)
|
|
32
|
+
assert.equal(error.code, 'BODY_VALIDATION_FAILED')
|
|
33
|
+
assert.equal(error.details?.requestType, 'EntitySaveReqVO')
|
|
34
|
+
assert.ok((error.details?.issues ?? []).some((item) => item.path.startsWith('body.layout')))
|
|
35
|
+
assert.ok((error.details?.tsHints ?? []).some((item) => item.file.startsWith('/opt/arcubase-sdk/')))
|
|
36
|
+
return true
|
|
37
|
+
})
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
test('admin create entity executes with valid body file', async () => {
|
|
41
|
+
const bodyFile = tempJSONFile({ name: '订单', parent: 0 })
|
|
42
|
+
const out = await executeCLI(
|
|
43
|
+
'admin',
|
|
44
|
+
['app', 'create-entity', '--id', 'app_1', '--body-file', bodyFile],
|
|
45
|
+
env as any,
|
|
46
|
+
async (url, init) => new Response(JSON.stringify({ ok: true, url, method: init?.method }), { status: 200 })
|
|
47
|
+
)
|
|
48
|
+
assert.equal(out.kind, 'result')
|
|
49
|
+
assert.equal(out.status, 200)
|
|
50
|
+
})
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import test from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
import { executeCLI } from '../runtime/execute.js'
|
|
4
|
+
|
|
5
|
+
const env = {
|
|
6
|
+
ARCUBASE_BASE_URL: 'https://arcubase.example.com/api/',
|
|
7
|
+
ARCUBASE_TENANT_ID: 'tenant_1',
|
|
8
|
+
ARCUBASE_ACCESS_TOKEN: 'tok',
|
|
9
|
+
ARCUBASE_TRACE_ID: '',
|
|
10
|
+
ARCUBASE_SUBJECT_TYPE: '',
|
|
11
|
+
ARCUBASE_SUBJECT_ID: '',
|
|
12
|
+
ARCUBASE_TOKEN_EXPIRES_AT: '',
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
test('root help prints modules', async () => {
|
|
16
|
+
const out = await executeCLI('admin', ['--help'], env as any, async () => new Response('{}'))
|
|
17
|
+
assert.equal(out.kind, 'help')
|
|
18
|
+
assert.match(out.text, /app/)
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
test('module help prints create-entity command', async () => {
|
|
22
|
+
const out = await executeCLI('admin', ['app', '--help'], env as any, async () => new Response('{}'))
|
|
23
|
+
assert.equal(out.kind, 'help')
|
|
24
|
+
assert.match(out.text, /create-entity/)
|
|
25
|
+
})
|