@ultimat3/cli 30.0.0 → 30.2.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/package.json +28 -28
- package/src/cmd-exercise.ts +1 -1
- package/src/exercise-subject.ts +3 -0
- package/src/seo-routes.ts +4 -1
- package/src/site-config.ts +1 -1
- package/src/site-seo-write.ts +9 -2
- package/src/templates/scaffold-entries.ts +1 -1
- package/wiki/SEO.md +1 -1
- package/wiki/Testing.md +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ultimat3/cli",
|
|
3
|
-
"version": "30.
|
|
3
|
+
"version": "30.2.0",
|
|
4
4
|
"description": "The `x` binary: new, dev, build, verify, generate, db, mcp, doctor, deploy",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ultimate",
|
|
@@ -53,33 +53,33 @@
|
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
55
55
|
"@babel/core": "^8.0.7",
|
|
56
|
-
"@ultimat3/action": "30.
|
|
57
|
-
"@ultimat3/admin": "30.
|
|
58
|
-
"@ultimat3/ai": "30.
|
|
59
|
-
"@ultimat3/auth": "30.
|
|
60
|
-
"@ultimat3/cache": "30.
|
|
61
|
-
"@ultimat3/core": "30.
|
|
62
|
-
"@ultimat3/db": "30.
|
|
63
|
-
"@ultimat3/entity": "30.
|
|
64
|
-
"@ultimat3/flags": "30.
|
|
65
|
-
"@ultimat3/http": "30.
|
|
66
|
-
"@ultimat3/i18n": "30.
|
|
67
|
-
"@ultimat3/jobs": "30.
|
|
68
|
-
"@ultimat3/mail": "30.
|
|
69
|
-
"@ultimat3/manifest": "30.
|
|
70
|
-
"@ultimat3/mcp": "30.
|
|
71
|
-
"@ultimat3/money": "30.
|
|
72
|
-
"@ultimat3/notify": "30.
|
|
73
|
-
"@ultimat3/policy": "30.
|
|
74
|
-
"@ultimat3/pwa": "30.
|
|
75
|
-
"@ultimat3/query": "30.
|
|
76
|
-
"@ultimat3/realtime": "30.
|
|
77
|
-
"@ultimat3/render": "30.
|
|
78
|
-
"@ultimat3/schema": "30.
|
|
79
|
-
"@ultimat3/seo": "30.
|
|
80
|
-
"@ultimat3/storage": "30.
|
|
81
|
-
"@ultimat3/testing": "30.
|
|
82
|
-
"@ultimat3/time": "30.
|
|
56
|
+
"@ultimat3/action": "30.2.0",
|
|
57
|
+
"@ultimat3/admin": "30.2.0",
|
|
58
|
+
"@ultimat3/ai": "30.2.0",
|
|
59
|
+
"@ultimat3/auth": "30.2.0",
|
|
60
|
+
"@ultimat3/cache": "30.2.0",
|
|
61
|
+
"@ultimat3/core": "30.2.0",
|
|
62
|
+
"@ultimat3/db": "30.2.0",
|
|
63
|
+
"@ultimat3/entity": "30.2.0",
|
|
64
|
+
"@ultimat3/flags": "30.2.0",
|
|
65
|
+
"@ultimat3/http": "30.2.0",
|
|
66
|
+
"@ultimat3/i18n": "30.2.0",
|
|
67
|
+
"@ultimat3/jobs": "30.2.0",
|
|
68
|
+
"@ultimat3/mail": "30.2.0",
|
|
69
|
+
"@ultimat3/manifest": "30.2.0",
|
|
70
|
+
"@ultimat3/mcp": "30.2.0",
|
|
71
|
+
"@ultimat3/money": "30.2.0",
|
|
72
|
+
"@ultimat3/notify": "30.2.0",
|
|
73
|
+
"@ultimat3/policy": "30.2.0",
|
|
74
|
+
"@ultimat3/pwa": "30.2.0",
|
|
75
|
+
"@ultimat3/query": "30.2.0",
|
|
76
|
+
"@ultimat3/realtime": "30.2.0",
|
|
77
|
+
"@ultimat3/render": "30.2.0",
|
|
78
|
+
"@ultimat3/schema": "30.2.0",
|
|
79
|
+
"@ultimat3/seo": "30.2.0",
|
|
80
|
+
"@ultimat3/storage": "30.2.0",
|
|
81
|
+
"@ultimat3/testing": "30.2.0",
|
|
82
|
+
"@ultimat3/time": "30.2.0",
|
|
83
83
|
"babel-preset-solid": "^1.9.16"
|
|
84
84
|
}
|
|
85
85
|
}
|
package/src/cmd-exercise.ts
CHANGED
|
@@ -95,7 +95,7 @@ async function exercise(
|
|
|
95
95
|
proved.length === 0
|
|
96
96
|
? []
|
|
97
97
|
: await withExerciseDatabase(root, ctx.env, (client) =>
|
|
98
|
-
subjectProofFindings(proved, cores, client),
|
|
98
|
+
subjectProofFindings(proved, cores, client, overrides?.factories),
|
|
99
99
|
);
|
|
100
100
|
const report = exerciseReport(records, seed);
|
|
101
101
|
// The whole pass, not a one-primitive run's slice of it, is what the file is for — but a
|
package/src/exercise-subject.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { identifier, sql } from '@ultimat3/db';
|
|
|
9
9
|
import type { EntityCore, RegistryEntry } from '@ultimat3/entity';
|
|
10
10
|
import type { Subject } from '@ultimat3/jobs';
|
|
11
11
|
import { planSubject } from '@ultimat3/jobs';
|
|
12
|
+
import type { SeedFactory } from '@ultimat3/testing';
|
|
12
13
|
import { entitySeedWriter, subjectProof } from '@ultimat3/testing';
|
|
13
14
|
import type { Finding } from './output';
|
|
14
15
|
|
|
@@ -39,6 +40,7 @@ export async function subjectProofFindings(
|
|
|
39
40
|
subjects: readonly Subject<never>[],
|
|
40
41
|
cores: Readonly<Record<string, EntityCore>>,
|
|
41
42
|
client: DbClient,
|
|
43
|
+
factories?: Readonly<Record<string, SeedFactory>>,
|
|
42
44
|
): Promise<readonly Finding[]> {
|
|
43
45
|
const findings: Finding[] = [];
|
|
44
46
|
for (const [index, subject] of subjects.entries()) {
|
|
@@ -49,6 +51,7 @@ export async function subjectProofFindings(
|
|
|
49
51
|
read: (entity) =>
|
|
50
52
|
client.query<Record<string, unknown>>(sql`select * from ${identifier(entity.$table)}`),
|
|
51
53
|
seed: SUBJECT_PROOF_SEED + index,
|
|
54
|
+
factories,
|
|
52
55
|
}).then(
|
|
53
56
|
(result) => result.problems.filter((p) => p.code === 'X_SUBJECT_ERASE_INCOMPLETE'),
|
|
54
57
|
(error: unknown) => (isUltimateError(error) ? [error] : Promise.reject(error)),
|
package/src/seo-routes.ts
CHANGED
|
@@ -183,7 +183,7 @@ export function seoRoutes(options: SeoRoutesOptions): readonly Route[] {
|
|
|
183
183
|
return first.answer;
|
|
184
184
|
};
|
|
185
185
|
|
|
186
|
-
|
|
186
|
+
const routes: Route[] = [
|
|
187
187
|
{
|
|
188
188
|
method: 'GET',
|
|
189
189
|
path: ROBOTS_PATH,
|
|
@@ -256,4 +256,7 @@ export function seoRoutes(options: SeoRoutesOptions): readonly Route[] {
|
|
|
256
256
|
},
|
|
257
257
|
},
|
|
258
258
|
];
|
|
259
|
+
// `seo.llms: false` hands the path to the app: an app that writes its own `/llms.txt` (plans,
|
|
260
|
+
// prices) mounted it beside this one and every boot refused with X_ROUTE_CONFLICT.
|
|
261
|
+
return options.site?.llms === false ? routes.filter((route) => route.path !== LLMS_PATH) : routes;
|
|
259
262
|
}
|
package/src/site-config.ts
CHANGED
|
@@ -26,7 +26,7 @@ export interface SiteSettings {
|
|
|
26
26
|
* `SiteSettings` literal written before it existed still compiles.
|
|
27
27
|
*/
|
|
28
28
|
readonly favicon?: SiteFavicon | undefined;
|
|
29
|
-
/** `seo.llms` — `/llms.txt` on (the default) or off. Optional for `robotsGroups`' reason. */
|
|
29
|
+
/** `seo.llms` — `/llms.txt` on (the default) or off: off, the path is the app's to mount. Optional for `robotsGroups`' reason. */
|
|
30
30
|
readonly llms?: boolean | undefined;
|
|
31
31
|
}
|
|
32
32
|
|
package/src/site-seo-write.ts
CHANGED
|
@@ -9,10 +9,17 @@ import { ROBOTS_PATH, type SiteSeo } from './site-seo';
|
|
|
9
9
|
/**
|
|
10
10
|
* Writes every file of `seo` under `out` and answers their site-relative paths, the sitemap files
|
|
11
11
|
* first. `Bun.write` creates `sitemaps/` on the way, so an index and its parts land together.
|
|
12
|
+
* `llms: false` (`seo.llms`) writes no `llms.txt`: the path is the app's, as on a running web role.
|
|
12
13
|
*/
|
|
13
|
-
export async function writeSiteSeo(
|
|
14
|
+
export async function writeSiteSeo(
|
|
15
|
+
out: string,
|
|
16
|
+
seo: SiteSeo,
|
|
17
|
+
options: { readonly llms?: boolean | undefined } = {},
|
|
18
|
+
): Promise<readonly string[]> {
|
|
14
19
|
for (const file of seo.sitemaps) await Bun.write(join(out, file.path), file.xml);
|
|
15
20
|
await Bun.write(join(out, ROBOTS_PATH), seo.robots);
|
|
21
|
+
const sitemaps = seo.sitemaps.map((file) => file.path);
|
|
22
|
+
if (options.llms === false) return [...sitemaps, ROBOTS_PATH];
|
|
16
23
|
await Bun.write(join(out, LLMS_PATH), seo.llms);
|
|
17
|
-
return [...
|
|
24
|
+
return [...sitemaps, ROBOTS_PATH, LLMS_PATH];
|
|
18
25
|
}
|
|
@@ -119,7 +119,7 @@ async function writeSeoFiles(report: PrerenderReport): Promise<readonly string[]
|
|
|
119
119
|
entries: report.entries,
|
|
120
120
|
llmsPages: report.llmsPages,
|
|
121
121
|
});
|
|
122
|
-
return await writeSiteSeo(out, seo);
|
|
122
|
+
return await writeSiteSeo(out, seo, { llms: settings.llms });
|
|
123
123
|
}
|
|
124
124
|
|
|
125
125
|
if (import.meta.main) {
|
package/wiki/SEO.md
CHANGED
|
@@ -265,7 +265,7 @@ description, in path order, under the home page's title and description:
|
|
|
265
265
|
| served | `GET /llms.txt` from the web role (`text/markdown`, the sitemap's hour of cache); the static export writes the same bytes (`writeSiteSeo`) |
|
|
266
266
|
| listed | public, indexable `site/` pages, in the default locale, less `seo.robots.disallow`. A dynamic route (or one with a `load`) is expanded through `prerender()` page by page, each line the title and description that page's own `meta` gives it through its `load` — the export lists only the pages it emitted, as the sitemap does; a page whose `meta` says `robots: { index: false }` is left out. `prerender()` runs once per answer for the sitemap and this file together |
|
|
267
267
|
| outside production | an empty document, for `robots.txt`'s reason |
|
|
268
|
-
| opt out | `seo: { llms: false }` in `app.config.ts`:
|
|
268
|
+
| opt out | `seo: { llms: false }` in `app.config.ts`: the framework mounts no `GET /llms.txt` and the static export writes none — the path is the app's own route to serve, or a 404 |
|
|
269
269
|
| build error | while on, a listed page with no `meta.description` is `X_SEO_LLMS_DESCRIPTION_MISSING` in the `seo` step. The step reads `meta` without a database, so it checks the pages whose `meta` needs no `load`; an expanded page with no description is listed by its title alone |
|
|
270
270
|
|
|
271
271
|
The repository's own `llms.txt` is a separate, hand-kept file and is not this.
|
package/wiki/Testing.md
CHANGED
|
@@ -987,6 +987,7 @@ shape, same unreached code. `As of 2026-11`.
|
|
|
987
987
|
| Async input | an override's `input` may be `async`: it is awaited inside the call's request, as the call's actor, so it can mint what the action checks (a one-time token, a passkey assertion). `x drill`'s load skips an `async` input and says why |
|
|
988
988
|
| Room | network sealed, mail captured, jobs in memory, clock frozen. A call out is refused and counted, so the primitive is `unreached` |
|
|
989
989
|
| Overrides | `apps/web/exercise.ts`: `export const exercise = defineExercise({ publishPost: { actor, input }, sendInvoice: { off: '<why>' } })`. Each value is `.contract({ exercise })`'s option. An unknown name is `X_DECLARATION_UNKNOWN`; a blank `off` is `X_CONTRACT_EXERCISE_UNDECLARED` |
|
|
990
|
+
| Rows a generated value cannot satisfy | a cross-column `c.satisfies(…)` reads `X_SEED_GRAPH_UNSATISFIED`: `defineExercise({ … }, { factories: { bosses: defineFactory(bosses, { defaults: (i) => ({ … }) }) } })`, keyed by entity name, the graph's keys kept. A column that follows from a key or reference the graph picked: `derive: (row) => ({ scopeKey: … })` beside `build`/`reset`, reading `row.ownerOrgId`. A partial unique whose columns the factory builds is the database's to judge. An unknown name is `X_DECLARATION_UNKNOWN` |
|
|
990
991
|
| Report | `.x/exercise.json` and `--json`: `{ seed, counts, records[] }`, each record `{ kind, name, outcome, code?, reason?, statements, effects, ms, override?, tenant?, crash? }`. The fields before `tenant` are A's call. The human table's `tenant` and `crash` columns are the verdicts, `-` where none ran |
|
|
991
992
|
| Gate | the `exercise` step, after `job`, runs `x exercise --json` as a child and is red on its findings: `X_CONTRACT_EXERCISE_UNREACHED`, `X_TENANT_LEAK`, `X_TENANT_WRITE_CROSSED`, `X_JOB_CRASH_DIVERGED`, and — on a whole pass, not `x exercise <name>` — `X_ENTITY_TENANT_UNDECLARED`. `unreached` is never sampled or skipped: it is a finding whose `fix:` is the entry to paste |
|
|
992
993
|
|