@ultimat3/cli 27.15.0 → 27.16.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-mcp.ts +3 -0
- package/src/seo-routes.ts +2 -1
- package/src/site-config.ts +10 -3
- package/src/site-seo.ts +7 -1
- package/src/templates/scaffold-entries.ts +3 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ultimat3/cli",
|
|
3
|
-
"version": "27.
|
|
3
|
+
"version": "27.16.0",
|
|
4
4
|
"description": "The `x` binary: new, dev, build, verify, generate, db, mcp, doctor, deploy",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -39,33 +39,33 @@
|
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
41
|
"@babel/core": "^8.0.1",
|
|
42
|
-
"@ultimat3/action": "27.
|
|
43
|
-
"@ultimat3/admin": "27.
|
|
44
|
-
"@ultimat3/ai": "27.
|
|
45
|
-
"@ultimat3/auth": "27.
|
|
46
|
-
"@ultimat3/cache": "27.
|
|
47
|
-
"@ultimat3/core": "27.
|
|
48
|
-
"@ultimat3/db": "27.
|
|
49
|
-
"@ultimat3/entity": "27.
|
|
50
|
-
"@ultimat3/flags": "27.
|
|
51
|
-
"@ultimat3/http": "27.
|
|
52
|
-
"@ultimat3/i18n": "27.
|
|
53
|
-
"@ultimat3/jobs": "27.
|
|
54
|
-
"@ultimat3/mail": "27.
|
|
55
|
-
"@ultimat3/manifest": "27.
|
|
56
|
-
"@ultimat3/mcp": "27.
|
|
57
|
-
"@ultimat3/money": "27.
|
|
58
|
-
"@ultimat3/notify": "27.
|
|
59
|
-
"@ultimat3/policy": "27.
|
|
60
|
-
"@ultimat3/pwa": "27.
|
|
61
|
-
"@ultimat3/query": "27.
|
|
62
|
-
"@ultimat3/realtime": "27.
|
|
63
|
-
"@ultimat3/render": "27.
|
|
64
|
-
"@ultimat3/schema": "27.
|
|
65
|
-
"@ultimat3/seo": "27.
|
|
66
|
-
"@ultimat3/storage": "27.
|
|
67
|
-
"@ultimat3/testing": "27.
|
|
68
|
-
"@ultimat3/time": "27.
|
|
42
|
+
"@ultimat3/action": "27.16.0",
|
|
43
|
+
"@ultimat3/admin": "27.16.0",
|
|
44
|
+
"@ultimat3/ai": "27.16.0",
|
|
45
|
+
"@ultimat3/auth": "27.16.0",
|
|
46
|
+
"@ultimat3/cache": "27.16.0",
|
|
47
|
+
"@ultimat3/core": "27.16.0",
|
|
48
|
+
"@ultimat3/db": "27.16.0",
|
|
49
|
+
"@ultimat3/entity": "27.16.0",
|
|
50
|
+
"@ultimat3/flags": "27.16.0",
|
|
51
|
+
"@ultimat3/http": "27.16.0",
|
|
52
|
+
"@ultimat3/i18n": "27.16.0",
|
|
53
|
+
"@ultimat3/jobs": "27.16.0",
|
|
54
|
+
"@ultimat3/mail": "27.16.0",
|
|
55
|
+
"@ultimat3/manifest": "27.16.0",
|
|
56
|
+
"@ultimat3/mcp": "27.16.0",
|
|
57
|
+
"@ultimat3/money": "27.16.0",
|
|
58
|
+
"@ultimat3/notify": "27.16.0",
|
|
59
|
+
"@ultimat3/policy": "27.16.0",
|
|
60
|
+
"@ultimat3/pwa": "27.16.0",
|
|
61
|
+
"@ultimat3/query": "27.16.0",
|
|
62
|
+
"@ultimat3/realtime": "27.16.0",
|
|
63
|
+
"@ultimat3/render": "27.16.0",
|
|
64
|
+
"@ultimat3/schema": "27.16.0",
|
|
65
|
+
"@ultimat3/seo": "27.16.0",
|
|
66
|
+
"@ultimat3/storage": "27.16.0",
|
|
67
|
+
"@ultimat3/testing": "27.16.0",
|
|
68
|
+
"@ultimat3/time": "27.16.0",
|
|
69
69
|
"babel-preset-solid": "^1.9.15"
|
|
70
70
|
}
|
|
71
71
|
}
|
package/src/cmd-mcp.ts
CHANGED
|
@@ -114,6 +114,9 @@ export async function startMcpHttp(host: CliMcpServer, port: number): Promise<Mc
|
|
|
114
114
|
handle = Bun.serve({
|
|
115
115
|
port,
|
|
116
116
|
hostname: 'localhost',
|
|
117
|
+
// A tool call (`verify_run`, `tests_run`) runs for minutes and writes nothing until it
|
|
118
|
+
// answers; Bun's 10 s default cut it. No deadline here, so no idle cut (0 = disabled).
|
|
119
|
+
idleTimeout: 0,
|
|
117
120
|
fetch: (request: Request, server): Response | Promise<Response> => {
|
|
118
121
|
const url = new URL(request.url);
|
|
119
122
|
if (request.method !== route.method || url.pathname !== route.path) {
|
package/src/seo-routes.ts
CHANGED
|
@@ -20,7 +20,7 @@ import { ROBOTS_PATH, SITEMAP_PATH, siteSeo } from './site-seo';
|
|
|
20
20
|
|
|
21
21
|
export interface SeoRoutesOptions {
|
|
22
22
|
readonly env: Readonly<Record<string, string | undefined>>;
|
|
23
|
-
/** `site.origin`, `seo.robots
|
|
23
|
+
/** `site.origin`, `seo.robots` and `seo.sitemap` from `app.config.ts` (`loadSiteSettings`). */
|
|
24
24
|
readonly site?: SiteSettings;
|
|
25
25
|
/** The app root route files are relative to — `seo.sitemap.lastmod: 'git' | 'mtime'` reads them. */
|
|
26
26
|
readonly root?: string;
|
|
@@ -84,6 +84,7 @@ export function seoRoutes(options: SeoRoutesOptions): readonly Route[] {
|
|
|
84
84
|
baseUrl,
|
|
85
85
|
environment,
|
|
86
86
|
disallow: options.site?.disallow ?? [],
|
|
87
|
+
robotsGroups: options.site?.robotsGroups ?? [],
|
|
87
88
|
...(options.site === undefined ? {} : { sitemap: options.site.sitemap }),
|
|
88
89
|
...(options.root === undefined ? {} : { root: options.root }),
|
|
89
90
|
});
|
package/src/site-config.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
// The public origin and the crawler rules out of `app.config.ts` — `site.origin
|
|
2
|
-
// `seo.robots.disallow` — and the one resolution of "which origin is this site served on" every
|
|
1
|
+
// The public origin and the crawler rules out of `app.config.ts` — `site.origin`,
|
|
2
|
+
// `seo.robots.disallow` and `seo.robots.groups` — and the one resolution of "which origin is this site served on" every
|
|
3
3
|
// absolute URL a document, a sitemap or a robots file carries is built against. Read off the one
|
|
4
4
|
// loader (`app-config-load.ts`).
|
|
5
5
|
|
|
6
|
-
import type { Environment, SeoSitemapConfig } from '@ultimat3/core';
|
|
6
|
+
import type { Environment, SeoRobotsGroup, SeoSitemapConfig } from '@ultimat3/core';
|
|
7
7
|
import { loadAppConfig } from './app-config-load';
|
|
8
8
|
|
|
9
9
|
export interface SiteSettings {
|
|
@@ -11,6 +11,11 @@ export interface SiteSettings {
|
|
|
11
11
|
readonly origin: string | null;
|
|
12
12
|
/** `seo.robots.disallow` — the paths a production `robots.txt` keeps crawlers out of. */
|
|
13
13
|
readonly disallow: readonly string[];
|
|
14
|
+
/**
|
|
15
|
+
* `seo.robots.groups` — per-crawler `User-agent` groups; empty means one `*` group. Optional so
|
|
16
|
+
* a `SiteSettings` literal written before it existed still compiles.
|
|
17
|
+
*/
|
|
18
|
+
readonly robotsGroups?: readonly SeoRobotsGroup[] | undefined;
|
|
14
19
|
/** `seo.sitemap` — public `app/` pages to list, where `<lastmod>` comes from, sections, page size. */
|
|
15
20
|
readonly sitemap: SeoSitemapConfig;
|
|
16
21
|
}
|
|
@@ -18,6 +23,7 @@ export interface SiteSettings {
|
|
|
18
23
|
export const NO_SITE_SETTINGS: SiteSettings = {
|
|
19
24
|
origin: null,
|
|
20
25
|
disallow: [],
|
|
26
|
+
robotsGroups: [],
|
|
21
27
|
sitemap: { extra: [], lastmod: 'none', sections: [], pageSize: 50_000 },
|
|
22
28
|
};
|
|
23
29
|
|
|
@@ -30,6 +36,7 @@ export async function loadSiteSettings(root: string): Promise<SiteSettings> {
|
|
|
30
36
|
return {
|
|
31
37
|
origin: origin === null ? null : trimSlash(origin),
|
|
32
38
|
disallow: [...config.seo.robots.disallow],
|
|
39
|
+
robotsGroups: [...(config.seo.robots.groups ?? [])],
|
|
33
40
|
sitemap: config.seo.sitemap,
|
|
34
41
|
};
|
|
35
42
|
}
|
package/src/site-seo.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// (`apps/web/prerender.ts` writes it into the artifact) and the running web role (`seo-routes.ts`
|
|
3
3
|
// serves it), so a crawler reads the same two files from a CDN and from a container.
|
|
4
4
|
|
|
5
|
-
import type { Environment, SeoSitemapConfig } from '@ultimat3/core';
|
|
5
|
+
import type { Environment, SeoRobotsGroup, SeoSitemapConfig } from '@ultimat3/core';
|
|
6
6
|
import { UltimateError } from '@ultimat3/core';
|
|
7
7
|
import { localizedPath, unlocalizedPath } from '@ultimat3/i18n';
|
|
8
8
|
import { appLocaleSet } from '@ultimat3/i18n/app-catalogs';
|
|
@@ -34,6 +34,11 @@ export interface SiteSeoOptions {
|
|
|
34
34
|
* `User-agent: *` group; a non-production `robots.txt` still disallows everything.
|
|
35
35
|
*/
|
|
36
36
|
readonly disallow?: readonly string[] | undefined;
|
|
37
|
+
/**
|
|
38
|
+
* `seo.robots.groups` from `app.config.ts` (`loadSiteSettings`): the production file's per-crawler
|
|
39
|
+
* groups. They shape `robots.txt` alone — the sitemap is filtered by `disallow`, every crawler's.
|
|
40
|
+
*/
|
|
41
|
+
readonly robotsGroups?: readonly SeoRobotsGroup[] | undefined;
|
|
37
42
|
/**
|
|
38
43
|
* `seo.sitemap` from `app.config.ts` (`loadSiteSettings`): the public `app/` pages to list beside
|
|
39
44
|
* the `site/` ones, where each `<lastmod>` comes from, and the sections and page size of an
|
|
@@ -237,6 +242,7 @@ export async function siteSeo(options: SiteSeoOptions): Promise<SiteSeo> {
|
|
|
237
242
|
baseUrl: options.baseUrl,
|
|
238
243
|
sitemaps: [SITEMAP_PATH],
|
|
239
244
|
...(options.disallow === undefined ? {} : { disallow: options.disallow }),
|
|
245
|
+
...(options.robotsGroups === undefined ? {} : { groups: options.robotsGroups }),
|
|
240
246
|
...(options.environment === undefined ? {} : { environment: options.environment }),
|
|
241
247
|
});
|
|
242
248
|
return { robots, sitemaps };
|
|
@@ -101,13 +101,14 @@ const origin = Bun.env.SITE_ORIGIN;
|
|
|
101
101
|
* \`Disallow: /\` and advertises no sitemap — so a preview build cannot outrank the real site.
|
|
102
102
|
*/
|
|
103
103
|
async function writeSeoFiles(report: PrerenderReport): Promise<readonly string[]> {
|
|
104
|
-
// The same
|
|
105
|
-
// (app.config.ts seo.robots.disallow and seo.sitemap).
|
|
104
|
+
// The same crawler rules and sitemap settings the running server answers with
|
|
105
|
+
// (app.config.ts seo.robots.disallow, seo.robots.groups and seo.sitemap).
|
|
106
106
|
const settings = await loadSiteSettings(root);
|
|
107
107
|
const seo = await siteSeo({
|
|
108
108
|
// The origin the pages were built against, so the sitemap and every canonical agree.
|
|
109
109
|
baseUrl: report.origin,
|
|
110
110
|
disallow: settings.disallow,
|
|
111
|
+
robotsGroups: settings.robotsGroups,
|
|
111
112
|
sitemap: settings.sitemap,
|
|
112
113
|
root,
|
|
113
114
|
pagesFor: (route) =>
|