@antelopejs/dms 0.16.2 → 0.18.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/CHANGELOG.md +46 -0
- package/README.md +4 -8
- package/bin/control/base.mjs +3 -3
- package/bin/control/commands-verify.mjs +1 -29
- package/bin/control/renderer.test.mjs +0 -57
- package/bin/control/verify/decode.mjs +0 -2
- package/bin/control/verify/decode.test.mjs +1 -1
- package/bin/control/verify/doors.test.mjs +78 -33
- package/bin/control/verify/judgement.mjs +0 -1
- package/bin/control/verify/map.mjs +4 -51
- package/bin/control/verify/skill.mjs +4 -6
- package/bin/control/verify/types.mjs +3 -4
- package/bin/control/verify/verdict.mjs +8 -51
- package/bin/dms-control.mjs +0 -1
- package/dist/config/internal/state.d.ts +2 -2
- package/dist/config/internal/state.js.map +1 -1
- package/dist/utils/sign-in-country.d.ts +0 -1
- package/dist/utils/sign-in-country.js +7 -17
- package/dist/utils/sign-in-country.js.map +1 -1
- package/frontend-vue/layers/dms-layout/app/composables/layout/usePageHeaderActions.ts +1 -1
- package/frontend-vue/package.json +1 -1
- package/frontend-vue/scripts/check-autoimport-boundaries.mjs +19 -3
- package/frontend-vue/scripts/test-autoimport-boundaries.mjs +19 -0
- package/package.json +5 -5
- package/bin/control/verify/seeded.mjs +0 -217
- package/data/NOTICE.md +0 -12
- package/data/dbip-country-lite.mmdb +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,51 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v0.18.0
|
|
4
|
+
|
|
5
|
+
[compare changes](https://github.com/AntelopeJS/dms/compare/v0.17.0...v0.18.0)
|
|
6
|
+
|
|
7
|
+
### 🚀 Enhancements
|
|
8
|
+
|
|
9
|
+
- **dms:** Read the sign-in country without a bundled database ([#776](https://github.com/AntelopeJS/dms/pull/776))
|
|
10
|
+
|
|
11
|
+
### 🩹 Fixes
|
|
12
|
+
|
|
13
|
+
- **dms-layout:** Re-export usePageHeaderActions by relative path so unimport scans it ([#777](https://github.com/AntelopeJS/dms/pull/777))
|
|
14
|
+
|
|
15
|
+
### 📖 Documentation
|
|
16
|
+
|
|
17
|
+
- **dms:** Explain what the AntelopeJS agent plugin unlocks, External database included ([#773](https://github.com/AntelopeJS/dms/pull/773))
|
|
18
|
+
- **dms:** Mark the ResourceForm mode and the source tables in the docs examples ([#780](https://github.com/AntelopeJS/dms/pull/780))
|
|
19
|
+
|
|
20
|
+
### 📦 Build
|
|
21
|
+
|
|
22
|
+
- **deps:** Bump @antelopejs packages to the 2026-11-06 releases ([#781](https://github.com/AntelopeJS/dms/pull/781))
|
|
23
|
+
|
|
24
|
+
### 🏡 Chore
|
|
25
|
+
|
|
26
|
+
- **dms:** Commit the module locks the test runs record ([#775](https://github.com/AntelopeJS/dms/pull/775))
|
|
27
|
+
- **ci:** Remove the weekly mutation caller ([#778](https://github.com/AntelopeJS/dms/pull/778))
|
|
28
|
+
|
|
29
|
+
### 🤖 CI
|
|
30
|
+
|
|
31
|
+
- Call ci-node.yml@v1 ([#779](https://github.com/AntelopeJS/dms/pull/779))
|
|
32
|
+
|
|
33
|
+
### ❤️ Contributors
|
|
34
|
+
|
|
35
|
+
- Antony Rizzitelli ([@Upd4ting](http://github.com/Upd4ting))
|
|
36
|
+
|
|
37
|
+
## v0.17.0
|
|
38
|
+
|
|
39
|
+
[compare changes](https://github.com/AntelopeJS/dms/compare/v0.16.2...v0.17.0)
|
|
40
|
+
|
|
41
|
+
### 🏡 Chore
|
|
42
|
+
|
|
43
|
+
- **dms-control:** Remove seeded runs and containment journeys from verification ([#774](https://github.com/AntelopeJS/dms/pull/774))
|
|
44
|
+
|
|
45
|
+
### ❤️ Contributors
|
|
46
|
+
|
|
47
|
+
- Antony Rizzitelli ([@Upd4ting](http://github.com/Upd4ting))
|
|
48
|
+
|
|
3
49
|
## v0.16.2
|
|
4
50
|
|
|
5
51
|
[compare changes](https://github.com/AntelopeJS/dms/compare/v0.16.1...v0.16.2)
|
package/README.md
CHANGED
|
@@ -79,15 +79,15 @@ export class SalesPage extends PageController("sales", {
|
|
|
79
79
|
|
|
80
80
|
| Package | Supported | Tested with |
|
|
81
81
|
| ------------------------------------------- | ------------------ | ----------- |
|
|
82
|
-
| `@antelopejs/core` | `>=1.
|
|
82
|
+
| `@antelopejs/core` | `>=1.21.0 <2` | `1.21.0` |
|
|
83
83
|
| `@antelopejs/interface-api` | `>=0.2.0 <1.0.0` | `0.2.0` |
|
|
84
84
|
| `@antelopejs/interface-api-util` | `>=0.3.0 <1.0.0` | `0.3.0` |
|
|
85
85
|
| `@antelopejs/interface-core` | `>=0.4.0 <1.0.0` | `0.4.0` |
|
|
86
86
|
| `@antelopejs/interface-data-api` | `>=0.6.0 <1.0.0` | `0.6.0` |
|
|
87
|
-
| `@antelopejs/interface-database` | `>=0.3.0 <1.0.0` | `0.3.
|
|
88
|
-
| `@antelopejs/interface-database-decorators` | `>=0.2.0 <1.0.0` | `0.2.
|
|
87
|
+
| `@antelopejs/interface-database` | `>=0.3.0 <1.0.0` | `0.3.1` |
|
|
88
|
+
| `@antelopejs/interface-database-decorators` | `>=0.2.0 <1.0.0` | `0.2.1` |
|
|
89
89
|
| `@antelopejs/interface-dms` | `>=0.12.0 <0.13.0` | `0.12.0` |
|
|
90
|
-
| `@antelopejs/interface-dms-automation` | `>=0.6.0 <1.0.0` | `0.6.
|
|
90
|
+
| `@antelopejs/interface-dms-automation` | `>=0.6.0 <1.0.0` | `0.6.1` |
|
|
91
91
|
| `@antelopejs/interface-email` | `>=0.0.6 <1.0.0` | `0.0.6` |
|
|
92
92
|
| `@antelopejs/interface-file-storage` | `>=0.1.4 <1.0.0` | `0.1.4` |
|
|
93
93
|
| `@antelopejs/interface-redis` | `>=0.0.9 <1.0.0` | `0.0.9` |
|
|
@@ -114,10 +114,6 @@ The package ships the core Claude Code skills (`dms`, `dms-auth`, `dms-best-prac
|
|
|
114
114
|
|
|
115
115
|
This package lives in the [`AntelopeJS/dms`](https://github.com/AntelopeJS/dms) pnpm workspace, next to `@antelopejs/interface-dms`. Read [CONTRIBUTING.md](https://github.com/AntelopeJS/dms/blob/main/CONTRIBUTING.md) and the [repository README](https://github.com/AntelopeJS/dms#contributing) for the development commands.
|
|
116
116
|
|
|
117
|
-
## Third-party data
|
|
118
|
-
|
|
119
|
-
The package ships the DB-IP IP to Country Lite database (`data/`) to tell which country a sign-in came from. It is licensed under the [Creative Commons Attribution 4.0 International License](https://creativecommons.org/licenses/by/4.0/): IP Geolocation by [DB-IP](https://db-ip.com). `pnpm country-database` downloads the current edition; packing the package for a release runs it.
|
|
120
|
-
|
|
121
117
|
## License
|
|
122
118
|
|
|
123
119
|
[Apache-2.0](./LICENSE)
|
package/bin/control/base.mjs
CHANGED
|
@@ -43,9 +43,9 @@ function baseCheckout(state, skill) {
|
|
|
43
43
|
|
|
44
44
|
/**
|
|
45
45
|
* Runs `action` while the backend is the base version's, on the run's
|
|
46
|
-
* current database, then starts the run's own backend again.
|
|
47
|
-
*
|
|
48
|
-
*
|
|
46
|
+
* current database, then starts the run's own backend again. An upgrade
|
|
47
|
+
* journey writes data with the base version inside `action` and reads it
|
|
48
|
+
* back with the head version after it.
|
|
49
49
|
*
|
|
50
50
|
* @param {import("./run-state.mjs").RunState} state The run
|
|
51
51
|
* @param {import("./verify/skill.mjs").Skill} skill The skill, whose `prepare` builds the base
|
|
@@ -16,7 +16,6 @@ import {
|
|
|
16
16
|
readStatus,
|
|
17
17
|
setStatus,
|
|
18
18
|
} from "./verify/publish.mjs";
|
|
19
|
-
import { runSeededBreaks } from "./verify/seeded.mjs";
|
|
20
19
|
import { journeysFile, loadSkill } from "./verify/skill.mjs";
|
|
21
20
|
import { renderVerdict } from "./verify/verdict.mjs";
|
|
22
21
|
|
|
@@ -86,31 +85,6 @@ async function commandJourney(dir, feature, ids, values) {
|
|
|
86
85
|
return results.every((result) => result.status === "PASS") ? 0 : 1;
|
|
87
86
|
}
|
|
88
87
|
|
|
89
|
-
/**
|
|
90
|
-
* @param {string[]} features The features whose seeded breaks run
|
|
91
|
-
* @param {Values} values The options of the command line
|
|
92
|
-
* @returns {Promise<number>} The exit code
|
|
93
|
-
*/
|
|
94
|
-
async function commandSeeded(features, values) {
|
|
95
|
-
const skill = await skillOf(values);
|
|
96
|
-
const outcomes = runSeededBreaks(skill, features);
|
|
97
|
-
for (const outcome of outcomes) {
|
|
98
|
-
print(
|
|
99
|
-
`${outcome.caught ? "CAUGHT" : "MISSED"} ${outcome.feature} by ${outcome.caughtBy.join(", ") || "no journey"} (evidence ${outcome.run}/evidence/${outcome.feature}, control ${outcome.controlRun}/evidence/${outcome.feature})`,
|
|
100
|
-
);
|
|
101
|
-
if (outcome.failingInControl.length > 0)
|
|
102
|
-
print(
|
|
103
|
-
` already failing without the patch, not counted: ${outcome.failingInControl.join(", ")}`,
|
|
104
|
-
);
|
|
105
|
-
if (outcome.harnessErrors.length > 0)
|
|
106
|
-
print(
|
|
107
|
-
` failed without a failing check (harness error), not counted: ${outcome.harnessErrors.join(", ")}`,
|
|
108
|
-
);
|
|
109
|
-
if (!outcome.caught) print(outcome.output.replace(/^/gm, " "));
|
|
110
|
-
}
|
|
111
|
-
return outcomes.every((outcome) => outcome.caught) ? 0 : 1;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
88
|
/**
|
|
115
89
|
* @param {string | undefined} patch A patch file, or none for the diff
|
|
116
90
|
* @param {Values} values The options of the command line
|
|
@@ -201,7 +175,7 @@ async function commandJudge(command, dir, values) {
|
|
|
201
175
|
|
|
202
176
|
/**
|
|
203
177
|
* Runs a command of the verification harness: `map`, `plan`, `journey`,
|
|
204
|
-
* `
|
|
178
|
+
* `doors`, `formal`, `verdict` and `publish`.
|
|
205
179
|
*
|
|
206
180
|
* @param {string} command The command
|
|
207
181
|
* @param {string[]} rest Its positional arguments
|
|
@@ -216,8 +190,6 @@ export function runVerifyCommand(command, rest, values) {
|
|
|
216
190
|
return commandPlan(values);
|
|
217
191
|
case "journey":
|
|
218
192
|
return commandJourney(rest[0], rest[1], rest.slice(2), values);
|
|
219
|
-
case "seeded":
|
|
220
|
-
return commandSeeded(rest, values);
|
|
221
193
|
case "doors":
|
|
222
194
|
return commandDoors(rest[0], values);
|
|
223
195
|
case "formal":
|
|
@@ -15,7 +15,6 @@ import { targetOf } from "./commands-stack.mjs";
|
|
|
15
15
|
import { git } from "./git.mjs";
|
|
16
16
|
import { resolveRenderer } from "./renderer.mjs";
|
|
17
17
|
import { decodeSkillConfig } from "./verify/decode.mjs";
|
|
18
|
-
import { upArguments } from "./verify/seeded.mjs";
|
|
19
18
|
import { cliPaths, layOutCopy } from "./stack.mjs";
|
|
20
19
|
|
|
21
20
|
/**
|
|
@@ -266,59 +265,3 @@ await test("a run with a renderer has no base backend", async () => {
|
|
|
266
265
|
/no base backend/,
|
|
267
266
|
);
|
|
268
267
|
});
|
|
269
|
-
|
|
270
|
-
/**
|
|
271
|
-
* @param {Partial<import("./verify/skill.mjs").SkillConfig>} config What differs from a skill with a playground in its repository
|
|
272
|
-
* @returns {import("./verify/skill.mjs").Skill} The skill
|
|
273
|
-
*/
|
|
274
|
-
function skillWith(config) {
|
|
275
|
-
return {
|
|
276
|
-
dir: "/repo/.agents/skills/verify-x",
|
|
277
|
-
name: "verify-x",
|
|
278
|
-
repo: "/repo",
|
|
279
|
-
config: { ...decodeSkillConfig(CONFIG, "verify.config.ts"), ...config },
|
|
280
|
-
featuresDir: "",
|
|
281
|
-
journeysDir: "",
|
|
282
|
-
seededBreaksDir: "",
|
|
283
|
-
seededDoorsDir: "",
|
|
284
|
-
};
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
await test("seeded starts each run on the worktree's renderer and an external playground as it is", () => {
|
|
288
|
-
const prepared = {
|
|
289
|
-
worktree: "/work/seeded-auth-1",
|
|
290
|
-
skill: "/work/seeded-auth-1/skill",
|
|
291
|
-
};
|
|
292
|
-
assert.deepEqual(
|
|
293
|
-
upArguments(
|
|
294
|
-
skillWith({ playground: "/dms/playground", renderer: "." }),
|
|
295
|
-
prepared,
|
|
296
|
-
),
|
|
297
|
-
[
|
|
298
|
-
"up",
|
|
299
|
-
"--playground",
|
|
300
|
-
"/dms/playground",
|
|
301
|
-
"--renderer",
|
|
302
|
-
"/work/seeded-auth-1",
|
|
303
|
-
"--skill",
|
|
304
|
-
"/work/seeded-auth-1/skill",
|
|
305
|
-
],
|
|
306
|
-
);
|
|
307
|
-
});
|
|
308
|
-
|
|
309
|
-
await test("seeded without a renderer starts the worktree's own playground", () => {
|
|
310
|
-
const prepared = {
|
|
311
|
-
worktree: "/work/seeded-auth-1",
|
|
312
|
-
skill: "/work/seeded-auth-1/skill",
|
|
313
|
-
};
|
|
314
|
-
assert.deepEqual(
|
|
315
|
-
upArguments(skillWith({ playground: "packages/dms/playground" }), prepared),
|
|
316
|
-
[
|
|
317
|
-
"up",
|
|
318
|
-
"--playground",
|
|
319
|
-
"/work/seeded-auth-1/packages/dms/playground",
|
|
320
|
-
"--skill",
|
|
321
|
-
"/work/seeded-auth-1/skill",
|
|
322
|
-
],
|
|
323
|
-
);
|
|
324
|
-
});
|
|
@@ -98,7 +98,6 @@ export const isRawJudgement = objectOf(
|
|
|
98
98
|
location: isString,
|
|
99
99
|
irreversible: isString,
|
|
100
100
|
containment: isString,
|
|
101
|
-
containmentJourney: isString,
|
|
102
101
|
},
|
|
103
102
|
),
|
|
104
103
|
),
|
|
@@ -141,7 +140,6 @@ const isFormalCheck = objectOf(
|
|
|
141
140
|
const isDoorRule = objectOf({
|
|
142
141
|
globs: strings,
|
|
143
142
|
funnels: strings,
|
|
144
|
-
containments: strings,
|
|
145
143
|
why: isString,
|
|
146
144
|
});
|
|
147
145
|
|
|
@@ -124,7 +124,7 @@ const CONFIG = `export default {
|
|
|
124
124
|
userFacing: ["src/**"],
|
|
125
125
|
smoke: "smoke",
|
|
126
126
|
smokeJourney: "onboard",
|
|
127
|
-
doors: { data: { globs: ["src/**"], funnels: [],
|
|
127
|
+
doors: { data: { globs: ["src/**"], funnels: [], why: "rows" }, format: null },
|
|
128
128
|
formal: [],
|
|
129
129
|
};
|
|
130
130
|
`;
|
|
@@ -1,40 +1,85 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import test from "node:test";
|
|
6
|
-
import { flaggedDoors, patchFiles } from "./doors.mjs";
|
|
7
|
-
import { loadSkill } from "./skill.mjs";
|
|
6
|
+
import { doorLine, flaggedDoors, patchFiles } from "./doors.mjs";
|
|
8
7
|
|
|
9
|
-
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
8
|
+
/** @type {import("./skill.mjs").Skill} */
|
|
9
|
+
const skill = {
|
|
10
|
+
dir: "/repo/.agents/skills/verify-x",
|
|
11
|
+
name: "verify-x",
|
|
12
|
+
repo: "/repo",
|
|
13
|
+
featuresDir: "",
|
|
14
|
+
journeysDir: "",
|
|
15
|
+
config: {
|
|
16
|
+
playground: "playground",
|
|
17
|
+
prepare: [],
|
|
18
|
+
userFacing: [],
|
|
19
|
+
notUserFacing: [],
|
|
20
|
+
smoke: "smoke",
|
|
21
|
+
smokeJourney: "",
|
|
22
|
+
formal: [],
|
|
23
|
+
doors: {
|
|
24
|
+
data: { globs: ["src/crons/**"], funnels: [], why: "deletes rows" },
|
|
25
|
+
outbound: {
|
|
26
|
+
globs: ["src/utils/*-mail.ts"],
|
|
27
|
+
funnels: ["src/emails.ts"],
|
|
28
|
+
why: "sends mail",
|
|
29
|
+
},
|
|
30
|
+
publish: null,
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
};
|
|
16
34
|
|
|
17
|
-
|
|
18
|
-
|
|
35
|
+
await test("flaggedDoors names each kind a file hits, with its files, through globs and funnels", () => {
|
|
36
|
+
assert.deepEqual(
|
|
37
|
+
flaggedDoors(skill, [
|
|
38
|
+
"src/crons/purge.ts",
|
|
39
|
+
"src/emails.ts",
|
|
40
|
+
"src/utils/invite-mail.ts",
|
|
41
|
+
"src/pages/home.ts",
|
|
42
|
+
]),
|
|
43
|
+
[
|
|
44
|
+
{ kind: "data", files: ["src/crons/purge.ts"] },
|
|
45
|
+
{
|
|
46
|
+
kind: "outbound",
|
|
47
|
+
files: ["src/emails.ts", "src/utils/invite-mail.ts"],
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
);
|
|
51
|
+
});
|
|
19
52
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}),
|
|
27
|
-
`${kind}.patch no longer applies; refresh it against the current source`,
|
|
28
|
-
);
|
|
29
|
-
});
|
|
53
|
+
await test("flaggedDoors flags nothing for files outside every rule, a null rule included", () => {
|
|
54
|
+
assert.deepEqual(
|
|
55
|
+
flaggedDoors(skill, ["package.json", "src/pages/home.ts"]),
|
|
56
|
+
[],
|
|
57
|
+
);
|
|
58
|
+
});
|
|
30
59
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
60
|
+
await test("patchFiles reads the new paths of a unified diff, not the old ones", (t) => {
|
|
61
|
+
const dir = mkdtempSync(join(tmpdir(), "dms-control-doors-test-"));
|
|
62
|
+
t.after(() => rmSync(dir, { recursive: true, force: true }));
|
|
63
|
+
const patch = join(dir, "change.patch");
|
|
64
|
+
writeFileSync(
|
|
65
|
+
patch,
|
|
66
|
+
[
|
|
67
|
+
"diff --git a/src/old.ts b/src/crons/purge.ts",
|
|
68
|
+
"--- a/src/old.ts",
|
|
69
|
+
"+++ b/src/crons/purge.ts",
|
|
70
|
+
"@@ -1 +1 @@",
|
|
71
|
+
"-a",
|
|
72
|
+
"+b",
|
|
73
|
+
"",
|
|
74
|
+
].join("\n"),
|
|
75
|
+
);
|
|
76
|
+
assert.deepEqual(patchFiles(patch), ["src/crons/purge.ts"]);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
await test("doorLine lists the flagged kinds, and is empty when none is", () => {
|
|
80
|
+
assert.equal(
|
|
81
|
+
doorLine([{ kind: "data" }, { kind: "outbound" }]),
|
|
82
|
+
"One-way-door: data, outbound",
|
|
83
|
+
);
|
|
84
|
+
assert.equal(doorLine([]), "");
|
|
85
|
+
});
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { existsSync, readdirSync } from "node:fs";
|
|
2
2
|
import { join, matchesGlob } from "node:path";
|
|
3
3
|
import { git } from "../git.mjs";
|
|
4
|
-
import { flaggedDoors, patchFiles } from "./doors.mjs";
|
|
5
4
|
import { loadJourneys } from "../journey.mjs";
|
|
6
5
|
import { journeysFile, readFeatures } from "./skill.mjs";
|
|
7
6
|
|
|
@@ -82,20 +81,6 @@ async function featureProblems(skill, feature, tracked) {
|
|
|
82
81
|
problems.push(`${feature.file}: docs path ${doc} does not exist`);
|
|
83
82
|
}
|
|
84
83
|
}
|
|
85
|
-
const patch = join(skill.seededBreaksDir, `${feature.name}.patch`);
|
|
86
|
-
if (!existsSync(patch)) {
|
|
87
|
-
problems.push(
|
|
88
|
-
`${feature.file}: seeded-breaks/${feature.name}.patch is missing`,
|
|
89
|
-
);
|
|
90
|
-
}
|
|
91
|
-
const always = feature.name === skill.config.smoke;
|
|
92
|
-
for (const file of existsSync(patch) && !always ? patchFiles(patch) : []) {
|
|
93
|
-
if (!matches(file, feature.covers)) {
|
|
94
|
-
problems.push(
|
|
95
|
-
`${feature.file}: its seeded break edits ${file}, which its covers globs miss, so plan would not run it`,
|
|
96
|
-
);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
84
|
return problems;
|
|
100
85
|
}
|
|
101
86
|
|
|
@@ -108,11 +93,6 @@ function doorProblems(skill, tracked) {
|
|
|
108
93
|
const problems = [];
|
|
109
94
|
for (const [kind, rule] of Object.entries(skill.config.doors)) {
|
|
110
95
|
if (!rule) continue;
|
|
111
|
-
if (!existsSync(join(skill.seededDoorsDir, `${kind}.patch`))) {
|
|
112
|
-
problems.push(
|
|
113
|
-
`seeded-doors/${kind}.patch is missing for the ${kind} door rule`,
|
|
114
|
-
);
|
|
115
|
-
}
|
|
116
96
|
for (const glob of [...rule.globs, ...rule.funnels]) {
|
|
117
97
|
if (!tracked.some((path) => matchesGlob(path, glob))) {
|
|
118
98
|
problems.push(
|
|
@@ -126,11 +106,9 @@ function doorProblems(skill, tracked) {
|
|
|
126
106
|
|
|
127
107
|
/**
|
|
128
108
|
* Checks the map against the repository: every user-facing file is
|
|
129
|
-
* covered, every feature has journeys, each described in its file,
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
* pages, every feature and door glob
|
|
133
|
-
* matches a tracked file, and every door rule has a seeded door.
|
|
109
|
+
* covered, every feature has journeys, each described in its file,
|
|
110
|
+
* existing docs pages, and every feature and door glob matches a tracked
|
|
111
|
+
* file.
|
|
134
112
|
*
|
|
135
113
|
* @param {import("./skill.mjs").Skill} skill The skill
|
|
136
114
|
* @returns {Promise<string[]>} One line per problem
|
|
@@ -159,33 +137,10 @@ export async function checkMap(skill) {
|
|
|
159
137
|
return [...problems, ...doorProblems(skill, tracked)];
|
|
160
138
|
}
|
|
161
139
|
|
|
162
|
-
const CONTAINED_KINDS = ["data", "format"];
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* The containment journeys, as `feature/journey`, of the Data and Format
|
|
166
|
-
* doors a set of changed files opens: a door row must name one of them,
|
|
167
|
-
* passing in the run.
|
|
168
|
-
*
|
|
169
|
-
* @param {import("./skill.mjs").Skill} skill The skill
|
|
170
|
-
* @param {string[]} changed Repository paths
|
|
171
|
-
* @returns {string[]} The journeys, each once
|
|
172
|
-
*/
|
|
173
|
-
function containmentJourneys(skill, changed) {
|
|
174
|
-
const kinds = flaggedDoors(skill, changed)
|
|
175
|
-
.map((door) => door.kind)
|
|
176
|
-
.filter((kind) => CONTAINED_KINDS.includes(kind));
|
|
177
|
-
return [
|
|
178
|
-
...new Set(
|
|
179
|
-
kinds.flatMap((kind) => skill.config.doors[kind]?.containments ?? []),
|
|
180
|
-
),
|
|
181
|
-
];
|
|
182
|
-
}
|
|
183
|
-
|
|
184
140
|
/**
|
|
185
141
|
* Maps the diff from the merge base of `baseRef` to HEAD onto features:
|
|
186
142
|
* every touched feature plus the smoke feature runs, a user-facing file
|
|
187
|
-
* outside every glob is an unmapped surface
|
|
188
|
-
* journeys that can contain each Data or Format door the diff opens.
|
|
143
|
+
* outside every glob is an unmapped surface.
|
|
189
144
|
*
|
|
190
145
|
* @param {import("./skill.mjs").Skill} skill The skill
|
|
191
146
|
* @param {string} baseRef The base, such as `origin/main`
|
|
@@ -216,7 +171,6 @@ export function planFor(skill, baseRef) {
|
|
|
216
171
|
),
|
|
217
172
|
];
|
|
218
173
|
const smoke = skill.config.smoke;
|
|
219
|
-
const containments = containmentJourneys(skill, changed);
|
|
220
174
|
return {
|
|
221
175
|
baseRef,
|
|
222
176
|
baseSha,
|
|
@@ -225,7 +179,6 @@ export function planFor(skill, baseRef) {
|
|
|
225
179
|
touched,
|
|
226
180
|
unmapped,
|
|
227
181
|
docs,
|
|
228
|
-
containments,
|
|
229
182
|
run: [smoke, ...[...touchedNames].filter((name) => name !== smoke)],
|
|
230
183
|
};
|
|
231
184
|
}
|
|
@@ -10,8 +10,8 @@ const CONFIG_FILE = "verify.config.ts";
|
|
|
10
10
|
const OUTPUT_LIMIT = 64 * 1024 * 1024;
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
* @typedef {{ dir: string, name: string, repo: string, config: SkillConfig, featuresDir: string, journeysDir: string
|
|
14
|
-
* @typedef {{ playground: string, renderer?: string, prepare: string[][], userFacing: string[], notUserFacing: string[], smoke: string, smokeJourney: string, doors: Record<string, { globs: string[], funnels: string[],
|
|
13
|
+
* @typedef {{ dir: string, name: string, repo: string, config: SkillConfig, featuresDir: string, journeysDir: string }} Skill
|
|
14
|
+
* @typedef {{ playground: string, renderer?: string, prepare: string[][], userFacing: string[], notUserFacing: string[], smoke: string, smokeJourney: string, doors: Record<string, { globs: string[], funnels: string[], why: string } | null>, formal: import("./types.mjs").FormalCheck[] }} SkillConfig
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
/**
|
|
@@ -60,8 +60,8 @@ function discover(cwd) {
|
|
|
60
60
|
|
|
61
61
|
/**
|
|
62
62
|
* Loads a verification skill: its `verify.config.ts` (imported with Node's
|
|
63
|
-
* TypeScript type stripping), its feature files and where its journeys
|
|
64
|
-
*
|
|
63
|
+
* TypeScript type stripping), its feature files and where its journeys
|
|
64
|
+
* live. Without `--skill`, the one
|
|
65
65
|
* `.agents/skills/verify-*` folder of the checkout holding `cwd`.
|
|
66
66
|
*
|
|
67
67
|
* @param {string | undefined} option The `--skill` folder
|
|
@@ -86,8 +86,6 @@ export async function loadSkill(option, cwd) {
|
|
|
86
86
|
config,
|
|
87
87
|
featuresDir: join(dir, "features"),
|
|
88
88
|
journeysDir: join(dir, "journeys"),
|
|
89
|
-
seededBreaksDir: join(dir, "seeded-breaks"),
|
|
90
|
-
seededDoorsDir: join(dir, "seeded-doors"),
|
|
91
89
|
};
|
|
92
90
|
}
|
|
93
91
|
|
|
@@ -10,12 +10,12 @@
|
|
|
10
10
|
* @typedef {{ exitCode: number | null, violated: string[], passing: string[] }} Rules
|
|
11
11
|
* @typedef {{ name: string, exitCode: number | null, violated: string[], passing: string[], folder: string, commits?: Commits, behaviour?: BehaviourChange, checkChanged?: boolean, base?: Rules | null, fixed?: string[], missing?: string[] }} FormalResult
|
|
12
12
|
* @typedef {{ feature: string, files: string[] }} TouchedFeature
|
|
13
|
-
* @typedef {{ baseRef: string, baseSha: string, headSha: string, changed: string[], touched: TouchedFeature[], unmapped: string[], docs: string[],
|
|
14
|
-
* @typedef {{ kind?: string, location?: string, irreversible?: string, containment?: string
|
|
13
|
+
* @typedef {{ baseRef: string, baseSha: string, headSha: string, changed: string[], touched: TouchedFeature[], unmapped: string[], docs: string[], run: string[] }} Plan
|
|
14
|
+
* @typedef {{ kind?: string, location?: string, irreversible?: string, containment?: string }} RawDoorRow
|
|
15
15
|
* @typedef {{ kind?: string, location?: string, reason?: string }} RawFalsePositive
|
|
16
16
|
* @typedef {{ page: string, matches?: boolean, note?: string }} RawDocsJudgement
|
|
17
17
|
* @typedef {{ verifier?: string, notVerified?: string[], attention?: string[], docs?: RawDocsJudgement[], doors?: { none?: string, rows?: RawDoorRow[], falsePositives?: RawFalsePositive[] } }} RawJudgement
|
|
18
|
-
* @typedef {{ kind: string, location: string, irreversible: string, containment: string
|
|
18
|
+
* @typedef {{ kind: string, location: string, irreversible: string, containment: string }} DoorRow
|
|
19
19
|
* @typedef {{ kind: string, location: string, reason: string }} FalsePositive
|
|
20
20
|
* @typedef {{ page: string, matches: boolean, note: string }} DocsJudgement
|
|
21
21
|
* @typedef {{ notVerified: string[], attention: string[], docs: DocsJudgement[], doorsNone: string | null, doors: DoorRow[], falsePositives: FalsePositive[], verifier: string }} Judgement
|
|
@@ -23,7 +23,6 @@
|
|
|
23
23
|
* @typedef {{ feature: string, journey: string, title: string, entryPoint: string, status: "PASS" | "FAIL", database: string, steps: { name: string, checks: JourneyCheck[] }[], error: string | null, folder: string }} FeatureResult
|
|
24
24
|
* @typedef {{ result: string, headSha: string, skillSha: string, pr: number, reasons: string[] }} VerdictCore
|
|
25
25
|
* @typedef {VerdictCore & { markdown: string }} Verdict
|
|
26
|
-
* @typedef {{ feature: string, caught: boolean, run: string, controlRun: string, caughtBy: string[], failingInControl: string[], harnessErrors: string[], output: string }} SeededOutcome
|
|
27
26
|
*/
|
|
28
27
|
|
|
29
28
|
export {};
|
|
@@ -12,8 +12,6 @@ import {
|
|
|
12
12
|
import { planFor } from "./map.mjs";
|
|
13
13
|
import { journeysFile } from "./skill.mjs";
|
|
14
14
|
|
|
15
|
-
const CONTAINED_KINDS = new Set(["data", "format"]);
|
|
16
|
-
|
|
17
15
|
/**
|
|
18
16
|
* The GitHub `owner/repo` of the checkout's origin remote.
|
|
19
17
|
*
|
|
@@ -58,15 +56,13 @@ function readResults(state, feature) {
|
|
|
58
56
|
* @param {import("../run-state.mjs").RunState} state The run
|
|
59
57
|
* @param {import("./types.mjs").Plan} plan The plan
|
|
60
58
|
* @param {(path: string) => string} link The evidence link of a path
|
|
61
|
-
* @returns {Promise<{ rows: string[], reasons: string[]
|
|
59
|
+
* @returns {Promise<{ rows: string[], reasons: string[] }>} The table rows and why the verdict fails
|
|
62
60
|
*/
|
|
63
61
|
async function journeyRows(skill, state, plan, link) {
|
|
64
62
|
/** @type {string[]} */
|
|
65
63
|
const rows = [];
|
|
66
64
|
/** @type {string[]} */
|
|
67
65
|
const reasons = [];
|
|
68
|
-
/** @type {Map<string, import("./types.mjs").FeatureResult>} */
|
|
69
|
-
const byJourney = new Map();
|
|
70
66
|
for (const feature of plan.run) {
|
|
71
67
|
const results = readResults(state, feature);
|
|
72
68
|
const file = journeysFile(skill, feature);
|
|
@@ -85,56 +81,24 @@ async function journeyRows(skill, state, plan, link) {
|
|
|
85
81
|
);
|
|
86
82
|
if (result.status === "FAIL")
|
|
87
83
|
reasons.push(`${feature}/${result.journey} failed`);
|
|
88
|
-
byJourney.set(`${feature}/${result.journey}`, result);
|
|
89
84
|
const outcome = result.status === "PASS" ? "pass" : "**fail**";
|
|
90
85
|
rows.push(
|
|
91
86
|
`| ${feature} | \`${result.journey}\`: ${result.entryPoint} | ${outcome}: ${result.title} | [${result.folder}](${link(result.folder)}) |`,
|
|
92
87
|
);
|
|
93
88
|
}
|
|
94
89
|
}
|
|
95
|
-
return { rows, reasons
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* @param {import("../run-state.mjs").RunState} state The run
|
|
100
|
-
* @param {Map<string, import("./types.mjs").FeatureResult>} byJourney The results of this verdict's journeys
|
|
101
|
-
* @param {string} id The containment journey, as `feature/journey`
|
|
102
|
-
* @returns {import("./types.mjs").FeatureResult | undefined} Its result
|
|
103
|
-
*/
|
|
104
|
-
function containmentResult(state, byJourney, id) {
|
|
105
|
-
const [feature, journey] = id.split("/");
|
|
106
|
-
return (
|
|
107
|
-
byJourney.get(id) ??
|
|
108
|
-
readResults(state, feature).find((result) => result.journey === journey)
|
|
109
|
-
);
|
|
90
|
+
return { rows, reasons };
|
|
110
91
|
}
|
|
111
92
|
|
|
112
93
|
/**
|
|
113
94
|
* @param {import("./skill.mjs").Skill} skill The skill
|
|
114
|
-
* @param {import("../run-state.mjs").RunState} state The run
|
|
115
95
|
* @param {import("./types.mjs").Judgement} judgement The verifier's judgement
|
|
116
|
-
* @param {Map<string, import("./types.mjs").FeatureResult>} byJourney The results of this verdict's journeys
|
|
117
96
|
* @param {import("./types.mjs").Plan} plan The plan
|
|
118
97
|
* @returns {string[]} Why the doors fail the verdict
|
|
119
98
|
*/
|
|
120
|
-
function doorReasons(skill,
|
|
99
|
+
function doorReasons(skill, judgement, plan) {
|
|
121
100
|
/** @type {string[]} */
|
|
122
101
|
const reasons = [];
|
|
123
|
-
for (const row of judgement.doors.filter((door) =>
|
|
124
|
-
CONTAINED_KINDS.has(door.kind),
|
|
125
|
-
)) {
|
|
126
|
-
const allowed = skill.config.doors[row.kind]?.containments ?? [];
|
|
127
|
-
const journey = containmentResult(state, byJourney, row.containmentJourney);
|
|
128
|
-
if (!allowed.includes(row.containmentJourney)) {
|
|
129
|
-
reasons.push(
|
|
130
|
-
`${row.kind} door at ${row.location} names ${row.containmentJourney || "no journey"}; its containment must be one of ${allowed.join(", ")}`,
|
|
131
|
-
);
|
|
132
|
-
} else if (journey?.status !== "PASS") {
|
|
133
|
-
reasons.push(
|
|
134
|
-
`${row.kind} door at ${row.location}: containment journey ${row.containmentJourney} did not pass in this run`,
|
|
135
|
-
);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
102
|
for (const door of flaggedDoors(skill, plan.changed)) {
|
|
139
103
|
for (const file of door.files) {
|
|
140
104
|
const listed = judgement.doors.some(
|
|
@@ -168,12 +132,10 @@ function doorsSection(judgement) {
|
|
|
168
132
|
...dismissed,
|
|
169
133
|
"| Kind | Location | What cannot be undone | Containment |",
|
|
170
134
|
"|---|---|---|---|",
|
|
171
|
-
...judgement.doors.map(
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
return `| ${door.kind} | \`${door.location}\` | ${door.irreversible} | ${door.containment}${journey} |`;
|
|
176
|
-
}),
|
|
135
|
+
...judgement.doors.map(
|
|
136
|
+
(door) =>
|
|
137
|
+
`| ${door.kind} | \`${door.location}\` | ${door.irreversible} | ${door.containment} |`,
|
|
138
|
+
),
|
|
177
139
|
];
|
|
178
140
|
}
|
|
179
141
|
|
|
@@ -266,11 +228,6 @@ function markdown(skill, verdict, parts) {
|
|
|
266
228
|
"### Touched features",
|
|
267
229
|
...bullets(touched, "none"),
|
|
268
230
|
`- ${skill.config.smoke}: ${skill.config.smokeJourney}`,
|
|
269
|
-
...(plan.containments.length > 0
|
|
270
|
-
? [
|
|
271
|
-
`- containment journeys of the Data and Format doors: ${plan.containments.map((feature) => `\`${feature}\``).join(", ")}`,
|
|
272
|
-
]
|
|
273
|
-
: []),
|
|
274
231
|
"",
|
|
275
232
|
...(plan.unmapped.length > 0
|
|
276
233
|
? [
|
|
@@ -344,7 +301,7 @@ export async function renderVerdict(skill, state, pr, judgement) {
|
|
|
344
301
|
...journeys.reasons,
|
|
345
302
|
...plan.unmapped.map((file) => `unmapped surface ${file}`),
|
|
346
303
|
...docsReasons(judgement, plan),
|
|
347
|
-
...doorReasons(skill,
|
|
304
|
+
...doorReasons(skill, judgement, plan),
|
|
348
305
|
...formal.reasons,
|
|
349
306
|
);
|
|
350
307
|
const attention = judgement.attention;
|
package/bin/dms-control.mjs
CHANGED
|
@@ -28,7 +28,6 @@ Verify a change (the .agents/skills/verify-* folder of the checkout, or --skill
|
|
|
28
28
|
map check the feature map covers every user-facing file
|
|
29
29
|
plan [--base <ref>] map the diff to features; list unmapped surfaces
|
|
30
30
|
journey <run> <feature> [ids...] run a feature's journeys and record evidence
|
|
31
|
-
seeded <feature...> run each feature unpatched and with its seeded break
|
|
32
31
|
doors [patch] [--base <ref>] print the One-way-door line for a patch, or for the diff
|
|
33
32
|
formal <run> [--all] run the formal checks that cover the diff (or every one)
|
|
34
33
|
verdict <run> --pr <n> render evidence/verdict.md from the results and evidence/judgement.json
|
|
@@ -12,8 +12,8 @@ export interface OAuthConfig {
|
|
|
12
12
|
callbackBaseUrl?: string;
|
|
13
13
|
}
|
|
14
14
|
export interface SignInCountryConfig {
|
|
15
|
-
header?: string;
|
|
16
|
-
database?: string
|
|
15
|
+
header?: string | false;
|
|
16
|
+
database?: string;
|
|
17
17
|
}
|
|
18
18
|
export interface AuthConfig {
|
|
19
19
|
jwtSecret: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"state.js","sourceRoot":"","sources":["../../../src/config/internal/state.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"state.js","sourceRoot":"","sources":["../../../src/config/internal/state.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.BUNDLED_COUNTRY_DATABASE = void 0;
|
|
7
3
|
exports.resetCountryLookup = resetCountryLookup;
|
|
8
4
|
exports.normalizeCountryCode = normalizeCountryCode;
|
|
9
5
|
exports.lookupCountry = lookupCountry;
|
|
@@ -11,34 +7,27 @@ exports.resolveClientOrigin = resolveClientOrigin;
|
|
|
11
7
|
exports.countryDisplayName = countryDisplayName;
|
|
12
8
|
const own_value_1 = require("./own-value");
|
|
13
9
|
const node_fs_1 = require("node:fs");
|
|
14
|
-
const node_path_1 = __importDefault(require("node:path"));
|
|
15
10
|
const logging_1 = require("@antelopejs/interface-core/logging");
|
|
16
11
|
const mmdb_lib_1 = require("mmdb-lib");
|
|
17
12
|
const config_1 = require("../config");
|
|
18
13
|
const FALLBACK_LANGUAGE = "en";
|
|
19
14
|
const COUNTRY_CODE_PATTERN = /^[A-Z]{2}$/;
|
|
20
15
|
const NON_COUNTRY_CODES = new Set(["XX", "T1"]);
|
|
21
|
-
|
|
16
|
+
const DEFAULT_COUNTRY_HEADER = "CF-IPCountry";
|
|
22
17
|
let cachedLookup;
|
|
23
|
-
function databasePath() {
|
|
24
|
-
const configured = (0, config_1.getAuthConfig)().signInCountry?.database;
|
|
25
|
-
if (configured === false)
|
|
26
|
-
return undefined;
|
|
27
|
-
return configured ?? exports.BUNDLED_COUNTRY_DATABASE;
|
|
28
|
-
}
|
|
29
18
|
function openDatabase(file) {
|
|
30
19
|
try {
|
|
31
20
|
return new mmdb_lib_1.Reader((0, node_fs_1.readFileSync)(file));
|
|
32
21
|
}
|
|
33
22
|
catch (error) {
|
|
34
|
-
logging_1.Logging.Warn(`[DMS]
|
|
23
|
+
logging_1.Logging.Warn(`[DMS] Cannot read the sign-in country database \`auth.signInCountry.database\` names ("${file}"): without the header, sign-ins are told apart by device only (${String(error)})`);
|
|
35
24
|
return null;
|
|
36
25
|
}
|
|
37
26
|
}
|
|
38
27
|
function countryLookup() {
|
|
39
28
|
if (cachedLookup !== undefined)
|
|
40
29
|
return cachedLookup;
|
|
41
|
-
const file =
|
|
30
|
+
const file = (0, config_1.getAuthConfig)().signInCountry?.database;
|
|
42
31
|
cachedLookup = file ? openDatabase(file) : null;
|
|
43
32
|
return cachedLookup;
|
|
44
33
|
}
|
|
@@ -66,9 +55,10 @@ function lookupCountry(ip, lookup = countryLookup()) {
|
|
|
66
55
|
}
|
|
67
56
|
}
|
|
68
57
|
function resolveClientOrigin(ip, headers) {
|
|
69
|
-
const
|
|
70
|
-
const
|
|
71
|
-
|
|
58
|
+
const config = (0, config_1.getAuthConfig)().signInCountry;
|
|
59
|
+
const header = config?.header ?? (config?.database ? undefined : DEFAULT_COUNTRY_HEADER);
|
|
60
|
+
const country = (header
|
|
61
|
+
? normalizeCountryCode((0, own_value_1.ownValue)(headers, header.toLowerCase()))
|
|
72
62
|
: undefined) ?? lookupCountry(ip);
|
|
73
63
|
return { ip, country };
|
|
74
64
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sign-in-country.js","sourceRoot":"","sources":["../../src/utils/sign-in-country.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"sign-in-country.js","sourceRoot":"","sources":["../../src/utils/sign-in-country.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export * from "
|
|
1
|
+
export * from "../../../../dms-ui/app/composables/layout/usePageHeaderActions";
|
|
@@ -71,8 +71,24 @@ function own(name, file, registry = owners) {
|
|
|
71
71
|
entries.add(file);
|
|
72
72
|
registry.set(name, entries);
|
|
73
73
|
}
|
|
74
|
-
|
|
75
|
-
|
|
74
|
+
const unresolved = [];
|
|
75
|
+
const warn = console.warn;
|
|
76
|
+
console.warn = (...args) => {
|
|
77
|
+
const message = args.join(" ");
|
|
78
|
+
if (message.startsWith("[unimport]")) unresolved.push(message);
|
|
79
|
+
else warn(...args);
|
|
80
|
+
};
|
|
81
|
+
let scanned;
|
|
82
|
+
try {
|
|
83
|
+
scanned = await scanDirExports(globs);
|
|
84
|
+
} finally {
|
|
85
|
+
console.warn = warn;
|
|
86
|
+
}
|
|
87
|
+
for (const message of unresolved) {
|
|
88
|
+
console.error(`FAIL auto-import scan ${message}`);
|
|
89
|
+
process.exitCode = 1;
|
|
90
|
+
}
|
|
91
|
+
for (const item of scanned) own(item.as ?? item.name, item.from);
|
|
76
92
|
const registry = ts.createSourceFile(
|
|
77
93
|
"registry.ts",
|
|
78
94
|
readFileSync(join(frontend, "dms.frontend.ts"), "utf8"),
|
|
@@ -271,7 +287,7 @@ for (const file of program.getSourceFiles()) {
|
|
|
271
287
|
visit(file);
|
|
272
288
|
}
|
|
273
289
|
if (failures) process.exitCode = 1;
|
|
274
|
-
else
|
|
290
|
+
else if (!unresolved.length)
|
|
275
291
|
console.log(
|
|
276
292
|
`PASS frontend generated auto-import and template owners (${owners.size} providers)`,
|
|
277
293
|
);
|
|
@@ -139,6 +139,25 @@ try {
|
|
|
139
139
|
assert.equal(autoResult.status, 1, autoResult.stderr);
|
|
140
140
|
assert.match(autoResult.stderr, /useHigher/);
|
|
141
141
|
console.log("PASS reject generated auto-import owner");
|
|
142
|
+
rmSync(join(pkg, "layers"), { recursive: true, force: true });
|
|
143
|
+
mkdirSync(join(pkg, app), { recursive: true });
|
|
144
|
+
write(
|
|
145
|
+
"layers/dms-ui/app/composables/useShared.ts",
|
|
146
|
+
"export const useShared = () => 1;",
|
|
147
|
+
);
|
|
148
|
+
write(
|
|
149
|
+
"layers/dms-layout/app/composables/useShared.ts",
|
|
150
|
+
'export * from "../../../dms-ui/app/composables/useShared";',
|
|
151
|
+
);
|
|
152
|
+
assert.equal(run().status, 0, "relative star re-export resolves");
|
|
153
|
+
write(
|
|
154
|
+
"layers/dms-layout/app/composables/useShared.ts",
|
|
155
|
+
'export * from "#dms-ui/app/composables/useShared";',
|
|
156
|
+
);
|
|
157
|
+
const aliasResult = run();
|
|
158
|
+
assert.equal(aliasResult.status, 1, aliasResult.stderr);
|
|
159
|
+
assert.match(aliasResult.stderr, /FAIL auto-import scan .*#dms-ui/);
|
|
160
|
+
console.log("PASS reject unresolvable star re-export");
|
|
142
161
|
} finally {
|
|
143
162
|
rmSync(scratch, { recursive: true, force: true });
|
|
144
163
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@antelopejs/dms",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"description": "AntelopeJS DMS. Plug in your PostgreSQL, MongoDB or RethinkDB database and your coding agent builds a back office and analytics dashboard as typed TypeScript. Real-time updates on the data the DMS manages.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"antelopejs",
|
|
@@ -53,6 +53,7 @@
|
|
|
53
53
|
"!frontend-vue/pnpm-workspace.yaml",
|
|
54
54
|
"skills",
|
|
55
55
|
"data",
|
|
56
|
+
"!data/*.mmdb",
|
|
56
57
|
"package.json",
|
|
57
58
|
"README.md",
|
|
58
59
|
"CHANGELOG.md",
|
|
@@ -93,8 +94,8 @@
|
|
|
93
94
|
"zod": "~3.24.2"
|
|
94
95
|
},
|
|
95
96
|
"devDependencies": {
|
|
96
|
-
"@antelopejs/core": ">=1.
|
|
97
|
-
"@antelopejs/tooling-configs": "0.
|
|
97
|
+
"@antelopejs/core": ">=1.21.0 <2",
|
|
98
|
+
"@antelopejs/tooling-configs": "^0.3.3",
|
|
98
99
|
"@types/archiver": "^6.0.4",
|
|
99
100
|
"@types/chai": "^4.3.20",
|
|
100
101
|
"@types/jsonwebtoken": "^9.0.10",
|
|
@@ -142,7 +143,7 @@
|
|
|
142
143
|
"@antelopejs/interface-redis": ">=0.0.9 <1.0.0"
|
|
143
144
|
},
|
|
144
145
|
"peerDependencies": {
|
|
145
|
-
"@antelopejs/core": ">=1.
|
|
146
|
+
"@antelopejs/core": ">=1.21.0 <2"
|
|
146
147
|
},
|
|
147
148
|
"peerDependenciesMeta": {
|
|
148
149
|
"@antelopejs/core": {
|
|
@@ -161,7 +162,6 @@
|
|
|
161
162
|
"test:integration": "node scripts/unless-prebuilt.mjs pnpm run build && DMS_TEST_DIR=dist/test/integration NODE_OPTIONS=\"${NODE_OPTIONS:+$NODE_OPTIONS }--require=\\\"$PWD/scripts/backend-runtime-boundaries.cjs\\\"\" ajs module test .",
|
|
162
163
|
"test:watch": "tsc -w & tsc-alias -w",
|
|
163
164
|
"release": "pnpm --filter @antelopejs/interface-dms build && pnpm --dir frontend-vue install --frozen-lockfile && release-it",
|
|
164
|
-
"country-database": "node scripts/fetch-country-database.mjs",
|
|
165
165
|
"knip": "knip --dependencies",
|
|
166
166
|
"knip:all": "knip",
|
|
167
167
|
"docs:catalog": "node scripts/docs-catalog.mjs",
|
|
@@ -1,217 +0,0 @@
|
|
|
1
|
-
import { spawnSync } from "node:child_process";
|
|
2
|
-
import {
|
|
3
|
-
cpSync,
|
|
4
|
-
existsSync,
|
|
5
|
-
mkdtempSync,
|
|
6
|
-
rmSync,
|
|
7
|
-
writeFileSync,
|
|
8
|
-
} from "node:fs";
|
|
9
|
-
import { tmpdir } from "node:os";
|
|
10
|
-
import { join, relative, resolve } from "node:path";
|
|
11
|
-
import { git } from "../git.mjs";
|
|
12
|
-
import { CONTROL_BIN } from "../stack.mjs";
|
|
13
|
-
import { readFeatureResults } from "./decode.mjs";
|
|
14
|
-
import { runPrepare } from "./skill.mjs";
|
|
15
|
-
|
|
16
|
-
const OUTPUT_LIMIT = 64 * 1024 * 1024;
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* @param {string} cwd Where to run it
|
|
20
|
-
* @param {string} program The program
|
|
21
|
-
* @param {string[]} args Its arguments
|
|
22
|
-
* @returns {import("node:child_process").SpawnSyncReturns<string>} The run
|
|
23
|
-
*/
|
|
24
|
-
function run(cwd, program, args) {
|
|
25
|
-
return spawnSync(program, args, {
|
|
26
|
-
cwd,
|
|
27
|
-
encoding: "utf8",
|
|
28
|
-
env: { ...process.env, CI: "1" },
|
|
29
|
-
maxBuffer: OUTPUT_LIMIT,
|
|
30
|
-
});
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* @param {string} cwd Where to run it
|
|
35
|
-
* @param {string} program The program
|
|
36
|
-
* @param {string[]} args Its arguments
|
|
37
|
-
* @returns {string} Its trimmed output
|
|
38
|
-
*/
|
|
39
|
-
function mustRun(cwd, program, args) {
|
|
40
|
-
const result = run(cwd, program, args);
|
|
41
|
-
if (result.status !== 0) {
|
|
42
|
-
throw new Error(
|
|
43
|
-
`${program} ${args.join(" ")} failed in ${cwd}:\n${result.stdout}${result.stderr}`,
|
|
44
|
-
);
|
|
45
|
-
}
|
|
46
|
-
return result.stdout.trim();
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* A scratch worktree of HEAD with the current skill copied in, the patch
|
|
51
|
-
* applied when there is one, and the config's `prepare` commands run. The
|
|
52
|
-
* worktree is the skill's repository, which is also the renderer checkout
|
|
53
|
-
* when the config names a `renderer`: the patch changes what the run serves.
|
|
54
|
-
*
|
|
55
|
-
* @param {import("./skill.mjs").Skill} skill The skill
|
|
56
|
-
* @param {string} label What the worktree is for
|
|
57
|
-
* @param {string | null} patch The patch file, or `null` for the control
|
|
58
|
-
* @returns {{ worktree: string, skill: string }} The worktree and its skill copy
|
|
59
|
-
*/
|
|
60
|
-
function prepareWorktree(skill, label, patch) {
|
|
61
|
-
const worktree = mkdtempSync(
|
|
62
|
-
join(tmpdir(), `${skill.name}-seeded-${label}-`),
|
|
63
|
-
);
|
|
64
|
-
git(["worktree", "add", "--detach", "--force", worktree, "HEAD"], skill.repo);
|
|
65
|
-
const copy = join(worktree, relative(skill.repo, skill.dir));
|
|
66
|
-
rmSync(copy, { recursive: true, force: true });
|
|
67
|
-
cpSync(skill.dir, copy, { recursive: true });
|
|
68
|
-
if (patch) mustRun(worktree, "git", ["apply", patch]);
|
|
69
|
-
runPrepare(skill, worktree);
|
|
70
|
-
return { worktree, skill: copy };
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* @param {import("./skill.mjs").Skill} skill The skill
|
|
75
|
-
* @param {string} worktree The scratch worktree
|
|
76
|
-
*/
|
|
77
|
-
function removeWorktree(skill, worktree) {
|
|
78
|
-
git(["worktree", "remove", "--force", worktree], skill.repo);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* The `up` arguments for a scratch worktree: the config's playground and
|
|
83
|
-
* renderer, resolved in the worktree unless absolute.
|
|
84
|
-
*
|
|
85
|
-
* @param {import("./skill.mjs").Skill} skill The skill
|
|
86
|
-
* @param {{ worktree: string, skill: string }} prepared The scratch worktree and its skill copy
|
|
87
|
-
* @returns {string[]} The arguments
|
|
88
|
-
*/
|
|
89
|
-
export function upArguments(skill, prepared) {
|
|
90
|
-
const { playground, renderer } = skill.config;
|
|
91
|
-
return [
|
|
92
|
-
"up",
|
|
93
|
-
"--playground",
|
|
94
|
-
resolve(prepared.worktree, playground),
|
|
95
|
-
...(renderer ? ["--renderer", resolve(prepared.worktree, renderer)] : []),
|
|
96
|
-
"--skill",
|
|
97
|
-
prepared.skill,
|
|
98
|
-
];
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* @param {{ worktree: string, skill: string }} prepared The scratch worktree and its skill copy
|
|
103
|
-
* @param {import("./skill.mjs").Skill} skill The skill
|
|
104
|
-
* @param {string} feature The feature
|
|
105
|
-
* @returns {{ run: string, results: import("./types.mjs").FeatureResult[], output: string }} The run folder, its results and output
|
|
106
|
-
*/
|
|
107
|
-
function runFeatureIn(prepared, skill, feature) {
|
|
108
|
-
/**
|
|
109
|
-
* @param {string[]} args The dms-control arguments
|
|
110
|
-
* @returns {import("node:child_process").SpawnSyncReturns<string>} The run
|
|
111
|
-
*/
|
|
112
|
-
const control = (args) =>
|
|
113
|
-
run(prepared.worktree, process.execPath, [CONTROL_BIN, ...args]);
|
|
114
|
-
const started = control(upArguments(skill, prepared));
|
|
115
|
-
const dir = started.stdout.trim().split("\n").at(-1) ?? "";
|
|
116
|
-
if (started.status !== 0 || !existsSync(join(dir, "run.json"))) {
|
|
117
|
-
return {
|
|
118
|
-
run: dir,
|
|
119
|
-
results: [],
|
|
120
|
-
output: `up failed:\n${started.stdout}${started.stderr}`,
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
const journeys = control([
|
|
124
|
-
"journey",
|
|
125
|
-
dir,
|
|
126
|
-
feature,
|
|
127
|
-
"--skill",
|
|
128
|
-
prepared.skill,
|
|
129
|
-
]);
|
|
130
|
-
control(["down", dir]);
|
|
131
|
-
const file = join(dir, "evidence", feature, "feature.json");
|
|
132
|
-
const results = readFeatureResults(file);
|
|
133
|
-
return { run: dir, results, output: `${journeys.stdout}${journeys.stderr}` };
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* @param {import("./types.mjs").FeatureResult} result A journey result
|
|
138
|
-
* @returns {boolean} Whether one of its checks failed
|
|
139
|
-
*/
|
|
140
|
-
function failsACheck(result) {
|
|
141
|
-
return result.steps.some((step) => step.checks.some((check) => !check.pass));
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* @param {string} feature The feature
|
|
146
|
-
* @param {ReturnType<typeof runFeatureIn>} control The unpatched run
|
|
147
|
-
* @param {ReturnType<typeof runFeatureIn>} seeded The patched run
|
|
148
|
-
* @returns {import("./types.mjs").SeededOutcome} The outcome
|
|
149
|
-
*/
|
|
150
|
-
function judge(feature, control, seeded) {
|
|
151
|
-
const passedInControl = new Set(
|
|
152
|
-
control.results
|
|
153
|
-
.filter((result) => result.status === "PASS")
|
|
154
|
-
.map((result) => result.journey),
|
|
155
|
-
);
|
|
156
|
-
const failed = seeded.results.filter((result) => result.status === "FAIL");
|
|
157
|
-
const caughtBy = failed
|
|
158
|
-
.filter(
|
|
159
|
-
(result) => passedInControl.has(result.journey) && failsACheck(result),
|
|
160
|
-
)
|
|
161
|
-
.map((result) => result.journey);
|
|
162
|
-
return {
|
|
163
|
-
feature,
|
|
164
|
-
caught: caughtBy.length > 0,
|
|
165
|
-
run: seeded.run,
|
|
166
|
-
controlRun: control.run,
|
|
167
|
-
caughtBy,
|
|
168
|
-
failingInControl: control.results
|
|
169
|
-
.filter((result) => result.status === "FAIL")
|
|
170
|
-
.map((result) => result.journey),
|
|
171
|
-
harnessErrors: failed
|
|
172
|
-
.filter((result) => !failsACheck(result))
|
|
173
|
-
.map((result) => result.journey),
|
|
174
|
-
output: seeded.output,
|
|
175
|
-
};
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* Runs each feature's journeys twice from scratch worktrees of HEAD: once
|
|
180
|
-
* unpatched as the control, once with `seeded-breaks/<feature>.patch`. A
|
|
181
|
-
* break is caught only when a journey that passes in the control fails one
|
|
182
|
-
* of its checks with the patch; a failure without a failing check is a
|
|
183
|
-
* harness error and does not count. Worktrees are removed; both runs keep
|
|
184
|
-
* their evidence, and `seeded.json` in the patched run records the outcome.
|
|
185
|
-
*
|
|
186
|
-
* @param {import("./skill.mjs").Skill} skill The skill
|
|
187
|
-
* @param {string[]} features The features
|
|
188
|
-
* @returns {import("./types.mjs").SeededOutcome[]} One outcome per feature
|
|
189
|
-
*/
|
|
190
|
-
export function runSeededBreaks(skill, features) {
|
|
191
|
-
const control = prepareWorktree(skill, "control", null);
|
|
192
|
-
try {
|
|
193
|
-
return features.map((feature) => {
|
|
194
|
-
const unpatched = runFeatureIn(control, skill, feature);
|
|
195
|
-
const patch = join(skill.seededBreaksDir, `${feature}.patch`);
|
|
196
|
-
const seeded = prepareWorktree(skill, feature, patch);
|
|
197
|
-
try {
|
|
198
|
-
const outcome = judge(
|
|
199
|
-
feature,
|
|
200
|
-
unpatched,
|
|
201
|
-
runFeatureIn(seeded, skill, feature),
|
|
202
|
-
);
|
|
203
|
-
if (existsSync(outcome.run)) {
|
|
204
|
-
writeFileSync(
|
|
205
|
-
join(outcome.run, "evidence", "seeded.json"),
|
|
206
|
-
`${JSON.stringify(outcome, null, 2)}\n`,
|
|
207
|
-
);
|
|
208
|
-
}
|
|
209
|
-
return outcome;
|
|
210
|
-
} finally {
|
|
211
|
-
removeWorktree(skill, seeded.worktree);
|
|
212
|
-
}
|
|
213
|
-
});
|
|
214
|
-
} finally {
|
|
215
|
-
removeWorktree(skill, control.worktree);
|
|
216
|
-
}
|
|
217
|
-
}
|
package/data/NOTICE.md
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
# Third-party data
|
|
2
|
-
|
|
3
|
-
`dbip-country-lite.mmdb` is the [DB-IP](https://db-ip.com) IP to Country Lite
|
|
4
|
-
database, licensed under the
|
|
5
|
-
[Creative Commons Attribution 4.0 International License](https://creativecommons.org/licenses/by/4.0/).
|
|
6
|
-
|
|
7
|
-
IP Geolocation by [DB-IP](https://db-ip.com).
|
|
8
|
-
|
|
9
|
-
The file is not kept in the repository: `scripts/fetch-country-database.mjs`
|
|
10
|
-
downloads the current edition when the package is packed for a release. The DMS
|
|
11
|
-
reads it to tell which country a sign-in came from; see `auth.signInCountry` in
|
|
12
|
-
the configuration reference.
|
|
Binary file
|