@objectstack/cli 4.0.1 → 4.0.3
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/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +33 -0
- package/dist/commands/auth/login.d.ts +13 -0
- package/dist/commands/auth/login.d.ts.map +1 -0
- package/dist/commands/auth/login.js +167 -0
- package/dist/commands/auth/login.js.map +1 -0
- package/dist/commands/auth/logout.d.ts +10 -0
- package/dist/commands/auth/logout.d.ts.map +1 -0
- package/dist/commands/auth/logout.js +46 -0
- package/dist/commands/auth/logout.js.map +1 -0
- package/dist/commands/auth/whoami.d.ts +12 -0
- package/dist/commands/auth/whoami.d.ts.map +1 -0
- package/dist/commands/auth/whoami.js +76 -0
- package/dist/commands/auth/whoami.js.map +1 -0
- package/dist/commands/data/create.d.ts +17 -0
- package/dist/commands/data/create.d.ts.map +1 -0
- package/dist/commands/data/create.js +106 -0
- package/dist/commands/data/create.js.map +1 -0
- package/dist/commands/data/delete.d.ts +16 -0
- package/dist/commands/data/delete.d.ts.map +1 -0
- package/dist/commands/data/delete.js +78 -0
- package/dist/commands/data/delete.js.map +1 -0
- package/dist/commands/data/get.d.ts +16 -0
- package/dist/commands/data/get.d.ts.map +1 -0
- package/dist/commands/data/get.js +79 -0
- package/dist/commands/data/get.js.map +1 -0
- package/dist/commands/data/query.d.ts +20 -0
- package/dist/commands/data/query.d.ts.map +1 -0
- package/dist/commands/data/query.js +118 -0
- package/dist/commands/data/query.js.map +1 -0
- package/dist/commands/data/update.d.ts +18 -0
- package/dist/commands/data/update.d.ts.map +1 -0
- package/dist/commands/data/update.js +110 -0
- package/dist/commands/data/update.js.map +1 -0
- package/dist/commands/meta/delete.d.ts +16 -0
- package/dist/commands/meta/delete.d.ts.map +1 -0
- package/dist/commands/meta/delete.js +73 -0
- package/dist/commands/meta/delete.js.map +1 -0
- package/dist/commands/meta/get.d.ts +16 -0
- package/dist/commands/meta/get.d.ts.map +1 -0
- package/dist/commands/meta/get.js +65 -0
- package/dist/commands/meta/get.js.map +1 -0
- package/dist/commands/meta/list.d.ts +15 -0
- package/dist/commands/meta/list.d.ts.map +1 -0
- package/dist/commands/meta/list.js +103 -0
- package/dist/commands/meta/list.js.map +1 -0
- package/dist/commands/meta/register.d.ts +16 -0
- package/dist/commands/meta/register.d.ts.map +1 -0
- package/dist/commands/meta/register.js +89 -0
- package/dist/commands/meta/register.js.map +1 -0
- package/dist/commands/serve.d.ts.map +1 -1
- package/dist/commands/serve.js +33 -0
- package/dist/commands/serve.js.map +1 -1
- package/dist/utils/api-client.d.ts +42 -0
- package/dist/utils/api-client.d.ts.map +1 -0
- package/dist/utils/api-client.js +53 -0
- package/dist/utils/api-client.js.map +1 -0
- package/dist/utils/auth-config.d.ts +50 -0
- package/dist/utils/auth-config.d.ts.map +1 -0
- package/dist/utils/auth-config.js +73 -0
- package/dist/utils/auth-config.js.map +1 -0
- package/dist/utils/output-formatter.d.ts +9 -0
- package/dist/utils/output-formatter.d.ts.map +1 -0
- package/dist/utils/output-formatter.js +80 -0
- package/dist/utils/output-formatter.js.map +1 -0
- package/package.json +19 -14
- package/src/commands/auth/login.ts +188 -0
- package/src/commands/auth/logout.ts +51 -0
- package/src/commands/auth/whoami.ts +85 -0
- package/src/commands/data/create.ts +110 -0
- package/src/commands/data/delete.ts +84 -0
- package/src/commands/data/get.ts +84 -0
- package/src/commands/data/query.ts +127 -0
- package/src/commands/data/update.ts +114 -0
- package/src/commands/meta/delete.ts +79 -0
- package/src/commands/meta/get.ts +73 -0
- package/src/commands/meta/list.ts +105 -0
- package/src/commands/meta/register.ts +97 -0
- package/src/commands/serve.ts +38 -0
- package/src/utils/api-client.ts +88 -0
- package/src/utils/auth-config.ts +107 -0
- package/src/utils/output-formatter.ts +91 -0
- package/test/remote-api-commands.test.ts +188 -0
- package/test/remote-api-utils.test.ts +196 -0
package/src/commands/serve.ts
CHANGED
|
@@ -263,6 +263,22 @@ export default class Serve extends Command {
|
|
|
263
263
|
}
|
|
264
264
|
}
|
|
265
265
|
|
|
266
|
+
// 5. Auto-register SetupPlugin BEFORE config plugins so that other
|
|
267
|
+
// plugins (e.g. AuthPlugin) can call setupNav.contribute() during init.
|
|
268
|
+
const hasSetupPlugin = plugins.some(
|
|
269
|
+
(p: any) => p.name === 'com.objectstack.setup' || p.constructor?.name === 'SetupPlugin'
|
|
270
|
+
);
|
|
271
|
+
if (!hasSetupPlugin) {
|
|
272
|
+
try {
|
|
273
|
+
const setupPkg = '@objectstack/plugin-setup';
|
|
274
|
+
const { SetupPlugin } = await import(/* webpackIgnore: true */ setupPkg);
|
|
275
|
+
await kernel.use(new SetupPlugin());
|
|
276
|
+
trackPlugin('Setup');
|
|
277
|
+
} catch {
|
|
278
|
+
// @objectstack/plugin-setup not installed — setup app unavailable
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
266
282
|
if (plugins.length > 0) {
|
|
267
283
|
for (const plugin of plugins) {
|
|
268
284
|
try {
|
|
@@ -318,6 +334,28 @@ export default class Serve extends Command {
|
|
|
318
334
|
}
|
|
319
335
|
}
|
|
320
336
|
|
|
337
|
+
// 4. Auto-register AIServicePlugin if not already loaded by config plugins.
|
|
338
|
+
// Registered AFTER Dispatcher so that the ai:routes hook listener is
|
|
339
|
+
// already in place when AIServicePlugin.start() fires the hook.
|
|
340
|
+
const hasAIPlugin = plugins.some(
|
|
341
|
+
(p: any) => p.name === 'com.objectstack.service-ai'
|
|
342
|
+
|| p.constructor?.name === 'AIServicePlugin'
|
|
343
|
+
);
|
|
344
|
+
if (!hasAIPlugin) {
|
|
345
|
+
try {
|
|
346
|
+
const aiPkg = '@objectstack/service-ai';
|
|
347
|
+
const { AIServicePlugin } = await import(/* webpackIgnore: true */ aiPkg);
|
|
348
|
+
|
|
349
|
+
// AIServicePlugin will auto-detect LLM provider from environment variables
|
|
350
|
+
// (AI_GATEWAY_MODEL, OPENAI_API_KEY, ANTHROPIC_API_KEY, GOOGLE_GENERATIVE_AI_API_KEY)
|
|
351
|
+
// No need to manually construct the adapter here.
|
|
352
|
+
await kernel.use(new AIServicePlugin());
|
|
353
|
+
trackPlugin('AIService');
|
|
354
|
+
} catch {
|
|
355
|
+
// @objectstack/service-ai not installed — AI features unavailable
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
321
359
|
// ── Studio UI ─────────────────────────────────────────────────
|
|
322
360
|
// In dev mode, Studio UI is enabled by default (use --no-ui to disable).
|
|
323
361
|
// Always serves the pre-built dist/ — no Vite dev server, no extra port.
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
|
|
2
|
+
|
|
3
|
+
import { ObjectStackClient } from '@objectstack/client';
|
|
4
|
+
import { readAuthConfig } from './auth-config.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* API client configuration options for CLI commands
|
|
8
|
+
*/
|
|
9
|
+
export interface ApiClientOptions {
|
|
10
|
+
/**
|
|
11
|
+
* Server URL (defaults to OBJECTSTACK_URL env var or http://localhost:3000)
|
|
12
|
+
*/
|
|
13
|
+
url?: string;
|
|
14
|
+
/**
|
|
15
|
+
* Authentication token (defaults to stored credentials or OBJECTSTACK_TOKEN env var)
|
|
16
|
+
*/
|
|
17
|
+
token?: string;
|
|
18
|
+
/**
|
|
19
|
+
* Enable debug logging
|
|
20
|
+
*/
|
|
21
|
+
debug?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Result returned by createApiClient — exposes the resolved token so commands
|
|
26
|
+
* can call requireAuth() without accessing private client fields.
|
|
27
|
+
*/
|
|
28
|
+
export interface ApiClientResult {
|
|
29
|
+
client: ObjectStackClient;
|
|
30
|
+
token?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Create an authenticated ObjectStack API client for CLI commands.
|
|
35
|
+
*
|
|
36
|
+
* Resolves configuration in this priority order:
|
|
37
|
+
* 1. Explicit options passed to the function
|
|
38
|
+
* 2. Environment variables (OBJECTSTACK_URL, OBJECTSTACK_TOKEN)
|
|
39
|
+
* 3. Stored credentials from `os auth login`
|
|
40
|
+
* 4. Defaults (http://localhost:3000)
|
|
41
|
+
*/
|
|
42
|
+
export async function createApiClient(options: ApiClientOptions = {}): Promise<ApiClientResult> {
|
|
43
|
+
// Resolve server URL (without applying defaults yet)
|
|
44
|
+
let baseUrl = options.url || process.env.OBJECTSTACK_URL;
|
|
45
|
+
|
|
46
|
+
// Resolve authentication token
|
|
47
|
+
let token = options.token || process.env.OBJECTSTACK_TOKEN;
|
|
48
|
+
|
|
49
|
+
// If URL or token is missing, try to load from stored credentials
|
|
50
|
+
if (!baseUrl || !token) {
|
|
51
|
+
try {
|
|
52
|
+
const authConfig = await readAuthConfig();
|
|
53
|
+
if (!token && authConfig.token) {
|
|
54
|
+
token = authConfig.token;
|
|
55
|
+
}
|
|
56
|
+
if (!baseUrl && authConfig.url) {
|
|
57
|
+
baseUrl = authConfig.url;
|
|
58
|
+
}
|
|
59
|
+
} catch {
|
|
60
|
+
// No stored credentials - commands will fail if auth is required
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Apply final default for baseUrl if still not resolved
|
|
65
|
+
if (!baseUrl) {
|
|
66
|
+
baseUrl = 'http://localhost:3000';
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const client = new ObjectStackClient({
|
|
70
|
+
baseUrl,
|
|
71
|
+
token,
|
|
72
|
+
debug: options.debug || false,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
return { client, token };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Ensure authentication is present, throwing an error if not.
|
|
80
|
+
* Use this in commands that require authentication.
|
|
81
|
+
*/
|
|
82
|
+
export function requireAuth(token?: string): void {
|
|
83
|
+
if (!token) {
|
|
84
|
+
throw new Error(
|
|
85
|
+
'Authentication required. Please run `os auth login` or set OBJECTSTACK_TOKEN environment variable.'
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
|
|
2
|
+
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { readFile, writeFile, mkdir, chmod } from 'node:fs/promises';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Authentication configuration stored in ~/.objectstack/credentials.json
|
|
9
|
+
*/
|
|
10
|
+
export interface AuthConfig {
|
|
11
|
+
/**
|
|
12
|
+
* Server URL (base URL for the ObjectStack instance)
|
|
13
|
+
*/
|
|
14
|
+
url: string;
|
|
15
|
+
/**
|
|
16
|
+
* Authentication token (Bearer token)
|
|
17
|
+
*/
|
|
18
|
+
token: string;
|
|
19
|
+
/**
|
|
20
|
+
* User email (for display purposes)
|
|
21
|
+
*/
|
|
22
|
+
email?: string;
|
|
23
|
+
/**
|
|
24
|
+
* User ID
|
|
25
|
+
*/
|
|
26
|
+
userId?: string;
|
|
27
|
+
/**
|
|
28
|
+
* Timestamp when credentials were created
|
|
29
|
+
*/
|
|
30
|
+
createdAt: string;
|
|
31
|
+
/**
|
|
32
|
+
* Timestamp when credentials were last used
|
|
33
|
+
*/
|
|
34
|
+
lastUsedAt?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Get the path to the credentials file
|
|
39
|
+
*/
|
|
40
|
+
export function getCredentialsPath(): string {
|
|
41
|
+
return join(homedir(), '.objectstack', 'credentials.json');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Read stored authentication configuration
|
|
46
|
+
*/
|
|
47
|
+
export async function readAuthConfig(): Promise<AuthConfig> {
|
|
48
|
+
const path = getCredentialsPath();
|
|
49
|
+
try {
|
|
50
|
+
const content = await readFile(path, 'utf-8');
|
|
51
|
+
return JSON.parse(content) as AuthConfig;
|
|
52
|
+
} catch (error: any) {
|
|
53
|
+
if (error.code === 'ENOENT') {
|
|
54
|
+
throw new Error('No stored credentials found. Please run `os auth login` first.');
|
|
55
|
+
}
|
|
56
|
+
throw new Error(`Failed to read credentials: ${error.message}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Write authentication configuration
|
|
62
|
+
*/
|
|
63
|
+
export async function writeAuthConfig(config: AuthConfig): Promise<void> {
|
|
64
|
+
const path = getCredentialsPath();
|
|
65
|
+
const dir = join(homedir(), '.objectstack');
|
|
66
|
+
|
|
67
|
+
// Ensure directory exists
|
|
68
|
+
await mkdir(dir, { recursive: true });
|
|
69
|
+
|
|
70
|
+
// Write credentials file
|
|
71
|
+
await writeFile(path, JSON.stringify(config, null, 2), { mode: 0o600 });
|
|
72
|
+
|
|
73
|
+
// Explicitly enforce permissions in case the file already existed with broader perms
|
|
74
|
+
try {
|
|
75
|
+
await chmod(path, 0o600);
|
|
76
|
+
} catch {
|
|
77
|
+
// Best-effort — platforms that don't support chmod will silently continue
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Delete stored authentication configuration
|
|
83
|
+
*/
|
|
84
|
+
export async function deleteAuthConfig(): Promise<void> {
|
|
85
|
+
const path = getCredentialsPath();
|
|
86
|
+
try {
|
|
87
|
+
const { unlink } = await import('node:fs/promises');
|
|
88
|
+
await unlink(path);
|
|
89
|
+
} catch (error: any) {
|
|
90
|
+
if (error.code !== 'ENOENT') {
|
|
91
|
+
throw new Error(`Failed to delete credentials: ${error.message}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Update last used timestamp
|
|
98
|
+
*/
|
|
99
|
+
export async function touchAuthConfig(): Promise<void> {
|
|
100
|
+
try {
|
|
101
|
+
const config = await readAuthConfig();
|
|
102
|
+
config.lastUsedAt = new Date().toISOString();
|
|
103
|
+
await writeAuthConfig(config);
|
|
104
|
+
} catch {
|
|
105
|
+
// Ignore errors - this is best-effort
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
|
|
2
|
+
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import yaml from 'yaml';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Output format options for CLI commands
|
|
8
|
+
*/
|
|
9
|
+
export type OutputFormat = 'json' | 'table' | 'yaml';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Format and output data according to the specified format
|
|
13
|
+
*/
|
|
14
|
+
export function formatOutput(data: any, format: OutputFormat = 'json'): void {
|
|
15
|
+
switch (format) {
|
|
16
|
+
case 'json':
|
|
17
|
+
console.log(JSON.stringify(data, null, 2));
|
|
18
|
+
break;
|
|
19
|
+
|
|
20
|
+
case 'yaml':
|
|
21
|
+
console.log(yaml.stringify(data));
|
|
22
|
+
break;
|
|
23
|
+
|
|
24
|
+
case 'table':
|
|
25
|
+
// For table format, handle different data structures
|
|
26
|
+
if (Array.isArray(data)) {
|
|
27
|
+
printTable(data);
|
|
28
|
+
} else if (data && typeof data === 'object') {
|
|
29
|
+
// For single objects, print as key-value pairs
|
|
30
|
+
printKeyValue(data);
|
|
31
|
+
} else {
|
|
32
|
+
console.log(String(data));
|
|
33
|
+
}
|
|
34
|
+
break;
|
|
35
|
+
|
|
36
|
+
default:
|
|
37
|
+
console.log(JSON.stringify(data, null, 2));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Print data as a table (for arrays of objects)
|
|
43
|
+
*/
|
|
44
|
+
function printTable(data: any[]): void {
|
|
45
|
+
if (data.length === 0) {
|
|
46
|
+
console.log(chalk.dim('(no data)'));
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Get all unique keys from all objects
|
|
51
|
+
const keys = Array.from(
|
|
52
|
+
new Set(data.flatMap(item => Object.keys(item)))
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
// Print header
|
|
56
|
+
console.log(chalk.bold(keys.join(' | ')));
|
|
57
|
+
console.log(chalk.dim('─'.repeat(keys.join(' | ').length)));
|
|
58
|
+
|
|
59
|
+
// Print rows
|
|
60
|
+
for (const item of data) {
|
|
61
|
+
const values = keys.map(key => {
|
|
62
|
+
const value = item[key];
|
|
63
|
+
if (value === null || value === undefined) return chalk.dim('-');
|
|
64
|
+
if (typeof value === 'object') return chalk.dim('[object]');
|
|
65
|
+
return String(value);
|
|
66
|
+
});
|
|
67
|
+
console.log(values.join(' | '));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
console.log(chalk.dim(`\n${data.length} row(s)`));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Print object as key-value pairs
|
|
75
|
+
*/
|
|
76
|
+
function printKeyValue(data: Record<string, any>, indent = 0): void {
|
|
77
|
+
const prefix = ' '.repeat(indent);
|
|
78
|
+
|
|
79
|
+
for (const [key, value] of Object.entries(data)) {
|
|
80
|
+
if (value === null || value === undefined) {
|
|
81
|
+
console.log(`${prefix}${chalk.dim(key + ':')} ${chalk.dim('null')}`);
|
|
82
|
+
} else if (typeof value === 'object' && !Array.isArray(value)) {
|
|
83
|
+
console.log(`${prefix}${chalk.bold(key + ':')}`);
|
|
84
|
+
printKeyValue(value, indent + 1);
|
|
85
|
+
} else if (Array.isArray(value)) {
|
|
86
|
+
console.log(`${prefix}${chalk.dim(key + ':')} ${chalk.dim(`[${value.length} items]`)}`);
|
|
87
|
+
} else {
|
|
88
|
+
console.log(`${prefix}${chalk.dim(key + ':')} ${chalk.white(String(value))}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import AuthLogin from '../src/commands/auth/login';
|
|
3
|
+
import AuthLogout from '../src/commands/auth/logout';
|
|
4
|
+
import AuthWhoami from '../src/commands/auth/whoami';
|
|
5
|
+
import DataQuery from '../src/commands/data/query';
|
|
6
|
+
import DataGet from '../src/commands/data/get';
|
|
7
|
+
import DataCreate from '../src/commands/data/create';
|
|
8
|
+
import DataUpdate from '../src/commands/data/update';
|
|
9
|
+
import DataDelete from '../src/commands/data/delete';
|
|
10
|
+
import MetaList from '../src/commands/meta/list';
|
|
11
|
+
import MetaGet from '../src/commands/meta/get';
|
|
12
|
+
import MetaRegister from '../src/commands/meta/register';
|
|
13
|
+
import MetaDelete from '../src/commands/meta/delete';
|
|
14
|
+
|
|
15
|
+
describe('Remote API Commands (oclif)', () => {
|
|
16
|
+
describe('Auth Commands', () => {
|
|
17
|
+
it('should have auth login command', () => {
|
|
18
|
+
expect(AuthLogin.description).toContain('Authenticate');
|
|
19
|
+
expect(AuthLogin.flags).toHaveProperty('url');
|
|
20
|
+
expect(AuthLogin.flags).toHaveProperty('email');
|
|
21
|
+
expect(AuthLogin.flags).toHaveProperty('password');
|
|
22
|
+
expect(AuthLogin.flags).toHaveProperty('json');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('should have auth logout command', () => {
|
|
26
|
+
expect(AuthLogout.description).toContain('Clear');
|
|
27
|
+
expect(AuthLogout.flags).toHaveProperty('json');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('should have auth whoami command', () => {
|
|
31
|
+
expect(AuthWhoami.description).toContain('session');
|
|
32
|
+
expect(AuthWhoami.flags).toHaveProperty('url');
|
|
33
|
+
expect(AuthWhoami.flags).toHaveProperty('token');
|
|
34
|
+
expect(AuthWhoami.flags).toHaveProperty('format');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('auth commands should have examples', () => {
|
|
38
|
+
expect(AuthLogin.examples).toBeDefined();
|
|
39
|
+
expect(AuthLogin.examples.length).toBeGreaterThan(0);
|
|
40
|
+
expect(AuthLogout.examples).toBeDefined();
|
|
41
|
+
expect(AuthWhoami.examples).toBeDefined();
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe('Data Commands', () => {
|
|
46
|
+
it('should have data query command', () => {
|
|
47
|
+
expect(DataQuery.description).toContain('Query');
|
|
48
|
+
expect(DataQuery.args).toHaveProperty('object');
|
|
49
|
+
expect(DataQuery.flags).toHaveProperty('filter');
|
|
50
|
+
expect(DataQuery.flags).toHaveProperty('fields');
|
|
51
|
+
expect(DataQuery.flags).toHaveProperty('sort');
|
|
52
|
+
expect(DataQuery.flags).toHaveProperty('limit');
|
|
53
|
+
expect(DataQuery.flags).toHaveProperty('offset');
|
|
54
|
+
expect(DataQuery.flags).toHaveProperty('format');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('should have data get command', () => {
|
|
58
|
+
expect(DataGet.description).toContain('single record');
|
|
59
|
+
expect(DataGet.args).toHaveProperty('object');
|
|
60
|
+
expect(DataGet.args).toHaveProperty('id');
|
|
61
|
+
expect(DataGet.flags).toHaveProperty('format');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('should have data create command', () => {
|
|
65
|
+
expect(DataCreate.description).toContain('Create');
|
|
66
|
+
expect(DataCreate.args).toHaveProperty('object');
|
|
67
|
+
expect(DataCreate.flags).toHaveProperty('data');
|
|
68
|
+
expect(DataCreate.flags).toHaveProperty('format');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('should have data update command', () => {
|
|
72
|
+
expect(DataUpdate.description).toContain('Update');
|
|
73
|
+
expect(DataUpdate.args).toHaveProperty('object');
|
|
74
|
+
expect(DataUpdate.args).toHaveProperty('id');
|
|
75
|
+
expect(DataUpdate.flags).toHaveProperty('data');
|
|
76
|
+
expect(DataUpdate.flags).toHaveProperty('format');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('should have data delete command', () => {
|
|
80
|
+
expect(DataDelete.description).toContain('Delete');
|
|
81
|
+
expect(DataDelete.args).toHaveProperty('object');
|
|
82
|
+
expect(DataDelete.args).toHaveProperty('id');
|
|
83
|
+
expect(DataDelete.flags).toHaveProperty('format');
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('data commands should support common flags', () => {
|
|
87
|
+
const commands = [DataQuery, DataGet, DataCreate, DataUpdate, DataDelete];
|
|
88
|
+
commands.forEach(cmd => {
|
|
89
|
+
expect(cmd.flags).toHaveProperty('url');
|
|
90
|
+
expect(cmd.flags).toHaveProperty('token');
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('data commands should have examples', () => {
|
|
95
|
+
expect(DataQuery.examples).toBeDefined();
|
|
96
|
+
expect(DataQuery.examples.length).toBeGreaterThan(0);
|
|
97
|
+
expect(DataGet.examples).toBeDefined();
|
|
98
|
+
expect(DataCreate.examples).toBeDefined();
|
|
99
|
+
expect(DataUpdate.examples).toBeDefined();
|
|
100
|
+
expect(DataDelete.examples).toBeDefined();
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
describe('Metadata Commands', () => {
|
|
105
|
+
it('should have meta list command', () => {
|
|
106
|
+
expect(MetaList.description).toContain('List metadata');
|
|
107
|
+
expect(MetaList.args).toHaveProperty('type');
|
|
108
|
+
expect(MetaList.flags).toHaveProperty('format');
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('should have meta get command', () => {
|
|
112
|
+
expect(MetaGet.description).toContain('Get');
|
|
113
|
+
expect(MetaGet.args).toHaveProperty('type');
|
|
114
|
+
expect(MetaGet.args).toHaveProperty('name');
|
|
115
|
+
expect(MetaGet.flags).toHaveProperty('format');
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('should have meta register command', () => {
|
|
119
|
+
expect(MetaRegister.description).toContain('Register');
|
|
120
|
+
expect(MetaRegister.args).toHaveProperty('type');
|
|
121
|
+
expect(MetaRegister.flags).toHaveProperty('data');
|
|
122
|
+
expect(MetaRegister.flags).toHaveProperty('format');
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('should have meta delete command', () => {
|
|
126
|
+
expect(MetaDelete.description).toContain('Delete');
|
|
127
|
+
expect(MetaDelete.args).toHaveProperty('type');
|
|
128
|
+
expect(MetaDelete.args).toHaveProperty('name');
|
|
129
|
+
expect(MetaDelete.flags).toHaveProperty('format');
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('meta commands should support common flags', () => {
|
|
133
|
+
const commands = [MetaList, MetaGet, MetaRegister, MetaDelete];
|
|
134
|
+
commands.forEach(cmd => {
|
|
135
|
+
expect(cmd.flags).toHaveProperty('url');
|
|
136
|
+
expect(cmd.flags).toHaveProperty('token');
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('meta commands should have examples', () => {
|
|
141
|
+
expect(MetaList.examples).toBeDefined();
|
|
142
|
+
expect(MetaList.examples.length).toBeGreaterThan(0);
|
|
143
|
+
expect(MetaGet.examples).toBeDefined();
|
|
144
|
+
expect(MetaRegister.examples).toBeDefined();
|
|
145
|
+
expect(MetaDelete.examples).toBeDefined();
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
describe('Command Conventions', () => {
|
|
150
|
+
it('all remote commands should support --url flag with OBJECTSTACK_URL env var', () => {
|
|
151
|
+
const commands = [
|
|
152
|
+
AuthLogin, AuthWhoami,
|
|
153
|
+
DataQuery, DataGet, DataCreate, DataUpdate, DataDelete,
|
|
154
|
+
MetaList, MetaGet, MetaRegister, MetaDelete
|
|
155
|
+
];
|
|
156
|
+
|
|
157
|
+
commands.forEach(cmd => {
|
|
158
|
+
expect(cmd.flags).toHaveProperty('url');
|
|
159
|
+
expect(cmd.flags.url).toHaveProperty('env', 'OBJECTSTACK_URL');
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('authenticated commands should support --token flag with OBJECTSTACK_TOKEN env var', () => {
|
|
164
|
+
const commands = [
|
|
165
|
+
AuthWhoami,
|
|
166
|
+
DataQuery, DataGet, DataCreate, DataUpdate, DataDelete,
|
|
167
|
+
MetaList, MetaGet, MetaRegister, MetaDelete
|
|
168
|
+
];
|
|
169
|
+
|
|
170
|
+
commands.forEach(cmd => {
|
|
171
|
+
expect(cmd.flags).toHaveProperty('token');
|
|
172
|
+
expect(cmd.flags.token).toHaveProperty('env', 'OBJECTSTACK_TOKEN');
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('all commands should support output formatting', () => {
|
|
177
|
+
const commands = [
|
|
178
|
+
AuthWhoami,
|
|
179
|
+
DataQuery, DataGet, DataCreate, DataUpdate, DataDelete,
|
|
180
|
+
MetaList, MetaGet, MetaRegister, MetaDelete
|
|
181
|
+
];
|
|
182
|
+
|
|
183
|
+
commands.forEach(cmd => {
|
|
184
|
+
expect(cmd.flags).toHaveProperty('format');
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
});
|