@crowi/api 2.0.0-alpha.2 → 2.0.0-alpha.3

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.
Files changed (49) hide show
  1. package/dist/collab/attach.d.ts +34 -10
  2. package/dist/collab/attach.js +77 -1
  3. package/dist/collab/attach.js.map +1 -1
  4. package/dist/crowi/index.js +7 -6
  5. package/dist/crowi/index.js.map +1 -1
  6. package/dist/hono/handlers/page-collab.js +4 -0
  7. package/dist/hono/handlers/page-collab.js.map +1 -1
  8. package/dist/migration/cli-api.d.ts +8 -1
  9. package/dist/migration/cli-api.js +2 -0
  10. package/dist/migration/cli-api.js.map +1 -1
  11. package/dist/migration/migrations/code-mask.d.ts +76 -0
  12. package/dist/migration/migrations/code-mask.js +224 -0
  13. package/dist/migration/migrations/code-mask.js.map +1 -0
  14. package/dist/migration/migrations/files-url-to-attachments.d.ts +65 -1
  15. package/dist/migration/migrations/files-url-to-attachments.js +15 -1
  16. package/dist/migration/migrations/files-url-to-attachments.js.map +1 -1
  17. package/dist/migration/migrations/page-status-default.d.ts +31 -1
  18. package/dist/migration/migrations/relocate-reserved-api-paths.d.ts +37 -1
  19. package/dist/migration/migrations/relocate-reserved-api-paths.js +11 -3
  20. package/dist/migration/migrations/relocate-reserved-api-paths.js.map +1 -1
  21. package/dist/migration/migrations/revisions-schema-unify.d.ts +35 -1
  22. package/dist/migration/migrations/user-unique-prepare.d.ts +55 -1
  23. package/dist/migration/migrations/user-unique-prepare.js +5 -0
  24. package/dist/migration/migrations/user-unique-prepare.js.map +1 -1
  25. package/dist/migration/migrations/wikilink-format.d.ts +75 -1
  26. package/dist/migration/migrations/wikilink-format.js +18 -1
  27. package/dist/migration/migrations/wikilink-format.js.map +1 -1
  28. package/dist/migration/migrations/wikilink-html-recover.d.ts +76 -1
  29. package/dist/migration/migrations/wikilink-html-recover.js +33 -7
  30. package/dist/migration/migrations/wikilink-html-recover.js.map +1 -1
  31. package/dist/migration/registry.d.ts +8 -2
  32. package/dist/migration/registry.js +5 -1
  33. package/dist/migration/registry.js.map +1 -1
  34. package/dist/migration/run-boot-migrations.d.ts +4 -1
  35. package/dist/migration/run-boot-migrations.js +55 -23
  36. package/dist/migration/run-boot-migrations.js.map +1 -1
  37. package/dist/migration/runner.js +8 -1
  38. package/dist/migration/runner.js.map +1 -1
  39. package/dist/migration/types.d.ts +57 -5
  40. package/dist/migration/types.js.map +1 -1
  41. package/dist/models/page.d.ts +20 -3
  42. package/dist/models/page.js +60 -5
  43. package/dist/models/page.js.map +1 -1
  44. package/dist/plugin/plugin-manager.js +1 -1
  45. package/dist/renderer/registry.js +1 -1
  46. package/dist/util/ws-token.d.ts +13 -0
  47. package/dist/util/ws-token.js +43 -3
  48. package/dist/util/ws-token.js.map +1 -1
  49. package/package.json +3 -3
