@applica-software-guru/sdd 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.
Files changed (56) hide show
  1. package/dist/commands/build.d.ts +3 -0
  2. package/dist/commands/build.d.ts.map +1 -0
  3. package/dist/commands/build.js +37 -0
  4. package/dist/commands/build.js.map +1 -0
  5. package/dist/commands/cr.d.ts +3 -0
  6. package/dist/commands/cr.d.ts.map +1 -0
  7. package/dist/commands/cr.js +76 -0
  8. package/dist/commands/cr.js.map +1 -0
  9. package/dist/commands/diff.d.ts +3 -0
  10. package/dist/commands/diff.d.ts.map +1 -0
  11. package/dist/commands/diff.js +40 -0
  12. package/dist/commands/diff.js.map +1 -0
  13. package/dist/commands/init.d.ts +3 -0
  14. package/dist/commands/init.d.ts.map +1 -0
  15. package/dist/commands/init.js +108 -0
  16. package/dist/commands/init.js.map +1 -0
  17. package/dist/commands/mark-synced.d.ts +3 -0
  18. package/dist/commands/mark-synced.d.ts.map +1 -0
  19. package/dist/commands/mark-synced.js +28 -0
  20. package/dist/commands/mark-synced.js.map +1 -0
  21. package/dist/commands/status.d.ts +3 -0
  22. package/dist/commands/status.d.ts.map +1 -0
  23. package/dist/commands/status.js +43 -0
  24. package/dist/commands/status.js.map +1 -0
  25. package/dist/commands/sync.d.ts +3 -0
  26. package/dist/commands/sync.d.ts.map +1 -0
  27. package/dist/commands/sync.js +15 -0
  28. package/dist/commands/sync.js.map +1 -0
  29. package/dist/commands/validate.d.ts +3 -0
  30. package/dist/commands/validate.d.ts.map +1 -0
  31. package/dist/commands/validate.js +41 -0
  32. package/dist/commands/validate.js.map +1 -0
  33. package/dist/index.d.ts +3 -0
  34. package/dist/index.d.ts.map +1 -0
  35. package/dist/index.js +28 -0
  36. package/dist/index.js.map +1 -0
  37. package/dist/ui/banner.d.ts +2 -0
  38. package/dist/ui/banner.d.ts.map +1 -0
  39. package/dist/ui/banner.js +13 -0
  40. package/dist/ui/banner.js.map +1 -0
  41. package/dist/ui/format.d.ts +14 -0
  42. package/dist/ui/format.d.ts.map +1 -0
  43. package/dist/ui/format.js +90 -0
  44. package/dist/ui/format.js.map +1 -0
  45. package/package.json +21 -0
  46. package/src/commands/cr.ts +84 -0
  47. package/src/commands/diff.ts +43 -0
  48. package/src/commands/init.ts +120 -0
  49. package/src/commands/mark-synced.ts +26 -0
  50. package/src/commands/status.ts +38 -0
  51. package/src/commands/sync.ts +13 -0
  52. package/src/commands/validate.ts +44 -0
  53. package/src/index.ts +29 -0
  54. package/src/ui/banner.ts +7 -0
  55. package/src/ui/format.ts +88 -0
  56. package/tsconfig.json +8 -0
