@we-scrum/cli 1.0.8 → 1.0.9
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/cli.js +39 -27
- package/package.json +3 -3
- package/src/cli.ts +13 -154
- package/src/commands/auth.ts +25 -0
- package/src/commands/index.ts +5 -0
- package/src/commands/policy.ts +16 -0
- package/src/commands/project.ts +43 -0
- package/src/commands/story.ts +35 -0
- package/src/commands/task.ts +30 -0
- package/src/helpers/project-helper.ts +2 -2
- package/src/utils/index.ts +3 -0
- package/src/utils/require-auth.ts +7 -0
- package/src/utils/require-project.ts +6 -0
- package/src/utils/run-command.ts +15 -0
- package/src/utils/run-project-command.ts +11 -0
- package/templates/analysis-dsl-legend.hbs +1 -1
- package/templates/develop-story-skill.hbs +12 -0
- package/tsconfig.json +2 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@we-scrum/cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.9",
|
|
4
4
|
"description": "Cli tool for we-scrum application",
|
|
5
5
|
"main": "dist/cli.js",
|
|
6
6
|
"bin": {
|
|
@@ -37,11 +37,11 @@
|
|
|
37
37
|
"@types/node": "^22.19.17",
|
|
38
38
|
"tsup": "^8.5.1",
|
|
39
39
|
"typescript": "^5.9.3",
|
|
40
|
+
"@my-devkit/core": "1.0.0",
|
|
40
41
|
"@my-devkit/cli": "2.1.0",
|
|
41
|
-
"@we-scrum/enums": "1.0.0",
|
|
42
42
|
"@we-scrum/commands": "1.0.0",
|
|
43
|
-
"@my-devkit/core": "1.0.0",
|
|
44
43
|
"@we-scrum/models": "1.0.0",
|
|
44
|
+
"@we-scrum/enums": "1.0.0",
|
|
45
45
|
"@we-scrum/utils": "1.0.0"
|
|
46
46
|
},
|
|
47
47
|
"scripts": {
|
package/src/cli.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import { assert, Logger } from '@my-devkit/core';
|
|
4
|
-
import { UserProjectModel } from '@we-scrum/models';
|
|
2
|
+
import { Logger } from '@my-devkit/core';
|
|
5
3
|
import { Command } from 'commander';
|
|
6
4
|
import { version } from '../package.json';
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
import {
|
|
6
|
+
registerAuthCommands,
|
|
7
|
+
registerPolicyCommands,
|
|
8
|
+
registerProjectCommands,
|
|
9
|
+
registerStoryCommands,
|
|
10
|
+
registerTaskCommands,
|
|
11
|
+
} from './commands';
|
|
9
12
|
|
|
10
13
|
Logger.registerLogger(new Logger.ConsoleImplementation());
|
|
11
14
|
|
|
@@ -13,154 +16,10 @@ const program = new Command();
|
|
|
13
16
|
|
|
14
17
|
program.name('we-scrum').description('CLI for we-scrum application').version(version);
|
|
15
18
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
async function requireProject(): Promise<void> {
|
|
22
|
-
const root = ProjectHelper.requireRoot();
|
|
23
|
-
await ProjectHelper.init(root);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function runCommand<T = void>(fn: (options: T) => Promise<string | void>) {
|
|
27
|
-
return async (options: T) => {
|
|
28
|
-
try {
|
|
29
|
-
const output = await fn(options);
|
|
30
|
-
if (output) process.stdout.write(output + '\n');
|
|
31
|
-
process.exit(0);
|
|
32
|
-
} catch (error) {
|
|
33
|
-
if (error?.name === 'ExitPromptError') process.exit(0);
|
|
34
|
-
Logger.error(error?.message ?? 'Command failed');
|
|
35
|
-
process.exit(1);
|
|
36
|
-
}
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function runProjectCommand<T = void>(fn: (options: T) => Promise<string | void>) {
|
|
41
|
-
return runCommand<T>(async (options) => {
|
|
42
|
-
await requireProject();
|
|
43
|
-
await requireAuth();
|
|
44
|
-
return fn(options);
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
program
|
|
49
|
-
.command('login')
|
|
50
|
-
.description('Authenticate with your Google account')
|
|
51
|
-
.action(
|
|
52
|
-
runCommand(async () => {
|
|
53
|
-
await AuthHelper.loginWithGoogle();
|
|
54
|
-
}),
|
|
55
|
-
);
|
|
56
|
-
|
|
57
|
-
program
|
|
58
|
-
.command('logout')
|
|
59
|
-
.description('Log out and remove stored credentials')
|
|
60
|
-
.action(
|
|
61
|
-
runCommand(async () => {
|
|
62
|
-
AuthHelper.logout();
|
|
63
|
-
Logger.info('Logged out successfully.');
|
|
64
|
-
}),
|
|
65
|
-
);
|
|
66
|
-
|
|
67
|
-
program
|
|
68
|
-
.command('use-project')
|
|
69
|
-
.description('Select the active project and initialize we-scrum.json')
|
|
70
|
-
.option('--projectId <id>', 'Set the active project directly without the interactive prompt')
|
|
71
|
-
.action(
|
|
72
|
-
runCommand<{ projectId?: string }>(async (options) => {
|
|
73
|
-
await requireAuth();
|
|
74
|
-
|
|
75
|
-
const projects = await FirebaseHelper.getCollection<UserProjectModel>(`users/${AuthHelper.userId}/projects`);
|
|
76
|
-
assert(projects.length > 0, 'No projects found for your account. Please create a project in the we-scrum web app first.');
|
|
77
|
-
|
|
78
|
-
let selectedProjectId: string;
|
|
79
|
-
|
|
80
|
-
if (options.projectId) {
|
|
81
|
-
const match = projects.find((p) => p.projectId === options.projectId);
|
|
82
|
-
assert(!!match, `Project "${options.projectId}" not found in your account.`);
|
|
83
|
-
selectedProjectId = match.projectId;
|
|
84
|
-
} else {
|
|
85
|
-
selectedProjectId = await select({
|
|
86
|
-
message: 'Select a project',
|
|
87
|
-
choices: projects.map((p) => ({
|
|
88
|
-
name: p.name,
|
|
89
|
-
value: p.projectId,
|
|
90
|
-
description: p.projectId,
|
|
91
|
-
})),
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const projectRoot = await ProjectHelper.confirmAndCreate(selectedProjectId);
|
|
96
|
-
await ProjectHelper.init(projectRoot);
|
|
97
|
-
|
|
98
|
-
Logger.info(`Project configured: ${projects.find((p) => p.projectId === selectedProjectId)?.name}`);
|
|
99
|
-
}),
|
|
100
|
-
);
|
|
101
|
-
|
|
102
|
-
program
|
|
103
|
-
.command('start-task')
|
|
104
|
-
.description('Mark a specific task as in progress')
|
|
105
|
-
.requiredOption('--identificationNumber <id>', 'Story identification number')
|
|
106
|
-
.requiredOption('--taskId <id>', 'Unique identifier of the change to act on')
|
|
107
|
-
.action(
|
|
108
|
-
runProjectCommand<{ identificationNumber: string; taskId: string }>(async (options) => {
|
|
109
|
-
await WeScrumHelper.startTask(options.identificationNumber, options.taskId);
|
|
110
|
-
Logger.info('Task started successfully. You can now make changes to the codebase.');
|
|
111
|
-
}),
|
|
112
|
-
);
|
|
113
|
-
|
|
114
|
-
program
|
|
115
|
-
.command('complete-task')
|
|
116
|
-
.description('Mark the current in-progress task as done')
|
|
117
|
-
.requiredOption('--identificationNumber <id>', 'Story identification number')
|
|
118
|
-
.requiredOption('--taskId <id>', 'Unique identifier of the change to act on')
|
|
119
|
-
.action(
|
|
120
|
-
runProjectCommand<{ identificationNumber: string; taskId: string }>(async (options) => {
|
|
121
|
-
await WeScrumHelper.completeTask(options.identificationNumber, options.taskId);
|
|
122
|
-
Logger.info('Task completed successfully.');
|
|
123
|
-
}),
|
|
124
|
-
);
|
|
125
|
-
|
|
126
|
-
program
|
|
127
|
-
.command('get-story-description')
|
|
128
|
-
.description('Get the description of a story')
|
|
129
|
-
.requiredOption('--identificationNumber <id>', 'Story identification number')
|
|
130
|
-
.action(
|
|
131
|
-
runProjectCommand<{ identificationNumber: string }>(async (options) => {
|
|
132
|
-
return WeScrumHelper.getStoryDescription(options.identificationNumber);
|
|
133
|
-
}),
|
|
134
|
-
);
|
|
135
|
-
|
|
136
|
-
program
|
|
137
|
-
.command('get-story-analysis')
|
|
138
|
-
.description('Get the analysis DSL for a story')
|
|
139
|
-
.requiredOption('--identificationNumber <id>', 'Story identification number')
|
|
140
|
-
.action(
|
|
141
|
-
runProjectCommand<{ identificationNumber: string }>(async (options) => {
|
|
142
|
-
return WeScrumHelper.getStoryAnalysis(options.identificationNumber);
|
|
143
|
-
}),
|
|
144
|
-
);
|
|
145
|
-
|
|
146
|
-
program
|
|
147
|
-
.command('get-next-task')
|
|
148
|
-
.description('Get the next pending task for a story')
|
|
149
|
-
.requiredOption('--identificationNumber <id>', 'Story identification number')
|
|
150
|
-
.action(
|
|
151
|
-
runProjectCommand<{ identificationNumber: string }>(async (options) => {
|
|
152
|
-
return WeScrumHelper.getNextTask(options.identificationNumber);
|
|
153
|
-
}),
|
|
154
|
-
);
|
|
155
|
-
|
|
156
|
-
program
|
|
157
|
-
.command('synchronize-development-policies')
|
|
158
|
-
.description('Sync development guidelines from we-scrum into the project')
|
|
159
|
-
.action(
|
|
160
|
-
runProjectCommand(async () => {
|
|
161
|
-
const count = await WeScrumHelper.synchronizeDevelopmentPolicies();
|
|
162
|
-
Logger.info(`Synchronized ${count} development ${count === 1 ? 'policy' : 'policies'}.`);
|
|
163
|
-
}),
|
|
164
|
-
);
|
|
19
|
+
registerAuthCommands(program);
|
|
20
|
+
registerProjectCommands(program);
|
|
21
|
+
registerStoryCommands(program);
|
|
22
|
+
registerTaskCommands(program);
|
|
23
|
+
registerPolicyCommands(program);
|
|
165
24
|
|
|
166
25
|
program.parse(process.argv);
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { AuthHelper } from '@helpers';
|
|
2
|
+
import { Logger } from '@my-devkit/core';
|
|
3
|
+
import { runCommand } from '@utils';
|
|
4
|
+
import { Command } from 'commander';
|
|
5
|
+
|
|
6
|
+
export function registerAuthCommands(program: Command): void {
|
|
7
|
+
program
|
|
8
|
+
.command('login')
|
|
9
|
+
.description('Authenticate with your Google account')
|
|
10
|
+
.action(
|
|
11
|
+
runCommand(async () => {
|
|
12
|
+
await AuthHelper.loginWithGoogle();
|
|
13
|
+
}),
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
program
|
|
17
|
+
.command('logout')
|
|
18
|
+
.description('Log out and remove stored credentials')
|
|
19
|
+
.action(
|
|
20
|
+
runCommand(async () => {
|
|
21
|
+
AuthHelper.logout();
|
|
22
|
+
Logger.info('Logged out successfully.');
|
|
23
|
+
}),
|
|
24
|
+
);
|
|
25
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { WeScrumHelper } from '@helpers';
|
|
2
|
+
import { Logger } from '@my-devkit/core';
|
|
3
|
+
import { runProjectCommand } from '@utils';
|
|
4
|
+
import { Command } from 'commander';
|
|
5
|
+
|
|
6
|
+
export function registerPolicyCommands(program: Command): void {
|
|
7
|
+
program
|
|
8
|
+
.command('synchronize-development-policies')
|
|
9
|
+
.description('Sync development guidelines from we-scrum into the project')
|
|
10
|
+
.action(
|
|
11
|
+
runProjectCommand(async () => {
|
|
12
|
+
const count = await WeScrumHelper.synchronizeDevelopmentPolicies();
|
|
13
|
+
Logger.info(`Synchronized ${count} development ${count === 1 ? 'policy' : 'policies'}.`);
|
|
14
|
+
}),
|
|
15
|
+
);
|
|
16
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { AuthHelper, FirebaseHelper, ProjectHelper } from '@helpers';
|
|
2
|
+
import { select } from '@inquirer/prompts';
|
|
3
|
+
import { assert, Logger } from '@my-devkit/core';
|
|
4
|
+
import { requireAuth, runCommand } from '@utils';
|
|
5
|
+
import { UserProjectModel } from '@we-scrum/models';
|
|
6
|
+
import { Command } from 'commander';
|
|
7
|
+
|
|
8
|
+
export function registerProjectCommands(program: Command): void {
|
|
9
|
+
program
|
|
10
|
+
.command('use-project')
|
|
11
|
+
.description('Select the active project and initialize we-scrum.json')
|
|
12
|
+
.option('--projectId <id>', 'Set the active project directly without the interactive prompt')
|
|
13
|
+
.action(
|
|
14
|
+
runCommand<{ projectId?: string }>(async (options) => {
|
|
15
|
+
await requireAuth();
|
|
16
|
+
|
|
17
|
+
const projects = await FirebaseHelper.getCollection<UserProjectModel>(`users/${AuthHelper.userId}/projects`);
|
|
18
|
+
assert(projects.length > 0, 'No projects found for your account. Please create a project in the we-scrum web app first.');
|
|
19
|
+
|
|
20
|
+
let selectedProjectId: string;
|
|
21
|
+
|
|
22
|
+
if (options.projectId) {
|
|
23
|
+
const match = projects.find((p) => p.projectId === options.projectId);
|
|
24
|
+
assert(!!match, `Project "${options.projectId}" not found in your account.`);
|
|
25
|
+
selectedProjectId = match.projectId;
|
|
26
|
+
} else {
|
|
27
|
+
selectedProjectId = await select({
|
|
28
|
+
message: 'Select a project',
|
|
29
|
+
choices: projects.map((p) => ({
|
|
30
|
+
name: p.name,
|
|
31
|
+
value: p.projectId,
|
|
32
|
+
description: p.projectId,
|
|
33
|
+
})),
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const projectRoot = await ProjectHelper.confirmAndCreate(selectedProjectId);
|
|
38
|
+
await ProjectHelper.init(projectRoot);
|
|
39
|
+
|
|
40
|
+
Logger.info(`Project configured: ${projects.find((p) => p.projectId === selectedProjectId)?.name}`);
|
|
41
|
+
}),
|
|
42
|
+
);
|
|
43
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { WeScrumHelper } from '@helpers';
|
|
2
|
+
import { runProjectCommand } from '@utils';
|
|
3
|
+
import { Command } from 'commander';
|
|
4
|
+
|
|
5
|
+
export function registerStoryCommands(program: Command): void {
|
|
6
|
+
program
|
|
7
|
+
.command('get-story-description')
|
|
8
|
+
.description('Get the description of a story')
|
|
9
|
+
.requiredOption('-i, --identificationNumber <id>', 'Story identification number')
|
|
10
|
+
.action(
|
|
11
|
+
runProjectCommand<{ identificationNumber: string }>(async (options) => {
|
|
12
|
+
return WeScrumHelper.getStoryDescription(options.identificationNumber);
|
|
13
|
+
}),
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
program
|
|
17
|
+
.command('get-story-analysis')
|
|
18
|
+
.description('Get the analysis DSL for a story')
|
|
19
|
+
.requiredOption('-i, --identificationNumber <id>', 'Story identification number')
|
|
20
|
+
.action(
|
|
21
|
+
runProjectCommand<{ identificationNumber: string }>(async (options) => {
|
|
22
|
+
return WeScrumHelper.getStoryAnalysis(options.identificationNumber);
|
|
23
|
+
}),
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
program
|
|
27
|
+
.command('get-next-task')
|
|
28
|
+
.description('Get the next pending task for a story')
|
|
29
|
+
.requiredOption('-i, --identificationNumber <id>', 'Story identification number')
|
|
30
|
+
.action(
|
|
31
|
+
runProjectCommand<{ identificationNumber: string }>(async (options) => {
|
|
32
|
+
return WeScrumHelper.getNextTask(options.identificationNumber);
|
|
33
|
+
}),
|
|
34
|
+
);
|
|
35
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { WeScrumHelper } from '@helpers';
|
|
2
|
+
import { Logger } from '@my-devkit/core';
|
|
3
|
+
import { runProjectCommand } from '@utils';
|
|
4
|
+
import { Command } from 'commander';
|
|
5
|
+
|
|
6
|
+
export function registerTaskCommands(program: Command): void {
|
|
7
|
+
program
|
|
8
|
+
.command('start-task')
|
|
9
|
+
.description('Mark a specific task as in progress')
|
|
10
|
+
.requiredOption('-i, --identificationNumber <id>', 'Story identification number')
|
|
11
|
+
.requiredOption('-t, --taskId <id>', 'Unique identifier of the change to act on')
|
|
12
|
+
.action(
|
|
13
|
+
runProjectCommand<{ identificationNumber: string; taskId: string }>(async (options) => {
|
|
14
|
+
await WeScrumHelper.startTask(options.identificationNumber, options.taskId);
|
|
15
|
+
Logger.info('Task started successfully. You can now make changes to the codebase.');
|
|
16
|
+
}),
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
program
|
|
20
|
+
.command('complete-task')
|
|
21
|
+
.description('Mark the current in-progress task as done')
|
|
22
|
+
.requiredOption('-i, --identificationNumber <id>', 'Story identification number')
|
|
23
|
+
.requiredOption('-t, --taskId <id>', 'Unique identifier of the change to act on')
|
|
24
|
+
.action(
|
|
25
|
+
runProjectCommand<{ identificationNumber: string; taskId: string }>(async (options) => {
|
|
26
|
+
await WeScrumHelper.completeTask(options.identificationNumber, options.taskId);
|
|
27
|
+
Logger.info('Task completed successfully.');
|
|
28
|
+
}),
|
|
29
|
+
);
|
|
30
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { assert, Logger } from '@my-devkit/core';
|
|
2
1
|
import { confirm, input } from '@inquirer/prompts';
|
|
2
|
+
import { assert, Logger } from '@my-devkit/core';
|
|
3
3
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
4
4
|
import { dirname, join, resolve } from 'node:path';
|
|
5
5
|
import { version as cliVersion } from '../../package.json';
|
|
@@ -37,7 +37,7 @@ export class ProjectHelper {
|
|
|
37
37
|
|
|
38
38
|
public static write(root: string, partial: Partial<WeScrumProjectConfig>): void {
|
|
39
39
|
const existing = existsSync(join(root, CONFIG_FILE)) ? this.read(root) : ({} as WeScrumProjectConfig);
|
|
40
|
-
writeFileSync(join(root, CONFIG_FILE), JSON.stringify({ ...existing, ...partial }, null,
|
|
40
|
+
writeFileSync(join(root, CONFIG_FILE), JSON.stringify({ ...existing, ...partial }, null, 4) + '\n');
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
public static async init(root: string): Promise<void> {
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { AuthHelper } from '@helpers';
|
|
2
|
+
import { assert } from '@my-devkit/core';
|
|
3
|
+
|
|
4
|
+
export async function requireAuth(): Promise<void> {
|
|
5
|
+
const initialized = await AuthHelper.initializeFromStoredCredentials();
|
|
6
|
+
assert(initialized, 'Not authenticated. Please run "we-scrum login" first.');
|
|
7
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Logger } from '@my-devkit/core';
|
|
2
|
+
|
|
3
|
+
export function runCommand<T = void>(fn: (options: T) => Promise<string | void>) {
|
|
4
|
+
return async (options: T) => {
|
|
5
|
+
try {
|
|
6
|
+
const output = await fn(options);
|
|
7
|
+
if (output) process.stdout.write(output + '\n');
|
|
8
|
+
process.exit(0);
|
|
9
|
+
} catch (error) {
|
|
10
|
+
if (error?.name === 'ExitPromptError') process.exit(0);
|
|
11
|
+
Logger.error(error?.message ?? 'Command failed');
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { requireAuth } from './require-auth';
|
|
2
|
+
import { requireProject } from './require-project';
|
|
3
|
+
import { runCommand } from './run-command';
|
|
4
|
+
|
|
5
|
+
export function runProjectCommand<T = void>(fn: (options: T) => Promise<string | void>) {
|
|
6
|
+
return runCommand<T>(async (options) => {
|
|
7
|
+
await requireProject();
|
|
8
|
+
await requireAuth();
|
|
9
|
+
return fn(options);
|
|
10
|
+
});
|
|
11
|
+
}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
**Analysis DSL** — `+` Create · `-` Delete · `~` Update (operation prefixes, not markdown bullets).
|
|
2
2
|
These appear on both change headers (`## + Object Foo`) and property lines (`+ name: String`, `~ price: Number`, `- email: String`).
|
|
3
|
-
|
|
3
|
+
Handler relations use `>>` as separator: `+ ObjectA >> ObjectB >> ObjectC`.
|
|
@@ -44,3 +44,15 @@ we-scrum get-next-task --identificationNumber <identificationNumber>
|
|
|
44
44
|
```
|
|
45
45
|
|
|
46
46
|
Repeat until all tasks are complete.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Developer context
|
|
51
|
+
|
|
52
|
+
A TypeScript watcher is already running in parallel — compiled output is always up to date. Do not trigger manual application builds.
|
|
53
|
+
|
|
54
|
+
Before committing any task, run ESLint on every modified file and fix all reported violations:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
npx eslint <file1> <file2> ...
|
|
58
|
+
```
|