@caelo-cms/shared 0.10.46 → 0.10.48
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/dist/ai-tools.d.ts +1 -0
- package/dist/ai-tools.d.ts.map +1 -1
- package/dist/ai-tools.js +8 -3
- package/dist/ai-tools.js.map +1 -1
- package/dist/build-page.d.ts +2 -0
- package/dist/build-page.d.ts.map +1 -1
- package/dist/build-page.js +7 -1
- package/dist/build-page.js.map +1 -1
- package/dist/cloud-shell-install.d.ts +27 -0
- package/dist/cloud-shell-install.d.ts.map +1 -0
- package/dist/cloud-shell-install.js +38 -0
- package/dist/cloud-shell-install.js.map +1 -0
- package/dist/content.d.ts +21 -0
- package/dist/content.d.ts.map +1 -1
- package/dist/content.js +25 -0
- package/dist/content.js.map +1 -1
- package/dist/database-url.d.ts +15 -0
- package/dist/database-url.d.ts.map +1 -1
- package/dist/database-url.js +15 -0
- package/dist/database-url.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/platform-updates.d.ts +154 -0
- package/dist/platform-updates.d.ts.map +1 -0
- package/dist/platform-updates.js +238 -0
- package/dist/platform-updates.js.map +1 -0
- package/dist/subagents.d.ts +1 -1
- package/dist/version.d.ts +2 -2
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/src/ai-tools.ts +8 -3
- package/src/build-page.ts +9 -3
- package/src/cloud-shell-install.test.ts +20 -0
- package/src/cloud-shell-install.ts +47 -0
- package/src/content.ts +27 -0
- package/src/database-url.test.ts +16 -1
- package/src/database-url.ts +21 -0
- package/src/index.ts +2 -0
- package/src/page-ref-limits.test.ts +23 -0
- package/src/platform-updates.test.ts +155 -0
- package/src/platform-updates.ts +313 -0
- package/src/version.ts +1 -1
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,mCAAmC;AAEnC,MAAM,CAAC,MAAM,YAAY,GAAG,WAAW,CAAC;AAExC,cAAc,eAAe,CAAC;AAC9B,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAC9B,cAAc,iBAAiB,CAAC;AAChC,cAAc,mBAAmB,CAAC;AAClC,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,wBAAwB,CAAC;AACvC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,yBAAyB,CAAC;AACxC,cAAc,wBAAwB,CAAC;AACvC,cAAc,kBAAkB,CAAC;AACjC,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,wBAAwB,CAAC;AACvC,cAAc,oBAAoB,CAAC;AACnC,cAAc,yBAAyB,CAAC;AACxC,cAAc,mBAAmB,CAAC;AAClC,OAAO,EAKL,UAAU,EACV,aAAa,EACb,MAAM,GAGP,MAAM,aAAa,CAAC;AACrB,cAAc,YAAY,CAAC;AAC3B,cAAc,2BAA2B,CAAC;AAC1C,cAAc,eAAe,CAAC;AAC9B,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AACvC,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,UAAU,CAAC;AACzB,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,0BAA0B,CAAC;AACzC,cAAc,kBAAkB,CAAC;AACjC,cAAc,sBAAsB,CAAC;AACrC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,sBAAsB,CAAC;AACrC,cAAc,kCAAkC,CAAC;AACjD,cAAc,2BAA2B,CAAC;AAC1C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,uCAAuC,CAAC;AACtD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,sBAAsB,CAAC;AACrC,cAAc,iBAAiB,CAAC;AAChC,cAAc,mBAAmB,CAAC;AAClC,cAAc,aAAa,CAAC;AAC5B,cAAc,oBAAoB,CAAC;AACnC,cAAc,UAAU,CAAC;AACzB,OAAO,EACL,aAAa,EACb,aAAa,EAEb,cAAc,EACd,YAAY,GACb,MAAM,cAAc,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,mCAAmC;AAEnC,MAAM,CAAC,MAAM,YAAY,GAAG,WAAW,CAAC;AAExC,cAAc,eAAe,CAAC;AAC9B,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAC9B,cAAc,iBAAiB,CAAC;AAChC,cAAc,mBAAmB,CAAC;AAClC,cAAc,0BAA0B,CAAC;AACzC,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,wBAAwB,CAAC;AACvC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,yBAAyB,CAAC;AACxC,cAAc,wBAAwB,CAAC;AACvC,cAAc,kBAAkB,CAAC;AACjC,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,wBAAwB,CAAC;AACvC,cAAc,oBAAoB,CAAC;AACnC,cAAc,yBAAyB,CAAC;AACxC,cAAc,mBAAmB,CAAC;AAClC,OAAO,EAKL,UAAU,EACV,aAAa,EACb,MAAM,GAGP,MAAM,aAAa,CAAC;AACrB,cAAc,YAAY,CAAC;AAC3B,cAAc,2BAA2B,CAAC;AAC1C,cAAc,eAAe,CAAC;AAC9B,cAAc,uBAAuB,CAAC;AACtC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AACvC,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,UAAU,CAAC;AACzB,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,0BAA0B,CAAC;AACzC,cAAc,kBAAkB,CAAC;AACjC,cAAc,sBAAsB,CAAC;AACrC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,sBAAsB,CAAC;AACrC,cAAc,kCAAkC,CAAC;AACjD,cAAc,2BAA2B,CAAC;AAC1C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,uCAAuC,CAAC;AACtD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,sBAAsB,CAAC;AACrC,cAAc,iBAAiB,CAAC;AAChC,cAAc,mBAAmB,CAAC;AAClC,cAAc,aAAa,CAAC;AAC5B,cAAc,oBAAoB,CAAC;AACnC,cAAc,UAAU,CAAC;AACzB,OAAO,EACL,aAAa,EACb,aAAa,EAEb,cAAc,EACd,YAAY,GACb,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-product Caelo updates (epic #696): the vocabulary the admin (Settings →
|
|
3
|
+
* Updates, the `platform_updates.*` ops), the upgrader job and the Phase 5
|
|
4
|
+
* scheduler share. One module so the timeline the page renders, the states
|
|
5
|
+
* the job writes and the database CHECK constraints (migration 0267) agree.
|
|
6
|
+
*/
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
/** Every state of an update request (`platform_update_requests.status`). */
|
|
9
|
+
export declare const PLATFORM_UPDATE_STATUSES: readonly ["pending", "running", "succeeded", "rolled_back", "failed", "refused", "needs_cloud_shell", "cancelled"];
|
|
10
|
+
export type PlatformUpdateStatusValue = (typeof PLATFORM_UPDATE_STATUSES)[number];
|
|
11
|
+
/** States in which a request is still in flight (at most one at a time). */
|
|
12
|
+
export declare const ACTIVE_UPDATE_STATUSES: readonly PlatformUpdateStatusValue[];
|
|
13
|
+
/** Whether `status` is an end state. */
|
|
14
|
+
export declare function isFinishedUpdateStatus(status: PlatformUpdateStatusValue): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* The phases of an upgrader run, in order (`platform_update_events.phase`).
|
|
17
|
+
* `rollback` only appears when a roll had to be undone.
|
|
18
|
+
*/
|
|
19
|
+
export declare const PLATFORM_UPDATE_PHASES: readonly ["queued", "start", "verify", "assess", "backup", "health-baseline", "migrations", "roll", "health", "rollback", "record", "self-update", "finish"];
|
|
20
|
+
export type PlatformUpdatePhase = (typeof PLATFORM_UPDATE_PHASES)[number];
|
|
21
|
+
/** Severity of one timeline line. */
|
|
22
|
+
export declare const PLATFORM_UPDATE_EVENT_LEVELS: readonly ["info", "success", "warn", "error"];
|
|
23
|
+
export type PlatformUpdateEventLevel = (typeof PLATFORM_UPDATE_EVENT_LEVELS)[number];
|
|
24
|
+
/** Who started a request: the Owner's click, or the Phase 5 scheduler. */
|
|
25
|
+
export declare const PLATFORM_UPDATE_TRIGGERS: readonly ["manual", "auto"];
|
|
26
|
+
export type PlatformUpdateTrigger = (typeof PLATFORM_UPDATE_TRIGGERS)[number];
|
|
27
|
+
/**
|
|
28
|
+
* A run still `running` this long after it started died (the upgrader job's
|
|
29
|
+
* task timeout is 60 minutes). The next job run, and the next install
|
|
30
|
+
* request, end it as failed — so a crashed run never blocks updates.
|
|
31
|
+
*/
|
|
32
|
+
export declare const PLATFORM_UPDATE_STALE_MINUTES = 90;
|
|
33
|
+
/**
|
|
34
|
+
* Why a stale run was ended (shown on its timeline). Provider-neutral: the
|
|
35
|
+
* same text ends a dead Google Cloud job run and a dead self-hosted agent
|
|
36
|
+
* run, and Settings → Updates shows each install its own way to upgrade by
|
|
37
|
+
* hand (Cloud Shell there, the upgrade command on the server here).
|
|
38
|
+
*/
|
|
39
|
+
export declare const PLATFORM_UPDATE_STALE_REASON = "The upgrader stopped before it finished (its run timed out or crashed). The previous release may still serve, or the new one may be partly rolled out: open the site, and if it misbehaves run the upgrade by hand once \u2014 Settings \u2192 Updates shows how for this install.";
|
|
40
|
+
/** Longest an event message may be (DB CHECK). */
|
|
41
|
+
export declare const PLATFORM_UPDATE_EVENT_MAX = 4000;
|
|
42
|
+
/** A SemVer release version without a leading `v` (`0.11.0`, `0.11.0-rc.1`). */
|
|
43
|
+
export declare const releaseVersionSchema: z.ZodString;
|
|
44
|
+
/** How big a step from `from` to `to` is, by SemVer position. */
|
|
45
|
+
export type UpdateKind = "major" | "minor" | "patch";
|
|
46
|
+
/**
|
|
47
|
+
* Classify the step from the installed release to a newer one.
|
|
48
|
+
*
|
|
49
|
+
* @returns null when `to` is not newer than `from` or either is not SemVer.
|
|
50
|
+
* @example updateKind("0.10.45", "0.10.46") // → "patch"
|
|
51
|
+
*/
|
|
52
|
+
export declare function updateKind(from: string, to: string): UpdateKind | null;
|
|
53
|
+
/** The automatic-update modes. */
|
|
54
|
+
export declare const UPDATE_POLICY_MODES: readonly ["off", "notify", "auto"];
|
|
55
|
+
export type UpdatePolicyMode = (typeof UPDATE_POLICY_MODES)[number];
|
|
56
|
+
/** Release channels a policy may follow (only `stable` installs automatically). */
|
|
57
|
+
export declare const UPDATE_POLICY_CHANNELS: readonly ["stable", "rc", "beta", "alpha"];
|
|
58
|
+
/** Whether `tz` is an IANA time zone this runtime knows. */
|
|
59
|
+
export declare function isKnownTimeZone(tz: string): boolean;
|
|
60
|
+
/**
|
|
61
|
+
* The Owner's automatic-update policy (`platform_update_policy`), as the
|
|
62
|
+
* ops read and write it. Major releases have no toggle: they are never
|
|
63
|
+
* installed automatically before 1.0.
|
|
64
|
+
*/
|
|
65
|
+
export declare const updatePolicySchema: z.ZodObject<{
|
|
66
|
+
mode: z.ZodEnum<{
|
|
67
|
+
auto: "auto";
|
|
68
|
+
off: "off";
|
|
69
|
+
notify: "notify";
|
|
70
|
+
}>;
|
|
71
|
+
autoSecurity: z.ZodBoolean;
|
|
72
|
+
autoPatch: z.ZodBoolean;
|
|
73
|
+
autoMinor: z.ZodBoolean;
|
|
74
|
+
channel: z.ZodEnum<{
|
|
75
|
+
stable: "stable";
|
|
76
|
+
rc: "rc";
|
|
77
|
+
beta: "beta";
|
|
78
|
+
alpha: "alpha";
|
|
79
|
+
}>;
|
|
80
|
+
windowStart: z.ZodNullable<z.ZodString>;
|
|
81
|
+
windowEnd: z.ZodNullable<z.ZodString>;
|
|
82
|
+
windowTimezone: z.ZodString;
|
|
83
|
+
soakHours: z.ZodNumber;
|
|
84
|
+
}, z.core.$strict>;
|
|
85
|
+
export type UpdatePolicy = z.infer<typeof updatePolicySchema>;
|
|
86
|
+
/** The policy plus its pause state, as stored. */
|
|
87
|
+
export interface StoredUpdatePolicy extends UpdatePolicy {
|
|
88
|
+
readonly paused: boolean;
|
|
89
|
+
readonly pausedReason: string | null;
|
|
90
|
+
readonly pausedAt: string | null;
|
|
91
|
+
readonly updatedAt: string;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Whether `at` falls inside the policy's maintenance window. A window whose
|
|
95
|
+
* end is before its start spans midnight (`22:00`–`04:00`).
|
|
96
|
+
*/
|
|
97
|
+
export declare function inMaintenanceWindow(policy: Pick<UpdatePolicy, "windowStart" | "windowEnd" | "windowTimezone">, at: Date): boolean;
|
|
98
|
+
/** What the policy needs to know about a release to decide on an automatic install. */
|
|
99
|
+
export interface AutoInstallCandidate {
|
|
100
|
+
readonly kind: UpdateKind;
|
|
101
|
+
/** From the signed manifest AND auto-installable (assessAutoInstall's `kind`). */
|
|
102
|
+
readonly security: boolean;
|
|
103
|
+
/** Manifest `migrations.contract` (or a skipped contract release). */
|
|
104
|
+
readonly contractMigrations: boolean;
|
|
105
|
+
/** The release's channel. */
|
|
106
|
+
readonly channel: string;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Whether the policy lets the upgrader install `candidate` unattended at
|
|
110
|
+
* `at` — the job's check for `trigger = 'auto'` requests (the scheduler
|
|
111
|
+
* applies the soak period before it ever queues one).
|
|
112
|
+
*
|
|
113
|
+
* @returns null when allowed, else the reason in plain words
|
|
114
|
+
*/
|
|
115
|
+
export declare function autoInstallRefusal(policy: Pick<StoredUpdatePolicy, "mode" | "autoSecurity" | "autoPatch" | "autoMinor" | "paused" | "windowStart" | "windowEnd" | "windowTimezone">, candidate: AutoInstallCandidate, at: Date): string | null;
|
|
116
|
+
/**
|
|
117
|
+
* How long a verified Google sign-in authorizes one guarded action. Long
|
|
118
|
+
* enough to read the confirmation dialog, short enough that an unattended
|
|
119
|
+
* browser is useless soon after.
|
|
120
|
+
*/
|
|
121
|
+
export declare const STEP_UP_MAX_AGE_SECONDS = 600;
|
|
122
|
+
/** What a step-up is for (`step_up_challenges.purpose`). */
|
|
123
|
+
export declare const STEP_UP_PURPOSES: readonly ["platform_update", "update_policy", "step_up_client"];
|
|
124
|
+
export type StepUpPurpose = (typeof STEP_UP_PURPOSES)[number];
|
|
125
|
+
/**
|
|
126
|
+
* Self-hosted installs (#696 Phase 4c): the host agent (`caelo-agent`, a
|
|
127
|
+
* systemd timer on the install's host) polls for update requests this
|
|
128
|
+
* often and writes a heartbeat each time it looks.
|
|
129
|
+
*/
|
|
130
|
+
export declare const PLATFORM_UPDATE_AGENT_POLL_SECONDS = 60;
|
|
131
|
+
/**
|
|
132
|
+
* The admin offers "Install update" on a self-hosted install only while the
|
|
133
|
+
* agent's last heartbeat is younger than this (five missed polls): a request
|
|
134
|
+
* nobody picks up would just sit there.
|
|
135
|
+
*/
|
|
136
|
+
export declare const PLATFORM_UPDATE_AGENT_FRESH_SECONDS = 300;
|
|
137
|
+
/** The host agent's last check-in (`platform_update_agent`), as the admin reads it. */
|
|
138
|
+
export interface PlatformUpdateAgentStatus {
|
|
139
|
+
/** The Caelo release of the agent's own code. */
|
|
140
|
+
readonly agentVersion: string;
|
|
141
|
+
/** The host it runs on (its hostname). */
|
|
142
|
+
readonly host: string;
|
|
143
|
+
readonly lastSeenAt: string;
|
|
144
|
+
/**
|
|
145
|
+
* What would make an install fail on that host (cosign missing, Docker
|
|
146
|
+
* not reachable), in plain words; null when its preflight passed.
|
|
147
|
+
*/
|
|
148
|
+
readonly problem: string | null;
|
|
149
|
+
/** Whether {@link lastSeenAt} is within {@link PLATFORM_UPDATE_AGENT_FRESH_SECONDS}. */
|
|
150
|
+
readonly checkedInRecently: boolean;
|
|
151
|
+
}
|
|
152
|
+
/** Whether an agent last seen at `lastSeenAt` counts as running at `now`. */
|
|
153
|
+
export declare function agentCheckedInRecently(lastSeenAt: Date | string, now: Date): boolean;
|
|
154
|
+
//# sourceMappingURL=platform-updates.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"platform-updates.d.ts","sourceRoot":"","sources":["../src/platform-updates.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
2
|
+
/**
|
|
3
|
+
* In-product Caelo updates (epic #696): the vocabulary the admin (Settings →
|
|
4
|
+
* Updates, the `platform_updates.*` ops), the upgrader job and the Phase 5
|
|
5
|
+
* scheduler share. One module so the timeline the page renders, the states
|
|
6
|
+
* the job writes and the database CHECK constraints (migration 0267) agree.
|
|
7
|
+
*/
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
/** Every state of an update request (`platform_update_requests.status`). */
|
|
10
|
+
export const PLATFORM_UPDATE_STATUSES = [
|
|
11
|
+
"pending",
|
|
12
|
+
"running",
|
|
13
|
+
"succeeded",
|
|
14
|
+
"rolled_back",
|
|
15
|
+
"failed",
|
|
16
|
+
"refused",
|
|
17
|
+
"needs_cloud_shell",
|
|
18
|
+
"cancelled",
|
|
19
|
+
];
|
|
20
|
+
/** States in which a request is still in flight (at most one at a time). */
|
|
21
|
+
export const ACTIVE_UPDATE_STATUSES = ["pending", "running"];
|
|
22
|
+
/** Whether `status` is an end state. */
|
|
23
|
+
export function isFinishedUpdateStatus(status) {
|
|
24
|
+
return !ACTIVE_UPDATE_STATUSES.includes(status);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The phases of an upgrader run, in order (`platform_update_events.phase`).
|
|
28
|
+
* `rollback` only appears when a roll had to be undone.
|
|
29
|
+
*/
|
|
30
|
+
export const PLATFORM_UPDATE_PHASES = [
|
|
31
|
+
"queued",
|
|
32
|
+
"start",
|
|
33
|
+
"verify",
|
|
34
|
+
"assess",
|
|
35
|
+
"backup",
|
|
36
|
+
"health-baseline",
|
|
37
|
+
"migrations",
|
|
38
|
+
"roll",
|
|
39
|
+
"health",
|
|
40
|
+
"rollback",
|
|
41
|
+
"record",
|
|
42
|
+
"self-update",
|
|
43
|
+
"finish",
|
|
44
|
+
];
|
|
45
|
+
/** Severity of one timeline line. */
|
|
46
|
+
export const PLATFORM_UPDATE_EVENT_LEVELS = ["info", "success", "warn", "error"];
|
|
47
|
+
/** Who started a request: the Owner's click, or the Phase 5 scheduler. */
|
|
48
|
+
export const PLATFORM_UPDATE_TRIGGERS = ["manual", "auto"];
|
|
49
|
+
/**
|
|
50
|
+
* A run still `running` this long after it started died (the upgrader job's
|
|
51
|
+
* task timeout is 60 minutes). The next job run, and the next install
|
|
52
|
+
* request, end it as failed — so a crashed run never blocks updates.
|
|
53
|
+
*/
|
|
54
|
+
export const PLATFORM_UPDATE_STALE_MINUTES = 90;
|
|
55
|
+
/**
|
|
56
|
+
* Why a stale run was ended (shown on its timeline). Provider-neutral: the
|
|
57
|
+
* same text ends a dead Google Cloud job run and a dead self-hosted agent
|
|
58
|
+
* run, and Settings → Updates shows each install its own way to upgrade by
|
|
59
|
+
* hand (Cloud Shell there, the upgrade command on the server here).
|
|
60
|
+
*/
|
|
61
|
+
export const PLATFORM_UPDATE_STALE_REASON = "The upgrader stopped before it finished (its run timed out or crashed). The previous release may still serve, or the new one may be partly rolled out: open the site, and if it misbehaves run the upgrade by hand once — Settings → Updates shows how for this install.";
|
|
62
|
+
/** Longest an event message may be (DB CHECK). */
|
|
63
|
+
export const PLATFORM_UPDATE_EVENT_MAX = 4000;
|
|
64
|
+
const SEMVER = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/;
|
|
65
|
+
/** A SemVer release version without a leading `v` (`0.11.0`, `0.11.0-rc.1`). */
|
|
66
|
+
export const releaseVersionSchema = z
|
|
67
|
+
.string()
|
|
68
|
+
.regex(SEMVER, "expected a release version like 0.11.0 (no leading v)");
|
|
69
|
+
/**
|
|
70
|
+
* Classify the step from the installed release to a newer one.
|
|
71
|
+
*
|
|
72
|
+
* @returns null when `to` is not newer than `from` or either is not SemVer.
|
|
73
|
+
* @example updateKind("0.10.45", "0.10.46") // → "patch"
|
|
74
|
+
*/
|
|
75
|
+
export function updateKind(from, to) {
|
|
76
|
+
const a = SEMVER.exec(from);
|
|
77
|
+
const b = SEMVER.exec(to);
|
|
78
|
+
if (!a || !b)
|
|
79
|
+
return null;
|
|
80
|
+
const [fa, fb, fc] = [Number(a[1]), Number(a[2]), Number(a[3])];
|
|
81
|
+
const [ta, tb, tc] = [Number(b[1]), Number(b[2]), Number(b[3])];
|
|
82
|
+
if (ta !== fa)
|
|
83
|
+
return ta > fa ? "major" : null;
|
|
84
|
+
if (tb !== fb)
|
|
85
|
+
return tb > fb ? "minor" : null;
|
|
86
|
+
if (tc !== fc)
|
|
87
|
+
return tc > fc ? "patch" : null;
|
|
88
|
+
// Same X.Y.Z: a stable release over its own pre-release, or a later
|
|
89
|
+
// pre-release (rc.1 → rc.2), is a step.
|
|
90
|
+
if (a[4] === undefined)
|
|
91
|
+
return null;
|
|
92
|
+
if (b[4] === undefined)
|
|
93
|
+
return "patch";
|
|
94
|
+
return comparePrerelease(b[4], a[4]) > 0 ? "patch" : null;
|
|
95
|
+
}
|
|
96
|
+
/** SemVer §11 precedence of two pre-release identifier lists. */
|
|
97
|
+
function comparePrerelease(x, y) {
|
|
98
|
+
const xs = x.split(".");
|
|
99
|
+
const ys = y.split(".");
|
|
100
|
+
for (let i = 0; i < Math.max(xs.length, ys.length); i++) {
|
|
101
|
+
const p = xs[i];
|
|
102
|
+
const q = ys[i];
|
|
103
|
+
if (p === undefined)
|
|
104
|
+
return -1;
|
|
105
|
+
if (q === undefined)
|
|
106
|
+
return 1;
|
|
107
|
+
const pn = /^\d+$/.test(p);
|
|
108
|
+
const qn = /^\d+$/.test(q);
|
|
109
|
+
if (pn && qn && Number(p) !== Number(q))
|
|
110
|
+
return Number(p) - Number(q);
|
|
111
|
+
if (pn !== qn)
|
|
112
|
+
return pn ? -1 : 1;
|
|
113
|
+
if (p !== q)
|
|
114
|
+
return p < q ? -1 : 1;
|
|
115
|
+
}
|
|
116
|
+
return 0;
|
|
117
|
+
}
|
|
118
|
+
/** The automatic-update modes. */
|
|
119
|
+
export const UPDATE_POLICY_MODES = ["off", "notify", "auto"];
|
|
120
|
+
/** Release channels a policy may follow (only `stable` installs automatically). */
|
|
121
|
+
export const UPDATE_POLICY_CHANNELS = ["stable", "rc", "beta", "alpha"];
|
|
122
|
+
const WALL_CLOCK = /^([01]\d|2[0-3]):[0-5]\d$/;
|
|
123
|
+
/** Whether `tz` is an IANA time zone this runtime knows. */
|
|
124
|
+
export function isKnownTimeZone(tz) {
|
|
125
|
+
try {
|
|
126
|
+
new Intl.DateTimeFormat("en-US", { timeZone: tz });
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* The Owner's automatic-update policy (`platform_update_policy`), as the
|
|
135
|
+
* ops read and write it. Major releases have no toggle: they are never
|
|
136
|
+
* installed automatically before 1.0.
|
|
137
|
+
*/
|
|
138
|
+
export const updatePolicySchema = z
|
|
139
|
+
.object({
|
|
140
|
+
mode: z.enum(UPDATE_POLICY_MODES),
|
|
141
|
+
autoSecurity: z.boolean(),
|
|
142
|
+
autoPatch: z.boolean(),
|
|
143
|
+
autoMinor: z.boolean(),
|
|
144
|
+
channel: z.enum(UPDATE_POLICY_CHANNELS),
|
|
145
|
+
/** `HH:MM` local time in `windowTimezone`, or null with windowEnd null = any time. */
|
|
146
|
+
windowStart: z.string().regex(WALL_CLOCK, "expected HH:MM").nullable(),
|
|
147
|
+
windowEnd: z.string().regex(WALL_CLOCK, "expected HH:MM").nullable(),
|
|
148
|
+
windowTimezone: z.string().min(1).max(64).refine(isKnownTimeZone, "unknown time zone"),
|
|
149
|
+
soakHours: z.number().int().min(0).max(720),
|
|
150
|
+
})
|
|
151
|
+
.strict()
|
|
152
|
+
.refine((p) => (p.windowStart === null) === (p.windowEnd === null), {
|
|
153
|
+
message: "set both ends of the maintenance window, or neither",
|
|
154
|
+
})
|
|
155
|
+
.refine((p) => p.mode !== "auto" || p.channel === "stable", {
|
|
156
|
+
message: "automatic installs follow the stable channel only",
|
|
157
|
+
});
|
|
158
|
+
/** Minutes since local midnight of `at` in `timeZone`. */
|
|
159
|
+
function localMinutes(at, timeZone) {
|
|
160
|
+
const parts = new Intl.DateTimeFormat("en-GB", {
|
|
161
|
+
timeZone,
|
|
162
|
+
hour: "2-digit",
|
|
163
|
+
minute: "2-digit",
|
|
164
|
+
hourCycle: "h23",
|
|
165
|
+
}).formatToParts(at);
|
|
166
|
+
const n = (t) => Number(parts.find((p) => p.type === t)?.value ?? "0");
|
|
167
|
+
return n("hour") * 60 + n("minute");
|
|
168
|
+
}
|
|
169
|
+
const toMinutes = (hhmm) => Number(hhmm.slice(0, 2)) * 60 + Number(hhmm.slice(3, 5));
|
|
170
|
+
/**
|
|
171
|
+
* Whether `at` falls inside the policy's maintenance window. A window whose
|
|
172
|
+
* end is before its start spans midnight (`22:00`–`04:00`).
|
|
173
|
+
*/
|
|
174
|
+
export function inMaintenanceWindow(policy, at) {
|
|
175
|
+
if (policy.windowStart === null || policy.windowEnd === null)
|
|
176
|
+
return true;
|
|
177
|
+
const now = localMinutes(at, policy.windowTimezone);
|
|
178
|
+
const start = toMinutes(policy.windowStart);
|
|
179
|
+
const end = toMinutes(policy.windowEnd);
|
|
180
|
+
if (start === end)
|
|
181
|
+
return true;
|
|
182
|
+
return start < end ? now >= start && now < end : now >= start || now < end;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Whether the policy lets the upgrader install `candidate` unattended at
|
|
186
|
+
* `at` — the job's check for `trigger = 'auto'` requests (the scheduler
|
|
187
|
+
* applies the soak period before it ever queues one).
|
|
188
|
+
*
|
|
189
|
+
* @returns null when allowed, else the reason in plain words
|
|
190
|
+
*/
|
|
191
|
+
export function autoInstallRefusal(policy, candidate, at) {
|
|
192
|
+
if (policy.mode !== "auto")
|
|
193
|
+
return "automatic installs are switched off in the update policy";
|
|
194
|
+
if (policy.paused)
|
|
195
|
+
return "automatic installs are paused after a failed install";
|
|
196
|
+
if (candidate.channel !== "stable")
|
|
197
|
+
return "only stable releases install automatically";
|
|
198
|
+
if (candidate.contractMigrations) {
|
|
199
|
+
return "the release carries destructive database changes; the Owner installs it by hand";
|
|
200
|
+
}
|
|
201
|
+
if (candidate.kind === "major")
|
|
202
|
+
return "major releases never install automatically";
|
|
203
|
+
const allowed = (candidate.security && policy.autoSecurity) ||
|
|
204
|
+
(candidate.kind === "patch" && policy.autoPatch) ||
|
|
205
|
+
(candidate.kind === "minor" && policy.autoMinor);
|
|
206
|
+
if (!allowed) {
|
|
207
|
+
return `the policy does not install ${candidate.security ? "security fixes or " : ""}${candidate.kind} releases automatically`;
|
|
208
|
+
}
|
|
209
|
+
if (!inMaintenanceWindow(policy, at))
|
|
210
|
+
return "outside the maintenance window";
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* How long a verified Google sign-in authorizes one guarded action. Long
|
|
215
|
+
* enough to read the confirmation dialog, short enough that an unattended
|
|
216
|
+
* browser is useless soon after.
|
|
217
|
+
*/
|
|
218
|
+
export const STEP_UP_MAX_AGE_SECONDS = 600;
|
|
219
|
+
/** What a step-up is for (`step_up_challenges.purpose`). */
|
|
220
|
+
export const STEP_UP_PURPOSES = ["platform_update", "update_policy", "step_up_client"];
|
|
221
|
+
/**
|
|
222
|
+
* Self-hosted installs (#696 Phase 4c): the host agent (`caelo-agent`, a
|
|
223
|
+
* systemd timer on the install's host) polls for update requests this
|
|
224
|
+
* often and writes a heartbeat each time it looks.
|
|
225
|
+
*/
|
|
226
|
+
export const PLATFORM_UPDATE_AGENT_POLL_SECONDS = 60;
|
|
227
|
+
/**
|
|
228
|
+
* The admin offers "Install update" on a self-hosted install only while the
|
|
229
|
+
* agent's last heartbeat is younger than this (five missed polls): a request
|
|
230
|
+
* nobody picks up would just sit there.
|
|
231
|
+
*/
|
|
232
|
+
export const PLATFORM_UPDATE_AGENT_FRESH_SECONDS = 300;
|
|
233
|
+
/** Whether an agent last seen at `lastSeenAt` counts as running at `now`. */
|
|
234
|
+
export function agentCheckedInRecently(lastSeenAt, now) {
|
|
235
|
+
const seen = new Date(lastSeenAt).getTime();
|
|
236
|
+
return (Number.isFinite(seen) && now.getTime() - seen <= PLATFORM_UPDATE_AGENT_FRESH_SECONDS * 1000);
|
|
237
|
+
}
|
|
238
|
+
//# sourceMappingURL=platform-updates.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"platform-updates.js","sourceRoot":"","sources":["../src/platform-updates.ts"],"names":[],"mappings":"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"}
|
package/dist/subagents.d.ts
CHANGED
|
@@ -159,8 +159,8 @@ export declare const rebuildReturnShape: z.ZodObject<{
|
|
|
159
159
|
slug: z.ZodOptional<z.ZodString>;
|
|
160
160
|
status: z.ZodEnum<{
|
|
161
161
|
rebuilt: "rebuilt";
|
|
162
|
-
skipped: "skipped";
|
|
163
162
|
failed: "failed";
|
|
163
|
+
skipped: "skipped";
|
|
164
164
|
needs_escalation: "needs_escalation";
|
|
165
165
|
}>;
|
|
166
166
|
notes: z.ZodOptional<z.ZodString>;
|
package/dist/version.d.ts
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* Format: SemVer 2.0.0. Pre-1.0 minor bumps are breaking; post-1.0
|
|
20
20
|
* follow standard SemVer.
|
|
21
21
|
*/
|
|
22
|
-
export declare const CAELO_VERSION = "0.10.
|
|
22
|
+
export declare const CAELO_VERSION = "0.10.48";
|
|
23
23
|
/**
|
|
24
24
|
* Deprecated alias for back-compat — early P17 work spelled this
|
|
25
25
|
* `CALEO_VERSION` (typo of "Caelo"). New code should import
|
|
@@ -31,7 +31,7 @@ export declare const CAELO_VERSION = "0.10.46";
|
|
|
31
31
|
*
|
|
32
32
|
* @deprecated use CAELO_VERSION
|
|
33
33
|
*/
|
|
34
|
-
export declare const CALEO_VERSION = "0.10.
|
|
34
|
+
export declare const CALEO_VERSION = "0.10.48";
|
|
35
35
|
/**
|
|
36
36
|
* Parsed shape — major/minor/patch + optional pre-release tag.
|
|
37
37
|
* Stable interface for callers that need to feature-gate (rare —
|
package/dist/version.js
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* Format: SemVer 2.0.0. Pre-1.0 minor bumps are breaking; post-1.0
|
|
21
21
|
* follow standard SemVer.
|
|
22
22
|
*/
|
|
23
|
-
export const CAELO_VERSION = "0.10.
|
|
23
|
+
export const CAELO_VERSION = "0.10.48";
|
|
24
24
|
/**
|
|
25
25
|
* Deprecated alias for back-compat — early P17 work spelled this
|
|
26
26
|
* `CALEO_VERSION` (typo of "Caelo"). New code should import
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@caelo-cms/shared",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.48",
|
|
4
4
|
"private": false,
|
|
5
5
|
"license": "MPL-2.0",
|
|
6
6
|
"description": "Shared Zod schemas + types + the two-pass HTML composer used by every Caelo CMS workspace package. Stable enough for plugin authors and embedders to depend on directly.",
|
package/src/ai-tools.ts
CHANGED
|
@@ -174,8 +174,9 @@ export type LoadSkillToolInput = z.infer<typeof loadSkillToolInput>;
|
|
|
174
174
|
export const addModuleToolInput = z
|
|
175
175
|
.object({
|
|
176
176
|
target: z.enum(["page", "layout", "template"]),
|
|
177
|
-
/**
|
|
178
|
-
|
|
177
|
+
/** Page: id or URL path. Layout / template: slug or id. Resolved server-side. */
|
|
178
|
+
// A page path can run to ~390 chars (6 levels x 64-char slugs).
|
|
179
|
+
targetRef: z.string().min(1).max(500),
|
|
179
180
|
blockName: z.string().min(1).max(80),
|
|
180
181
|
position: positionInputSchema,
|
|
181
182
|
/** Reuse an existing module by id (any target). Mutually exclusive with the
|
|
@@ -636,7 +637,8 @@ export const createTemplateToolInput = z
|
|
|
636
637
|
export const removeModuleFromToolInput = z
|
|
637
638
|
.object({
|
|
638
639
|
target: z.enum(["page", "layout"]),
|
|
639
|
-
|
|
640
|
+
// A page path can run to ~390 chars (6 levels x 64-char slugs).
|
|
641
|
+
targetRef: z.string().min(1).max(500),
|
|
640
642
|
moduleId: z.string().uuid(),
|
|
641
643
|
})
|
|
642
644
|
.strict();
|
|
@@ -713,6 +715,9 @@ export const duplicatePageToolInput = z
|
|
|
713
715
|
newName: z.string().min(1).max(256).optional(),
|
|
714
716
|
newTitle: z.string().min(1).max(256).optional(),
|
|
715
717
|
targetTemplateId: z.string().uuid().optional(),
|
|
718
|
+
/** Where the copy sits: a parent page id, or null for the top level.
|
|
719
|
+
* Omitted = next to the source (same parent). */
|
|
720
|
+
newParentPageId: z.string().uuid().nullable().optional(),
|
|
716
721
|
})
|
|
717
722
|
.strict();
|
|
718
723
|
|
package/src/build-page.ts
CHANGED
|
@@ -195,6 +195,12 @@ export const buildPageTargetSchema = z
|
|
|
195
195
|
name: z.string().min(1).max(256).optional(),
|
|
196
196
|
templateId: z.string().uuid().optional(),
|
|
197
197
|
status: pageStatusSchema.optional(),
|
|
198
|
+
/**
|
|
199
|
+
* 0263 — create the page UNDER this parent page; its URL becomes
|
|
200
|
+
* "<parent's path>/<slug>". Create mode only (an existing page moves
|
|
201
|
+
* with `pages.move_many`).
|
|
202
|
+
*/
|
|
203
|
+
parentPageId: z.string().uuid().optional(),
|
|
198
204
|
/**
|
|
199
205
|
* Migration linkage (issue #278 flow): the `import_pages` row this
|
|
200
206
|
* page rebuilds. The op stamps `import_pages.accepted_page_id` with
|
|
@@ -208,9 +214,9 @@ export const buildPageTargetSchema = z
|
|
|
208
214
|
})
|
|
209
215
|
.strict()
|
|
210
216
|
.superRefine((page, ctx) => {
|
|
211
|
-
const createKeys = (
|
|
212
|
-
|
|
213
|
-
);
|
|
217
|
+
const createKeys = (
|
|
218
|
+
["slug", "title", "name", "templateId", "status", "parentPageId"] as const
|
|
219
|
+
).filter((k) => page[k] !== undefined);
|
|
214
220
|
if (page.pageId !== undefined) {
|
|
215
221
|
if (createKeys.length > 0) {
|
|
216
222
|
ctx.addIssue({
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
2
|
+
|
|
3
|
+
import { describe, expect, it } from "bun:test";
|
|
4
|
+
import { cloudShellInstallUrl } from "./cloud-shell-install.js";
|
|
5
|
+
|
|
6
|
+
describe("cloudShellInstallUrl", () => {
|
|
7
|
+
it("pins the release branch of the install repo and the provider's tutorial", () => {
|
|
8
|
+
expect(cloudShellInstallUrl("0.10.47", "gcp")).toBe(
|
|
9
|
+
"https://shell.cloud.google.com/cloudshell/editor?cloudshell_git_repo=https://github.com/caelo-cms/install&cloudshell_git_branch=release-v0.10.47&cloudshell_tutorial=gcp.md",
|
|
10
|
+
);
|
|
11
|
+
expect(cloudShellInstallUrl("v1.2.3", "gcp-firebase")).toContain(
|
|
12
|
+
"cloudshell_git_branch=release-v1.2.3&cloudshell_tutorial=gcp-firebase.md",
|
|
13
|
+
);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("refuses pre-releases and garbage: they have no pinned branch", () => {
|
|
17
|
+
expect(() => cloudShellInstallUrl("0.11.0-rc.1", "gcp")).toThrow(/only stable releases/);
|
|
18
|
+
expect(() => cloudShellInstallUrl("latest", "gcp")).toThrow();
|
|
19
|
+
});
|
|
20
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The "Open in Google Cloud Shell" install link (epic #696, Phase 3).
|
|
5
|
+
*
|
|
6
|
+
* The link opens github.com/caelo-cms/install on the `release-v<version>`
|
|
7
|
+
* branch that repository's track-release workflow creates for every stable
|
|
8
|
+
* release (and never moves), with the provider's walkthrough tutorial. Its
|
|
9
|
+
* `run.sh` pins and verifies exactly that release of the provisioner. One
|
|
10
|
+
* builder for every place that shows the link — README, docs, and the
|
|
11
|
+
* admin's "run this release in Cloud Shell" hint — so they can't drift.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/** The repository the Cloud Shell button clones. */
|
|
15
|
+
export const CLOUD_SHELL_INSTALL_REPO = "https://github.com/caelo-cms/install";
|
|
16
|
+
|
|
17
|
+
/** The button image Google publishes for "Open in Cloud Shell" links. */
|
|
18
|
+
export const CLOUD_SHELL_BUTTON_IMAGE = "https://gstatic.com/cloudssh/images/open-btn.svg";
|
|
19
|
+
|
|
20
|
+
/** Providers the Cloud Shell install covers (one tutorial each). */
|
|
21
|
+
export type CloudShellProvider = "gcp" | "gcp-firebase";
|
|
22
|
+
|
|
23
|
+
const STABLE = /^v?(\d+\.\d+\.\d+)$/;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The Cloud Shell install link for a stable release.
|
|
27
|
+
*
|
|
28
|
+
* @throws for a pre-release or non-SemVer version — only stable releases
|
|
29
|
+
* get a `release-v<version>` branch in the install repository
|
|
30
|
+
*
|
|
31
|
+
* @example cloudShellInstallUrl("0.10.47", "gcp")
|
|
32
|
+
* // → "https://shell.cloud.google.com/cloudshell/editor?cloudshell_git_repo=https://github.com/caelo-cms/install&cloudshell_git_branch=release-v0.10.47&cloudshell_tutorial=gcp.md"
|
|
33
|
+
*/
|
|
34
|
+
export function cloudShellInstallUrl(version: string, provider: CloudShellProvider): string {
|
|
35
|
+
const m = STABLE.exec(version);
|
|
36
|
+
if (!m) {
|
|
37
|
+
throw new Error(
|
|
38
|
+
`no Cloud Shell install link for "${version}": only stable releases are pinned in ${CLOUD_SHELL_INSTALL_REPO}`,
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
return [
|
|
42
|
+
"https://shell.cloud.google.com/cloudshell/editor",
|
|
43
|
+
`?cloudshell_git_repo=${CLOUD_SHELL_INSTALL_REPO}`,
|
|
44
|
+
`&cloudshell_git_branch=release-v${m[1]}`,
|
|
45
|
+
`&cloudshell_tutorial=${provider}.md`,
|
|
46
|
+
].join("");
|
|
47
|
+
}
|
package/src/content.ts
CHANGED
|
@@ -563,6 +563,33 @@ export const pageCreateSchema = z
|
|
|
563
563
|
* left the first page unpublished.)
|
|
564
564
|
*/
|
|
565
565
|
status: pageStatusSchema.optional(),
|
|
566
|
+
/**
|
|
567
|
+
* 0263 — nested page URLs: create the page under this parent; its URL
|
|
568
|
+
* becomes "<parent's path>/<slug>". Omit (or null) for a top-level page.
|
|
569
|
+
*/
|
|
570
|
+
parentPageId: z.string().uuid().nullable().optional(),
|
|
571
|
+
})
|
|
572
|
+
.strict();
|
|
573
|
+
|
|
574
|
+
/**
|
|
575
|
+
* 0263 — `pages.move_many`: reparent pages (their whole subtrees move
|
|
576
|
+
* with them) and optionally rename them in the same step.
|
|
577
|
+
*/
|
|
578
|
+
export const pageMoveSchema = z
|
|
579
|
+
.object({
|
|
580
|
+
pageId: z.string().uuid(),
|
|
581
|
+
/** The new parent page, or null for the top level. */
|
|
582
|
+
parentPageId: z.string().uuid().nullable(),
|
|
583
|
+
/** Rename while moving — required when the target URL is taken. */
|
|
584
|
+
newSlug: slugSchema.optional(),
|
|
585
|
+
})
|
|
586
|
+
.strict();
|
|
587
|
+
|
|
588
|
+
export const pagesMoveManySchema = z
|
|
589
|
+
.object({
|
|
590
|
+
moves: z.array(pageMoveSchema).min(1).max(200),
|
|
591
|
+
/** `'skip'` suppresses the 301s from the old URLs (operator asked for 404s). */
|
|
592
|
+
redirectFromOld: z.enum(["auto", "skip"]).default("auto"),
|
|
566
593
|
})
|
|
567
594
|
.strict();
|
|
568
595
|
|