@@ -0,0 +1,224 @@
1
+ "use strict";
2
+ /**
3
+ * Markdown code-region segmentation for the body-rewrite migrations.
4
+ *
5
+ * `WIKILINK_DETECTION_REGEX` (and the sibling `files-url` / `html-recover`
6
+ * probes) scan a page's *raw* body with no Markdown awareness, so `</…>`
7
+ * tokens written as code examples — inside a ```` ``` ```` fence or `` `…` ``
8
+ * inline span — get misdetected as v1 wikilinks and rewritten, corrupting the
9
+ * code (e.g. ```` ```tsx\n</AppShell>\n``` ```` → `[[/AppShell]]`).
10
+ *
11
+ * The renderer already solves this at the AST level — `renderer/core/
12
+ * wikilinks.ts` skips `node.type === 'code' || 'inlineCode'`. The migration
13
+ * layer cannot import the renderer pipeline (jiti/ESM), so this is a
14
+ * string-level parallel of that intent: split the body into ordered
15
+ * `code` / non-`code` segments so callers can run their rewrite **only on
16
+ * the non-`code` segments** and re-join in original order.
17
+ *
18
+ * Why segmentation and NOT a same-length `\x00` fill + slice restore: a fill
19
+ * pass keeps the body length-invariant, but the rewrite step changes the
20
+ * length of the *non-code* text, so a positional restore drifts. Worse, three
21
+ * structural failures cause silent data loss — adjacent code regions whose
22
+ * fills touch merge into one `/\x00+/g` match (one region is dropped), a
23
+ * fence-first stash with text-order restore swaps two regions, and a `\x00`
24
+ * already present in pasted/binary content collides with the sentinel. By
25
+ * returning an ordered segment list with no restore step, all three are
26
+ * avoided *by construction*: adjacent regions are distinct segments (no
27
+ * merge), order is preserved by the list (no swap), and no sentinel is ever
28
+ * introduced (no collision).
29
+ *
30
+ * Coverage: fenced code blocks (CommonMark §4.5, backtick **and** tilde) and
31
+ * inline code spans (§6.1). Indented code blocks (4-space / 1-tab, §4.4) are
32
+ * deliberately NOT covered: a flat "4-space line = code" rule over-suppresses
33
+ * (mdast treats a 4-space paragraph/list continuation line as text/html, i.e.
34
+ * rewritable), so an indented-code `</…>` token stays rewritable — an accepted
35
+ * known divergence from the renderer (see the spec's "out of scope").
36
+ */
37
+ Object.defineProperty(exports, "__esModule", { value: true });
38
+ exports.splitCodeSegments = splitCodeSegments;
39
+ exports.rewriteOutsideCode = rewriteOutsideCode;
40
+ /** A fenced-code-block opener: ≥3 backticks or ≥3 tildes with 0–3 leading spaces. */
41
+ const FENCE_OPEN_REGEX = /^( {0,3})(`{3,}|~{3,})/;
42
+ /**
43
+ * Split `body` into ordered `code` / non-`code` segments.
44
+ *
45
+ * Fenced blocks are matched line-by-line (a fence runs to its closing fence of
46
+ * the same char, ≥ the opening length; an unclosed fence runs to EOF). Within
47
+ * non-fence text, inline code spans are matched by the CommonMark §6.1 rule: an
48
+ * opening run of N backticks closed by the next run of *exactly* N backticks.
49
+ * An unmatched backtick run is left as literal non-code text.
50
+ *
51
+ * Pure: no I/O, no mutation of inputs. Concatenating the returned segments'
52
+ * `text` reproduces `body` byte-for-byte.
53
+ */
54
+ function splitCodeSegments(body) {
55
+ const segments = [];
56
+ // Buffer of pending non-code text; flushed (after inline-span extraction)
57
+ // whenever a fence boundary or EOF is reached.
58
+ let pendingText = '';
59
+ const flushPendingText = () => {
60
+ if (pendingText.length === 0)
61
+ return;
62
+ for (const seg of splitInlineCode(pendingText))
63
+ segments.push(seg);
64
+ pendingText = '';
65
+ };
66
+ // Walk the body line by line, preserving each line's trailing newline so the
67
+ // re-join is byte-identical. `lineStart` indexes the current line's first
68
+ // char in `body`.
69
+ let lineStart = 0;
70
+ while (lineStart < body.length) {
71
+ let lineEnd = body.indexOf('\n', lineStart);
72
+ if (lineEnd === -1)
73
+ lineEnd = body.length;
74
+ else
75
+ lineEnd += 1; // include the newline
76
+ const line = body.slice(lineStart, lineEnd);
77
+ // No `\r?\n` strip on the opener: `FENCE_OPEN_REGEX` is `^`-anchored and
78
+ // carries no end-of-line pattern, so a trailing newline never affects the
79
+ // match (unlike the closing-fence test below, where the strip is
80
+ // load-bearing — see `closeRegex.test(...)`).
81
+ const fenceMatch = FENCE_OPEN_REGEX.exec(line);
82
+ if (fenceMatch) {
83
+ // A fenced code block opens here. Flush text accumulated before it, then
84
+ // consume lines up to (and including) the matching closing fence or EOF.
85
+ flushPendingText();
86
+ // `fenceChar` is exactly one of `` ` `` / `~` because group 2 of
87
+ // `FENCE_OPEN_REGEX` is `` (`{3,}|~{3,}) ``, so it can be inlined into the
88
+ // close pattern verbatim (no per-char ternary needed).
89
+ const fenceChar = fenceMatch[2][0];
90
+ const fenceLen = fenceMatch[2].length;
91
+ const closeRegex = new RegExp(`^ {0,3}${fenceChar}{${fenceLen},}[ \\t]*$`);
92
+ // Consume lines up to (and including) the closing fence; an unclosed
93
+ // fence runs to EOF. Either way the whole span is one code segment.
94
+ let fenceText = line;
95
+ let cursor = lineEnd;
96
+ while (cursor < body.length) {
97
+ let nextEnd = body.indexOf('\n', cursor);
98
+ if (nextEnd === -1)
99
+ nextEnd = body.length;
100
+ else
101
+ nextEnd += 1;
102
+ const nextLine = body.slice(cursor, nextEnd);
103
+ fenceText += nextLine;
104
+ cursor = nextEnd;
105
+ // The `\r?\n` strip here is LOAD-BEARING — do NOT remove it. `closeRegex`
106
+ // ends in `[ \t]*$` and is built with no `m` flag, so its `$` anchors at
107
+ // end-of-string. `nextLine` still carries its trailing newline (we keep
108
+ // it for a byte-identical re-join), and `[ \t]*` cannot consume a `\n`,
109
+ // so without the strip the close test fails on every newline-terminated
110
+ // fence line (CRLF or LF). The fence would then run to EOF, wrongly
111
+ // swallowing text that follows the closing fence into the code segment.
112
+ // (An EOF-only fence happens to pass either way because `$` also matches
113
+ // end-of-string — which is why a content-after-close-fence test is what
114
+ // actually guards this. See code-mask.test.ts.)
115
+ if (closeRegex.test(nextLine.replace(/\r?\n$/, '')))
116
+ break;
117
+ }
118
+ segments.push({ code: true, text: fenceText });
119
+ lineStart = cursor;
120
+ continue;
121
+ }
122
+ pendingText += line;
123
+ lineStart = lineEnd;
124
+ }
125
+ flushPendingText();
126
+ // The common "no code at all" body yields exactly one `{ code: false }`
127
+ // segment (the whole body, via `splitInlineCode`'s trailing `pushPlain`), so
128
+ // the caller's by-reference cheap-skip still works on the re-join.
129
+ return segments;
130
+ }
131
+ /**
132
+ * Apply `fn` only to the non-code segments of `body`, re-joining in original
133
+ * order. Returns `body` BY REFERENCE when `fn` left every non-code segment
134
+ * unchanged (preserving the caller's `result === body` cheap-skip for
135
+ * isPending). Code regions (fenced + inline, per `splitCodeSegments`) are
136
+ * passed through byte-identical.
137
+ *
138
+ * `fn` may be impure (carry an accumulator in a closure) — e.g. a `body.replace`
139
+ * callback that pushes occurrences / bumps counts. It is invoked once per
140
+ * non-code segment in document order; the code segments between them are never
141
+ * passed to `fn`, so a token written as a code example stays untouched and is
142
+ * never misdetected.
143
+ *
144
+ * This is the shared entry point for all three body-rewrite migrations
145
+ * (`wikilink-format`, `files-url-to-attachments`, `wikilink-html-recover`); the
146
+ * segment-and-rewrite scheme (vs. a fill/restore one) is justified in this
147
+ * file's header. The by-reference cheap-skip is owned here, via per-segment
148
+ * `!==` identity tracking, so a caller that still keeps its own accumulator
149
+ * guard stays consistent (both agree on "changed").
150
+ */
151
+ function rewriteOutsideCode(body, fn) {
152
+ let changed = false;
153
+ const out = splitCodeSegments(body).map((seg) => {
154
+ if (seg.code)
155
+ return seg.text;
156
+ const next = fn(seg.text);
157
+ if (next !== seg.text)
158
+ changed = true;
159
+ return next;
160
+ });
161
+ return changed ? out.join('') : body;
162
+ }
163
+ /**
164
+ * Split a run of non-fence text into inline-code (`code: true`) and plain
165
+ * (`code: false`) segments per CommonMark §6.1: an opening run of N backticks
166
+ * closed by the next run of *exactly* N backticks is a code span. An unmatched
167
+ * backtick run stays literal (folded into the surrounding plain segment).
168
+ */
169
+ function splitInlineCode(text) {
170
+ const segments = [];
171
+ let plainStart = 0;
172
+ let i = 0;
173
+ const pushPlain = (end) => {
174
+ if (end > plainStart)
175
+ segments.push({ code: false, text: text.slice(plainStart, end) });
176
+ };
177
+ while (i < text.length) {
178
+ if (text[i] !== '`') {
179
+ i += 1;
180
+ continue;
181
+ }
182
+ // Measure the opening backtick run length.
183
+ const openStart = i;
184
+ let runLen = 0;
185
+ while (i < text.length && text[i] === '`') {
186
+ runLen += 1;
187
+ i += 1;
188
+ }
189
+ // Look for a closing run of exactly `runLen` backticks.
190
+ let scan = i;
191
+ let closeStart = -1;
192
+ while (scan < text.length) {
193
+ if (text[scan] !== '`') {
194
+ scan += 1;
195
+ continue;
196
+ }
197
+ let closeLen = 0;
198
+ const thisRunStart = scan;
199
+ while (scan < text.length && text[scan] === '`') {
200
+ closeLen += 1;
201
+ scan += 1;
202
+ }
203
+ if (closeLen === runLen) {
204
+ closeStart = thisRunStart;
205
+ break;
206
+ }
207
+ // A run of a different length is not a valid closer; keep scanning.
208
+ }
209
+ if (closeStart === -1) {
210
+ // Unmatched opener — leave the backtick run as literal text and continue
211
+ // scanning from just after it (so a later valid span is still found).
212
+ continue;
213
+ }
214
+ // Emit the plain text before the span, then the span itself.
215
+ pushPlain(openStart);
216
+ const spanEnd = closeStart + runLen;
217
+ segments.push({ code: true, text: text.slice(openStart, spanEnd) });
218
+ plainStart = spanEnd;
219
+ i = spanEnd;
220
+ }
221
+ pushPlain(text.length);
222
+ return segments;
223
+ }
224
+ //# sourceMappingURL=code-mask.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"code-mask.js","sourceRoot":"","sources":["../../../src/migration/migrations/code-mask.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,4 @@
1
+ import type { MigrationContext } from '../types';
1
2
  /**
2
3
  * Per-body breakdown of what a rewrite touched / skipped, returned alongside
3
4
  * the rewritten body so callers walk the regex once.
@@ -18,6 +19,14 @@ export interface FilesUrlRewriteCounts {
18
19
  *
19
20
  * `origins` is the self-host allow-list (`getAppOrigins()`); pass `[]` to
20
21
  * disable rule 2 (CLIENT_URL / BASE_URL unset).
22
+ *
23
+ * Code regions (fenced blocks + inline spans) are excluded via the shared
24
+ * `rewriteOutsideCode` primitive: a `/files/<id>` written as a code example is
25
+ * left byte-identical (never rewritten, never counted) so it cannot be
26
+ * corrupted, nor falsely report the migration as pending. Indented code is an
27
+ * accepted divergence (still rewritten). AD-2 (an `![alt](url)` whose alt text
28
+ * contains an inline-code span straddles a segment boundary, leaving its URL
29
+ * unrewritten) is documented in the spec — an accepted false-negative.
21
30
  */
