devrites 3.2.5 → 3.2.7

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 (36) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +1 -1
  3. package/docs/cli.md +0 -1
  4. package/docs/command-map.md +1 -1
  5. package/docs/orchestration.md +1 -2
  6. package/engine/commands.go +1 -1
  7. package/engine/hooks_agent_dispatch.go +98 -33
  8. package/engine/internal/lib/reconcile.go +1 -1
  9. package/engine/internal/lib/reconcile_test.go +4 -12
  10. package/engine/internal/lib/util.go +2 -2
  11. package/engine/main.go +0 -3
  12. package/engine/root_routing_test.go +0 -1
  13. package/engine/tests/hook_test.go +235 -0
  14. package/pack/.claude/skills/rite-build/reference/forge.md +2 -2
  15. package/pack/.claude/skills/rite-build/reference/phase-contract.md +11 -20
  16. package/pack/.claude/skills/rite-build/reference/wright-dispatch.md +2 -5
  17. package/pack/.claude/skills/rite-prove/reference/acceptance-proof.md +0 -1
  18. package/pack/.claude/skills/rite-seal/reference/final-evidence.md +0 -1
  19. package/pack/generated/claude/skills/rite-build/reference/forge.md +2 -2
  20. package/pack/generated/claude/skills/rite-build/reference/phase-contract.md +11 -20
  21. package/pack/generated/claude/skills/rite-build/reference/wright-dispatch.md +2 -5
  22. package/pack/generated/claude/skills/rite-prove/reference/acceptance-proof.md +0 -1
  23. package/pack/generated/claude/skills/rite-seal/reference/final-evidence.md +0 -1
  24. package/pack/generated/codex/skills/rite-build/reference/forge.md +2 -2
  25. package/pack/generated/codex/skills/rite-build/reference/phase-contract.md +11 -20
  26. package/pack/generated/codex/skills/rite-build/reference/wright-dispatch.md +2 -5
  27. package/pack/generated/codex/skills/rite-prove/reference/acceptance-proof.md +0 -1
  28. package/pack/generated/codex/skills/rite-seal/reference/final-evidence.md +0 -1
  29. package/package.json +1 -1
  30. package/scripts/build-release-tarball.sh +1 -0
  31. package/scripts/validate.sh +0 -1
  32. package/engine/internal/lib/packageexistence.go +0 -968
  33. package/engine/internal/lib/packageexistence_test.go +0 -506
  34. package/engine/testdata/golden/TestParityPackageExistence/declared.golden +0 -2
  35. package/engine/testdata/golden/TestParityPackageExistence/default-import-subpath.golden +0 -2
  36. package/engine/tests/parity_packageexistence_test.go +0 -64
