ace-interview-prep 0.1.0 → 0.1.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/README.md +44 -25
- package/dist/commands/feedback.js +57 -22
- package/dist/commands/generate.js +31 -13
- package/dist/commands/init.js +90 -37
- package/dist/commands/reset.js +60 -27
- package/dist/commands/score.js +41 -26
- package/dist/commands/setup.js +42 -4
- package/dist/commands/test.js +19 -11
- package/dist/index.js +26 -2
- package/dist/lib/categories.js +12 -4
- package/dist/lib/llm.js +30 -1
- package/dist/lib/scaffold.js +1 -11
- package/dist/lib/scorecard.js +32 -1
- package/dist/prompts/question-brainstorm.md +2 -2
- package/dist/prompts/question-generate.md +19 -10
- package/dist/templates/web-components/Component.test.tsx.hbs +16 -0
- package/dist/templates/web-components/Component.tsx.hbs +29 -0
- package/package.json +7 -2
- package/dist/templates/web-components/component.test.ts.hbs +0 -11
- package/dist/templates/web-components/component.ts.hbs +0 -22
- package/dist/templates/web-components/index.html.hbs +0 -12
- package/questions/web-components/star-rating/README.md +0 -45
- package/questions/web-components/star-rating/component.test.ts +0 -64
- package/questions/web-components/star-rating/component.ts +0 -28
- package/questions/web-components/star-rating/index.html +0 -14
- package/questions/web-components/star-rating/scorecard.json +0 -9
package/README.md
CHANGED
|
@@ -50,7 +50,10 @@ npm install vitest happy-dom @testing-library/jest-dom
|
|
|
50
50
|
### 3. Practice
|
|
51
51
|
|
|
52
52
|
```bash
|
|
53
|
-
# Generate a question
|
|
53
|
+
# Generate a question interactively (prompts for category, difficulty, topic)
|
|
54
|
+
ace generate
|
|
55
|
+
|
|
56
|
+
# Or pass flags to skip prompts
|
|
54
57
|
ace generate --topic "debounce" --category js-ts --difficulty medium
|
|
55
58
|
|
|
56
59
|
# Interactive brainstorm mode
|
|
@@ -61,43 +64,59 @@ ace add
|
|
|
61
64
|
|
|
62
65
|
# List all questions
|
|
63
66
|
ace list
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### 4. Test, Review, Track
|
|
64
70
|
|
|
71
|
+
All commands below work in three modes:
|
|
72
|
+
- **Interactive** — run with no arguments to pick from a selectable list
|
|
73
|
+
- **Direct** — pass a slug to target a specific question
|
|
74
|
+
- **All** — pass `--all` to operate on every question
|
|
75
|
+
|
|
76
|
+
```bash
|
|
65
77
|
# Run tests
|
|
66
|
-
ace test
|
|
67
|
-
ace test
|
|
68
|
-
ace test --
|
|
78
|
+
ace test # pick from list
|
|
79
|
+
ace test debounce # specific question
|
|
80
|
+
ace test --all # run all tests
|
|
81
|
+
ace test --watch # watch mode (with --all)
|
|
69
82
|
|
|
70
|
-
# Get LLM feedback
|
|
71
|
-
ace feedback
|
|
83
|
+
# Get LLM feedback on your solution
|
|
84
|
+
ace feedback # pick from list
|
|
85
|
+
ace feedback debounce # specific question
|
|
86
|
+
ace feedback --all # review all questions (confirms each one)
|
|
72
87
|
|
|
73
88
|
# View scorecard
|
|
74
|
-
ace score
|
|
75
|
-
|
|
76
|
-
#
|
|
77
|
-
|
|
89
|
+
ace score # pick from list
|
|
90
|
+
ace score debounce # specific question
|
|
91
|
+
ace score --all # show all scorecards
|
|
92
|
+
|
|
93
|
+
# Reset a question to its stub
|
|
94
|
+
ace reset # pick from list
|
|
95
|
+
ace reset debounce # specific question
|
|
96
|
+
ace reset --all # reset everything (with confirmation)
|
|
78
97
|
```
|
|
79
98
|
|
|
80
99
|
## Question Categories
|
|
81
100
|
|
|
82
|
-
| Category | Slug | Type |
|
|
83
|
-
|
|
84
|
-
| JS/TS Puzzles | `js-ts` | Coding |
|
|
85
|
-
|
|
|
86
|
-
| React Web Apps | `react-apps` | Coding |
|
|
87
|
-
| LeetCode Data Structures | `leetcode-ds` | Coding |
|
|
88
|
-
| LeetCode Algorithms | `leetcode-algo` | Coding |
|
|
89
|
-
| System Design — Frontend | `design-fe` | Design |
|
|
90
|
-
| System Design — Backend | `design-be` | Design |
|
|
91
|
-
| System Design — Full Stack | `design-full` | Design |
|
|
101
|
+
| Category | Slug | Type | Focus |
|
|
102
|
+
|----------|------|------|-------|
|
|
103
|
+
| JS/TS Puzzles | `js-ts` | Coding | Closures, async patterns, type utilities |
|
|
104
|
+
| React Components | `web-components` | Coding | Props, events, composition, reusable UI |
|
|
105
|
+
| React Web Apps | `react-apps` | Coding | Hooks, state, routing, full features |
|
|
106
|
+
| LeetCode Data Structures | `leetcode-ds` | Coding | Trees, graphs, heaps, hash maps |
|
|
107
|
+
| LeetCode Algorithms | `leetcode-algo` | Coding | DP, greedy, two pointers, sorting |
|
|
108
|
+
| System Design — Frontend | `design-fe` | Design | Component architecture, state, rendering |
|
|
109
|
+
| System Design — Backend | `design-be` | Design | APIs, databases, caching, queues |
|
|
110
|
+
| System Design — Full Stack | `design-full` | Design | End-to-end systems, trade-offs |
|
|
92
111
|
|
|
93
112
|
## How It Works
|
|
94
113
|
|
|
95
|
-
1. **
|
|
114
|
+
1. **Generate a question** — run `ace generate` and follow the prompts (category, difficulty, topic), or use `ace generate --brainstorm` for an interactive design session.
|
|
96
115
|
2. **Open the question folder** — read `README.md` for the problem statement.
|
|
97
|
-
3. **Write your solution** in the solution file (`solution.ts`, `App.tsx`, `
|
|
98
|
-
4. **Run tests** with `ace test
|
|
99
|
-
5. **Get feedback** with `ace feedback
|
|
100
|
-
6. **Track progress** with `ace score
|
|
116
|
+
3. **Write your solution** in the solution file (`solution.ts`, `App.tsx`, `Component.tsx`, or `notes.md`).
|
|
117
|
+
4. **Run tests** with `ace test` to pick a question and check your work.
|
|
118
|
+
5. **Get feedback** with `ace feedback` for an LLM-powered code or design review.
|
|
119
|
+
6. **Track progress** with `ace score` and `ace list`.
|
|
101
120
|
|
|
102
121
|
## Configuration
|
|
103
122
|
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import prompts from "prompts";
|
|
3
4
|
import chalk from "chalk";
|
|
4
|
-
import { findQuestion, readScorecard, writeScorecard } from "../lib/scorecard.js";
|
|
5
|
+
import { findQuestion, readScorecard, writeScorecard, getAllQuestions, promptForSlug } from "../lib/scorecard.js";
|
|
5
6
|
import { CATEGORIES, isDesignCategory } from "../lib/categories.js";
|
|
6
7
|
import { chatStream, requireProvider } from "../lib/llm.js";
|
|
7
8
|
import { resolveWorkspaceRoot, isWorkspaceInitialized } from "../lib/paths.js";
|
|
@@ -12,35 +13,25 @@ function loadPrompt(name) {
|
|
|
12
13
|
function parseArgs(args) {
|
|
13
14
|
let slug;
|
|
14
15
|
let provider;
|
|
16
|
+
let all = false;
|
|
15
17
|
for (let i = 0; i < args.length; i++) {
|
|
16
18
|
const arg = args[i];
|
|
17
19
|
if (arg === "--provider" && args[i + 1]) {
|
|
18
20
|
provider = args[++i];
|
|
21
|
+
} else if (arg === "--all" || arg === "all") {
|
|
22
|
+
all = true;
|
|
19
23
|
} else if (!arg.startsWith("--")) {
|
|
20
24
|
slug = arg;
|
|
21
25
|
}
|
|
22
26
|
}
|
|
23
|
-
return { slug, provider };
|
|
27
|
+
return { slug, provider, all };
|
|
24
28
|
}
|
|
25
|
-
async function
|
|
26
|
-
const
|
|
27
|
-
if (!isWorkspaceInitialized(root)) {
|
|
28
|
-
console.error(chalk.red("\nError: Workspace not initialized."));
|
|
29
|
-
console.error(chalk.dim("Run `ace init` in this folder first.\n"));
|
|
30
|
-
process.exit(1);
|
|
31
|
-
}
|
|
32
|
-
const parsed = parseArgs(args);
|
|
33
|
-
if (!parsed.slug) {
|
|
34
|
-
console.error(chalk.red("Missing question slug."));
|
|
35
|
-
console.error(chalk.dim("Usage: npm run ace feedback <slug>"));
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
const question = findQuestion(parsed.slug);
|
|
29
|
+
async function runFeedbackForSlug(slug, provider) {
|
|
30
|
+
const question = findQuestion(slug);
|
|
39
31
|
if (!question) {
|
|
40
|
-
console.error(chalk.red(`Question not found: ${
|
|
32
|
+
console.error(chalk.red(`Question not found: ${slug}`));
|
|
41
33
|
return;
|
|
42
34
|
}
|
|
43
|
-
const provider = requireProvider(parsed.provider);
|
|
44
35
|
const config = CATEGORIES[question.category];
|
|
45
36
|
const isDesign = isDesignCategory(question.category);
|
|
46
37
|
const readmePath = path.join(question.dir, "README.md");
|
|
@@ -53,7 +44,7 @@ async function run(args) {
|
|
|
53
44
|
const notes = fs.existsSync(notesPath) ? fs.readFileSync(notesPath, "utf-8") : "";
|
|
54
45
|
if (!notes.trim() || notes.includes("<!-- List the core features")) {
|
|
55
46
|
console.error(chalk.yellow("Notes file appears to be empty. Write your design notes first!"));
|
|
56
|
-
console.error(chalk.dim(`Edit: questions/${question.category}/${
|
|
47
|
+
console.error(chalk.dim(`Edit: questions/${question.category}/${slug}/notes.md`));
|
|
57
48
|
return;
|
|
58
49
|
}
|
|
59
50
|
const designSubType = question.category === "design-fe" ? "frontend" : question.category === "design-be" ? "backend" : "full-stack";
|
|
@@ -102,7 +93,7 @@ ${solutionContent}
|
|
|
102
93
|
${testContent}`;
|
|
103
94
|
}
|
|
104
95
|
console.log(chalk.cyan(`
|
|
105
|
-
--- LLM ${isDesign ? "Design" : "Code"} Review: ${
|
|
96
|
+
--- LLM ${isDesign ? "Design" : "Code"} Review: ${slug} ---`));
|
|
106
97
|
console.log(chalk.dim(`Provider: ${provider}
|
|
107
98
|
`));
|
|
108
99
|
const messages = [
|
|
@@ -116,7 +107,7 @@ ${testContent}`;
|
|
|
116
107
|
fullResponse += chunk;
|
|
117
108
|
}
|
|
118
109
|
console.log("\n");
|
|
119
|
-
const scorecard = readScorecard(question.category,
|
|
110
|
+
const scorecard = readScorecard(question.category, slug);
|
|
120
111
|
if (scorecard) {
|
|
121
112
|
scorecard.llmFeedback = fullResponse;
|
|
122
113
|
const scoreMatch = fullResponse.match(/Overall.*?(\d+(?:\.\d+)?)\s*\/\s*5/i);
|
|
@@ -124,10 +115,54 @@ ${testContent}`;
|
|
|
124
115
|
const lastAttempt = scorecard.attempts[scorecard.attempts.length - 1];
|
|
125
116
|
lastAttempt.llmScore = parseFloat(scoreMatch[1]);
|
|
126
117
|
}
|
|
127
|
-
writeScorecard(question.category,
|
|
118
|
+
writeScorecard(question.category, slug, scorecard);
|
|
128
119
|
console.log(chalk.dim("Feedback saved to scorecard."));
|
|
129
120
|
}
|
|
130
121
|
}
|
|
122
|
+
async function run(args) {
|
|
123
|
+
const root = resolveWorkspaceRoot();
|
|
124
|
+
if (!isWorkspaceInitialized(root)) {
|
|
125
|
+
console.error(chalk.red("\nError: Workspace not initialized."));
|
|
126
|
+
console.error(chalk.dim("Run `ace init` in this folder first.\n"));
|
|
127
|
+
process.exit(1);
|
|
128
|
+
}
|
|
129
|
+
const parsed = parseArgs(args);
|
|
130
|
+
const provider = requireProvider(parsed.provider);
|
|
131
|
+
if (parsed.all) {
|
|
132
|
+
const questions = getAllQuestions();
|
|
133
|
+
if (questions.length === 0) {
|
|
134
|
+
console.log(chalk.yellow("No questions found. Create one first with `ace generate` or `ace add`."));
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
console.log(chalk.cyan(`
|
|
138
|
+
Running feedback for ${questions.length} question(s)...
|
|
139
|
+
`));
|
|
140
|
+
for (let i = 0; i < questions.length; i++) {
|
|
141
|
+
const q = questions[i];
|
|
142
|
+
console.log(chalk.bold(`
|
|
143
|
+
[${i + 1}/${questions.length}] ${q.slug}`));
|
|
144
|
+
const { confirm } = await prompts({
|
|
145
|
+
type: "confirm",
|
|
146
|
+
name: "confirm",
|
|
147
|
+
message: `Run feedback for "${q.slug}"?`,
|
|
148
|
+
initial: true
|
|
149
|
+
});
|
|
150
|
+
if (!confirm) {
|
|
151
|
+
console.log(chalk.dim("Skipped."));
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
await runFeedbackForSlug(q.slug, provider);
|
|
155
|
+
}
|
|
156
|
+
console.log(chalk.green("\nCompleted feedback for all questions."));
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
let selectedSlug = parsed.slug;
|
|
160
|
+
if (!selectedSlug) {
|
|
161
|
+
selectedSlug = await promptForSlug();
|
|
162
|
+
if (!selectedSlug) return;
|
|
163
|
+
}
|
|
164
|
+
await runFeedbackForSlug(selectedSlug, provider);
|
|
165
|
+
}
|
|
131
166
|
export {
|
|
132
167
|
run
|
|
133
168
|
};
|
|
@@ -56,6 +56,9 @@ Question type: ${categoryConfig.type}`;
|
|
|
56
56
|
return;
|
|
57
57
|
}
|
|
58
58
|
const slug = parsed.slug || slugify(parsed.title || topic);
|
|
59
|
+
if (parsed.solutionCode) {
|
|
60
|
+
console.log(chalk.dim("Note: Discarded LLM solutionCode; using signature-based stub."));
|
|
61
|
+
}
|
|
59
62
|
const questionDir = scaffoldQuestion({
|
|
60
63
|
title: parsed.title || topic,
|
|
61
64
|
slug,
|
|
@@ -63,8 +66,7 @@ Question type: ${categoryConfig.type}`;
|
|
|
63
66
|
difficulty,
|
|
64
67
|
description: parsed.description || "",
|
|
65
68
|
signature: parsed.signature,
|
|
66
|
-
testCode: parsed.testCode
|
|
67
|
-
solutionCode: parsed.solutionCode
|
|
69
|
+
testCode: parsed.testCode
|
|
68
70
|
});
|
|
69
71
|
console.log(chalk.green(`
|
|
70
72
|
Created: questions/${category}/${slug}/`));
|
|
@@ -116,7 +118,11 @@ async function brainstormMode(provider) {
|
|
|
116
118
|
type: "select",
|
|
117
119
|
name: "category",
|
|
118
120
|
message: "Which category?",
|
|
119
|
-
choices: CATEGORY_SLUGS.map((s) => ({
|
|
121
|
+
choices: CATEGORY_SLUGS.map((s) => ({
|
|
122
|
+
title: CATEGORIES[s].name,
|
|
123
|
+
description: CATEGORIES[s].hint,
|
|
124
|
+
value: s
|
|
125
|
+
}))
|
|
120
126
|
});
|
|
121
127
|
const { difficulty } = await prompts({
|
|
122
128
|
type: "select",
|
|
@@ -158,6 +164,9 @@ ${brainstormSummary}`
|
|
|
158
164
|
return;
|
|
159
165
|
}
|
|
160
166
|
const slug = parsed.slug || slugify(parsed.title || "brainstorm-question");
|
|
167
|
+
if (parsed.solutionCode) {
|
|
168
|
+
console.log(chalk.dim("Note: Discarded LLM solutionCode; using signature-based stub."));
|
|
169
|
+
}
|
|
161
170
|
const questionDir = scaffoldQuestion({
|
|
162
171
|
title: parsed.title,
|
|
163
172
|
slug,
|
|
@@ -165,8 +174,7 @@ ${brainstormSummary}`
|
|
|
165
174
|
difficulty,
|
|
166
175
|
description: parsed.description || "",
|
|
167
176
|
signature: parsed.signature,
|
|
168
|
-
testCode: parsed.testCode
|
|
169
|
-
solutionCode: parsed.solutionCode
|
|
177
|
+
testCode: parsed.testCode
|
|
170
178
|
});
|
|
171
179
|
console.log(chalk.green(`
|
|
172
180
|
Created: questions/${category}/${slug}/`));
|
|
@@ -187,22 +195,23 @@ async function run(args) {
|
|
|
187
195
|
await brainstormMode(provider);
|
|
188
196
|
return;
|
|
189
197
|
}
|
|
190
|
-
if (!parsed.topic) {
|
|
191
|
-
console.error(chalk.red("Missing --topic. Use --brainstorm for interactive mode."));
|
|
192
|
-
console.error(chalk.dim('Example: npm run ace generate -- --topic "debounce" --category js-ts --difficulty medium'));
|
|
193
|
-
return;
|
|
194
|
-
}
|
|
195
198
|
let category = parsed.category;
|
|
196
199
|
let difficulty = parsed.difficulty;
|
|
200
|
+
let topic = parsed.topic;
|
|
197
201
|
if (!category) {
|
|
198
202
|
const result = await prompts({
|
|
199
203
|
type: "select",
|
|
200
204
|
name: "category",
|
|
201
205
|
message: "Which category?",
|
|
202
|
-
choices: CATEGORY_SLUGS.map((s) => ({
|
|
206
|
+
choices: CATEGORY_SLUGS.map((s) => ({
|
|
207
|
+
title: CATEGORIES[s].name,
|
|
208
|
+
description: CATEGORIES[s].hint,
|
|
209
|
+
value: s
|
|
210
|
+
}))
|
|
203
211
|
});
|
|
204
212
|
category = result.category;
|
|
205
213
|
}
|
|
214
|
+
if (!category) return;
|
|
206
215
|
if (!difficulty) {
|
|
207
216
|
const result = await prompts({
|
|
208
217
|
type: "select",
|
|
@@ -216,8 +225,17 @@ async function run(args) {
|
|
|
216
225
|
});
|
|
217
226
|
difficulty = result.difficulty;
|
|
218
227
|
}
|
|
219
|
-
if (!
|
|
220
|
-
|
|
228
|
+
if (!difficulty) return;
|
|
229
|
+
if (!topic) {
|
|
230
|
+
const result = await prompts({
|
|
231
|
+
type: "text",
|
|
232
|
+
name: "topic",
|
|
233
|
+
message: "What topic do you want to practice?"
|
|
234
|
+
});
|
|
235
|
+
topic = result.topic;
|
|
236
|
+
}
|
|
237
|
+
if (!topic) return;
|
|
238
|
+
await directMode(provider, topic, category, difficulty);
|
|
221
239
|
}
|
|
222
240
|
export {
|
|
223
241
|
run
|
package/dist/commands/init.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { execSync } from "node:child_process";
|
|
3
4
|
import chalk from "chalk";
|
|
4
5
|
import { getQuestionsDir, isWorkspaceInitialized } from "../lib/paths.js";
|
|
5
6
|
function parseArgs(args) {
|
|
6
7
|
return {
|
|
7
|
-
force: args.includes("--force")
|
|
8
|
-
writeScripts: args.includes("--write-scripts")
|
|
8
|
+
force: args.includes("--force")
|
|
9
9
|
};
|
|
10
10
|
}
|
|
11
11
|
const VITEST_CONFIG_TEMPLATE = `import { defineConfig } from 'vitest/config';
|
|
@@ -22,13 +22,44 @@ export default defineConfig({
|
|
|
22
22
|
`;
|
|
23
23
|
const VITEST_SETUP_TEMPLATE = `import '@testing-library/jest-dom/vitest';
|
|
24
24
|
`;
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
25
|
+
const PACKAGE_JSON_TEMPLATE = {
|
|
26
|
+
name: "ace-workspace",
|
|
27
|
+
private: true,
|
|
28
|
+
type: "module",
|
|
29
|
+
scripts: {
|
|
30
|
+
test: "vitest run",
|
|
31
|
+
"test:watch": "vitest"
|
|
32
|
+
},
|
|
33
|
+
devDependencies: {
|
|
34
|
+
"@testing-library/jest-dom": "^6.9.1",
|
|
35
|
+
"@testing-library/react": "^16.3.2",
|
|
36
|
+
"@types/react": "^19.2.14",
|
|
37
|
+
"@types/react-dom": "^19.2.3",
|
|
38
|
+
"happy-dom": "^20.6.1",
|
|
39
|
+
"react": "^19.2.4",
|
|
40
|
+
"react-dom": "^19.2.4",
|
|
41
|
+
"typescript": "^5.9.3",
|
|
42
|
+
"vitest": "^4.0.18"
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
const TSCONFIG_TEMPLATE = {
|
|
46
|
+
compilerOptions: {
|
|
47
|
+
target: "ES2022",
|
|
48
|
+
module: "ESNext",
|
|
49
|
+
moduleResolution: "bundler",
|
|
50
|
+
esModuleInterop: true,
|
|
51
|
+
allowImportingTsExtensions: true,
|
|
52
|
+
noEmit: true,
|
|
53
|
+
strict: true,
|
|
54
|
+
skipLibCheck: true,
|
|
55
|
+
resolveJsonModule: true,
|
|
56
|
+
isolatedModules: true,
|
|
57
|
+
jsx: "react-jsx"
|
|
58
|
+
},
|
|
59
|
+
include: ["questions/**/*"]
|
|
29
60
|
};
|
|
30
61
|
async function run(args) {
|
|
31
|
-
const { force
|
|
62
|
+
const { force } = parseArgs(args);
|
|
32
63
|
const root = process.cwd();
|
|
33
64
|
console.log(chalk.cyan("\n--- Initialize Workspace ---"));
|
|
34
65
|
console.log(chalk.dim(`Workspace: ${root}
|
|
@@ -44,38 +75,54 @@ async function run(args) {
|
|
|
44
75
|
fs.mkdirSync(questionsDir, { recursive: true });
|
|
45
76
|
changes.push("Created questions/");
|
|
46
77
|
}
|
|
47
|
-
const vitestConfigPath = path.join(root, "vitest.config.ts");
|
|
48
|
-
if (!fs.existsSync(vitestConfigPath) || force) {
|
|
49
|
-
fs.writeFileSync(vitestConfigPath, VITEST_CONFIG_TEMPLATE, "utf-8");
|
|
50
|
-
changes.push(force && fs.existsSync(vitestConfigPath) ? "Overwrote vitest.config.ts" : "Created vitest.config.ts");
|
|
51
|
-
}
|
|
52
|
-
const vitestSetupPath = path.join(root, "vitest.setup.ts");
|
|
53
|
-
if (!fs.existsSync(vitestSetupPath) || force) {
|
|
54
|
-
fs.writeFileSync(vitestSetupPath, VITEST_SETUP_TEMPLATE, "utf-8");
|
|
55
|
-
changes.push(force && fs.existsSync(vitestSetupPath) ? "Overwrote vitest.setup.ts" : "Created vitest.setup.ts");
|
|
56
|
-
}
|
|
57
78
|
const packageJsonPath = path.join(root, "package.json");
|
|
58
|
-
if (fs.existsSync(packageJsonPath)) {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
79
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
80
|
+
fs.writeFileSync(packageJsonPath, JSON.stringify(PACKAGE_JSON_TEMPLATE, null, 2) + "\n", "utf-8");
|
|
81
|
+
changes.push("Created package.json");
|
|
82
|
+
} else {
|
|
83
|
+
try {
|
|
84
|
+
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
|
85
|
+
let updated = false;
|
|
86
|
+
pkg.scripts = pkg.scripts || {};
|
|
87
|
+
for (const [key, value] of Object.entries(PACKAGE_JSON_TEMPLATE.scripts)) {
|
|
88
|
+
if (!pkg.scripts[key]) {
|
|
89
|
+
pkg.scripts[key] = value;
|
|
90
|
+
updated = true;
|
|
69
91
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
92
|
+
}
|
|
93
|
+
pkg.devDependencies = pkg.devDependencies || {};
|
|
94
|
+
for (const [key, value] of Object.entries(PACKAGE_JSON_TEMPLATE.devDependencies)) {
|
|
95
|
+
if (!pkg.devDependencies[key]) {
|
|
96
|
+
pkg.devDependencies[key] = value;
|
|
97
|
+
updated = true;
|
|
73
98
|
}
|
|
74
|
-
} catch (err) {
|
|
75
|
-
console.warn(chalk.yellow("Warning: Could not update package.json scripts"));
|
|
76
99
|
}
|
|
100
|
+
if (updated) {
|
|
101
|
+
fs.writeFileSync(packageJsonPath, JSON.stringify(pkg, null, 2) + "\n", "utf-8");
|
|
102
|
+
changes.push("Updated package.json (scripts and devDependencies)");
|
|
103
|
+
}
|
|
104
|
+
} catch (err) {
|
|
105
|
+
console.warn(chalk.yellow("Warning: Could not update package.json"));
|
|
77
106
|
}
|
|
78
107
|
}
|
|
108
|
+
const tsconfigPath = path.join(root, "tsconfig.json");
|
|
109
|
+
const tsconfigExisted = fs.existsSync(tsconfigPath);
|
|
110
|
+
if (!tsconfigExisted || force) {
|
|
111
|
+
fs.writeFileSync(tsconfigPath, JSON.stringify(TSCONFIG_TEMPLATE, null, 2) + "\n", "utf-8");
|
|
112
|
+
changes.push(tsconfigExisted ? "Overwrote tsconfig.json" : "Created tsconfig.json");
|
|
113
|
+
}
|
|
114
|
+
const vitestConfigPath = path.join(root, "vitest.config.ts");
|
|
115
|
+
const vitestConfigExisted = fs.existsSync(vitestConfigPath);
|
|
116
|
+
if (!vitestConfigExisted || force) {
|
|
117
|
+
fs.writeFileSync(vitestConfigPath, VITEST_CONFIG_TEMPLATE, "utf-8");
|
|
118
|
+
changes.push(vitestConfigExisted ? "Overwrote vitest.config.ts" : "Created vitest.config.ts");
|
|
119
|
+
}
|
|
120
|
+
const vitestSetupPath = path.join(root, "vitest.setup.ts");
|
|
121
|
+
const vitestSetupExisted = fs.existsSync(vitestSetupPath);
|
|
122
|
+
if (!vitestSetupExisted || force) {
|
|
123
|
+
fs.writeFileSync(vitestSetupPath, VITEST_SETUP_TEMPLATE, "utf-8");
|
|
124
|
+
changes.push(vitestSetupExisted ? "Overwrote vitest.setup.ts" : "Created vitest.setup.ts");
|
|
125
|
+
}
|
|
79
126
|
if (changes.length > 0) {
|
|
80
127
|
console.log(chalk.green("\u2713 Workspace initialized:\n"));
|
|
81
128
|
for (const change of changes) {
|
|
@@ -85,12 +132,18 @@ async function run(args) {
|
|
|
85
132
|
console.log(chalk.green("\u2713 Workspace already initialized (no changes needed)"));
|
|
86
133
|
}
|
|
87
134
|
console.log();
|
|
135
|
+
console.log(chalk.cyan("--- Installing dependencies ---"));
|
|
136
|
+
try {
|
|
137
|
+
execSync("npm install", { cwd: root, stdio: "inherit" });
|
|
138
|
+
console.log(chalk.green("\n\u2713 Dependencies installed"));
|
|
139
|
+
} catch {
|
|
140
|
+
console.error(chalk.red("\n\u2717 npm install failed. Please run it manually."));
|
|
141
|
+
}
|
|
142
|
+
console.log();
|
|
88
143
|
console.log(chalk.bold("Next steps:"));
|
|
89
|
-
console.log(chalk.dim(" 1.
|
|
90
|
-
console.log(chalk.dim(" npm install vitest happy-dom @testing-library/jest-dom"));
|
|
91
|
-
console.log(chalk.dim(" 2. Configure API keys:"));
|
|
144
|
+
console.log(chalk.dim(" 1. Configure API keys:"));
|
|
92
145
|
console.log(chalk.dim(" ace setup"));
|
|
93
|
-
console.log(chalk.dim("
|
|
146
|
+
console.log(chalk.dim(" 2. Generate or add questions:"));
|
|
94
147
|
console.log(chalk.dim(' ace generate --topic "debounce"'));
|
|
95
148
|
console.log(chalk.dim(" ace add"));
|
|
96
149
|
console.log();
|
package/dist/commands/reset.js
CHANGED
|
@@ -2,38 +2,16 @@ import fs from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import prompts from "prompts";
|
|
4
4
|
import chalk from "chalk";
|
|
5
|
-
import { findQuestion, readScorecard, resetScorecard, startNewAttempt, writeScorecard } from "../lib/scorecard.js";
|
|
5
|
+
import { findQuestion, readScorecard, resetScorecard, startNewAttempt, writeScorecard, getAllQuestions, promptForSlug } from "../lib/scorecard.js";
|
|
6
6
|
import { CATEGORIES, isDesignCategory } from "../lib/categories.js";
|
|
7
7
|
import { getStubContent } from "../lib/scaffold.js";
|
|
8
8
|
import { resolveWorkspaceRoot, isWorkspaceInitialized } from "../lib/paths.js";
|
|
9
|
-
|
|
10
|
-
const root = resolveWorkspaceRoot();
|
|
11
|
-
if (!isWorkspaceInitialized(root)) {
|
|
12
|
-
console.error(chalk.red("\nError: Workspace not initialized."));
|
|
13
|
-
console.error(chalk.dim("Run `ace init` in this folder first.\n"));
|
|
14
|
-
process.exit(1);
|
|
15
|
-
}
|
|
16
|
-
const slug = args.find((a) => !a.startsWith("--"));
|
|
17
|
-
if (!slug) {
|
|
18
|
-
console.error(chalk.red("Missing question slug."));
|
|
19
|
-
console.error(chalk.dim("Usage: npm run ace reset <slug>"));
|
|
20
|
-
return;
|
|
21
|
-
}
|
|
9
|
+
function resetQuestion(slug) {
|
|
22
10
|
const question = findQuestion(slug);
|
|
23
11
|
if (!question) {
|
|
24
12
|
console.error(chalk.red(`Question not found: ${slug}`));
|
|
25
13
|
return;
|
|
26
14
|
}
|
|
27
|
-
const { confirm } = await prompts({
|
|
28
|
-
type: "confirm",
|
|
29
|
-
name: "confirm",
|
|
30
|
-
message: `Reset "${slug}" to unanswered? This will clear your solution.`,
|
|
31
|
-
initial: false
|
|
32
|
-
});
|
|
33
|
-
if (!confirm) {
|
|
34
|
-
console.log(chalk.yellow("Cancelled."));
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
15
|
const config = CATEGORIES[question.category];
|
|
38
16
|
const isDesign = isDesignCategory(question.category);
|
|
39
17
|
if (isDesign) {
|
|
@@ -44,7 +22,6 @@ async function run(args) {
|
|
|
44
22
|
}
|
|
45
23
|
} else {
|
|
46
24
|
for (const file of config.solutionFiles) {
|
|
47
|
-
if (file === "index.html") continue;
|
|
48
25
|
const filePath = path.join(question.dir, file);
|
|
49
26
|
const stubContent = getStubContent(question.category, file);
|
|
50
27
|
if (stubContent) {
|
|
@@ -59,8 +36,64 @@ async function run(args) {
|
|
|
59
36
|
scorecard.status = "untouched";
|
|
60
37
|
writeScorecard(question.category, slug, scorecard);
|
|
61
38
|
}
|
|
62
|
-
console.log(chalk.green(`
|
|
63
|
-
|
|
39
|
+
console.log(chalk.green(`Reset: questions/${question.category}/${slug}/`));
|
|
40
|
+
}
|
|
41
|
+
async function run(args) {
|
|
42
|
+
const root = resolveWorkspaceRoot();
|
|
43
|
+
if (!isWorkspaceInitialized(root)) {
|
|
44
|
+
console.error(chalk.red("\nError: Workspace not initialized."));
|
|
45
|
+
console.error(chalk.dim("Run `ace init` in this folder first.\n"));
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
const hasAll = args.includes("--all") || args.includes("all");
|
|
49
|
+
const slug = args.find((a) => !a.startsWith("--") && a !== "all");
|
|
50
|
+
if (hasAll) {
|
|
51
|
+
const questions = getAllQuestions();
|
|
52
|
+
if (questions.length === 0) {
|
|
53
|
+
console.log(chalk.yellow("No questions found. Create one first with `ace generate` or `ace add`."));
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const { confirm: confirm2 } = await prompts({
|
|
57
|
+
type: "confirm",
|
|
58
|
+
name: "confirm",
|
|
59
|
+
message: `Reset ALL ${questions.length} question(s) to unanswered? This will clear all solutions.`,
|
|
60
|
+
initial: false
|
|
61
|
+
});
|
|
62
|
+
if (!confirm2) {
|
|
63
|
+
console.log(chalk.yellow("Cancelled."));
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
console.log(chalk.cyan(`
|
|
67
|
+
Resetting ${questions.length} question(s)...
|
|
68
|
+
`));
|
|
69
|
+
for (const q of questions) {
|
|
70
|
+
resetQuestion(q.slug);
|
|
71
|
+
}
|
|
72
|
+
console.log(chalk.green("\nCompleted reset for all questions."));
|
|
73
|
+
console.log(chalk.dim("Solution files restored to stubs. Scorecards updated."));
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
let selectedSlug = slug;
|
|
77
|
+
if (!selectedSlug) {
|
|
78
|
+
selectedSlug = await promptForSlug();
|
|
79
|
+
if (!selectedSlug) return;
|
|
80
|
+
}
|
|
81
|
+
const question = findQuestion(selectedSlug);
|
|
82
|
+
if (!question) {
|
|
83
|
+
console.error(chalk.red(`Question not found: ${selectedSlug}`));
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const { confirm } = await prompts({
|
|
87
|
+
type: "confirm",
|
|
88
|
+
name: "confirm",
|
|
89
|
+
message: `Reset "${selectedSlug}" to unanswered? This will clear your solution.`,
|
|
90
|
+
initial: false
|
|
91
|
+
});
|
|
92
|
+
if (!confirm) {
|
|
93
|
+
console.log(chalk.yellow("Cancelled."));
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
resetQuestion(selectedSlug);
|
|
64
97
|
console.log(chalk.dim("Solution files restored to stubs. Scorecard updated."));
|
|
65
98
|
}
|
|
66
99
|
export {
|