@yubild/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/dist/commands/deploy.d.ts +6 -0
- package/dist/commands/deploy.d.ts.map +1 -0
- package/dist/commands/deploy.js +73 -0
- package/dist/commands/deploy.js.map +1 -0
- package/dist/commands/diff.d.ts +6 -0
- package/dist/commands/diff.d.ts.map +1 -0
- package/dist/commands/diff.js +97 -0
- package/dist/commands/diff.js.map +1 -0
- package/dist/commands/export.d.ts +9 -0
- package/dist/commands/export.d.ts.map +1 -0
- package/dist/commands/export.js +93 -0
- package/dist/commands/export.js.map +1 -0
- package/dist/commands/init.d.ts +8 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +95 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/login.d.ts +9 -0
- package/dist/commands/login.d.ts.map +1 -0
- package/dist/commands/login.js +81 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/logout.d.ts +6 -0
- package/dist/commands/logout.d.ts.map +1 -0
- package/dist/commands/logout.js +17 -0
- package/dist/commands/logout.js.map +1 -0
- package/dist/commands/projects.d.ts +6 -0
- package/dist/commands/projects.d.ts.map +1 -0
- package/dist/commands/projects.js +76 -0
- package/dist/commands/projects.js.map +1 -0
- package/dist/commands/publish.d.ts +8 -0
- package/dist/commands/publish.d.ts.map +1 -0
- package/dist/commands/publish.js +81 -0
- package/dist/commands/publish.js.map +1 -0
- package/dist/commands/pull.d.ts +8 -0
- package/dist/commands/pull.d.ts.map +1 -0
- package/dist/commands/pull.js +86 -0
- package/dist/commands/pull.js.map +1 -0
- package/dist/commands/push.d.ts +6 -0
- package/dist/commands/push.d.ts.map +1 -0
- package/dist/commands/push.js +73 -0
- package/dist/commands/push.js.map +1 -0
- package/dist/commands/sync.d.ts +8 -0
- package/dist/commands/sync.d.ts.map +1 -0
- package/dist/commands/sync.js +111 -0
- package/dist/commands/sync.js.map +1 -0
- package/dist/commands/watch.d.ts +8 -0
- package/dist/commands/watch.d.ts.map +1 -0
- package/dist/commands/watch.js +91 -0
- package/dist/commands/watch.js.map +1 -0
- package/dist/commands/whoami.d.ts +6 -0
- package/dist/commands/whoami.d.ts.map +1 -0
- package/dist/commands/whoami.js +32 -0
- package/dist/commands/whoami.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +89 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/api-client.d.ts +141 -0
- package/dist/lib/api-client.d.ts.map +1 -0
- package/dist/lib/api-client.js +154 -0
- package/dist/lib/api-client.js.map +1 -0
- package/dist/lib/auth.d.ts +36 -0
- package/dist/lib/auth.d.ts.map +1 -0
- package/dist/lib/auth.js +108 -0
- package/dist/lib/auth.js.map +1 -0
- package/dist/lib/config.d.ts +28 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +100 -0
- package/dist/lib/config.js.map +1 -0
- package/package.json +30 -0
- package/src/commands/deploy.ts +42 -0
- package/src/commands/diff.ts +81 -0
- package/src/commands/export.ts +71 -0
- package/src/commands/init.ts +69 -0
- package/src/commands/login.ts +52 -0
- package/src/commands/logout.ts +16 -0
- package/src/commands/projects.ts +53 -0
- package/src/commands/publish.ts +51 -0
- package/src/commands/pull.ts +59 -0
- package/src/commands/push.ts +42 -0
- package/src/commands/sync.ts +86 -0
- package/src/commands/watch.ts +71 -0
- package/src/commands/whoami.ts +31 -0
- package/src/index.ts +104 -0
- package/src/lib/api-client.ts +231 -0
- package/src/lib/auth.ts +86 -0
- package/src/lib/config.ts +77 -0
- package/src/types/esm-modules.d.ts +41 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* yubild sync
|
|
3
|
+
* Sync design tokens between local project and Figma
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { loadConfig } from '../lib/config';
|
|
7
|
+
import { isAuthenticated } from '../lib/auth';
|
|
8
|
+
import { ApiClient } from '../lib/api-client';
|
|
9
|
+
|
|
10
|
+
export async function syncCommand(options: { autoMerge?: boolean }): Promise<void> {
|
|
11
|
+
if (!isAuthenticated()) {
|
|
12
|
+
console.error('Error: Not authenticated. Run `yubild login` first.');
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const config = loadConfig();
|
|
17
|
+
if (!config) {
|
|
18
|
+
console.error('Error: No .yubildrc found. Run `yubild init` first.');
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const { default: chalk } = await import('chalk');
|
|
23
|
+
const { default: ora } = await import('ora');
|
|
24
|
+
|
|
25
|
+
const spinner = ora('Syncing with Figma...').start();
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
const client = new ApiClient();
|
|
29
|
+
const result = await client.syncTokens(config.projectId, options.autoMerge ?? false);
|
|
30
|
+
|
|
31
|
+
if (result.synced) {
|
|
32
|
+
spinner.succeed('Sync complete!');
|
|
33
|
+
const summary = result.summary;
|
|
34
|
+
if (summary) {
|
|
35
|
+
console.log('');
|
|
36
|
+
console.log(` ${chalk.green(`+ ${summary.added} added`)}`);
|
|
37
|
+
console.log(` ${chalk.yellow(`~ ${summary.modified} modified`)}`);
|
|
38
|
+
console.log(` ${chalk.red(`- ${summary.removed} removed`)}`);
|
|
39
|
+
console.log(` ${chalk.gray(` ${summary.unchanged} unchanged`)}`);
|
|
40
|
+
}
|
|
41
|
+
if (result.warnings && result.warnings.length > 0) {
|
|
42
|
+
console.log('');
|
|
43
|
+
for (const warning of result.warnings) {
|
|
44
|
+
console.log(` ${chalk.yellow('Warning:')} ${warning}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
} else {
|
|
48
|
+
spinner.warn('Sync requires conflict resolution');
|
|
49
|
+
if (result.diff) {
|
|
50
|
+
console.log('');
|
|
51
|
+
console.log('Diff:');
|
|
52
|
+
console.log('');
|
|
53
|
+
const padKey = Math.max(...result.diff.entries.map((e) => e.tokenKey.length), 10);
|
|
54
|
+
const padStatus = 10;
|
|
55
|
+
console.log(
|
|
56
|
+
` ${'Token'.padEnd(padKey)} ${'Status'.padEnd(padStatus)} ${'Local'.padEnd(20)} Figma`
|
|
57
|
+
);
|
|
58
|
+
console.log(` ${''.padEnd(padKey, '-')} ${''.padEnd(padStatus, '-')} ${''.padEnd(20, '-')} ${''.padEnd(20, '-')}`);
|
|
59
|
+
for (const entry of result.diff.entries) {
|
|
60
|
+
const statusColor =
|
|
61
|
+
entry.status === 'added'
|
|
62
|
+
? chalk.green
|
|
63
|
+
: entry.status === 'modified'
|
|
64
|
+
? chalk.yellow
|
|
65
|
+
: entry.status === 'removed'
|
|
66
|
+
? chalk.red
|
|
67
|
+
: chalk.gray;
|
|
68
|
+
console.log(
|
|
69
|
+
` ${entry.tokenKey.padEnd(padKey)} ${statusColor(entry.status.padEnd(padStatus))} ${(entry.localValue ?? '-').padEnd(20)} ${entry.figmaValue ?? '-'}`
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
const s = result.diff.summary;
|
|
73
|
+
console.log('');
|
|
74
|
+
console.log(
|
|
75
|
+
` Summary: ${chalk.green(`+${s.added}`)} ${chalk.yellow(`~${s.modified}`)} ${chalk.red(`-${s.removed}`)} ${chalk.gray(`${s.unchanged} unchanged`)}`
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
console.log('');
|
|
79
|
+
console.log(` Use ${chalk.cyan('yubild sync --auto-merge')} to auto-merge, or resolve conflicts in the Yubild UI.`);
|
|
80
|
+
}
|
|
81
|
+
} catch (error) {
|
|
82
|
+
spinner.fail('Sync failed');
|
|
83
|
+
console.error(`\nFailed to sync tokens: ${(error as Error).message}`);
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* yubild watch
|
|
3
|
+
* Watch for token changes and auto-sync
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { loadConfig } from '../lib/config';
|
|
7
|
+
import { isAuthenticated } from '../lib/auth';
|
|
8
|
+
import { ApiClient } from '../lib/api-client';
|
|
9
|
+
|
|
10
|
+
export async function watchCommand(options: {
|
|
11
|
+
interval?: string;
|
|
12
|
+
}): Promise<void> {
|
|
13
|
+
if (!isAuthenticated()) {
|
|
14
|
+
console.error('Error: Not authenticated. Run `yubild login` first.');
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const config = loadConfig();
|
|
19
|
+
if (!config) {
|
|
20
|
+
console.error('Error: No .yubildrc found. Run `yubild init` first.');
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const { default: chalk } = await import('chalk');
|
|
25
|
+
|
|
26
|
+
const intervalSeconds = parseInt(options.interval ?? '30', 10);
|
|
27
|
+
if (isNaN(intervalSeconds) || intervalSeconds < 5) {
|
|
28
|
+
console.error('Error: Interval must be a number >= 5 seconds.');
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const client = new ApiClient();
|
|
33
|
+
|
|
34
|
+
console.log(chalk.cyan(`Watching for changes every ${intervalSeconds}s... (Ctrl+C to stop)`));
|
|
35
|
+
console.log('');
|
|
36
|
+
|
|
37
|
+
const check = async () => {
|
|
38
|
+
try {
|
|
39
|
+
const diff = await client.diffTokens(config.projectId);
|
|
40
|
+
|
|
41
|
+
const totalChanges = diff.summary.added + diff.summary.modified + diff.summary.removed;
|
|
42
|
+
|
|
43
|
+
if (totalChanges === 0) {
|
|
44
|
+
console.log(chalk.gray(`[${new Date().toLocaleTimeString()}] No changes detected.`));
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
console.log(
|
|
49
|
+
chalk.yellow(`[${new Date().toLocaleTimeString()}] Changes detected: `) +
|
|
50
|
+
`${chalk.green(`+${diff.summary.added}`)} ${chalk.yellow(`~${diff.summary.modified}`)} ${chalk.red(`-${diff.summary.removed}`)}`
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
console.log(chalk.cyan(' Auto-syncing...'));
|
|
54
|
+
const result = await client.syncTokens(config.projectId, true);
|
|
55
|
+
|
|
56
|
+
if (result.synced) {
|
|
57
|
+
console.log(chalk.green(' Sync complete!'));
|
|
58
|
+
} else {
|
|
59
|
+
console.log(chalk.yellow(' Sync requires manual resolution. Run `yubild sync` to resolve.'));
|
|
60
|
+
}
|
|
61
|
+
} catch (error) {
|
|
62
|
+
console.error(chalk.red(` Error: ${(error as Error).message}`));
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// Run immediately on start
|
|
67
|
+
await check();
|
|
68
|
+
|
|
69
|
+
// Then run on interval
|
|
70
|
+
setInterval(check, intervalSeconds * 1000);
|
|
71
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* yubild whoami
|
|
3
|
+
* Show the currently authenticated user
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { loadCredentials, isAuthenticated } from '../lib/auth';
|
|
7
|
+
import { ApiClient } from '../lib/api-client';
|
|
8
|
+
|
|
9
|
+
export async function whoamiCommand(): Promise<void> {
|
|
10
|
+
if (!isAuthenticated()) {
|
|
11
|
+
console.log('Not authenticated. Run `yubild login` to get started.');
|
|
12
|
+
process.exit(0);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const creds = loadCredentials();
|
|
16
|
+
if (!creds) {
|
|
17
|
+
console.log('Not authenticated. Run `yubild login` to get started.');
|
|
18
|
+
process.exit(0);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
const client = new ApiClient();
|
|
23
|
+
const { user } = await client.authenticate();
|
|
24
|
+
console.log(`Authenticated as: ${user.email}`);
|
|
25
|
+
console.log(`API URL: ${creds.apiUrl}`);
|
|
26
|
+
} catch {
|
|
27
|
+
console.log(`Stored email: ${creds.email || 'unknown'}`);
|
|
28
|
+
console.log(`API URL: ${creds.apiUrl}`);
|
|
29
|
+
console.log('(Could not verify - API may be unreachable)');
|
|
30
|
+
}
|
|
31
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Yubild CLI
|
|
5
|
+
* Pull design tokens from Yubild into your project
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { Command, Option } from 'commander';
|
|
9
|
+
import { loginCommand } from './commands/login';
|
|
10
|
+
import { initCommand } from './commands/init';
|
|
11
|
+
import { pullCommand } from './commands/pull';
|
|
12
|
+
import { whoamiCommand } from './commands/whoami';
|
|
13
|
+
import { logoutCommand } from './commands/logout';
|
|
14
|
+
import { syncCommand } from './commands/sync';
|
|
15
|
+
import { diffCommand } from './commands/diff';
|
|
16
|
+
import { pushCommand } from './commands/push';
|
|
17
|
+
import { exportCommand } from './commands/export';
|
|
18
|
+
import { deployCommand } from './commands/deploy';
|
|
19
|
+
import { publishCommand } from './commands/publish';
|
|
20
|
+
import { projectsCommand } from './commands/projects';
|
|
21
|
+
import { watchCommand } from './commands/watch';
|
|
22
|
+
|
|
23
|
+
const program = new Command();
|
|
24
|
+
|
|
25
|
+
program
|
|
26
|
+
.name('yubild')
|
|
27
|
+
.description('Yubild CLI - Design system token management')
|
|
28
|
+
.version('0.1.0');
|
|
29
|
+
|
|
30
|
+
program
|
|
31
|
+
.command('login')
|
|
32
|
+
.description('Authenticate with your Yubild account')
|
|
33
|
+
.option('--api-key <key>', 'API key (starts with nxui_sk_)')
|
|
34
|
+
.option('--api-url <url>', 'Yubild API URL', 'https://yubild.com')
|
|
35
|
+
.action(loginCommand);
|
|
36
|
+
|
|
37
|
+
program
|
|
38
|
+
.command('init')
|
|
39
|
+
.description('Initialize a project and create .yubildrc configuration')
|
|
40
|
+
.option('--project-id <id>', 'Yubild project ID')
|
|
41
|
+
.action(initCommand);
|
|
42
|
+
|
|
43
|
+
program
|
|
44
|
+
.command('pull')
|
|
45
|
+
.description('Pull latest design tokens into your project')
|
|
46
|
+
.option('-f, --format <format>', 'Specific format to pull (css, tailwind, json)')
|
|
47
|
+
.action(pullCommand);
|
|
48
|
+
|
|
49
|
+
program
|
|
50
|
+
.command('whoami')
|
|
51
|
+
.description('Show the currently authenticated user')
|
|
52
|
+
.action(whoamiCommand);
|
|
53
|
+
|
|
54
|
+
program
|
|
55
|
+
.command('logout')
|
|
56
|
+
.description('Clear stored authentication credentials')
|
|
57
|
+
.action(logoutCommand);
|
|
58
|
+
|
|
59
|
+
program
|
|
60
|
+
.command('sync')
|
|
61
|
+
.description('Sync design tokens between local project and Figma')
|
|
62
|
+
.option('--auto-merge', 'Automatically merge changes without prompting', false)
|
|
63
|
+
.action(syncCommand);
|
|
64
|
+
|
|
65
|
+
program
|
|
66
|
+
.command('diff')
|
|
67
|
+
.description('Show differences between local tokens and Figma tokens')
|
|
68
|
+
.action(diffCommand);
|
|
69
|
+
|
|
70
|
+
program
|
|
71
|
+
.command('push')
|
|
72
|
+
.description('Push design tokens to Figma')
|
|
73
|
+
.action(pushCommand);
|
|
74
|
+
|
|
75
|
+
program
|
|
76
|
+
.command('export')
|
|
77
|
+
.description('Export design tokens in a specific format')
|
|
78
|
+
.addOption(new Option('-f, --format <format>', 'Output format').choices(['css', 'tailwind', 'json', 'scss', 'react', 'vue', 'angular']).makeOptionMandatory())
|
|
79
|
+
.option('-o, --output <dir>', 'Output directory')
|
|
80
|
+
.action(exportCommand);
|
|
81
|
+
|
|
82
|
+
program
|
|
83
|
+
.command('deploy')
|
|
84
|
+
.description('Deploy design tokens to CDN')
|
|
85
|
+
.action(deployCommand);
|
|
86
|
+
|
|
87
|
+
program
|
|
88
|
+
.command('publish')
|
|
89
|
+
.description('Publish design tokens to Git via Pull Request')
|
|
90
|
+
.option('--repo <repositoryId>', 'Target repository ID')
|
|
91
|
+
.action(publishCommand);
|
|
92
|
+
|
|
93
|
+
program
|
|
94
|
+
.command('projects')
|
|
95
|
+
.description('List all projects')
|
|
96
|
+
.action(projectsCommand);
|
|
97
|
+
|
|
98
|
+
program
|
|
99
|
+
.command('watch')
|
|
100
|
+
.description('Watch for token changes and auto-sync')
|
|
101
|
+
.option('--interval <seconds>', 'Polling interval in seconds', '30')
|
|
102
|
+
.action(watchCommand);
|
|
103
|
+
|
|
104
|
+
program.parse();
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP client for Yubild API
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { getApiKey, getApiUrl } from './auth';
|
|
6
|
+
|
|
7
|
+
export class ApiClient {
|
|
8
|
+
private baseUrl: string;
|
|
9
|
+
private apiKey: string;
|
|
10
|
+
|
|
11
|
+
constructor(apiUrl?: string, apiKey?: string) {
|
|
12
|
+
this.baseUrl = apiUrl || getApiUrl();
|
|
13
|
+
this.apiKey = apiKey || getApiKey() || '';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
private async request<T>(
|
|
17
|
+
endpoint: string,
|
|
18
|
+
options: RequestInit = {}
|
|
19
|
+
): Promise<T> {
|
|
20
|
+
const url = `${this.baseUrl}/api${endpoint}`;
|
|
21
|
+
|
|
22
|
+
const response = await fetch(url, {
|
|
23
|
+
...options,
|
|
24
|
+
headers: {
|
|
25
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
26
|
+
'Content-Type': 'application/json',
|
|
27
|
+
'User-Agent': 'yubild-cli',
|
|
28
|
+
...options.headers,
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
if (response.status === 401) {
|
|
33
|
+
throw new Error('Authentication failed. Run `yubild login` to authenticate.');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (!response.ok) {
|
|
37
|
+
let errorMessage = `API error: ${response.status}`;
|
|
38
|
+
try {
|
|
39
|
+
const body = (await response.json()) as Record<string, string>;
|
|
40
|
+
errorMessage = body.error || body.message || errorMessage;
|
|
41
|
+
} catch {
|
|
42
|
+
// Use default
|
|
43
|
+
}
|
|
44
|
+
throw new Error(errorMessage);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return response.json() as Promise<T>;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
private async rawRequest(
|
|
51
|
+
endpoint: string,
|
|
52
|
+
options: RequestInit = {}
|
|
53
|
+
): Promise<Response> {
|
|
54
|
+
const url = `${this.baseUrl}/api${endpoint}`;
|
|
55
|
+
|
|
56
|
+
const response = await fetch(url, {
|
|
57
|
+
...options,
|
|
58
|
+
headers: {
|
|
59
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
60
|
+
'Content-Type': 'application/json',
|
|
61
|
+
'User-Agent': 'yubild-cli',
|
|
62
|
+
...options.headers,
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
if (response.status === 401) {
|
|
67
|
+
throw new Error('Authentication failed. Run `yubild login` to authenticate.');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (!response.ok) {
|
|
71
|
+
let errorMessage = `API error: ${response.status}`;
|
|
72
|
+
try {
|
|
73
|
+
const body = (await response.json()) as Record<string, string>;
|
|
74
|
+
errorMessage = body.error || body.message || errorMessage;
|
|
75
|
+
} catch {
|
|
76
|
+
// Use default
|
|
77
|
+
}
|
|
78
|
+
throw new Error(errorMessage);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return response;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Authenticate with API key
|
|
86
|
+
*/
|
|
87
|
+
async authenticate(): Promise<{ user: { email: string; name: string | null } }> {
|
|
88
|
+
return this.request('/cli/auth', {
|
|
89
|
+
method: 'POST',
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Get project status
|
|
95
|
+
*/
|
|
96
|
+
async getProjectStatus(projectId: string): Promise<{
|
|
97
|
+
project: { name: string; version: string; framework: string };
|
|
98
|
+
lastBuild: { version: string; createdAt: string } | null;
|
|
99
|
+
}> {
|
|
100
|
+
return this.request(`/cli/status?projectId=${projectId}`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Pull tokens for a project
|
|
105
|
+
*/
|
|
106
|
+
async pullTokens(
|
|
107
|
+
projectId: string,
|
|
108
|
+
formats: string[]
|
|
109
|
+
): Promise<{
|
|
110
|
+
files: Array<{ path: string; content: string }>;
|
|
111
|
+
version: string;
|
|
112
|
+
}> {
|
|
113
|
+
return this.request('/cli/pull', {
|
|
114
|
+
method: 'POST',
|
|
115
|
+
body: JSON.stringify({ projectId, formats }),
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Sync tokens between local and Figma
|
|
121
|
+
*/
|
|
122
|
+
async syncTokens(
|
|
123
|
+
projectId: string,
|
|
124
|
+
autoMerge: boolean
|
|
125
|
+
): Promise<{
|
|
126
|
+
synced: boolean;
|
|
127
|
+
diff?: {
|
|
128
|
+
entries: Array<{ tokenKey: string; status: string; localValue?: string; figmaValue?: string }>;
|
|
129
|
+
summary: { added: number; modified: number; removed: number; unchanged: number };
|
|
130
|
+
};
|
|
131
|
+
summary?: { added: number; modified: number; removed: number; unchanged: number };
|
|
132
|
+
warnings?: string[];
|
|
133
|
+
}> {
|
|
134
|
+
return this.request('/cli/sync', {
|
|
135
|
+
method: 'POST',
|
|
136
|
+
body: JSON.stringify({ projectId, autoMerge }),
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Diff tokens between local and Figma
|
|
142
|
+
*/
|
|
143
|
+
async diffTokens(projectId: string): Promise<{
|
|
144
|
+
entries: Array<{ tokenKey: string; status: string; localValue?: string; figmaValue?: string }>;
|
|
145
|
+
summary: { added: number; modified: number; removed: number; unchanged: number };
|
|
146
|
+
hasConflicts: boolean;
|
|
147
|
+
warnings?: string[];
|
|
148
|
+
}> {
|
|
149
|
+
return this.request('/cli/diff', {
|
|
150
|
+
method: 'POST',
|
|
151
|
+
body: JSON.stringify({ projectId }),
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Push tokens to Figma
|
|
157
|
+
*/
|
|
158
|
+
async pushToFigma(projectId: string): Promise<{
|
|
159
|
+
data: { variables: unknown[]; projectName: string; exportedAt: string };
|
|
160
|
+
}> {
|
|
161
|
+
return this.request('/cli/push', {
|
|
162
|
+
method: 'POST',
|
|
163
|
+
body: JSON.stringify({ projectId }),
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Export tokens in a specific format
|
|
169
|
+
*/
|
|
170
|
+
async exportTokens(
|
|
171
|
+
projectId: string,
|
|
172
|
+
format: string
|
|
173
|
+
): Promise<{
|
|
174
|
+
files: Array<{ path: string; content: string }>;
|
|
175
|
+
format: string;
|
|
176
|
+
version: string;
|
|
177
|
+
}> {
|
|
178
|
+
return this.request('/cli/export', {
|
|
179
|
+
method: 'POST',
|
|
180
|
+
body: JSON.stringify({ projectId, format }),
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Deploy tokens to CDN
|
|
186
|
+
*/
|
|
187
|
+
async deployToCdn(projectId: string): Promise<{
|
|
188
|
+
deployed: boolean;
|
|
189
|
+
version: string;
|
|
190
|
+
buildId: string;
|
|
191
|
+
cdnUrl: string;
|
|
192
|
+
}> {
|
|
193
|
+
return this.request('/cli/deploy', {
|
|
194
|
+
method: 'POST',
|
|
195
|
+
body: JSON.stringify({ projectId }),
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Publish tokens to Git via Pull Request
|
|
201
|
+
*/
|
|
202
|
+
async publishToGit(
|
|
203
|
+
projectId: string,
|
|
204
|
+
repositoryId?: string
|
|
205
|
+
): Promise<{
|
|
206
|
+
published: boolean;
|
|
207
|
+
prUrl?: string;
|
|
208
|
+
prNumber?: number;
|
|
209
|
+
}> {
|
|
210
|
+
return this.request('/cli/publish', {
|
|
211
|
+
method: 'POST',
|
|
212
|
+
body: JSON.stringify({ projectId, repositoryId }),
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* List all projects
|
|
218
|
+
*/
|
|
219
|
+
async listProjects(): Promise<{
|
|
220
|
+
projects: Array<{
|
|
221
|
+
id: string;
|
|
222
|
+
name: string;
|
|
223
|
+
slug: string;
|
|
224
|
+
version: string;
|
|
225
|
+
framework: string;
|
|
226
|
+
updatedAt: string;
|
|
227
|
+
}>;
|
|
228
|
+
}> {
|
|
229
|
+
return this.request('/cli/projects');
|
|
230
|
+
}
|
|
231
|
+
}
|
package/src/lib/auth.ts
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authentication management for CLI
|
|
3
|
+
* Stores credentials in user's home directory
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as fs from 'fs';
|
|
7
|
+
import * as path from 'path';
|
|
8
|
+
import * as os from 'os';
|
|
9
|
+
|
|
10
|
+
interface AuthCredentials {
|
|
11
|
+
apiKey: string;
|
|
12
|
+
apiUrl: string;
|
|
13
|
+
email?: string;
|
|
14
|
+
expiresAt?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const AUTH_DIR = path.join(os.homedir(), '.yubild');
|
|
18
|
+
const AUTH_FILE = path.join(AUTH_DIR, 'credentials.json');
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Store authentication credentials
|
|
22
|
+
*/
|
|
23
|
+
export function saveCredentials(credentials: AuthCredentials): void {
|
|
24
|
+
if (!fs.existsSync(AUTH_DIR)) {
|
|
25
|
+
fs.mkdirSync(AUTH_DIR, { recursive: true, mode: 0o700 });
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
fs.writeFileSync(
|
|
29
|
+
AUTH_FILE,
|
|
30
|
+
JSON.stringify(credentials, null, 2),
|
|
31
|
+
{ encoding: 'utf-8', mode: 0o600 }
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Load stored credentials
|
|
37
|
+
*/
|
|
38
|
+
export function loadCredentials(): AuthCredentials | null {
|
|
39
|
+
if (!fs.existsSync(AUTH_FILE)) return null;
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
const content = fs.readFileSync(AUTH_FILE, 'utf-8');
|
|
43
|
+
return JSON.parse(content) as AuthCredentials;
|
|
44
|
+
} catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Clear stored credentials
|
|
51
|
+
*/
|
|
52
|
+
export function clearCredentials(): void {
|
|
53
|
+
if (fs.existsSync(AUTH_FILE)) {
|
|
54
|
+
fs.unlinkSync(AUTH_FILE);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Check if authenticated
|
|
60
|
+
*/
|
|
61
|
+
export function isAuthenticated(): boolean {
|
|
62
|
+
const creds = loadCredentials();
|
|
63
|
+
if (!creds) return false;
|
|
64
|
+
|
|
65
|
+
if (creds.expiresAt && new Date(creds.expiresAt) < new Date()) {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return !!creds.apiKey;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Get the API key for making requests
|
|
74
|
+
*/
|
|
75
|
+
export function getApiKey(): string | null {
|
|
76
|
+
const creds = loadCredentials();
|
|
77
|
+
return creds?.apiKey || null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Get the API URL
|
|
82
|
+
*/
|
|
83
|
+
export function getApiUrl(): string {
|
|
84
|
+
const creds = loadCredentials();
|
|
85
|
+
return creds?.apiUrl || 'https://yubild.com';
|
|
86
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration file management for .yubildrc
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import * as fs from 'fs';
|
|
6
|
+
import * as path from 'path';
|
|
7
|
+
|
|
8
|
+
export interface YubildConfig {
|
|
9
|
+
projectId: string;
|
|
10
|
+
apiUrl: string;
|
|
11
|
+
outputDir: string;
|
|
12
|
+
formats: string[];
|
|
13
|
+
framework?: string;
|
|
14
|
+
watchInterval?: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const CONFIG_FILENAME = '.yubildrc';
|
|
18
|
+
const DEFAULT_CONFIG: Partial<YubildConfig> = {
|
|
19
|
+
apiUrl: 'https://yubild.com',
|
|
20
|
+
outputDir: './src/tokens',
|
|
21
|
+
formats: ['css', 'tailwind', 'json'],
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Find .yubildrc by walking up from cwd
|
|
26
|
+
*/
|
|
27
|
+
export function findConfigFile(): string | null {
|
|
28
|
+
let dir = process.cwd();
|
|
29
|
+
|
|
30
|
+
while (true) {
|
|
31
|
+
const configPath = path.join(dir, CONFIG_FILENAME);
|
|
32
|
+
if (fs.existsSync(configPath)) {
|
|
33
|
+
return configPath;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const parentDir = path.dirname(dir);
|
|
37
|
+
if (parentDir === dir) break;
|
|
38
|
+
dir = parentDir;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Load configuration from .yubildrc
|
|
46
|
+
*/
|
|
47
|
+
export function loadConfig(): YubildConfig | null {
|
|
48
|
+
const configPath = findConfigFile();
|
|
49
|
+
if (!configPath) return null;
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
const content = fs.readFileSync(configPath, 'utf-8');
|
|
53
|
+
const config = JSON.parse(content) as YubildConfig;
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
...DEFAULT_CONFIG,
|
|
57
|
+
...config,
|
|
58
|
+
} as YubildConfig;
|
|
59
|
+
} catch {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Save configuration to .yubildrc in cwd
|
|
66
|
+
*/
|
|
67
|
+
export function saveConfig(config: YubildConfig): void {
|
|
68
|
+
const configPath = path.join(process.cwd(), CONFIG_FILENAME);
|
|
69
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Get config path in cwd
|
|
74
|
+
*/
|
|
75
|
+
export function getConfigPath(): string {
|
|
76
|
+
return path.join(process.cwd(), CONFIG_FILENAME);
|
|
77
|
+
}
|