devrites 3.2.5 → 3.2.6
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/CHANGELOG.md +6 -0
- package/README.md +1 -1
- package/docs/cli.md +0 -1
- package/docs/command-map.md +1 -1
- package/docs/orchestration.md +1 -2
- package/engine/commands.go +1 -1
- package/engine/internal/lib/reconcile.go +1 -1
- package/engine/internal/lib/reconcile_test.go +4 -12
- package/engine/internal/lib/util.go +2 -2
- package/engine/main.go +0 -3
- package/engine/root_routing_test.go +0 -1
- package/pack/.claude/skills/rite-build/reference/forge.md +2 -2
- package/pack/.claude/skills/rite-build/reference/phase-contract.md +11 -20
- package/pack/.claude/skills/rite-build/reference/wright-dispatch.md +2 -5
- package/pack/.claude/skills/rite-prove/reference/acceptance-proof.md +0 -1
- package/pack/.claude/skills/rite-seal/reference/final-evidence.md +0 -1
- package/pack/generated/claude/skills/rite-build/reference/forge.md +2 -2
- package/pack/generated/claude/skills/rite-build/reference/phase-contract.md +11 -20
- package/pack/generated/claude/skills/rite-build/reference/wright-dispatch.md +2 -5
- package/pack/generated/claude/skills/rite-prove/reference/acceptance-proof.md +0 -1
- package/pack/generated/claude/skills/rite-seal/reference/final-evidence.md +0 -1
- package/pack/generated/codex/skills/rite-build/reference/forge.md +2 -2
- package/pack/generated/codex/skills/rite-build/reference/phase-contract.md +11 -20
- package/pack/generated/codex/skills/rite-build/reference/wright-dispatch.md +2 -5
- package/pack/generated/codex/skills/rite-prove/reference/acceptance-proof.md +0 -1
- package/pack/generated/codex/skills/rite-seal/reference/final-evidence.md +0 -1
- package/package.json +1 -1
- package/scripts/build-release-tarball.sh +1 -0
- package/scripts/validate.sh +0 -1
- package/engine/internal/lib/packageexistence.go +0 -968
- package/engine/internal/lib/packageexistence_test.go +0 -506
- package/engine/testdata/golden/TestParityPackageExistence/declared.golden +0 -2
- package/engine/testdata/golden/TestParityPackageExistence/default-import-subpath.golden +0 -2
- 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,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
|
-
}
|