create-monolayer-app 0.2.2 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/template/app/routes/files.ts +63 -0
- package/template/app/routes.ts +5 -2
- package/template/oxfmt.config.ts +6 -1
- package/template/oxlint.config.ts +3 -8
- package/template/vendor/file-bucket/client.ts +196 -0
- package/template/vendor/file-bucket/errors.ts +26 -0
- package/template/vendor/file-bucket/index.ts +7 -0
- package/template/vendor/file-bucket/multipart.ts +304 -0
- package/template/vendor/file-bucket/protocol.ts +222 -0
- package/template/vendor/file-bucket/react-router.ts +222 -0
- package/template/vendor/file-bucket/server.ts +544 -0
- package/template/vendor/file-bucket/transfers.ts +23 -0
- package/template/vendor/file-bucket/transport.ts +37 -0
- package/template/vendor/file-bucket/types.ts +153 -0
- package/template/vendor/file-bucket/vite.d.ts +7 -0
- package/template/vendor/file-bucket/vite.js +2482 -0
- package/template/vendor/file-bucket/xml.ts +325 -0
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zero-dependency XML parser, DOM builder, and serializer for S3 XML payloads.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// oxlint-disable-next-line max-classes-per-file
|
|
6
|
+
export const S3_NS = "http://s3.amazonaws.com/doc/2006-03-01/";
|
|
7
|
+
export const XML_DECL = '<?xml version="1.0" encoding="UTF-8"?>';
|
|
8
|
+
|
|
9
|
+
export class ParseError extends Error {
|
|
10
|
+
constructor(message: string) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.name = "ParseError";
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function escapeXml(
|
|
17
|
+
str: string | number | boolean | null | undefined
|
|
18
|
+
): string {
|
|
19
|
+
if (str === null || str === undefined) return "";
|
|
20
|
+
return String(str)
|
|
21
|
+
.replaceAll("&", "&")
|
|
22
|
+
.replaceAll("<", "<")
|
|
23
|
+
.replaceAll(">", ">")
|
|
24
|
+
.replaceAll('"', """)
|
|
25
|
+
.replaceAll("'", "'");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function unescapeXml(str: string): string {
|
|
29
|
+
return str
|
|
30
|
+
.replaceAll(""", '"')
|
|
31
|
+
.replaceAll("'", "'")
|
|
32
|
+
.replaceAll("<", "<")
|
|
33
|
+
.replaceAll(">", ">")
|
|
34
|
+
.replaceAll("&", "&")
|
|
35
|
+
.replaceAll(/&#(\d+);/gu, (_, dec) =>
|
|
36
|
+
String.fromCodePoint(Math.trunc(Number(dec)))
|
|
37
|
+
)
|
|
38
|
+
.replaceAll(/&#x([0-9a-fA-F]+);/gu, (_, hex) =>
|
|
39
|
+
String.fromCodePoint(Number.parseInt(hex, 16))
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Strips namespace prefix or Clark notation from a tag name.
|
|
45
|
+
* e.g. "{http://...}Rule" -> "Rule", "s3:Rule" -> "Rule", "Rule" -> "Rule"
|
|
46
|
+
*/
|
|
47
|
+
export function localTagName(tag: string): string {
|
|
48
|
+
if (tag.startsWith("{")) {
|
|
49
|
+
const idx = tag.indexOf("}");
|
|
50
|
+
if (idx !== -1) return tag.slice(idx + 1);
|
|
51
|
+
}
|
|
52
|
+
const colonIdx = tag.indexOf(":");
|
|
53
|
+
if (colonIdx !== -1) {
|
|
54
|
+
return tag.slice(colonIdx + 1);
|
|
55
|
+
}
|
|
56
|
+
return tag;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function isString(
|
|
60
|
+
value: string | Record<string, string> | undefined
|
|
61
|
+
): value is string {
|
|
62
|
+
return typeof value === "string";
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export class XmlElement {
|
|
66
|
+
public tag: string;
|
|
67
|
+
public attrs: Record<string, string>;
|
|
68
|
+
public text?: string;
|
|
69
|
+
public children: XmlElement[];
|
|
70
|
+
|
|
71
|
+
constructor(tag: string, attrs: Record<string, string> = {}, text?: string) {
|
|
72
|
+
this.tag = tag;
|
|
73
|
+
this.attrs = { ...attrs };
|
|
74
|
+
this.text = text;
|
|
75
|
+
this.children = [];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
public localName(): string {
|
|
79
|
+
return localTagName(this.tag);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
public subElement(
|
|
83
|
+
tag: string,
|
|
84
|
+
textOrAttrs?: string | Record<string, string>,
|
|
85
|
+
text?: string
|
|
86
|
+
): XmlElement {
|
|
87
|
+
const el = isString(textOrAttrs)
|
|
88
|
+
? new XmlElement(tag, {}, textOrAttrs)
|
|
89
|
+
: new XmlElement(tag, textOrAttrs || {}, text);
|
|
90
|
+
|
|
91
|
+
this.children.push(el);
|
|
92
|
+
return el;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
public find(tag: string): XmlElement | undefined {
|
|
96
|
+
const targetLocal = localTagName(tag);
|
|
97
|
+
return this.children.find(
|
|
98
|
+
(c) => c.tag === tag || c.localName() === targetLocal
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
public findAll(tag: string): XmlElement[] {
|
|
103
|
+
const targetLocal = localTagName(tag);
|
|
104
|
+
return this.children.filter(
|
|
105
|
+
(c) => c.tag === tag || c.localName() === targetLocal
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
public findText(tag: string): string | undefined {
|
|
110
|
+
const el = this.find(tag);
|
|
111
|
+
return el?.text;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
public findDescendant(tag: string): XmlElement | undefined {
|
|
115
|
+
const targetLocal = localTagName(tag);
|
|
116
|
+
for (const c of this.children) {
|
|
117
|
+
if (c.tag === tag || c.localName() === targetLocal) {
|
|
118
|
+
return c;
|
|
119
|
+
}
|
|
120
|
+
const deep = c.findDescendant(tag);
|
|
121
|
+
if (deep) return deep;
|
|
122
|
+
}
|
|
123
|
+
return undefined;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
public findAllDescendants(tag: string): XmlElement[] {
|
|
127
|
+
const targetLocal = localTagName(tag);
|
|
128
|
+
const result: XmlElement[] = [];
|
|
129
|
+
for (const c of this.children) {
|
|
130
|
+
if (c.tag === tag || c.localName() === targetLocal) {
|
|
131
|
+
result.push(c);
|
|
132
|
+
}
|
|
133
|
+
result.push(...c.findAllDescendants(tag));
|
|
134
|
+
}
|
|
135
|
+
return result;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
public iter(): XmlElement[] {
|
|
139
|
+
const result: XmlElement[] = [this];
|
|
140
|
+
for (const child of this.children) {
|
|
141
|
+
result.push(...child.iter());
|
|
142
|
+
}
|
|
143
|
+
return result;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
public toXml(includeDeclaration = false): string {
|
|
147
|
+
let s = includeDeclaration ? `${XML_DECL}\n` : "";
|
|
148
|
+
s += `<${this.tag}`;
|
|
149
|
+
for (const [k, v] of Object.entries(this.attrs)) {
|
|
150
|
+
s += ` ${k}="${escapeXml(v)}"`;
|
|
151
|
+
}
|
|
152
|
+
if (this.children.length === 0 && this.text === undefined) {
|
|
153
|
+
s += "/>";
|
|
154
|
+
return s;
|
|
155
|
+
}
|
|
156
|
+
s += ">";
|
|
157
|
+
if (this.text !== undefined) {
|
|
158
|
+
s += escapeXml(this.text);
|
|
159
|
+
}
|
|
160
|
+
for (const child of this.children) {
|
|
161
|
+
s += child.toXml(false);
|
|
162
|
+
}
|
|
163
|
+
s += `</${this.tag}>`;
|
|
164
|
+
return s;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function subElement(
|
|
169
|
+
parent: XmlElement,
|
|
170
|
+
tag: string,
|
|
171
|
+
textOrAttrs?: string | Record<string, string>,
|
|
172
|
+
text?: string
|
|
173
|
+
): XmlElement {
|
|
174
|
+
return parent.subElement(tag, textOrAttrs, text);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// oxlint-disable-next-line complexity
|
|
178
|
+
export function parseXml(xml: string | Buffer): XmlElement {
|
|
179
|
+
// String inputs are also used by browser upload handles.
|
|
180
|
+
// oxlint-disable-next-line anti-slop/no-runtime-typeof
|
|
181
|
+
let str = (typeof xml === "string" ? xml : xml.toString("utf-8")).trim();
|
|
182
|
+
if (!str) {
|
|
183
|
+
throw new ParseError("Empty XML document");
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Strip XML declaration if present
|
|
187
|
+
if (str.startsWith("<?xml")) {
|
|
188
|
+
const end = str.indexOf("?>");
|
|
189
|
+
if (end !== -1) {
|
|
190
|
+
str = str.slice(end + 2).trim();
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
let pos = 0;
|
|
195
|
+
const len = str.length;
|
|
196
|
+
let root: XmlElement | null = null;
|
|
197
|
+
const stack: XmlElement[] = [];
|
|
198
|
+
|
|
199
|
+
while (pos < len) {
|
|
200
|
+
const lt = str.indexOf("<", pos);
|
|
201
|
+
if (lt === -1) {
|
|
202
|
+
if (stack.length > 0) {
|
|
203
|
+
const text = unescapeXml(str.slice(pos).trim());
|
|
204
|
+
const top = stack.at(-1);
|
|
205
|
+
if (text && top) {
|
|
206
|
+
top.text = (top.text || "") + text;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
break;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (lt > pos && stack.length > 0) {
|
|
213
|
+
const text = unescapeXml(str.slice(pos, lt).trim());
|
|
214
|
+
const top = stack.at(-1);
|
|
215
|
+
if (text && top) {
|
|
216
|
+
top.text = (top.text || "") + text;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
pos = lt + 1;
|
|
221
|
+
|
|
222
|
+
// Comment
|
|
223
|
+
if (str.startsWith("!--", pos)) {
|
|
224
|
+
const end = str.indexOf("-->", pos + 3);
|
|
225
|
+
pos = end === -1 ? len : end + 3;
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// CDATA
|
|
230
|
+
if (str.startsWith("![CDATA[", pos)) {
|
|
231
|
+
const end = str.indexOf("]]>", pos + 8);
|
|
232
|
+
const cdata = end === -1 ? str.slice(pos + 8) : str.slice(pos + 8, end);
|
|
233
|
+
const top = stack.at(-1);
|
|
234
|
+
if (top) {
|
|
235
|
+
top.text = (top.text || "") + cdata;
|
|
236
|
+
}
|
|
237
|
+
pos = end === -1 ? len : end + 3;
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Processing instructions or DOCTYPE
|
|
242
|
+
if (str.startsWith("?", pos) || str.startsWith("!", pos)) {
|
|
243
|
+
const gt = str.indexOf(">", pos);
|
|
244
|
+
pos = gt === -1 ? len : gt + 1;
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Closing tag: </tag>
|
|
249
|
+
if (str[pos] === "/") {
|
|
250
|
+
const gt = str.indexOf(">", pos);
|
|
251
|
+
if (gt === -1) throw new ParseError("Unclosed closing tag");
|
|
252
|
+
const closeTag = str.slice(pos + 1, gt).trim();
|
|
253
|
+
const open = stack.pop();
|
|
254
|
+
if (!open) {
|
|
255
|
+
throw new ParseError(`Unexpected closing tag: </${closeTag}>`);
|
|
256
|
+
}
|
|
257
|
+
if (
|
|
258
|
+
localTagName(open.tag) !== localTagName(closeTag) &&
|
|
259
|
+
open.tag !== closeTag
|
|
260
|
+
) {
|
|
261
|
+
throw new ParseError(
|
|
262
|
+
`Mismatched closing tag: expected </${open.tag}>, got </${closeTag}>`
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
pos = gt + 1;
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Opening tag: <tag ...>
|
|
270
|
+
const gt = str.indexOf(">", pos);
|
|
271
|
+
if (gt === -1) {
|
|
272
|
+
throw new ParseError("Unclosed opening tag");
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
let tagBody = str.slice(pos, gt).trim();
|
|
276
|
+
const selfClosing = tagBody.endsWith("/");
|
|
277
|
+
if (selfClosing) {
|
|
278
|
+
tagBody = tagBody.slice(0, -1).trim();
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const spaceIdx = tagBody.search(/\s/u);
|
|
282
|
+
const tagName = spaceIdx === -1 ? tagBody : tagBody.slice(0, spaceIdx);
|
|
283
|
+
const attrs: Record<string, string> = {};
|
|
284
|
+
|
|
285
|
+
if (spaceIdx !== -1) {
|
|
286
|
+
const attrStr = tagBody.slice(spaceIdx).trim();
|
|
287
|
+
const attrRegex =
|
|
288
|
+
/([a-zA-Z0-9_:.-]+)\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+))/gu;
|
|
289
|
+
let m: RegExpExecArray | null;
|
|
290
|
+
while ((m = attrRegex.exec(attrStr)) !== null) {
|
|
291
|
+
attrs[m[1]] = unescapeXml(m[2] ?? m[3] ?? m[4] ?? "");
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const el = new XmlElement(tagName, attrs);
|
|
296
|
+
const top = stack.at(-1);
|
|
297
|
+
if (top) {
|
|
298
|
+
top.children.push(el);
|
|
299
|
+
} else {
|
|
300
|
+
if (root) {
|
|
301
|
+
throw new ParseError("Multiple root elements found in XML document");
|
|
302
|
+
}
|
|
303
|
+
root = el;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (!selfClosing) {
|
|
307
|
+
stack.push(el);
|
|
308
|
+
}
|
|
309
|
+
pos = gt + 1;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const lastUnclosed = stack.at(-1);
|
|
313
|
+
if (lastUnclosed) {
|
|
314
|
+
throw new ParseError(`Unclosed element: <${lastUnclosed.tag}>`);
|
|
315
|
+
}
|
|
316
|
+
if (!root) {
|
|
317
|
+
throw new ParseError("Empty XML document");
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return root;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
export function xmlBody(root: XmlElement): Buffer {
|
|
324
|
+
return Buffer.from(`${XML_DECL}\n${root.toXml(false)}`, "utf-8");
|
|
325
|
+
}
|