rtfct 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/.project/adrs/001-use-bun-typescript.md +52 -0
- package/.project/guardrails.md +65 -0
- package/.project/kanban/backlog.md +7 -0
- package/.project/kanban/done.md +240 -0
- package/.project/kanban/in-progress.md +11 -0
- package/.project/kickstart.md +63 -0
- package/.project/protocol.md +134 -0
- package/.project/specs/requirements.md +152 -0
- package/.project/testing/strategy.md +123 -0
- package/.project/theology.md +125 -0
- package/CLAUDE.md +119 -0
- package/README.md +143 -0
- package/package.json +31 -0
- package/src/args.ts +104 -0
- package/src/commands/add.ts +78 -0
- package/src/commands/init.ts +128 -0
- package/src/commands/praise.ts +19 -0
- package/src/commands/regenerate.ts +122 -0
- package/src/commands/status.ts +163 -0
- package/src/help.ts +52 -0
- package/src/index.ts +102 -0
- package/src/kanban.ts +83 -0
- package/src/manifest.ts +67 -0
- package/src/presets/base.ts +195 -0
- package/src/presets/elixir.ts +118 -0
- package/src/presets/github.ts +194 -0
- package/src/presets/index.ts +154 -0
- package/src/presets/typescript.ts +589 -0
- package/src/presets/zig.ts +494 -0
- package/tests/integration/add.test.ts +104 -0
- package/tests/integration/init.test.ts +197 -0
- package/tests/integration/praise.test.ts +36 -0
- package/tests/integration/regenerate.test.ts +154 -0
- package/tests/integration/status.test.ts +165 -0
- package/tests/unit/args.test.ts +144 -0
- package/tests/unit/kanban.test.ts +162 -0
- package/tests/unit/manifest.test.ts +155 -0
- package/tests/unit/presets.test.ts +295 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Zig Codex — Sacred Patterns for Zig Development
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { Preset } from "./index";
|
|
6
|
+
|
|
7
|
+
const TESTING_STRATEGY_MD = `# Zig Testing Strategy
|
|
8
|
+
|
|
9
|
+
*The Rites of Verification for Zig*
|
|
10
|
+
|
|
11
|
+
## The Sacred Command
|
|
12
|
+
|
|
13
|
+
\`\`\`bash
|
|
14
|
+
zig build test
|
|
15
|
+
\`\`\`
|
|
16
|
+
|
|
17
|
+
This single incantation runs all tests in the project. The Machine Spirit shall invoke it frequently.
|
|
18
|
+
|
|
19
|
+
## Test Organization
|
|
20
|
+
|
|
21
|
+
Tests live alongside the code they test. This is the Zig way.
|
|
22
|
+
|
|
23
|
+
\`\`\`zig
|
|
24
|
+
// src/math.zig
|
|
25
|
+
const std = @import("std");
|
|
26
|
+
const testing = std.testing;
|
|
27
|
+
|
|
28
|
+
pub fn add(a: i32, b: i32) i32 {
|
|
29
|
+
return a + b;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
test "add returns sum of two numbers" {
|
|
33
|
+
try testing.expectEqual(@as(i32, 5), add(2, 3));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
test "add handles negative numbers" {
|
|
37
|
+
try testing.expectEqual(@as(i32, 0), add(-5, 5));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
test "add handles overflow" {
|
|
41
|
+
// This should be tested if overflow behavior matters
|
|
42
|
+
const result = @addWithOverflow(std.math.maxInt(i32), 1);
|
|
43
|
+
try testing.expect(result[1] == 1); // overflow occurred
|
|
44
|
+
}
|
|
45
|
+
\`\`\`
|
|
46
|
+
|
|
47
|
+
## The build.zig Test Step
|
|
48
|
+
|
|
49
|
+
\`\`\`zig
|
|
50
|
+
const std = @import("std");
|
|
51
|
+
|
|
52
|
+
pub fn build(b: *std.Build) void {
|
|
53
|
+
const target = b.standardTargetOptions(.{});
|
|
54
|
+
const optimize = b.standardOptimizeOption(.{});
|
|
55
|
+
|
|
56
|
+
// Main executable
|
|
57
|
+
const exe = b.addExecutable(.{
|
|
58
|
+
.name = "myapp",
|
|
59
|
+
.root_source_file = b.path("src/main.zig"),
|
|
60
|
+
.target = target,
|
|
61
|
+
.optimize = optimize,
|
|
62
|
+
});
|
|
63
|
+
b.installArtifact(exe);
|
|
64
|
+
|
|
65
|
+
// Unit tests
|
|
66
|
+
const unit_tests = b.addTest(.{
|
|
67
|
+
.root_source_file = b.path("src/main.zig"),
|
|
68
|
+
.target = target,
|
|
69
|
+
.optimize = optimize,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const run_unit_tests = b.addRunArtifact(unit_tests);
|
|
73
|
+
const test_step = b.step("test", "Run unit tests");
|
|
74
|
+
test_step.dependOn(&run_unit_tests.step);
|
|
75
|
+
}
|
|
76
|
+
\`\`\`
|
|
77
|
+
|
|
78
|
+
## Testing with Allocators
|
|
79
|
+
|
|
80
|
+
Always use the testing allocator in tests — it detects memory leaks:
|
|
81
|
+
|
|
82
|
+
\`\`\`zig
|
|
83
|
+
test "allocation test" {
|
|
84
|
+
const allocator = std.testing.allocator;
|
|
85
|
+
|
|
86
|
+
const slice = try allocator.alloc(u8, 100);
|
|
87
|
+
defer allocator.free(slice);
|
|
88
|
+
|
|
89
|
+
// If you forget the defer, the test fails with a leak report
|
|
90
|
+
}
|
|
91
|
+
\`\`\`
|
|
92
|
+
|
|
93
|
+
## Comptime Testing
|
|
94
|
+
|
|
95
|
+
Test comptime functions at comptime:
|
|
96
|
+
|
|
97
|
+
\`\`\`zig
|
|
98
|
+
fn comptimeAdd(comptime a: i32, comptime b: i32) i32 {
|
|
99
|
+
return a + b;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
test "comptime add" {
|
|
103
|
+
// This is evaluated at compile time
|
|
104
|
+
comptime {
|
|
105
|
+
const result = comptimeAdd(2, 3);
|
|
106
|
+
if (result != 5) @compileError("comptime add failed");
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// This also works
|
|
110
|
+
try std.testing.expectEqual(@as(i32, 5), comptime comptimeAdd(2, 3));
|
|
111
|
+
}
|
|
112
|
+
\`\`\`
|
|
113
|
+
|
|
114
|
+
## Coverage Doctrine
|
|
115
|
+
|
|
116
|
+
- All public functions must have at least one test
|
|
117
|
+
- Edge cases (empty, zero, max, min) must be tested explicitly
|
|
118
|
+
- Error paths must be tested — verify errors are returned correctly
|
|
119
|
+
- Comptime functions need comptime verification
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
*The tests do not lie. Praise the Machine Spirit.*
|
|
124
|
+
`;
|
|
125
|
+
|
|
126
|
+
const GUARDRAILS_MD = `# Zig Guardrails — Forbidden Heresies
|
|
127
|
+
|
|
128
|
+
*Zig-specific patterns to avoid*
|
|
129
|
+
|
|
130
|
+
## Memory Heresies
|
|
131
|
+
|
|
132
|
+
### 1. Forgetting to Free
|
|
133
|
+
Every allocation needs a corresponding free. Use \`defer\` immediately:
|
|
134
|
+
|
|
135
|
+
\`\`\`zig
|
|
136
|
+
// HERESY
|
|
137
|
+
const ptr = try allocator.create(MyStruct);
|
|
138
|
+
// ... code that might return early or error
|
|
139
|
+
allocator.destroy(ptr); // might never be reached!
|
|
140
|
+
|
|
141
|
+
// SACRED
|
|
142
|
+
const ptr = try allocator.create(MyStruct);
|
|
143
|
+
defer allocator.destroy(ptr);
|
|
144
|
+
// Now it's always freed, even on error
|
|
145
|
+
\`\`\`
|
|
146
|
+
|
|
147
|
+
### 2. Use-After-Free
|
|
148
|
+
The allocator owns freed memory. Never access it:
|
|
149
|
+
|
|
150
|
+
\`\`\`zig
|
|
151
|
+
// HERESY
|
|
152
|
+
allocator.free(slice);
|
|
153
|
+
const x = slice[0]; // undefined behavior!
|
|
154
|
+
|
|
155
|
+
// SACRED
|
|
156
|
+
defer allocator.free(slice); // free at end of scope
|
|
157
|
+
const x = slice[0]; // valid access
|
|
158
|
+
\`\`\`
|
|
159
|
+
|
|
160
|
+
### 3. Unbounded Allocations
|
|
161
|
+
Always use bounded allocators in production:
|
|
162
|
+
|
|
163
|
+
\`\`\`zig
|
|
164
|
+
// HERESY in production
|
|
165
|
+
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
|
166
|
+
|
|
167
|
+
// SACRED — use with memory limit
|
|
168
|
+
var gpa = std.heap.GeneralPurposeAllocator(.{
|
|
169
|
+
.enable_memory_limit = true,
|
|
170
|
+
}){};
|
|
171
|
+
gpa.setMemoryLimit(1024 * 1024 * 100); // 100MB limit
|
|
172
|
+
\`\`\`
|
|
173
|
+
|
|
174
|
+
## Allocator Wisdom
|
|
175
|
+
|
|
176
|
+
Choose the right allocator for the job:
|
|
177
|
+
|
|
178
|
+
| Allocator | Use Case |
|
|
179
|
+
|-----------|----------|
|
|
180
|
+
| \`std.testing.allocator\` | Tests only — detects leaks |
|
|
181
|
+
| \`std.heap.page_allocator\` | Large, long-lived allocations |
|
|
182
|
+
| \`std.heap.GeneralPurposeAllocator\` | General use with safety checks |
|
|
183
|
+
| \`std.heap.ArenaAllocator\` | Request-scoped, bulk-free |
|
|
184
|
+
| \`std.heap.FixedBufferAllocator\` | Stack-based, no syscalls |
|
|
185
|
+
|
|
186
|
+
## Comptime Heresies
|
|
187
|
+
|
|
188
|
+
### 1. Comptime Side Effects
|
|
189
|
+
Comptime code must be pure — no I/O, no runtime state:
|
|
190
|
+
|
|
191
|
+
\`\`\`zig
|
|
192
|
+
// HERESY
|
|
193
|
+
comptime {
|
|
194
|
+
std.debug.print("hello", .{}); // Error!
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// SACRED — comptime is for computation only
|
|
198
|
+
const result = comptime blk: {
|
|
199
|
+
var sum: i32 = 0;
|
|
200
|
+
for (0..10) |i| sum += @intCast(i);
|
|
201
|
+
break :blk sum;
|
|
202
|
+
};
|
|
203
|
+
\`\`\`
|
|
204
|
+
|
|
205
|
+
### 2. Excessive Comptime
|
|
206
|
+
Runtime is fine for most things. Don't force comptime:
|
|
207
|
+
|
|
208
|
+
\`\`\`zig
|
|
209
|
+
// HERESY — overcomplicating
|
|
210
|
+
fn processData(comptime T: type, comptime N: usize) [N]T { ... }
|
|
211
|
+
|
|
212
|
+
// SACRED — if N can be runtime, let it be
|
|
213
|
+
fn processData(allocator: Allocator, data: []const u8) ![]u8 { ... }
|
|
214
|
+
\`\`\`
|
|
215
|
+
|
|
216
|
+
### 3. Unreadable Generics
|
|
217
|
+
If the type signature is a paragraph, simplify:
|
|
218
|
+
|
|
219
|
+
\`\`\`zig
|
|
220
|
+
// HERESY
|
|
221
|
+
fn process(
|
|
222
|
+
comptime T: type,
|
|
223
|
+
comptime U: type,
|
|
224
|
+
comptime V: type,
|
|
225
|
+
comptime transform: fn(T) U,
|
|
226
|
+
comptime combine: fn(U, V) T,
|
|
227
|
+
) fn([]const T, []const V) []T { ... }
|
|
228
|
+
|
|
229
|
+
// SACRED — break it down or use concrete types
|
|
230
|
+
\`\`\`
|
|
231
|
+
|
|
232
|
+
## Error Handling
|
|
233
|
+
|
|
234
|
+
### Never Discard Errors in Production
|
|
235
|
+
|
|
236
|
+
\`\`\`zig
|
|
237
|
+
// HERESY
|
|
238
|
+
const value = getData() catch unreachable;
|
|
239
|
+
|
|
240
|
+
// SACRED
|
|
241
|
+
const value = getData() catch |err| {
|
|
242
|
+
log.err("Failed to get data: {}", .{err});
|
|
243
|
+
return err;
|
|
244
|
+
};
|
|
245
|
+
\`\`\`
|
|
246
|
+
|
|
247
|
+
### Use Error Sets for Documentation
|
|
248
|
+
|
|
249
|
+
\`\`\`zig
|
|
250
|
+
const FileError = error{
|
|
251
|
+
NotFound,
|
|
252
|
+
PermissionDenied,
|
|
253
|
+
DiskFull,
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
fn readFile(path: []const u8) FileError![]u8 {
|
|
257
|
+
// Callers know exactly what can go wrong
|
|
258
|
+
}
|
|
259
|
+
\`\`\`
|
|
260
|
+
|
|
261
|
+
---
|
|
262
|
+
|
|
263
|
+
*The compiler is wise. Trust its warnings. Praise the Machine Spirit.*
|
|
264
|
+
`;
|
|
265
|
+
|
|
266
|
+
const BUILD_PATTERNS_MD = `# Zig build.zig Patterns
|
|
267
|
+
|
|
268
|
+
*Sacred patterns for the build system*
|
|
269
|
+
|
|
270
|
+
## The Minimal build.zig
|
|
271
|
+
|
|
272
|
+
\`\`\`zig
|
|
273
|
+
const std = @import("std");
|
|
274
|
+
|
|
275
|
+
pub fn build(b: *std.Build) void {
|
|
276
|
+
const target = b.standardTargetOptions(.{});
|
|
277
|
+
const optimize = b.standardOptimizeOption(.{});
|
|
278
|
+
|
|
279
|
+
const exe = b.addExecutable(.{
|
|
280
|
+
.name = "myapp",
|
|
281
|
+
.root_source_file = b.path("src/main.zig"),
|
|
282
|
+
.target = target,
|
|
283
|
+
.optimize = optimize,
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
b.installArtifact(exe);
|
|
287
|
+
|
|
288
|
+
// Run step
|
|
289
|
+
const run_cmd = b.addRunArtifact(exe);
|
|
290
|
+
run_cmd.step.dependOn(b.getInstallStep());
|
|
291
|
+
const run_step = b.step("run", "Run the application");
|
|
292
|
+
run_step.dependOn(&run_cmd.step);
|
|
293
|
+
|
|
294
|
+
// Test step
|
|
295
|
+
const tests = b.addTest(.{
|
|
296
|
+
.root_source_file = b.path("src/main.zig"),
|
|
297
|
+
.target = target,
|
|
298
|
+
.optimize = optimize,
|
|
299
|
+
});
|
|
300
|
+
const run_tests = b.addRunArtifact(tests);
|
|
301
|
+
const test_step = b.step("test", "Run unit tests");
|
|
302
|
+
test_step.dependOn(&run_tests.step);
|
|
303
|
+
}
|
|
304
|
+
\`\`\`
|
|
305
|
+
|
|
306
|
+
## Library with Tests
|
|
307
|
+
|
|
308
|
+
\`\`\`zig
|
|
309
|
+
const std = @import("std");
|
|
310
|
+
|
|
311
|
+
pub fn build(b: *std.Build) void {
|
|
312
|
+
const target = b.standardTargetOptions(.{});
|
|
313
|
+
const optimize = b.standardOptimizeOption(.{});
|
|
314
|
+
|
|
315
|
+
// Static library
|
|
316
|
+
const lib = b.addStaticLibrary(.{
|
|
317
|
+
.name = "mylib",
|
|
318
|
+
.root_source_file = b.path("src/lib.zig"),
|
|
319
|
+
.target = target,
|
|
320
|
+
.optimize = optimize,
|
|
321
|
+
});
|
|
322
|
+
b.installArtifact(lib);
|
|
323
|
+
|
|
324
|
+
// Module for dependents
|
|
325
|
+
_ = b.addModule("mylib", .{
|
|
326
|
+
.root_source_file = b.path("src/lib.zig"),
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
// Tests
|
|
330
|
+
const lib_tests = b.addTest(.{
|
|
331
|
+
.root_source_file = b.path("src/lib.zig"),
|
|
332
|
+
.target = target,
|
|
333
|
+
.optimize = optimize,
|
|
334
|
+
});
|
|
335
|
+
const run_lib_tests = b.addRunArtifact(lib_tests);
|
|
336
|
+
const test_step = b.step("test", "Run library tests");
|
|
337
|
+
test_step.dependOn(&run_lib_tests.step);
|
|
338
|
+
}
|
|
339
|
+
\`\`\`
|
|
340
|
+
|
|
341
|
+
## Adding Dependencies
|
|
342
|
+
|
|
343
|
+
\`\`\`zig
|
|
344
|
+
const std = @import("std");
|
|
345
|
+
|
|
346
|
+
pub fn build(b: *std.Build) void {
|
|
347
|
+
const target = b.standardTargetOptions(.{});
|
|
348
|
+
const optimize = b.standardOptimizeOption(.{});
|
|
349
|
+
|
|
350
|
+
// Fetch dependency
|
|
351
|
+
const zap = b.dependency("zap", .{
|
|
352
|
+
.target = target,
|
|
353
|
+
.optimize = optimize,
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
const exe = b.addExecutable(.{
|
|
357
|
+
.name = "myserver",
|
|
358
|
+
.root_source_file = b.path("src/main.zig"),
|
|
359
|
+
.target = target,
|
|
360
|
+
.optimize = optimize,
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
// Add dependency module
|
|
364
|
+
exe.root_module.addImport("zap", zap.module("zap"));
|
|
365
|
+
|
|
366
|
+
b.installArtifact(exe);
|
|
367
|
+
}
|
|
368
|
+
\`\`\`
|
|
369
|
+
|
|
370
|
+
With \`build.zig.zon\`:
|
|
371
|
+
|
|
372
|
+
\`\`\`zig
|
|
373
|
+
.{
|
|
374
|
+
.name = "myserver",
|
|
375
|
+
.version = "0.1.0",
|
|
376
|
+
.dependencies = .{
|
|
377
|
+
.zap = .{
|
|
378
|
+
.url = "https://github.com/zigzap/zap/archive/v0.6.0.tar.gz",
|
|
379
|
+
.hash = "...",
|
|
380
|
+
},
|
|
381
|
+
},
|
|
382
|
+
}
|
|
383
|
+
\`\`\`
|
|
384
|
+
|
|
385
|
+
## Cross-Compilation
|
|
386
|
+
|
|
387
|
+
\`\`\`zig
|
|
388
|
+
pub fn build(b: *std.Build) void {
|
|
389
|
+
// Default: host platform
|
|
390
|
+
const target = b.standardTargetOptions(.{});
|
|
391
|
+
|
|
392
|
+
// Or specify explicit targets
|
|
393
|
+
const linux_x86 = b.resolveTargetQuery(.{
|
|
394
|
+
.cpu_arch = .x86_64,
|
|
395
|
+
.os_tag = .linux,
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
const windows_x86 = b.resolveTargetQuery(.{
|
|
399
|
+
.cpu_arch = .x86_64,
|
|
400
|
+
.os_tag = .windows,
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
// Build for each target
|
|
404
|
+
inline for (.{ target, linux_x86, windows_x86 }) |t| {
|
|
405
|
+
const exe = b.addExecutable(.{
|
|
406
|
+
.name = "myapp",
|
|
407
|
+
.root_source_file = b.path("src/main.zig"),
|
|
408
|
+
.target = t,
|
|
409
|
+
.optimize = b.standardOptimizeOption(.{}),
|
|
410
|
+
});
|
|
411
|
+
b.installArtifact(exe);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
\`\`\`
|
|
415
|
+
|
|
416
|
+
## C Interop
|
|
417
|
+
|
|
418
|
+
\`\`\`zig
|
|
419
|
+
pub fn build(b: *std.Build) void {
|
|
420
|
+
const target = b.standardTargetOptions(.{});
|
|
421
|
+
const optimize = b.standardOptimizeOption(.{});
|
|
422
|
+
|
|
423
|
+
const exe = b.addExecutable(.{
|
|
424
|
+
.name = "myapp",
|
|
425
|
+
.root_source_file = b.path("src/main.zig"),
|
|
426
|
+
.target = target,
|
|
427
|
+
.optimize = optimize,
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
// Link C library
|
|
431
|
+
exe.linkSystemLibrary("sqlite3");
|
|
432
|
+
exe.linkLibC();
|
|
433
|
+
|
|
434
|
+
// Add C source files
|
|
435
|
+
exe.addCSourceFiles(.{
|
|
436
|
+
.files = &.{ "src/legacy.c", "src/wrapper.c" },
|
|
437
|
+
.flags = &.{ "-std=c99", "-Wall" },
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
// Include paths
|
|
441
|
+
exe.addIncludePath(b.path("include"));
|
|
442
|
+
|
|
443
|
+
b.installArtifact(exe);
|
|
444
|
+
}
|
|
445
|
+
\`\`\`
|
|
446
|
+
|
|
447
|
+
## Release Optimizations
|
|
448
|
+
|
|
449
|
+
\`\`\`zig
|
|
450
|
+
pub fn build(b: *std.Build) void {
|
|
451
|
+
const target = b.standardTargetOptions(.{});
|
|
452
|
+
const optimize = b.standardOptimizeOption(.{});
|
|
453
|
+
|
|
454
|
+
const exe = b.addExecutable(.{
|
|
455
|
+
.name = "myapp",
|
|
456
|
+
.root_source_file = b.path("src/main.zig"),
|
|
457
|
+
.target = target,
|
|
458
|
+
.optimize = optimize,
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
// Strip debug info in release
|
|
462
|
+
if (optimize != .Debug) {
|
|
463
|
+
exe.root_module.strip = true;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// Enable LTO for release
|
|
467
|
+
if (optimize == .ReleaseFast or optimize == .ReleaseSmall) {
|
|
468
|
+
exe.want_lto = true;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
b.installArtifact(exe);
|
|
472
|
+
}
|
|
473
|
+
\`\`\`
|
|
474
|
+
|
|
475
|
+
---
|
|
476
|
+
|
|
477
|
+
*The build system is sacred. Praise the Machine Spirit.*
|
|
478
|
+
`;
|
|
479
|
+
|
|
480
|
+
export const ZIG_PRESET: Preset = {
|
|
481
|
+
name: "zig",
|
|
482
|
+
manifest: {
|
|
483
|
+
name: "zig",
|
|
484
|
+
version: "0.1.0",
|
|
485
|
+
description: "The Zig Codex — Systems programming with safety",
|
|
486
|
+
depends: ["base"],
|
|
487
|
+
generated_paths: ["src/", "build.zig", "build.zig.zon"],
|
|
488
|
+
},
|
|
489
|
+
files: [
|
|
490
|
+
{ path: "testing/strategy.md", content: TESTING_STRATEGY_MD },
|
|
491
|
+
{ path: "guardrails.md", content: GUARDRAILS_MD },
|
|
492
|
+
{ path: "design/patterns.md", content: BUILD_PATTERNS_MD },
|
|
493
|
+
],
|
|
494
|
+
};
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration Tests for the Add Command
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, test, expect, beforeEach, afterEach } from "bun:test";
|
|
6
|
+
import { mkdtemp, rm, stat } from "fs/promises";
|
|
7
|
+
import { join } from "path";
|
|
8
|
+
import { runInit } from "../../src/commands/init";
|
|
9
|
+
import { runAdd, formatAdd } from "../../src/commands/add";
|
|
10
|
+
|
|
11
|
+
describe("add command", () => {
|
|
12
|
+
let testDir: string;
|
|
13
|
+
|
|
14
|
+
beforeEach(async () => {
|
|
15
|
+
testDir = await mkdtemp("/tmp/rtfct-add-test-");
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
afterEach(async () => {
|
|
19
|
+
await rm(testDir, { recursive: true, force: true });
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe("without existing project", () => {
|
|
23
|
+
test("fails if .project does not exist", async () => {
|
|
24
|
+
const result = await runAdd(testDir, "zig");
|
|
25
|
+
|
|
26
|
+
expect(result.success).toBe(false);
|
|
27
|
+
expect(result.message).toContain("No .project/ folder found");
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe("with existing project", () => {
|
|
32
|
+
beforeEach(async () => {
|
|
33
|
+
await runInit(testDir);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("adds preset successfully", async () => {
|
|
37
|
+
const result = await runAdd(testDir, "zig");
|
|
38
|
+
|
|
39
|
+
expect(result.success).toBe(true);
|
|
40
|
+
expect(result.message).toContain("incorporated");
|
|
41
|
+
|
|
42
|
+
const presetDir = join(testDir, ".project", "presets", "zig");
|
|
43
|
+
const stats = await stat(presetDir);
|
|
44
|
+
expect(stats.isDirectory()).toBe(true);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("adds typescript preset", async () => {
|
|
48
|
+
const result = await runAdd(testDir, "typescript");
|
|
49
|
+
|
|
50
|
+
expect(result.success).toBe(true);
|
|
51
|
+
|
|
52
|
+
const presetDir = join(testDir, ".project", "presets", "typescript");
|
|
53
|
+
const stats = await stat(presetDir);
|
|
54
|
+
expect(stats.isDirectory()).toBe(true);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("adds elixir preset", async () => {
|
|
58
|
+
const result = await runAdd(testDir, "elixir");
|
|
59
|
+
|
|
60
|
+
expect(result.success).toBe(true);
|
|
61
|
+
|
|
62
|
+
const presetDir = join(testDir, ".project", "presets", "elixir");
|
|
63
|
+
const stats = await stat(presetDir);
|
|
64
|
+
expect(stats.isDirectory()).toBe(true);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test("fails for unknown preset", async () => {
|
|
68
|
+
const result = await runAdd(testDir, "unknown");
|
|
69
|
+
|
|
70
|
+
expect(result.success).toBe(false);
|
|
71
|
+
expect(result.message).toContain("Unknown preset");
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("fails if preset already installed", async () => {
|
|
75
|
+
await runAdd(testDir, "zig");
|
|
76
|
+
const result = await runAdd(testDir, "zig");
|
|
77
|
+
|
|
78
|
+
expect(result.success).toBe(false);
|
|
79
|
+
expect(result.message).toContain("already incorporated");
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe("output formatting", () => {
|
|
84
|
+
beforeEach(async () => {
|
|
85
|
+
await runInit(testDir);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test("formats success message", async () => {
|
|
89
|
+
const result = await runAdd(testDir, "zig");
|
|
90
|
+
const output = formatAdd(result);
|
|
91
|
+
|
|
92
|
+
expect(output).toContain("✓");
|
|
93
|
+
expect(output).toContain("incorporated");
|
|
94
|
+
expect(output).toContain("Omnissiah");
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("formats failure message", async () => {
|
|
98
|
+
const result = await runAdd(testDir, "unknown");
|
|
99
|
+
const output = formatAdd(result);
|
|
100
|
+
|
|
101
|
+
expect(output).toContain("✗");
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
});
|