@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.
- package/dist/collab/attach.d.ts +34 -10
- package/dist/collab/attach.js +77 -1
- package/dist/collab/attach.js.map +1 -1
- package/dist/crowi/index.js +7 -6
- package/dist/crowi/index.js.map +1 -1
- package/dist/hono/handlers/page-collab.js +4 -0
- package/dist/hono/handlers/page-collab.js.map +1 -1
- package/dist/migration/cli-api.d.ts +8 -1
- package/dist/migration/cli-api.js +2 -0
- package/dist/migration/cli-api.js.map +1 -1
- package/dist/migration/migrations/code-mask.d.ts +76 -0
- package/dist/migration/migrations/code-mask.js +224 -0
- package/dist/migration/migrations/code-mask.js.map +1 -0
- package/dist/migration/migrations/files-url-to-attachments.d.ts +65 -1
- package/dist/migration/migrations/files-url-to-attachments.js +15 -1
- package/dist/migration/migrations/files-url-to-attachments.js.map +1 -1
- package/dist/migration/migrations/page-status-default.d.ts +31 -1
- package/dist/migration/migrations/relocate-reserved-api-paths.d.ts +37 -1
- package/dist/migration/migrations/relocate-reserved-api-paths.js +11 -3
- package/dist/migration/migrations/relocate-reserved-api-paths.js.map +1 -1
- package/dist/migration/migrations/revisions-schema-unify.d.ts +35 -1
- package/dist/migration/migrations/user-unique-prepare.d.ts +55 -1
- package/dist/migration/migrations/user-unique-prepare.js +5 -0
- package/dist/migration/migrations/user-unique-prepare.js.map +1 -1
- package/dist/migration/migrations/wikilink-format.d.ts +75 -1
- package/dist/migration/migrations/wikilink-format.js +18 -1
- package/dist/migration/migrations/wikilink-format.js.map +1 -1
- package/dist/migration/migrations/wikilink-html-recover.d.ts +76 -1
- package/dist/migration/migrations/wikilink-html-recover.js +33 -7
- package/dist/migration/migrations/wikilink-html-recover.js.map +1 -1
- package/dist/migration/registry.d.ts +8 -2
- package/dist/migration/registry.js +5 -1
- package/dist/migration/registry.js.map +1 -1
- package/dist/migration/run-boot-migrations.d.ts +4 -1
- package/dist/migration/run-boot-migrations.js +55 -23
- package/dist/migration/run-boot-migrations.js.map +1 -1
- package/dist/migration/runner.js +8 -1
- package/dist/migration/runner.js.map +1 -1
- package/dist/migration/types.d.ts +57 -5
- package/dist/migration/types.js.map +1 -1
- package/dist/models/page.d.ts +20 -3
- package/dist/models/page.js +60 -5
- package/dist/models/page.js.map +1 -1
- package/dist/plugin/plugin-manager.js +1 -1
- package/dist/renderer/registry.js +1 -1
- package/dist/util/ws-token.d.ts +13 -0
- package/dist/util/ws-token.js +43 -3
- package/dist/util/ws-token.js.map +1 -1
- 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 `` 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:
|
|
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 `` 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
|
|
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":";;;;;;
|
|
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:
|
|
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:
|
|
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
|
|
26
|
-
*
|
|
27
|
-
*
|
|
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":";;;
|
|
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:
|
|
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
|
-
|
|
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
|