22
31
  export declare function rewriteFilesUrls(body: string, origins: readonly string[]): {
23
32
  body: string;
@@ -32,4 +41,59 @@ export declare function rewriteFilesUrls(body: string, origins: readonly string[
32
41
  * Shared by `isPending` and `collectRewritablePages`.
33
42
  */
34
43
  export declare function bodyHasRewritableFilesUrl(body: string, origins: readonly string[]): boolean;
35
- export declare const filesUrlToAttachments: import("../types").MigrationDefinition;
44
+ export declare const filesUrlToAttachments: {
45
+ id: string;
46
+ fromVersion: string;
47
+ toVersion: string;
48
+ layer: "preflight";
49
+ severity: "cosmetic";
50
+ description: string;
51
+ /**
52
+ * Pending verdict = at least one published page whose **current** revision
53
+ * body still contains a rewritable v1 file URL. Mirrors `wikilink-format`:
54
+ *
55
+ * 1. The verdict uses the **full rule** (`bodyHasRewritableFilesUrl`), not
56
+ * the bare substring. `/files/` could appear in an external URL we leave
57
+ * alone (rule 3); counting that would report pending forever after apply
58
+ * (apply only rewrites self URLs), deadlocking boot under preflight +
59
+ * `block`. The full rule reports false once only external `/files/`
60
+ * references remain, so boot clears.
61
+ * 2. We scan only each page's **current** revision, never the whole
62
+ * `revisions` collection — historical revisions keep their original
63
+ * `/files/<id>` text forever (immutable), so a collection-wide scan
64
+ * would pend permanently after any such page ever existed.
65
+ *
66
+ * Short-circuits at the first rewritable page.
67
+ */
68
+ isPending: (ctx: MigrationContext) => Promise<boolean>;
69
+ /**
70
+ * Full scan for `plan`: affected page count plus the rewrite breakdown
71
+ * (relative / self-host absolute rewrites + external skips, the last
72
+ * aggregated across every `/files/` page — see `scanFilesUrls`). Not called
73
+ * at boot.
74
+ */
75
+ detect: (ctx: MigrationContext) => Promise<{
76
+ summary: string;
77
+ counts: {
78
+ pages: number;
79
+ rewrites: number;
80
+ relative: number;
81
+ selfHostAbsolute: number;
82
+ externalSkipped: number;
83
+ };
84
+ }>;
85
+ stages: {
86
+ name: string;
87
+ fn: (ctx: MigrationContext) => Promise<{
88
+ name: string;
89
+ transformed: number;
90
+ stats: {
91
+ wouldRewrite: number;
92
+ };
93
+ } | {
94
+ name: string;
95
+ transformed: number;
96
+ stats?: undefined;
97
+ }>;
98
+ }[];
99
+ };
@@ -10,6 +10,7 @@ const linkDetector_1 = __importDefault(require("../../util/linkDetector"));
10
10
  const page_1 = require("../../models/page");
11
11
  const helpers_1 = require("../helpers");
12
12
  const types_1 = require("../types");
13
+ const code_mask_1 = require("./code-mask");
13
14
  /**
14
15
  * v1 → v2 — `files-url-to-attachments` (preflight layer).
15
16
  *
@@ -110,10 +111,18 @@ function isSelfHostOrigin(schemeAndHost, origins) {
110
111
  *
111
112
  * `origins` is the self-host allow-list (`getAppOrigins()`); pass `[]` to
112
113
  * disable rule 2 (CLIENT_URL / BASE_URL unset).
114
+ *
115
+ * Code regions (fenced blocks + inline spans) are excluded via the shared
116
+ * `rewriteOutsideCode` primitive: a `/files/<id>` written as a code example is
117
+ * left byte-identical (never rewritten, never counted) so it cannot be
118
+ * corrupted, nor falsely report the migration as pending. Indented code is an
119
+ * accepted divergence (still rewritten). AD-2 (an `![alt](url)` whose alt text
120
+ * contains an inline-code span straddles a segment boundary, leaving its URL
121
+ * unrewritten) is documented in the spec — an accepted false-negative.
113
122
  */
114
123
  function rewriteFilesUrls(body, origins) {
115
124
  const counts = emptyCounts();
116
- const rewritten = body.replace(buildFilesUrlRegex(), (whole, head, schemeAndHost, id, rest) => {
125
+ const rewriteSegment = (text) => text.replace(buildFilesUrlRegex(), (whole, head, schemeAndHost, id, rest) => {
117
126
  if (schemeAndHost === undefined) {
118
127
  // Rule 1 — relative `/files/<id>` is unconditionally this site.
119
128
  counts.relative += 1;
@@ -128,6 +137,7 @@ function rewriteFilesUrls(body, origins) {
128
137
  counts.externalSkipped += 1;
129
138
  return whole;
130
139
  });
140
+ const rewritten = (0, code_mask_1.rewriteOutsideCode)(body, rewriteSegment);
131
141
  const changed = counts.relative > 0 || counts.selfHostAbsolute > 0;
132
142
  return { body: changed ? rewritten : body, counts };
133
143
  }
@@ -199,6 +209,10 @@ exports.filesUrlToAttachments = (0, types_1.defineMigration)({
199
209
  fromVersion: '1.x',
200
210
  toVersion: '2.1',
201
211
  layer: 'preflight',
212
+ // Body URL rewrite only; a `/files/<id>` → 302 fallback keeps pages working
213
+ // even unapplied. `isPending` scans the live corpus → re-triggers on new
214
+ // content. Cosmetic.
215
+ severity: 'cosmetic',
202
216
  description: 'Rewrite v1 /files/<id> attachment URLs to v2 /api/v2/attachments/<id>',
203
217
  /**
204
218
  * Pending verdict = at least one published page whose **current** revision
@@ -1 +1 @@
1
- {"version":3,"file":"files-url-to-attachments.js","sourceRoot":"","sources":["../../../src/migration/migrations/files-url-to-attachments.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"files-url-to-attachments.js","sourceRoot":"","sources":["../../../src/migration/migrations/files-url-to-attachments.ts"],"names":[],"mappings":"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"}
@@ -22,4 +22,34 @@
22
22
  * `draft` back to `published`, which would violate the one-way
23
23
  * transition rule.
24
24
  */
25
- export declare const pageStatusDefault: import("../types").MigrationDefinition;
25
+ export declare const pageStatusDefault: {
26
+ id: string;
27
+ fromVersion: string;
28
+ toVersion: string;
29
+ layer: "boot";
30
+ description: string;
31
+ /**
32
+ * Cheap, index-backed probe: `Page.status` is declared `index: true`
33
+ * (models/page.ts), so a single `findOne({ status: null })` lookup is
34
+ * covered by that index and never scans the collection. Pending iff at
35
+ * least one legacy page is still missing a status.
36
+ */
37
+ isPending: (ctx: import("../types").MigrationContext) => Promise<boolean>;
38
+ /**
39
+ * Optional `plan` detail: count the remaining legacy rows. Not called at
40
+ * boot — only `isPending` runs there.
41
+ */
42
+ detect: (ctx: import("../types").MigrationContext) => Promise<{
43
+ summary: string;
44
+ counts: {
45
+ remaining: number;
46
+ };
47
+ }>;
48
+ stages: {
49
+ name: string;
50
+ fn: (ctx: import("../types").MigrationContext) => Promise<{
51
+ name: string;
52
+ transformed: number;
53
+ }>;
54
+ }[];
55
+ };
@@ -1,3 +1,39 @@
1
+ import type { MigrationContext } from '../types';
1
2
  /** `/api` → `/api-legacy`, `/api/foo` → `/api-legacy/foo`, `/api/` → `/api-legacy/`. */
2
3
  export declare function relocatedApiPath(oldPath: string): string;
3
- export declare const relocateReservedApiPaths: import("../types").MigrationDefinition;
4
+ export declare const relocateReservedApiPaths: {
5
+ id: string;
6
+ fromVersion: string;
7
+ toVersion: string;
8
+ layer: "preflight";
9
+ severity: "cosmetic";
10
+ description: string;
11
+ /**
12
+ * Pending iff any page still lives under `/api/*`. The anchored regex on
13
+ * the unique-indexed `path` field lets the planner use the index for the
14
+ * `/api` literal prefix, so this stays an index-assisted existence probe
15
+ * rather than a full-collection scan (§4.2.1).
16
+ */
17
+ isPending: (ctx: MigrationContext) => Promise<boolean>;
18
+ /** Full scan for `plan`: count the pages that would move. Not called at boot. */
19
+ detect: (ctx: MigrationContext) => Promise<{
20
+ summary: string;
21
+ counts: {
22
+ pages: number;
23
+ };
24
+ }>;
25
+ stages: {
26
+ name: string;
27
+ fn: (ctx: MigrationContext) => Promise<{
28
+ name: string;
29
+ transformed: number;
30
+ stats: {
31
+ wouldMove: number;
32
+ };
33
+ } | {
34
+ name: string;
35
+ transformed: number;
36
+ stats?: undefined;
37
+ }>;
38
+ }[];
39
+ };
@@ -22,9 +22,12 @@ const types_1 = require("../types");
22
22
  * so `api-legacy` does not collide). A pre-existing page at the relocation
23
23
  * target is avoided by appending a `-N` suffix.
24
24
  *
25
- * It is `preflight` because it rewrites user-visible page paths: boot
26
- * blocks until an operator runs `crowi-admin migrate apply` in a
27
- * maintenance window. The move is done with plain `updateOne` /
25
+ * It is `preflight` because it rewrites user-visible page paths, so it runs
26
+ * explicitly from `crowi-admin migrate apply` in a maintenance window rather
27
+ * than auto-applying at boot. Its `severity` is `cosmetic`: a stranded
28
+ * `/api/*` page is a display / availability issue, not an autoIndex E11000
29
+ * hazard, so a pending probe only warns and never refuses boot (§4.2.7
30
+ * amendment). The move is done with plain `updateOne` /
28
31
  * `updateMany` on the Page + Revision collections rather than
29
32
  * `Page.rename`, deliberately bypassing the `pageEvent('update')` chain
30
33
  * (mention dispatch / render-cache / backlink) — a path-only relocation of
@@ -73,6 +76,11 @@ exports.relocateReservedApiPaths = (0, types_1.defineMigration)({
73
76
  fromVersion: '1.x',
74
77
  toVersion: '2.0',
75
78
  layer: 'preflight',
79
+ // Path move only; no E11000 hazard. `isPending` is `Page.exists({path:/^\/api/})`
80
+ // and stabilises post-apply (does not re-trigger). Unapplied means old
81
+ // `/api/*` pages 404, which is a recommended pre-go-live fix, not a
82
+ // data-integrity risk. Cosmetic.
83
+ severity: 'cosmetic',
76
84
  description: 'Relocate v1 pages out of the v2-reserved /api namespace into /api-legacy',
77
85
  /**
78
86
  * Pending iff any page still lives under `/api/*`. The anchored regex on
@@ -1 +1 @@
1
- {"version":3,"file":"relocate-reserved-api-paths.js","sourceRoot":"","sources":["../../../src/migration/migrations/relocate-reserved-api-paths.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"relocate-reserved-api-paths.js","sourceRoot":"","sources":["../../../src/migration/migrations/relocate-reserved-api-paths.ts"],"names":[],"mappings":"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"}
@@ -30,4 +30,38 @@
30
30
  * already omits an undefined `contributors`). `renderedAst` is the
31
31
  * `rebuild renderer` task's responsibility per RFC-0008.
32
32
  */
33
- export declare const revisionsSchemaUnify: import("../types").MigrationDefinition;
33
+ export declare const revisionsSchemaUnify: {
34
+ id: string;
35
+ fromVersion: string;
36
+ toVersion: string;
37
+ layer: "boot";
38
+ description: string;
39
+ /**
40
+ * Cheap, index-backed probe: `Revision.type` is declared `index: true`
41
+ * (models/revision.ts), so `findOne({ type: null })` is covered by that
42
+ * index and never scans the collection. `{ type: null }` matches both a
43
+ * missing field and an explicit `null` in MongoDB query semantics, so a
44
+ * single condition covers every legacy shape. Pending iff at least one
45
+ * legacy revision is still missing a type. After apply (and because new
46
+ * revisions are filled by the schema `default`) this stays false — no
47
+ * permanent boot block.
48
+ */
49
+ isPending: (ctx: import("../types").MigrationContext) => Promise<boolean>;
50
+ /**
51
+ * Optional `plan` detail: count the remaining legacy rows. Not called at
52
+ * boot — only `isPending` runs there. Index-backed count on `type`.
53
+ */
54
+ detect: (ctx: import("../types").MigrationContext) => Promise<{
55
+ summary: string;
56
+ counts: {
57
+ remaining: number;
58
+ };
59
+ }>;
60
+ stages: {
61
+ name: string;
62
+ fn: (ctx: import("../types").MigrationContext) => Promise<{
63
+ name: string;
64
+ transformed: number;
65
+ }>;
66
+ }[];
67
+ };
@@ -1 +1,55 @@
1
- export declare const userUniquePrepare: import("../types").MigrationDefinition;
1
+ import type { MigrationContext } from '../types';
2
+ export declare const userUniquePrepare: {
3
+ id: string;
4
+ fromVersion: string;
5
+ toVersion: string;
6
+ layer: "preflight";
7
+ severity: "blocking";
8
+ description: string;
9
+ /**
10
+ * Pending iff a plain unique index build would fail E11000. Two residual
11
+ * conditions (both folded to the index collation):
12
+ *
13
+ * - a living-vs-living duplicate on username or email, or
14
+ * - a not-yet-tombstoned DELETED user colliding with a living user.
15
+ *
16
+ * Conservative (§6.2): once `dedup-*` + `tombstone-deleted` run, every group
17
+ * is resolved and this returns false, so boot under preflight+block clears
18
+ * (no permanent block). Short-circuits at the first collision found.
19
+ */
20
+ isPending: (ctx: MigrationContext) => Promise<boolean>;
21
+ /** Full-scan report for `plan`: duplicate group counts + tombstone targets. */
22
+ detect: (ctx: MigrationContext) => Promise<{
23
+ summary: string;
24
+ counts: {
25
+ usernameGroups: number;
26
+ emailGroups: number;
27
+ tombstoneTargets: number;
28
+ };
29
+ }>;
30
+ stages: ({
31
+ name: string;
32
+ fn: (ctx: MigrationContext) => Promise<{
33
+ name: string;
34
+ transformed: number;
35
+ stats: {
36
+ groups: number;
37
+ reassigned: Record<string, number>;
38
+ deletedConflicting: number;
39
+ };
40
+ }>;
41
+ } | {
42
+ name: string;
43
+ fn: (ctx: MigrationContext) => Promise<{
44
+ name: string;
45
+ transformed: number;
46
+ stats: {
47
+ wouldTombstone: number;
48
+ };
49
+ } | {
50
+ name: string;
51
+ transformed: number;
52
+ stats?: undefined;
53
+ }>;
54
+ })[];
55
+ };
@@ -109,6 +109,11 @@ exports.userUniquePrepare = (0, types_1.defineMigration)({
109
109
  fromVersion: '1.x',
110
110
  toVersion: '2.0',
111
111
  layer: 'preflight',
112
+ // The ONLY blocking migration: dedups so the unique index can build without
113
+ // E11000 (RFC §9/§11). Booting unapplied risks an autoIndex failure, so a
114
+ // pending verdict must refuse boot under the `block` policy. Do NOT
115
+ // reclassify to cosmetic — that would re-expose E11000.
116
+ severity: 'blocking',
112
117
  description: 'Deduplicate users (unique index via autoIndex)',
113
118
  /**
114
119
  * Pending iff a plain unique index build would fail E11000. Two residual