@@ -1,506 +0,0 @@
1
- package lib
2
-
3
- import (
4
- "bytes"
5
- "os"
6
- "os/exec"
7
- "path/filepath"
8
- "strings"
9
- "testing"
10
- )
11
-
12
- func TestPackageExistenceJavaScriptWorkspaces(t *testing.T) {
13
- tests := []struct {
14
- name string
15
- sourcePath string
16
- workingDir string
17
- importLine string
18
- files map[string]string
19
- wantCode int
20
- wantFinding string
21
- }{
22
- {
23
- name: "nested workspace",
24
- sourcePath: "frontend/src/example.ts",
25
- workingDir: "frontend",
26
- importLine: `import { Dialog } from "@ark-ui/svelte/dialog";`,
27
- files: map[string]string{
28
- "frontend/package.json": `{"dependencies":{"@ark-ui/svelte":"^5.22.1"}}`,
29
- },
30
- },
31
- {
32
- name: "scoped package subpath",
33
- sourcePath: "app/src/example.ts",
34
- importLine: `import value from "@scope/package/subpath";`,
35
- files: map[string]string{
36
- "app/package.json": `{"devDependencies":{"@scope/package":"1.0.0"}}`,
37
- },
38
- },
39
- {
40
- name: "wildcard alias",
41
- sourcePath: "frontend/src/example.ts",
42
- importLine: `import Button from "$lib/components/button";`,
43
- files: map[string]string{
44
- "frontend/package.json": `{}`,
45
- "frontend/tsconfig.json": "{\n // SvelteKit generates the inherited aliases.\n \"extends\": \"./.svelte-kit/tsconfig.json\"\n}",
46
- "frontend/.svelte-kit/tsconfig.json": `{"compilerOptions":{"paths":{"$lib/*":["../src/lib/*"]}}}`,
47
- },
48
- },
49
- {
50
- name: "exact alias",
51
- sourcePath: "frontend/src/example.ts",
52
- importLine: `import library from "$lib";`,
53
- files: map[string]string{
54
- "frontend/package.json": `{}`,
55
- "frontend/tsconfig.json": `{"compilerOptions":{"paths":{"$lib":["./src/lib"]}}}`,
56
- },
57
- },
58
- {
59
- name: "package shaped alias",
60
- sourcePath: "frontend/src/example.svelte",
61
- importLine: `import { css } from "styled-system/css";`,
62
- files: map[string]string{
63
- "frontend/package.json": `{}`,
64
- "frontend/jsconfig.json": `{"compilerOptions":{"paths":{"styled-system/*":["./styled-system/*"]}}}`,
65
- },
66
- },
67
- {
68
- name: "undeclared dependency",
69
- sourcePath: "frontend/src/example.ts",
70
- importLine: `import missing from "definitely-missing-package";`,
71
- files: map[string]string{"frontend/package.json": `{}`},
72
- wantCode: 3,
73
- wantFinding: "definitely-missing-package",
74
- },
75
- {
76
- name: "sibling isolation",
77
- sourcePath: "workspace-a/src/example.ts",
78
- importLine: `import sibling from "sibling-only-package";`,
79
- files: map[string]string{
80
- "workspace-a/package.json": `{}`,
81
- "workspace-b/package.json": `{"dependencies":{"sibling-only-package":"1.0.0"}}`,
82
- },
83
- wantCode: 3,
84
- wantFinding: "sibling-only-package",
85
- },
86
- {
87
- name: "malformed config does not allow alias",
88
- sourcePath: "frontend/src/example.ts",
89
- importLine: `import { css } from "styled-system/css";`,
90
- files: map[string]string{
91
- "frontend/package.json": `{}`,
92
- "frontend/tsconfig.json": `{"compilerOptions":{"paths":{"styled-system/*":["./styled-system/*"]}}`,
93
- },
94
- wantCode: 3,
95
- wantFinding: "styled-system",
96
- },
97
- }
98
-
99
- for _, test := range tests {
100
- t.Run(test.name, func(t *testing.T) {
101
- repo := t.TempDir()
102
- writePackageExistenceFile(t, repo, "Cargo.toml", "[package]\nname = \"fixture\"\nversion = \"0.1.0\"\n")
103
- writePackageExistenceFile(t, repo, test.sourcePath, "// baseline\n")
104
- for path, content := range test.files {
105
- writePackageExistenceFile(t, repo, path, content+"\n")
106
- }
107
- gitPackageExistence(t, repo, "init", "-q")
108
- gitPackageExistence(t, repo, "config", "user.email", "test@example.com")
109
- gitPackageExistence(t, repo, "config", "user.name", "Test")
110
- gitPackageExistence(t, repo, "add", ".")
111
- gitPackageExistence(t, repo, "commit", "-qm", "baseline")
112
- if err := os.MkdirAll(featureDir(repo, "feat"), 0o755); err != nil {
113
- t.Fatal(err)
114
- }
115
- writePackageExistenceFile(t, repo, test.sourcePath, "// baseline\n"+test.importLine+"\n")
116
- t.Chdir(filepath.Join(repo, test.workingDir))
117
-
118
- var stdout, stderr bytes.Buffer
119
- gotCode := PackageExistence(repo, []string{"feat"}, &stdout, &stderr)
120
- if gotCode != test.wantCode {
121
- t.Fatalf("code = %d, want %d\nstdout: %s\nstderr: %s", gotCode, test.wantCode, stdout.String(), stderr.String())
122
- }
123
- if test.wantFinding != "" && !strings.Contains(stderr.String(), test.wantFinding) {
124
- t.Fatalf("stderr does not name %q: %s", test.wantFinding, stderr.String())
125
- }
126
- })
127
- }
128
- }
129
-
130
- func TestPackageExistenceCSSImports(t *testing.T) {
131
- tests := []struct {
132
- name string
133
- imports string
134
- manifest string
135
- wantCode int
136
- wantFinding string
137
- }{
138
- {
139
- name: "declared multi import ignores comment prefix",
140
- imports: "/* @import \"comment-only\"; */ @import \"tailwindcss\"; @import \"./theme.css\";",
141
- manifest: `{"dependencies":{"tailwindcss":"^4.1.0"}}`,
142
- },
143
- {
144
- name: "undeclared url import",
145
- imports: `@import url("missing-theme");`,
146
- manifest: `{}`,
147
- wantCode: 3,
148
- wantFinding: "missing-theme",
149
- },
150
- {
151
- name: "remote imports ignored",
152
- imports: `@import url("https://cdn.example.invalid/theme.css"); @import "//cdn.example.invalid/other.css";`,
153
- manifest: `{}`,
154
- },
155
- }
156
-
157
- for _, test := range tests {
158
- t.Run(test.name, func(t *testing.T) {
159
- repo := t.TempDir()
160
- writePackageExistenceFile(t, repo, "package.json", test.manifest+"\n")
161
- writePackageExistenceFile(t, repo, "styles/app.css", "/* baseline */\n")
162
- gitPackageExistence(t, repo, "init", "-q")
163
- gitPackageExistence(t, repo, "config", "user.email", "test@example.com")
164
- gitPackageExistence(t, repo, "config", "user.name", "Test")
165
- gitPackageExistence(t, repo, "add", ".")
166
- gitPackageExistence(t, repo, "commit", "-qm", "baseline")
167
- if err := os.MkdirAll(featureDir(repo, "feat"), 0o755); err != nil {
168
- t.Fatal(err)
169
- }
170
- writePackageExistenceFile(t, repo, "styles/app.css", "/* baseline */\n"+test.imports+"\n")
171
- t.Chdir(repo)
172
-
173
- var stdout, stderr bytes.Buffer
174
- gotCode := PackageExistence(repo, []string{"feat"}, &stdout, &stderr)
175
- if gotCode != test.wantCode {
176
- t.Fatalf("code = %d, want %d\nstdout: %s\nstderr: %s", gotCode, test.wantCode, stdout.String(), stderr.String())
177
- }
178
- if test.wantFinding != "" && !strings.Contains(stderr.String(), test.wantFinding) {
179
- t.Fatalf("stderr does not name %q: %s", test.wantFinding, stderr.String())
180
- }
181
- })
182
- }
183
- }
184
-
185
- func TestPackageExistenceCSSResolvesBareSiblingPath(t *testing.T) {
186
- repo := t.TempDir()
187
- writePackageExistenceFile(t, repo, "package.json", "{}\n")
188
- writePackageExistenceFile(t, repo, "styles/app.css", "/* baseline */\n")
189
- writePackageExistenceFile(t, repo, "styles/theme.css", ":root { color-scheme: dark; }\n")
190
- gitPackageExistence(t, repo, "init", "-q")
191
- gitPackageExistence(t, repo, "config", "user.email", "test@example.com")
192
- gitPackageExistence(t, repo, "config", "user.name", "Test")
193
- gitPackageExistence(t, repo, "add", ".")
194
- gitPackageExistence(t, repo, "commit", "-qm", "baseline")
195
- if err := os.MkdirAll(featureDir(repo, "feat"), 0o755); err != nil {
196
- t.Fatal(err)
197
- }
198
- writePackageExistenceFile(t, repo, "styles/app.css", "/* baseline */\n@import \"theme.css\";\n")
199
- t.Chdir(repo)
200
-
201
- var stdout, stderr bytes.Buffer
202
- if code := PackageExistence(repo, []string{"feat"}, &stdout, &stderr); code != 0 {
203
- t.Fatalf("code = %d, want 0\nstdout: %s\nstderr: %s", code, stdout.String(), stderr.String())
204
- }
205
- }
206
-
207
- func TestPackageExistenceCSSHonorsBaselineCommentContext(t *testing.T) {
208
- repo := t.TempDir()
209
- baseline := "/* comment opened on an unchanged line\nunchanged content\n*/\n"
210
- current := "/* comment opened on an unchanged line\n@import \"comment-only-package\";\nunchanged content\n*/\n"
211
- writePackageExistenceFile(t, repo, "package.json", "{}\n")
212
- writePackageExistenceFile(t, repo, "styles/app.css", baseline)
213
- gitPackageExistence(t, repo, "init", "-q")
214
- gitPackageExistence(t, repo, "config", "user.email", "test@example.com")
215
- gitPackageExistence(t, repo, "config", "user.name", "Test")
216
- gitPackageExistence(t, repo, "add", ".")
217
- gitPackageExistence(t, repo, "commit", "-qm", "baseline")
218
- if err := os.MkdirAll(featureDir(repo, "feat"), 0o755); err != nil {
219
- t.Fatal(err)
220
- }
221
- writePackageExistenceFile(t, repo, "styles/app.css", current)
222
- t.Chdir(repo)
223
-
224
- var stdout, stderr bytes.Buffer
225
- if code := PackageExistence(repo, []string{"feat"}, &stdout, &stderr); code != 0 {
226
- t.Fatalf("code = %d, want 0\nstdout: %s\nstderr: %s", code, stdout.String(), stderr.String())
227
- }
228
- }
229
-
230
- func TestPackageExistenceScansUntrackedSourceFiles(t *testing.T) {
231
- repo := t.TempDir()
232
- writePackageExistenceFile(t, repo, "package.json", "{}\n")
233
- gitPackageExistence(t, repo, "init", "-q")
234
- gitPackageExistence(t, repo, "config", "user.email", "test@example.com")
235
- gitPackageExistence(t, repo, "config", "user.name", "Test")
236
- gitPackageExistence(t, repo, "add", ".")
237
- gitPackageExistence(t, repo, "commit", "-qm", "baseline")
238
- if err := os.MkdirAll(featureDir(repo, "feat"), 0o755); err != nil {
239
- t.Fatal(err)
240
- }
241
- writePackageExistenceFile(t, repo, "src/new.ts", `import missing from "untracked-missing-package";`+"\n")
242
- t.Chdir(repo)
243
-
244
- var stdout, stderr bytes.Buffer
245
- gotCode := PackageExistence(repo, []string{"feat"}, &stdout, &stderr)
246
- if gotCode != 3 {
247
- t.Fatalf("code = %d, want 3\nstdout: %s\nstderr: %s", gotCode, stdout.String(), stderr.String())
248
- }
249
- if !strings.Contains(stderr.String(), "untracked-missing-package") {
250
- t.Fatalf("stderr does not name untracked dependency: %s", stderr.String())
251
- }
252
- }
253
-
254
- func TestPackageExistenceScansUntrackedFilesAddedAfterSliceSnapshot(t *testing.T) {
255
- repo := newGitRepo(t)
256
- root := workspace(t, "feat")
257
- writePackageExistenceFile(t, repo, "package.json", "{}\n")
258
- commitAll(t, repo, "manifest")
259
- writeWrightAllowlist(t, root, "feat", "src/new.ts")
260
- if code, out := runReconcile(t, root, "snapshot", "feat"); code != 0 {
261
- t.Fatalf("snapshot = %d, want 0\n%s", code, out)
262
- }
263
- writePackageExistenceFile(t, repo, "src/new.ts", `import missing from "slice-untracked-package";`+"\n")
264
- if code, out := runReconcile(t, root, "check", "feat"); code != 0 {
265
- t.Fatalf("reconcile check = %d, want 0\n%s", code, out)
266
- }
267
-
268
- var stdout, stderr bytes.Buffer
269
- code := PackageExistence(root, []string{"feat"}, &stdout, &stderr)
270
- if code != 3 {
271
- t.Fatalf("code = %d, want 3\nstdout: %s\nstderr: %s", code, stdout.String(), stderr.String())
272
- }
273
- if !strings.Contains(stderr.String(), "slice-untracked-package") {
274
- t.Fatalf("stderr does not name slice dependency: %s", stderr.String())
275
- }
276
- }
277
-
278
- func TestPackageExistenceFailsClosedOnPartialReconcileBaseline(t *testing.T) {
279
- newGitRepo(t)
280
- root := workspace(t, "feat")
281
- writeFile(t, filepath.Join(featureDir(root, "feat"), reconcileBaseName), "deadbeef\n")
282
-
283
- var stdout, stderr bytes.Buffer
284
- code := PackageExistence(root, []string{"feat"}, &stdout, &stderr)
285
- if code != 2 {
286
- t.Fatalf("code = %d, want 2\nstdout: %s\nstderr: %s", code, stdout.String(), stderr.String())
287
- }
288
- if !strings.Contains(stderr.String(), "partial lifecycle") {
289
- t.Fatalf("missing fail-closed lifecycle diagnostic:\n%s", stderr.String())
290
- }
291
- }
292
-
293
- func TestPackageExistenceNonJavaScriptWorkspaces(t *testing.T) {
294
- tests := []struct {
295
- name string
296
- sourcePath string
297
- addedSource string
298
- files map[string]string
299
- wantCode int
300
- wantFinding string
301
- }{
302
- {
303
- name: "go import block rejects undeclared module",
304
- sourcePath: "main.go",
305
- addedSource: "package main\n\nimport (\n\t\"definitely.invalid/missing\"\n)\n",
306
- files: map[string]string{"go.mod": "module example.com/app\n\ngo 1.26\n"},
307
- wantCode: 3,
308
- wantFinding: "definitely.invalid/missing",
309
- },
310
- {
311
- name: "go modules do not collide by hostname",
312
- sourcePath: "main.go",
313
- addedSource: "package main\n\nimport \"github.com/evil/missing\"\n",
314
- files: map[string]string{"go.mod": "module example.com/app\n\ngo 1.26\n\nrequire github.com/good/pkg v1.0.0\n"},
315
- wantCode: 3,
316
- wantFinding: "github.com/evil/missing",
317
- },
318
- {
319
- name: "nested go module declaration",
320
- sourcePath: "backend/main.go",
321
- addedSource: "package main\n\nimport \"github.com/good/pkg/sub\"\n",
322
- files: map[string]string{
323
- "Cargo.toml": "[package]\nname = \"root\"\nversion = \"0.1.0\"\n",
324
- "backend/go.mod": "module example.com/backend\n\ngo 1.26\n\nrequire github.com/good/pkg v1.0.0\n",
325
- },
326
- },
327
- {
328
- name: "go standard library subpackage",
329
- sourcePath: "main.go",
330
- addedSource: "package main\n\nimport \"encoding/json\"\n",
331
- files: map[string]string{"go.mod": "module example.com/app\n\ngo 1.26\n"},
332
- },
333
- {
334
- name: "go standard library typo remains undeclared",
335
- sourcePath: "main.go",
336
- addedSource: "package main\n\nimport \"encoding/josn\"\n",
337
- files: map[string]string{"go.mod": "module example.com/app\n\ngo 1.26\n"},
338
- wantCode: 3,
339
- wantFinding: "encoding/josn",
340
- },
341
- {
342
- name: "malformed go manifest does not declare import",
343
- sourcePath: "main.go",
344
- addedSource: "package main\n\nimport \"github.com/missing/pkg\"\n",
345
- files: map[string]string{"go.mod": "module example.com/app\n\ngo 1.26\n\nrequire (\n github.com/missing/pkg v1.0.0\n"},
346
- wantCode: 3,
347
- wantFinding: "github.com/missing/pkg",
348
- },
349
- {
350
- name: "rust rejects undeclared crate",
351
- sourcePath: "src/main.rs",
352
- addedSource: "use definitely_missing_crate::Thing;\n",
353
- files: map[string]string{"Cargo.toml": "[package]\nname = \"fixture\"\nversion = \"0.1.0\"\n\n[dependencies]\n"},
354
- wantCode: 3,
355
- wantFinding: "definitely_missing_crate",
356
- },
357
- {
358
- name: "nested rust dependency and hyphen normalization",
359
- sourcePath: "crates/app/src/main.rs",
360
- addedSource: "use serde_json::Value;\n",
361
- files: map[string]string{
362
- "Cargo.toml": "[workspace]\nmembers = [\"crates/app\"]\n",
363
- "crates/app/Cargo.toml": "[package]\nname = \"app\"\nversion = \"0.1.0\"\n\n[dependencies.serde-json]\nversion = \"1\"\n",
364
- },
365
- },
366
- {
367
- name: "python dotted import uses top-level module",
368
- sourcePath: "app.py",
369
- addedSource: "from requests.sessions import Session\n",
370
- files: map[string]string{"requirements.txt": "requests==2.32.0\n"},
371
- },
372
- {
373
- name: "python standard library module",
374
- sourcePath: "app.py",
375
- addedSource: "import asyncio\n",
376
- files: map[string]string{"requirements.txt": "requests==2.32.0\n"},
377
- },
378
- {
379
- name: "nested python manifest declaration",
380
- sourcePath: "backend/app.py",
381
- addedSource: "import requests\n",
382
- files: map[string]string{
383
- "Cargo.toml": "[package]\nname = \"root\"\nversion = \"0.1.0\"\n",
384
- "backend/requirements.txt": "requests==2.32.0\n",
385
- },
386
- },
387
- {
388
- name: "pyproject dependency declaration",
389
- sourcePath: "service/app.py",
390
- addedSource: "import requests\n",
391
- files: map[string]string{
392
- "service/pyproject.toml": "[project]\ndependencies = [\n \"requests[socks]>=2.32\",\n]\n",
393
- },
394
- },
395
- {
396
- name: "pipfile dependency declaration",
397
- sourcePath: "service/app.py",
398
- addedSource: "import requests\n",
399
- files: map[string]string{
400
- "service/Pipfile": "[packages]\nrequests = \"*\"\n",
401
- },
402
- },
403
- {
404
- name: "malformed pyproject does not declare import",
405
- sourcePath: "service/app.py",
406
- addedSource: "import definitely_missing\n",
407
- files: map[string]string{
408
- "service/pyproject.toml": "[project]\ndependencies = [\n \"definitely_missing\"\n",
409
- },
410
- wantCode: 3,
411
- wantFinding: "definitely_missing",
412
- },
413
- {
414
- name: "python multiple imports reject undeclared module",
415
- sourcePath: "app.py",
416
- addedSource: "import requests, definitely_missing\n",
417
- files: map[string]string{"requirements.txt": "requests==2.32.0\n"},
418
- wantCode: 3,
419
- wantFinding: "definitely_missing",
420
- },
421
- {
422
- name: "comments and other ecosystems do not declare python imports",
423
- sourcePath: "app.py",
424
- addedSource: "import definitely_missing\n",
425
- files: map[string]string{
426
- "Cargo.toml": "[package]\nname = \"definitely_missing\"\nversion = \"0.1.0\"\n",
427
- "requirements.txt": "# definitely_missing is intentionally absent\n",
428
- },
429
- wantCode: 3,
430
- wantFinding: "definitely_missing",
431
- },
432
- }
433
-
434
- for _, test := range tests {
435
- t.Run(test.name, func(t *testing.T) {
436
- repo := t.TempDir()
437
- for path, content := range test.files {
438
- writePackageExistenceFile(t, repo, path, content)
439
- }
440
- writePackageExistenceFile(t, repo, test.sourcePath, "// baseline\n")
441
- gitPackageExistence(t, repo, "init", "-q")
442
- gitPackageExistence(t, repo, "config", "user.email", "test@example.com")
443
- gitPackageExistence(t, repo, "config", "user.name", "Test")
444
- gitPackageExistence(t, repo, "add", ".")
445
- gitPackageExistence(t, repo, "commit", "-qm", "baseline")
446
- if err := os.MkdirAll(featureDir(repo, "feat"), 0o755); err != nil {
447
- t.Fatal(err)
448
- }
449
- writePackageExistenceFile(t, repo, test.sourcePath, test.addedSource)
450
- t.Chdir(repo)
451
-
452
- var stdout, stderr bytes.Buffer
453
- gotCode := PackageExistence(repo, []string{"feat"}, &stdout, &stderr)
454
- if gotCode != test.wantCode {
455
- t.Fatalf("code = %d, want %d\nstdout: %s\nstderr: %s", gotCode, test.wantCode, stdout.String(), stderr.String())
456
- }
457
- if test.wantFinding != "" && !strings.Contains(stderr.String(), test.wantFinding) {
458
- t.Fatalf("stderr does not name %q: %s", test.wantFinding, stderr.String())
459
- }
460
- })
461
- }
462
- }
463
-
464
- func TestPackageExistenceGenericResolverFailsClosed(t *testing.T) {
465
- repo := t.TempDir()
466
- writePackageExistenceFile(t, repo, "package.json", `{"dependencies":{"definitely-missing-package":"1.0.0"}}`)
467
- writePackageExistenceFile(t, repo, "src/example.custom", "// baseline\n")
468
- gitPackageExistence(t, repo, "init", "-q")
469
- gitPackageExistence(t, repo, "config", "user.email", "test@example.com")
470
- gitPackageExistence(t, repo, "config", "user.name", "Test")
471
- gitPackageExistence(t, repo, "add", ".")
472
- gitPackageExistence(t, repo, "commit", "-qm", "baseline")
473
- if err := os.MkdirAll(featureDir(repo, "feat"), 0o755); err != nil {
474
- t.Fatal(err)
475
- }
476
- writePackageExistenceFile(t, repo, "src/example.custom", "import \"definitely-missing-package\"\n")
477
- t.Chdir(repo)
478
-
479
- var stdout, stderr bytes.Buffer
480
- gotCode := PackageExistence(repo, []string{"feat"}, &stdout, &stderr)
481
- if gotCode != 3 {
482
- t.Fatalf("code = %d, want 3\nstdout: %s\nstderr: %s", gotCode, stdout.String(), stderr.String())
483
- }
484
- if !strings.Contains(stderr.String(), "definitely-missing-package") {
485
- t.Fatalf("stderr does not name unresolved import: %s", stderr.String())
486
- }
487
- }
488
-
489
- func writePackageExistenceFile(t *testing.T, root, path, content string) {
490
- t.Helper()
491
- fullPath := filepath.Join(root, filepath.FromSlash(path))
492
- if err := os.MkdirAll(filepath.Dir(fullPath), 0o755); err != nil {
493
- t.Fatal(err)
494
- }
495
- if err := os.WriteFile(fullPath, []byte(content), 0o644); err != nil {
496
- t.Fatal(err)
497
- }
498
- }
499
-
500
- func gitPackageExistence(t *testing.T, root string, args ...string) {
501
- t.Helper()
502
- cmd := exec.Command("git", append([]string{"-C", root}, args...)...)
503
- if out, err := cmd.CombinedOutput(); err != nil {
504
- t.Fatalf("git %s: %v\n%s", strings.Join(args, " "), err, out)
505
- }
506
- }
@@ -1,2 +0,0 @@
1
- exit 0
2
- package-existence: OK: every new third-party import is declared in a manifest.
@@ -1,2 +0,0 @@
1
- exit 0
2
- package-existence: OK: every new third-party import is declared in a manifest.
@@ -1,64 +0,0 @@
1
- package main_test
2
-
3
- import (
4
- "testing"
5
- )
6
-
7
- // TestParityPackageExistence sets up a git repo with a committed manifest, then a
8
- // working-tree diff that adds a new require(), and checks the command's stdout +
9
- // exit against the golden snapshot. Read-only w.r.t. git.
10
- func TestParityPackageExistence(t *testing.T) {
11
- requireGit(t)
12
-
13
- // newRepo commits a package.json declaring lodash plus an empty source file.
14
- newRepo := func(t *testing.T, manifest bool) string {
15
- t.Helper()
16
- work := t.TempDir()
17
- initGitRepo(t, work)
18
- if manifest {
19
- writeFile(t, work, "package.json", "{\n \"dependencies\": { \"lodash\": \"^4.0.0\" }\n}\n")
20
- }
21
- writeFile(t, work, "src/app.js", "// app\n")
22
- gitCommitAll(t, work, "baseline")
23
- makeFeatureDir(t, work, "feat")
24
- return work
25
- }
26
- runCase := func(t *testing.T, work string, goArgs ...string) {
27
- c := parityCase{
28
- workdir: work,
29
- env: libRootEnv(work),
30
- goArgs: append([]string{"package-existence"}, goArgs...),
31
- }
32
- c.assertEqual(t)
33
- }
34
-
35
- t.Run("declared", func(t *testing.T) {
36
- work := newRepo(t, true)
37
- writeFile(t, work, "src/app.js", "// app\nconst _ = require('lodash');\n")
38
- runCase(t, work, "feat")
39
- })
40
- t.Run("default-import-subpath", func(t *testing.T) {
41
- work := newRepo(t, true)
42
- writeFile(t, work, "src/app.js", "// app\nimport debounce from 'lodash/debounce';\n")
43
- runCase(t, work, "feat")
44
- })
45
- t.Run("undeclared", func(t *testing.T) {
46
- work := newRepo(t, true)
47
- writeFile(t, work, "src/app.js", "// app\nconst g = require('ghostpkg');\n")
48
- runCase(t, work, "feat")
49
- })
50
- t.Run("no-manifest", func(t *testing.T) {
51
- work := newRepo(t, false)
52
- writeFile(t, work, "src/app.js", "// app\nconst g = require('ghostpkg');\n")
53
- runCase(t, work, "feat")
54
- })
55
- t.Run("ghost-workspace", func(t *testing.T) {
56
- runCase(t, newRepo(t, true), "ghost")
57
- })
58
- t.Run("not-git", func(t *testing.T) {
59
- work := t.TempDir()
60
- makeFeatureDir(t, work, "feat")
61
- writeFile(t, work, "package.json", "{ \"dependencies\": { \"lodash\": \"^4.0.0\" } }\n")
62
- runCase(t, work, "feat")
63
- })
64
- }