newo 3.7.2 → 3.7.4
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/CHANGELOG.md +33 -1
- package/README.md +25 -0
- package/dist/domain/strategies/sync/V2ProjectSyncStrategy.d.ts +34 -0
- package/dist/domain/strategies/sync/V2ProjectSyncStrategy.js +316 -5
- package/dist/sync/json-attr-utils.d.ts +40 -11
- package/dist/sync/json-attr-utils.js +89 -22
- package/package.json +1 -1
- package/src/domain/strategies/sync/V2ProjectSyncStrategy.ts +429 -6
- package/src/sync/json-attr-utils.ts +84 -19
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* `value_type: json`. The API may return the `value` field as either a
|
|
9
9
|
* STRING containing JSON or as an already-parsed OBJECT.
|
|
10
10
|
*
|
|
11
|
-
* Without normalization,
|
|
11
|
+
* Without normalization, several bugs leak through:
|
|
12
12
|
*
|
|
13
13
|
* 1. When the API returns the value as an OBJECT, `yaml.dump` serializes
|
|
14
14
|
* it as a YAML structure (mappings/sequences). Pushing back then sends
|
|
@@ -21,10 +21,25 @@
|
|
|
21
21
|
* string vs object representations it triggers spurious pushes that
|
|
22
22
|
* overwrite the canvas with the wrong shape (Builder shows blank).
|
|
23
23
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
24
|
+
* 3. (Bug 3.7.2-a) Canvas JSON strings with structural newlines (real
|
|
25
|
+
* U+000A between tokens) can be emitted by yaml.dump as double-quoted
|
|
26
|
+
* scalars with `\n` escape sequences. patchYamlToPyyaml then converts
|
|
27
|
+
* those to single-quoted YAML scalars, where `\n` is treated as two
|
|
28
|
+
* literal chars (backslash + n). On push the platform stores those
|
|
29
|
+
* literal chars and the Builder calls JSON.parse, which fails on
|
|
30
|
+
* backslash-n as structural whitespace.
|
|
31
|
+
*
|
|
32
|
+
* 4. (Bug 3.7.2-b) Canvas body text contains Markdown with `\_`
|
|
33
|
+
* (backslash + underscore). `\_` is not a valid JSON escape sequence
|
|
34
|
+
* per RFC 8259 (valid ones: " \ / b f n r t uXXXX). Chrome V8's
|
|
35
|
+
* JSON.parse is strict: it throws SyntaxError on `\_`, silently
|
|
36
|
+
* blanking the Builder.
|
|
37
|
+
*
|
|
38
|
+
* The fix for (3) and (4): for `value_type: json` string values, strip
|
|
39
|
+
* invalid escape sequences then compact via JSON.parse + JSON.stringify.
|
|
40
|
+
* Compaction removes structural newlines and re-serializes all string
|
|
41
|
+
* values with only valid JSON escapes, producing a single-line string
|
|
42
|
+
* that round-trips through YAML without corruption.
|
|
28
43
|
*/
|
|
29
44
|
|
|
30
45
|
/**
|
|
@@ -38,22 +53,78 @@ export function isJsonValueType(valueType: unknown): boolean {
|
|
|
38
53
|
return lower === 'json' || lower.endsWith('.json');
|
|
39
54
|
}
|
|
40
55
|
|
|
56
|
+
/**
|
|
57
|
+
* Fix invalid JSON escape sequences inside JSON string values.
|
|
58
|
+
*
|
|
59
|
+
* Per RFC 8259, valid escape sequences inside a JSON string are:
|
|
60
|
+
* \" \\ \/ \b \f \n \r \t \uXXXX
|
|
61
|
+
* Anything else (e.g. `\_` `\.` from Markdown) is invalid and causes
|
|
62
|
+
* JSON.parse to throw. Fix: drop the backslash (e.g. `\_` → `_`).
|
|
63
|
+
*
|
|
64
|
+
* Only modifies characters inside JSON string values (tracks quote
|
|
65
|
+
* context). Structural characters outside strings are untouched.
|
|
66
|
+
*/
|
|
67
|
+
export function fixInvalidJsonEscapes(s: string): string {
|
|
68
|
+
const VALID_ESCAPES = new Set(['"', '\\', '/', 'b', 'f', 'n', 'r', 't', 'u']);
|
|
69
|
+
const result: string[] = [];
|
|
70
|
+
let inString = false;
|
|
71
|
+
let i = 0;
|
|
72
|
+
while (i < s.length) {
|
|
73
|
+
const c = s[i]!;
|
|
74
|
+
if (inString) {
|
|
75
|
+
if (c === '\\' && i + 1 < s.length) {
|
|
76
|
+
const next = s[i + 1]!;
|
|
77
|
+
if (VALID_ESCAPES.has(next)) {
|
|
78
|
+
result.push(c, next);
|
|
79
|
+
} else {
|
|
80
|
+
result.push(next); // drop the backslash — \_ → _, etc.
|
|
81
|
+
}
|
|
82
|
+
i += 2;
|
|
83
|
+
continue;
|
|
84
|
+
} else if (c === '"') {
|
|
85
|
+
inString = false;
|
|
86
|
+
result.push(c);
|
|
87
|
+
} else {
|
|
88
|
+
result.push(c);
|
|
89
|
+
}
|
|
90
|
+
} else {
|
|
91
|
+
if (c === '"') {
|
|
92
|
+
inString = true;
|
|
93
|
+
result.push(c);
|
|
94
|
+
} else {
|
|
95
|
+
result.push(c);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
i++;
|
|
99
|
+
}
|
|
100
|
+
return result.join('');
|
|
101
|
+
}
|
|
102
|
+
|
|
41
103
|
/**
|
|
42
104
|
* Coerce a JSON-typed attribute's value to a STRING suitable for storage
|
|
43
105
|
* in attributes.yaml and for sending to the platform.
|
|
44
106
|
*
|
|
45
107
|
* - `null` / `undefined` → `''`
|
|
46
108
|
* - object → compact JSON string (`JSON.stringify(value)`)
|
|
47
|
-
* - string →
|
|
109
|
+
* - string → fix invalid escapes (e.g. `\_` → `_`), then compact via
|
|
110
|
+
* JSON.parse + JSON.stringify. If parsing still fails after
|
|
111
|
+
* fixing escapes, return the fixed string as-is.
|
|
48
112
|
* - other → `String(value)`
|
|
49
113
|
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
114
|
+
* Compacting removes structural newlines and guarantees a single-line
|
|
115
|
+
* string that yaml.dump serializes without escape-sequence corruption in
|
|
116
|
+
* the patchYamlToPyyaml pass. See module-level comment for full context.
|
|
53
117
|
*/
|
|
54
118
|
export function normalizeJsonValueForStorage(value: unknown): string {
|
|
55
119
|
if (value == null) return '';
|
|
56
|
-
if (typeof value === 'string')
|
|
120
|
+
if (typeof value === 'string') {
|
|
121
|
+
const fixed = fixInvalidJsonEscapes(value);
|
|
122
|
+
try {
|
|
123
|
+
return JSON.stringify(JSON.parse(fixed));
|
|
124
|
+
} catch {
|
|
125
|
+
return fixed;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
57
128
|
if (typeof value === 'object') {
|
|
58
129
|
try {
|
|
59
130
|
return JSON.stringify(value);
|
|
@@ -68,18 +139,12 @@ export function normalizeJsonValueForStorage(value: unknown): string {
|
|
|
68
139
|
* Canonical comparison for JSON-typed attribute values.
|
|
69
140
|
*
|
|
70
141
|
* Returns the canonical form (compact JSON if parseable, otherwise the
|
|
71
|
-
*
|
|
72
|
-
* compact-printed JSON does not register as a change, and so that an
|
|
142
|
+
* fixed string). Use this on both sides of a comparison so that pretty-
|
|
143
|
+
* vs compact-printed JSON does not register as a change, and so that an
|
|
73
144
|
* object on one side equals its stringified form on the other side.
|
|
74
145
|
*/
|
|
75
146
|
export function canonicalJsonValue(value: unknown): string {
|
|
76
|
-
|
|
77
|
-
if (stringified === '') return '';
|
|
78
|
-
try {
|
|
79
|
-
return JSON.stringify(JSON.parse(stringified));
|
|
80
|
-
} catch {
|
|
81
|
-
return stringified;
|
|
82
|
-
}
|
|
147
|
+
return normalizeJsonValueForStorage(value);
|
|
83
148
|
}
|
|
84
149
|
|
|
85
150
|
/**
|