@@ -0,0 +1,90 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.statusIcon = statusIcon;
7
+ exports.statusLabel = statusLabel;
8
+ exports.createStatusTable = createStatusTable;
9
+ exports.heading = heading;
10
+ exports.success = success;
11
+ exports.warning = warning;
12
+ exports.error = error;
13
+ exports.info = info;
14
+ const chalk_1 = __importDefault(require("chalk"));
15
+ const cli_table3_1 = __importDefault(require("cli-table3"));
16
+ function statusIcon(status) {
17
+ switch (status) {
18
+ case 'synced':
19
+ return chalk_1.default.green('✓');
20
+ case 'new':
21
+ return chalk_1.default.cyan('+');
22
+ case 'changed':
23
+ return chalk_1.default.yellow('~');
24
+ case 'deleted':
25
+ return chalk_1.default.red('✗');
26
+ default:
27
+ return chalk_1.default.gray('?');
28
+ }
29
+ }
30
+ function statusLabel(status) {
31
+ switch (status) {
32
+ case 'synced':
33
+ return chalk_1.default.green.bold('synced');
34
+ case 'new':
35
+ return chalk_1.default.cyan('new');
36
+ case 'changed':
37
+ return chalk_1.default.yellow('changed');
38
+ case 'deleted':
39
+ return chalk_1.default.red('deleted');
40
+ default:
41
+ return chalk_1.default.gray(status);
42
+ }
43
+ }
44
+ function createStatusTable(files) {
45
+ const table = new cli_table3_1.default({
46
+ head: [
47
+ chalk_1.default.cyan.bold(''),
48
+ chalk_1.default.cyan.bold('File'),
49
+ chalk_1.default.cyan.bold('Version'),
50
+ chalk_1.default.cyan.bold('Status'),
51
+ ],
52
+ style: {
53
+ head: [],
54
+ border: ['gray'],
55
+ },
56
+ chars: {
57
+ top: '─', 'top-mid': '┬', 'top-left': '┌', 'top-right': '┐',
58
+ bottom: '─', 'bottom-mid': '┴', 'bottom-left': '└', 'bottom-right': '┘',
59
+ left: '│', 'left-mid': '├',
60
+ mid: '─', 'mid-mid': '┼',
61
+ right: '│', 'right-mid': '┤',
62
+ middle: '│',
63
+ },
64
+ });
65
+ for (const f of files) {
66
+ table.push([
67
+ statusIcon(f.status),
68
+ chalk_1.default.white(f.relativePath),
69
+ chalk_1.default.dim(`v${f.version}`),
70
+ statusLabel(f.status),
71
+ ]);
72
+ }
73
+ return table.toString();
74
+ }
75
+ function heading(text) {
76
+ return '\n' + chalk_1.default.cyan.bold(` ${text}`) + '\n';
77
+ }
78
+ function success(text) {
79
+ return chalk_1.default.green(` ✓ ${text}`);
80
+ }
81
+ function warning(text) {
82
+ return chalk_1.default.yellow(` ⚠ ${text}`);
83
+ }
84
+ function error(text) {
85
+ return chalk_1.default.red(` ✗ ${text}`);
86
+ }
87
+ function info(text) {
88
+ return chalk_1.default.dim(` ${text}`);
89
+ }
90
+ //# sourceMappingURL=format.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format.js","sourceRoot":"","sources":["../../src/ui/format.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "@applica-software-guru/sdd",
3
+ "version": "0.1.0",
4
+ "description": "CLI for Story Driven Development",
5
+ "main": "dist/index.js",
6
+ "bin": {
7
+ "sdd": "./dist/index.js"
8
+ },
9
+ "scripts": {
10
+ "build": "tsc"
11
+ },
12
+ "dependencies": {
13
+ "@inquirer/prompts": "^7.0.0",
14
+ "@applica-software-guru/sdd-core": "^0.1.0",
15
+ "chalk": "^4.1.2",
16
+ "cli-table3": "^0.6.5",
17
+ "clipboardy": "^2.3.0",
18
+ "commander": "^13.0.0",
19
+ "ora": "^5.4.1"
20
+ }
21
+ }
@@ -0,0 +1,84 @@
1
+ import { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+ import { SDD } from '@applica-software-guru/sdd-core';
4
+ import { heading, info } from '../ui/format.js';
5
+
6
+ function statusLabel(status: string): string {
7
+ switch (status) {
8
+ case 'draft':
9
+ return chalk.yellow('draft');
10
+ case 'applied':
11
+ return chalk.green('applied');
12
+ default:
13
+ return chalk.gray(status);
14
+ }
15
+ }
16
+
17
+ export function registerCR(program: Command): void {
18
+ const cr = program
19
+ .command('cr')
20
+ .description('Manage change requests');
21
+
22
+ cr.command('list')
23
+ .description('List all change requests with their status')
24
+ .action(async () => {
25
+ const sdd = new SDD({ root: process.cwd() });
26
+ const crs = await sdd.changeRequests();
27
+
28
+ console.log(heading('Change Requests'));
29
+
30
+ if (crs.length === 0) {
31
+ console.log(info('No change requests found.\n'));
32
+ return;
33
+ }
34
+
35
+ for (const cr of crs) {
36
+ const icon = cr.frontmatter.status === 'applied' ? chalk.green(' ✓') : chalk.yellow(' ●');
37
+ console.log(`${icon} ${chalk.white(cr.relativePath)} ${chalk.dim(`[${statusLabel(cr.frontmatter.status)}]`)} ${chalk.cyan(cr.frontmatter.title)}`);
38
+ }
39
+ console.log('');
40
+ });
41
+
42
+ cr.command('pending')
43
+ .description('Show draft change requests for the agent to process')
44
+ .action(async () => {
45
+ const sdd = new SDD({ root: process.cwd() });
46
+ const pending = await sdd.pendingChangeRequests();
47
+
48
+ if (pending.length === 0) {
49
+ console.log(heading('Change Requests'));
50
+ console.log(info('No pending change requests.\n'));
51
+ return;
52
+ }
53
+
54
+ console.log(heading(`Pending Change Requests (${pending.length})`));
55
+
56
+ for (const cr of pending) {
57
+ console.log(chalk.cyan.bold(` --- ${cr.relativePath} ---`));
58
+ console.log(chalk.cyan(` Title: ${cr.frontmatter.title}`));
59
+ console.log('');
60
+ console.log(cr.body.trim().split('\n').map((line: string) => ` ${line}`).join('\n'));
61
+ console.log('');
62
+ }
63
+ });
64
+
65
+ program
66
+ .command('mark-cr-applied [files...]')
67
+ .description('Mark change requests as applied')
68
+ .action(async (files: string[]) => {
69
+ const sdd = new SDD({ root: process.cwd() });
70
+ const marked = await sdd.markCRApplied(files.length > 0 ? files : undefined);
71
+
72
+ console.log(heading('Mark CR Applied'));
73
+
74
+ if (marked.length === 0) {
75
+ console.log(info('No draft change requests to mark.\n'));
76
+ return;
77
+ }
78
+
79
+ for (const f of marked) {
80
+ console.log(chalk.green(` ✓ ${f}`));
81
+ }
82
+ console.log(chalk.dim(`\n ${marked.length} change request(s) marked as applied.\n`));
83
+ });
84
+ }
@@ -0,0 +1,43 @@
1
+ import { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+ import { SDD } from '@applica-software-guru/sdd-core';
4
+ import { heading } from '../ui/format.js';
5
+
6
+ export function registerDiff(program: Command): void {
7
+ program
8
+ .command('diff')
9
+ .description('Show files that need to be synced')
10
+ .action(async () => {
11
+ const sdd = new SDD({ root: process.cwd() });
12
+ const pending = await sdd.pending();
13
+
14
+ console.log(heading('Pending'));
15
+
16
+ if (pending.length === 0) {
17
+ console.log(chalk.green(' ✓ Everything is synced.\n'));
18
+ return;
19
+ }
20
+
21
+ console.log(chalk.yellow(` ${pending.length} file(s) pending:\n`));
22
+
23
+ for (const f of pending) {
24
+ const { status } = f.frontmatter;
25
+ const icon =
26
+ status === 'deleted'
27
+ ? chalk.red(' ✗')
28
+ : status === 'new'
29
+ ? chalk.cyan(' +')
30
+ : chalk.yellow(' ~');
31
+
32
+ const label =
33
+ status === 'deleted'
34
+ ? chalk.red('deleted')
35
+ : status === 'new'
36
+ ? chalk.cyan('new')
37
+ : chalk.yellow('changed');
38
+
39
+ console.log(`${icon} ${chalk.white(f.relativePath)} ${chalk.dim(`(${label})`)}`);
40
+ }
41
+ console.log('');
42
+ });
43
+ }
@@ -0,0 +1,120 @@
1
+ import { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+ import { input } from '@inquirer/prompts';
4
+ import clipboardy from 'clipboardy';
5
+ import { existsSync, mkdirSync } from 'node:fs';
6
+ import { resolve } from 'node:path';
7
+ import ora from 'ora';
8
+ import { SDD } from '@applica-software-guru/sdd-core';
9
+ import { printBanner } from '../ui/banner.js';
10
+ import { success, info } from '../ui/format.js';
11
+
12
+ const START_PROMPT = `Read INSTRUCTIONS.md and the documentation in product/ and system/, then run \`sdd sync\` to start working.`;
13
+
14
+ function buildBootstrapPrompt(description: string): string {
15
+ return `Read INSTRUCTIONS.md first. This is a new SDD project.
16
+
17
+ Project goal: "${description}"
18
+
19
+ Your task: generate the initial documentation for this project. Ask me a few questions first to understand the project better (target users, main features, technical preferences), then create all documentation files:
20
+
21
+ - product/vision.md — Product vision and goals
22
+ - product/users.md — User personas
23
+ - product/features/*.md — One file per main feature
24
+ - system/entities.md — Data models (use ### headings per entity)
25
+ - system/architecture.md — Architecture decisions
26
+ - system/tech-stack.md — Technologies and frameworks
27
+ - system/interfaces.md — API contracts
28
+
29
+ Follow the file format described in INSTRUCTIONS.md for the YAML frontmatter. Do NOT write any code, only documentation.`;
30
+ }
31
+
32
+ export function registerInit(program: Command): void {
33
+ program
34
+ .command('init <project-name>')
35
+ .description('Initialize a new SDD project')
36
+ .option('--bootstrap', 'Generate a prompt to create initial documentation with an agent')
37
+ .action(async (projectName: string, options) => {
38
+ printBanner();
39
+
40
+ const projectDir = resolve(process.cwd(), projectName);
41
+
42
+ if (existsSync(resolve(projectDir, '.sdd'))) {
43
+ console.log(chalk.yellow(`\n SDD project already initialized at ${projectName}/\n`));
44
+ return;
45
+ }
46
+
47
+ const description = await input({
48
+ message: 'What should your project do?',
49
+ theme: {
50
+ prefix: chalk.cyan('?'),
51
+ style: { message: (text: string) => chalk.cyan.bold(text) },
52
+ },
53
+ });
54
+
55
+ if (!description.trim()) {
56
+ console.log(chalk.yellow('\n No description provided. Aborting.\n'));
57
+ return;
58
+ }
59
+
60
+ if (!existsSync(projectDir)) {
61
+ mkdirSync(projectDir, { recursive: true });
62
+ }
63
+
64
+ const spinner = ora({
65
+ text: 'Creating project structure...',
66
+ color: 'cyan',
67
+ }).start();
68
+
69
+ const sdd = new SDD({ root: projectDir });
70
+ const files = await sdd.init({ description: description.trim() });
71
+
72
+ spinner.stop();
73
+
74
+ // Project created
75
+ console.log(chalk.cyan.bold(`\n ${chalk.white(projectName)} is ready!\n`));
76
+
77
+ // Show what was created
78
+ console.log(chalk.dim(' Created:'));
79
+ for (const f of files) {
80
+ console.log(success(f));
81
+ }
82
+ console.log(success('product/'));
83
+ console.log(success('product/features/'));
84
+ console.log(success('system/'));
85
+ console.log(success('code/'));
86
+
87
+ // Next steps
88
+ console.log(chalk.cyan.bold('\n Next steps:\n'));
89
+
90
+ console.log(` ${chalk.white('1.')} Enter the project folder:\n`);
91
+ console.log(` ${chalk.green(`cd ${projectName}`)}\n`);
92
+
93
+ if (options.bootstrap) {
94
+ console.log(` ${chalk.white('2.')} Open your AI agent and paste the prompt below.`);
95
+ console.log(` It will ask you a few questions and generate the initial docs.\n`);
96
+ } else {
97
+ console.log(` ${chalk.white('2.')} Start writing your documentation in ${chalk.cyan('product/')} and ${chalk.cyan('system/')}.`);
98
+ console.log(` Check ${chalk.cyan('INSTRUCTIONS.md')} for the file format.\n`);
99
+
100
+ console.log(` ${chalk.white('3.')} When ready, let your AI agent run:\n`);
101
+ console.log(` ${chalk.green('sdd sync')}\n`);
102
+ }
103
+
104
+ // Prompt
105
+ const prompt = options.bootstrap
106
+ ? buildBootstrapPrompt(description.trim())
107
+ : START_PROMPT;
108
+
109
+ console.log(chalk.dim(' ─'.repeat(30)));
110
+ console.log(chalk.cyan.bold('\n Agent prompt:\n'));
111
+ console.log(chalk.white(` ${prompt.split('\n').join('\n ')}\n`));
112
+
113
+ try {
114
+ await clipboardy.write(prompt);
115
+ console.log(success('Copied to clipboard — paste it into your agent.\n'));
116
+ } catch {
117
+ console.log(info('Copy the prompt above into your agent.\n'));
118
+ }
119
+ });
120
+ }
@@ -0,0 +1,26 @@
1
+ import { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+ import { SDD } from '@applica-software-guru/sdd-core';
4
+ import { heading, success, info } from '../ui/format.js';
5
+
6
+ export function registerMarkSynced(program: Command): void {
7
+ program
8
+ .command('mark-synced [files...]')
9
+ .description('Mark specific files (or all) as synced')
10
+ .action(async (files: string[]) => {
11
+ const sdd = new SDD({ root: process.cwd() });
12
+ const marked = await sdd.markSynced(files.length > 0 ? files : undefined);
13
+
14
+ console.log(heading('Mark Synced'));
15
+
16
+ if (marked.length === 0) {
17
+ console.log(info('No pending files to mark.\n'));
18
+ return;
19
+ }
20
+
21
+ for (const f of marked) {
22
+ console.log(success(f));
23
+ }
24
+ console.log(chalk.dim(`\n ${marked.length} file(s) marked as synced.\n`));
25
+ });
26
+ }
@@ -0,0 +1,38 @@
1
+ import { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+ import { SDD } from '@applica-software-guru/sdd-core';
4
+ import { createStatusTable, heading, info } from '../ui/format.js';
5
+
6
+ export function registerStatus(program: Command): void {
7
+ program
8
+ .command('status')
9
+ .description('Show status of all story files')
10
+ .action(async () => {
11
+ const sdd = new SDD({ root: process.cwd() });
12
+ const result = await sdd.status();
13
+
14
+ if (result.files.length === 0) {
15
+ console.log(heading('Status'));
16
+ console.log(info('No story files found.\n'));
17
+ return;
18
+ }
19
+
20
+ const counts = {
21
+ new: result.files.filter((f) => f.status === 'new').length,
22
+ changed: result.files.filter((f) => f.status === 'changed').length,
23
+ deleted: result.files.filter((f) => f.status === 'deleted').length,
24
+ synced: result.files.filter((f) => f.status === 'synced').length,
25
+ };
26
+
27
+ console.log(heading('Story Files'));
28
+ console.log(createStatusTable(result.files));
29
+ console.log('');
30
+
31
+ const parts: string[] = [];
32
+ if (counts.new) parts.push(chalk.cyan.bold(`${counts.new} new`));
33
+ if (counts.changed) parts.push(chalk.yellow.bold(`${counts.changed} changed`));
34
+ if (counts.deleted) parts.push(chalk.red.bold(`${counts.deleted} deleted`));
35
+ if (counts.synced) parts.push(chalk.green.bold(`${counts.synced} synced`));
36
+ console.log(` ${parts.join(' ')}\n`);
37
+ });
38
+ }
@@ -0,0 +1,13 @@
1
+ import { Command } from 'commander';
2
+ import { SDD } from '@applica-software-guru/sdd-core';
3
+
4
+ export function registerSync(program: Command): void {
5
+ program
6
+ .command('sync')
7
+ .description('Output the sync prompt for pending files (new/changed/deleted)')
8
+ .action(async () => {
9
+ const sdd = new SDD({ root: process.cwd() });
10
+ const prompt = await sdd.sync();
11
+ process.stdout.write(prompt);
12
+ });
13
+ }
@@ -0,0 +1,44 @@
1
+ import { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+ import ora from 'ora';
4
+ import { SDD } from '@applica-software-guru/sdd-core';
5
+ import { heading, success, warning, error } from '../ui/format.js';
6
+
7
+ export function registerValidate(program: Command): void {
8
+ program
9
+ .command('validate')
10
+ .description('Validate documentation for issues')
11
+ .action(async () => {
12
+ const spinner = ora({ text: 'Validating...', color: 'cyan' }).start();
13
+
14
+ const sdd = new SDD({ root: process.cwd() });
15
+ const result = await sdd.validate();
16
+
17
+ spinner.stop();
18
+
19
+ console.log(heading('Validation'));
20
+
21
+ if (result.valid && result.issues.length === 0) {
22
+ console.log(success('No issues found.\n'));
23
+ return;
24
+ }
25
+
26
+ for (const issue of result.issues) {
27
+ if (issue.severity === 'error') {
28
+ console.log(error(`${chalk.white(issue.filePath)}: ${issue.message}`));
29
+ } else {
30
+ console.log(warning(`${chalk.white(issue.filePath)}: ${issue.message}`));
31
+ }
32
+ }
33
+
34
+ const errors = result.issues.filter((i) => i.severity === 'error').length;
35
+ const warnings = result.issues.filter((i) => i.severity === 'warning').length;
36
+ console.log(
37
+ `\n ${chalk.red.bold(String(errors))} error(s) ${chalk.yellow.bold(String(warnings))} warning(s)\n`
38
+ );
39
+
40
+ if (!result.valid) {
41
+ process.exit(1);
42
+ }
43
+ });
44
+ }
package/src/index.ts ADDED
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import { registerInit } from './commands/init.js';
4
+ import { registerStatus } from './commands/status.js';
5
+ import { registerDiff } from './commands/diff.js';
6
+ import { registerSync } from './commands/sync.js';
7
+ import { registerValidate } from './commands/validate.js';
8
+ import { registerMarkSynced } from './commands/mark-synced.js';
9
+ import { registerCR } from './commands/cr.js';
10
+
11
+ const program = new Command();
12
+
13
+ program
14
+ .name('sdd')
15
+ .description('Story Driven Development — manage apps through structured documentation')
16
+ .version('0.1.0');
17
+
18
+ registerInit(program);
19
+ registerStatus(program);
20
+ registerDiff(program);
21
+ registerSync(program);
22
+ registerValidate(program);
23
+ registerMarkSynced(program);
24
+ registerCR(program);
25
+
26
+ program.parseAsync().catch((err) => {
27
+ console.error(err.message);
28
+ process.exit(1);
29
+ });
@@ -0,0 +1,7 @@
1
+ import chalk from 'chalk';
2
+
3
+ export function printBanner(): void {
4
+ console.log('');
5
+ console.log(` ${chalk.cyan.bold('SDD')} ${chalk.dim('—')} ${chalk.white('Story Driven Development')}`);
6
+ console.log('');
7
+ }
@@ -0,0 +1,88 @@
1
+ import chalk from 'chalk';
2
+ import Table from 'cli-table3';
3
+
4
+ export function statusIcon(status: string): string {
5
+ switch (status) {
6
+ case 'synced':
7
+ return chalk.green('✓');
8
+ case 'new':
9
+ return chalk.cyan('+');
10
+ case 'changed':
11
+ return chalk.yellow('~');
12
+ case 'deleted':
13
+ return chalk.red('✗');
14
+ default:
15
+ return chalk.gray('?');
16
+ }
17
+ }
18
+
19
+ export function statusLabel(status: string): string {
20
+ switch (status) {
21
+ case 'synced':
22
+ return chalk.green.bold('synced');
23
+ case 'new':
24
+ return chalk.cyan('new');
25
+ case 'changed':
26
+ return chalk.yellow('changed');
27
+ case 'deleted':
28
+ return chalk.red('deleted');
29
+ default:
30
+ return chalk.gray(status);
31
+ }
32
+ }
33
+
34
+ export function createStatusTable(
35
+ files: Array<{ relativePath: string; status: string; version: string; lastModified?: string }>
36
+ ): string {
37
+ const table = new Table({
38
+ head: [
39
+ chalk.cyan.bold(''),
40
+ chalk.cyan.bold('File'),
41
+ chalk.cyan.bold('Version'),
42
+ chalk.cyan.bold('Status'),
43
+ ],
44
+ style: {
45
+ head: [],
46
+ border: ['gray'],
47
+ },
48
+ chars: {
49
+ top: '─', 'top-mid': '┬', 'top-left': '┌', 'top-right': '┐',
50
+ bottom: '─', 'bottom-mid': '┴', 'bottom-left': '└', 'bottom-right': '┘',
51
+ left: '│', 'left-mid': '├',
52
+ mid: '─', 'mid-mid': '┼',
53
+ right: '│', 'right-mid': '┤',
54
+ middle: '│',
55
+ },
56
+ });
57
+
58
+ for (const f of files) {
59
+ table.push([
60
+ statusIcon(f.status),
61
+ chalk.white(f.relativePath),
62
+ chalk.dim(`v${f.version}`),
63
+ statusLabel(f.status),
64
+ ]);
65
+ }
66
+
67
+ return table.toString();
68
+ }
69
+
70
+ export function heading(text: string): string {
71
+ return '\n' + chalk.cyan.bold(` ${text}`) + '\n';
72
+ }
73
+
74
+ export function success(text: string): string {
75
+ return chalk.green(` ✓ ${text}`);
76
+ }
77
+
78
+ export function warning(text: string): string {
79
+ return chalk.yellow(` ⚠ ${text}`);
80
+ }
81
+
82
+ export function error(text: string): string {
83
+ return chalk.red(` ✗ ${text}`);
84
+ }
85
+
86
+ export function info(text: string): string {
87
+ return chalk.dim(` ${text}`);
88
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist",
5
+ "rootDir": "src"
6
+ },
7
+ "include": ["src"]
8
+ }