@dokebi/template-inertia 0.4.1 → 0.4.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/AI.md +138 -25
- package/README.md +23 -7
- package/dist/boot-fallback.d.ts +61 -0
- package/dist/boot-fallback.js +131 -0
- package/dist/built-files.d.ts +9 -0
- package/dist/built-files.js +44 -0
- package/dist/errors.d.ts +2 -2
- package/dist/errors.js +3 -1
- package/dist/i18n-store.d.ts +64 -0
- package/dist/i18n-store.js +368 -0
- package/dist/i18n.d.ts +216 -0
- package/dist/i18n.js +901 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +458 -61
- package/dist/options.d.ts +28 -0
- package/dist/options.js +97 -1
- package/dist/prune.d.ts +30 -0
- package/dist/prune.js +159 -0
- package/dist/responder.d.ts +14 -4
- package/dist/responder.js +166 -51
- package/dist/vite-deps.d.ts +76 -0
- package/dist/vite-deps.js +249 -0
- package/dist/vite-dev.d.ts +42 -4
- package/dist/vite-dev.js +162 -16
- package/dist/vite-files.d.ts +129 -0
- package/dist/vite-files.js +633 -0
- package/dist/vite-host.js +67 -10
- package/dist/vite-plugin.d.ts +69 -0
- package/dist/vite-plugin.js +173 -0
- package/dist/vite-watch.d.ts +44 -0
- package/dist/vite-watch.js +176 -0
- package/options.schema.json +127 -1
- package/package.json +38 -11
package/dist/options.d.ts
CHANGED
|
@@ -1,13 +1,40 @@
|
|
|
1
1
|
import { type OptionsOf } from '@dokebi/core';
|
|
2
|
+
/**
|
|
3
|
+
* An asset version: empty, or 1 to 128 printable ASCII characters that do not start or end with a
|
|
4
|
+
* space. The client sends it back in the X-Inertia-Version header, where other characters cannot
|
|
5
|
+
* travel (a browser refuses them, a line break is refused by Node) and edge spaces are trimmed,
|
|
6
|
+
* so the version would never match and every visit would reload (409).
|
|
7
|
+
*/
|
|
8
|
+
export declare const VERSION = "^(?:[\\x21-\\x7e](?:[\\x20-\\x7e]{0,126}[\\x21-\\x7e])?)?$";
|
|
2
9
|
export declare const options: import("@dokebi/core").OptionsDeclaration<{
|
|
10
|
+
readonly assetCacheMaxBytes: number;
|
|
11
|
+
readonly bootFallback: {
|
|
12
|
+
readonly enabled: boolean;
|
|
13
|
+
readonly logo?: string;
|
|
14
|
+
readonly messages?: Readonly<Record<string, {
|
|
15
|
+
readonly failed?: string;
|
|
16
|
+
readonly loading?: string;
|
|
17
|
+
readonly reload?: string;
|
|
18
|
+
}>>;
|
|
19
|
+
readonly stylesheet?: string;
|
|
20
|
+
readonly timeoutMs: number;
|
|
21
|
+
};
|
|
22
|
+
readonly devDepsRepairIntervalMs: number;
|
|
23
|
+
readonly devHosts: readonly string[];
|
|
3
24
|
readonly encryptHistory: "sealed" | boolean;
|
|
4
25
|
readonly errorPage?: string;
|
|
26
|
+
readonly logRepeatWindowMs: number;
|
|
5
27
|
readonly rootId?: string;
|
|
28
|
+
readonly routeIds?: {
|
|
29
|
+
readonly assets?: string;
|
|
30
|
+
readonly viteDev?: string;
|
|
31
|
+
};
|
|
6
32
|
readonly scripts?: readonly string[];
|
|
7
33
|
readonly ssr?: {
|
|
8
34
|
readonly bundle: string;
|
|
9
35
|
readonly entry?: string;
|
|
10
36
|
readonly failure: "error" | "fallback";
|
|
37
|
+
readonly timeoutMs: number;
|
|
11
38
|
};
|
|
12
39
|
readonly styles?: readonly string[];
|
|
13
40
|
readonly title?: string;
|
|
@@ -17,6 +44,7 @@ export declare const options: import("@dokebi/core").OptionsDeclaration<{
|
|
|
17
44
|
readonly entry: string;
|
|
18
45
|
readonly manifest: string;
|
|
19
46
|
};
|
|
47
|
+
readonly viteManifestMaxBytes: number;
|
|
20
48
|
}>;
|
|
21
49
|
/** The parsed options of dokebi-template-inertia (`options.dokebi-template-inertia`). */
|
|
22
50
|
export type InertiaPluginOptions = OptionsOf<typeof options>;
|
package/dist/options.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { defineOptions, option as o } from '@dokebi/core';
|
|
2
|
+
import { routeIdsOption } from '@dokebi/web/points';
|
|
2
3
|
/*
|
|
3
4
|
* Options of dokebi-template-inertia (D-0063 §3): one declaration is the parser and, at build
|
|
4
5
|
* time, options.schema.json. The rules are the hand parser's before wave B; the asset URL rule
|
|
@@ -14,9 +15,18 @@ const localAsset = o.string({
|
|
|
14
15
|
},
|
|
15
16
|
],
|
|
16
17
|
});
|
|
18
|
+
/**
|
|
19
|
+
* An asset version: empty, or 1 to 128 printable ASCII characters that do not start or end with a
|
|
20
|
+
* space. The client sends it back in the X-Inertia-Version header, where other characters cannot
|
|
21
|
+
* travel (a browser refuses them, a line break is refused by Node) and edge spaces are trimmed,
|
|
22
|
+
* so the version would never match and every visit would reload (409).
|
|
23
|
+
*/
|
|
24
|
+
export const VERSION = '^(?:[\\x21-\\x7e](?:[\\x20-\\x7e]{0,126}[\\x21-\\x7e])?)?$';
|
|
17
25
|
export const options = defineOptions({
|
|
18
26
|
version: o.string({
|
|
19
|
-
|
|
27
|
+
// It travels back in the X-Inertia-Version header: printable ASCII, no edge spaces.
|
|
28
|
+
pattern: VERSION,
|
|
29
|
+
description: 'Asset version sent with every page; a client with another one reloads (409). Unset: development (and a warning). Up to 128 printable ASCII characters without leading or trailing spaces: the client sends it back in the X-Inertia-Version header.',
|
|
20
30
|
}),
|
|
21
31
|
rootId: o.string({
|
|
22
32
|
pattern: '^[A-Za-z][A-Za-z0-9_-]*$',
|
|
@@ -59,17 +69,103 @@ export const options = defineOptions({
|
|
|
59
69
|
pattern: '^(?!.*\\.\\.)',
|
|
60
70
|
description: 'Development (D-0045): source module of the bundle rendered through Vite, e.g. src/apps/admin/views/ssr.js. Default: ssr.js beside vite.entry',
|
|
61
71
|
}),
|
|
72
|
+
timeoutMs: o.integer({
|
|
73
|
+
minimum: 1,
|
|
74
|
+
maximum: 600_000,
|
|
75
|
+
default: 5000,
|
|
76
|
+
description: 'How long one render(page) may take in ms; past it the render fails with SSR_TIMEOUT, handled as failure says (fallback: the client-rendered page). Size it above your slowest page render (D-0107)',
|
|
77
|
+
}),
|
|
62
78
|
}, {
|
|
63
79
|
description: 'Render the first HTML of each page on the server (D-0039) with the SSR build output, imported once at start. A missing bundle stops the start.',
|
|
64
80
|
}),
|
|
81
|
+
devHosts: o.array(o.string({
|
|
82
|
+
maxLength: 254,
|
|
83
|
+
pattern: '^\\.?[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?(?:\\.[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?)*$',
|
|
84
|
+
}), {
|
|
85
|
+
maxItems: 256,
|
|
86
|
+
default: [],
|
|
87
|
+
description: 'Development (DOKEBI_VITE_DEV=1): host names besides loopback names, IP addresses and @dokebi/app domains that may load the Vite dev routes, such as a tunnel host; .example.com also allows its subdomains (D-0107)',
|
|
88
|
+
}),
|
|
89
|
+
devDepsRepairIntervalMs: o.integer({
|
|
90
|
+
minimum: 1000,
|
|
91
|
+
maximum: 3_600_000,
|
|
92
|
+
default: 10_000,
|
|
93
|
+
description: 'Development (dokebi dev): the Vite dev host re-optimizes the pre-bundled dependencies (node_modules/.vite/deps) when they changed on disk under it, at most once per this many ms; a cache that keeps changing faster is reported with INERTIA_VITE_DEPS_CHANGING (D-0128). Read by the host when dokebi dev starts.',
|
|
94
|
+
}),
|
|
65
95
|
encryptHistory: o.union([o.boolean(), o.literal('sealed')], {
|
|
66
96
|
default: 'sealed',
|
|
67
97
|
description: 'Inertia history encryption (D-0062): true, false, or sealed (default) = on for requests of seal hosts',
|
|
68
98
|
}),
|
|
99
|
+
viteManifestMaxBytes: o.integer({
|
|
100
|
+
minimum: 1024,
|
|
101
|
+
maximum: 67_108_864,
|
|
102
|
+
default: 4_194_304,
|
|
103
|
+
description: 'Largest Vite build manifest read at start; a larger one stops the start with VITE_MANIFEST_INVALID (D-0114). An app with thousands of chunks may need more.',
|
|
104
|
+
}),
|
|
105
|
+
assetCacheMaxBytes: o.integer({
|
|
106
|
+
minimum: 0,
|
|
107
|
+
maximum: 67_108_864,
|
|
108
|
+
default: 4_194_304,
|
|
109
|
+
description: 'Built files up to this size are kept in memory after their first request; larger ones are streamed from disk on every request (D-0114). 0 streams every file. Memory is at most the sum of the built files at or below it.',
|
|
110
|
+
}),
|
|
111
|
+
logRepeatWindowMs: o.integer({
|
|
112
|
+
minimum: 1000,
|
|
113
|
+
maximum: 3_600_000,
|
|
114
|
+
default: 10_000,
|
|
115
|
+
description: 'Window of the repeated per-request failure logs (an SSR render that failed, a shared-data or flash hook that threw) in ms: the first is logged in full, the rest of the window are counted (D-0114).',
|
|
116
|
+
}),
|
|
117
|
+
bootFallback: o.object({
|
|
118
|
+
enabled: o.boolean({
|
|
119
|
+
default: true,
|
|
120
|
+
description: 'Show the text in the root of the first HTML (false: an empty root, as before)',
|
|
121
|
+
}),
|
|
122
|
+
timeoutMs: o.integer({
|
|
123
|
+
minimum: 1000,
|
|
124
|
+
maximum: 120_000,
|
|
125
|
+
default: 10_000,
|
|
126
|
+
description: 'How long "loading" shows before "could not load — reload" (ms)',
|
|
127
|
+
}),
|
|
128
|
+
messages: o.record(o.object({
|
|
129
|
+
loading: o.string({ minLength: 1, maxLength: 200 }),
|
|
130
|
+
failed: o.string({ minLength: 1, maxLength: 200 }),
|
|
131
|
+
reload: o.string({ minLength: 1, maxLength: 200 }),
|
|
132
|
+
}), {
|
|
133
|
+
description: 'Texts by language tag of <html lang> (ko, en, pt-BR) over the built-in Korean and English ones',
|
|
134
|
+
}),
|
|
135
|
+
stylesheet: o.string({
|
|
136
|
+
pattern: '^/[^/]',
|
|
137
|
+
maxLength: 2048,
|
|
138
|
+
refine: [
|
|
139
|
+
{
|
|
140
|
+
test: (value) => ![...value].some((char) => '\\"\'<>'.includes(char) || char.charCodeAt(0) <= 32),
|
|
141
|
+
message: 'must be a local absolute URL without backslash, quote, angle bracket, space or control characters',
|
|
142
|
+
},
|
|
143
|
+
],
|
|
144
|
+
description: "A local stylesheet (/assets/boot.css) linked right after the built-in style: the app's own look of the box ([data-dokebi-boot] selectors), shown before any script of the app runs, also under dokebi dev",
|
|
145
|
+
}),
|
|
146
|
+
logo: o.string({
|
|
147
|
+
pattern: '^/[^/]',
|
|
148
|
+
maxLength: 2048,
|
|
149
|
+
refine: [
|
|
150
|
+
{
|
|
151
|
+
test: (value) => ![...value].some((char) => '\\"\'<>'.includes(char) || char.charCodeAt(0) <= 32),
|
|
152
|
+
message: 'must be a local absolute URL without backslash, quote, angle bracket, space or control characters',
|
|
153
|
+
},
|
|
154
|
+
],
|
|
155
|
+
description: 'A local image (/assets/logo.svg) above the spinner and the could-not-load text of the box, decorative (alt is empty)',
|
|
156
|
+
}),
|
|
157
|
+
}, {
|
|
158
|
+
default: { enabled: true, timeoutMs: 10_000 },
|
|
159
|
+
description: 'The first HTML while the app loads (Studio v2): a centered spinner ("loading" for screen readers), then "could not load" with a reload button after timeoutMs (by CSS, also under a CSP without inline scripts) or at once when the entry script fails to load; stylesheet restyles it. Not on a server-rendered page.',
|
|
160
|
+
}),
|
|
69
161
|
errorPage: o.string({
|
|
70
162
|
pattern: '^[A-Za-z0-9][A-Za-z0-9_-]*(?:/[A-Za-z0-9][A-Za-z0-9_-]*)*$',
|
|
71
163
|
description: 'Page component (for example Error) rendered with { status, error, message, hint } for error answers of Inertia visits and full page loads (D-0074). Unset: JSON for visits, a small HTML page for page loads.',
|
|
72
164
|
}),
|
|
165
|
+
routeIds: routeIdsOption('dokebi-template-inertia', {
|
|
166
|
+
assets: 'the GET routes of the built files of vite.manifest (one per file)',
|
|
167
|
+
viteDev: 'the GET <vite base>* routes of the Vite dev server (dokebi dev, one per base)',
|
|
168
|
+
}),
|
|
73
169
|
}, {
|
|
74
170
|
checks: [
|
|
75
171
|
{
|
package/dist/prune.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export interface PruneBuildOptions {
|
|
2
|
+
/** The current build's manifest, as `inertia.vite.manifest` names it (`dist/.vite/manifest.json`). */
|
|
3
|
+
readonly manifest: string;
|
|
4
|
+
/** Only files unchanged this long are removed, in ms (default 7 days; 0: every unnamed one). */
|
|
5
|
+
readonly olderThanMs?: number;
|
|
6
|
+
/** List what would be removed, remove nothing. */
|
|
7
|
+
readonly dryRun?: boolean;
|
|
8
|
+
/** A larger manifest is refused (default 4 MiB, the plugin's `viteManifestMaxBytes`). */
|
|
9
|
+
readonly maxManifestBytes?: number;
|
|
10
|
+
/** The time to count ages from, in ms since the epoch (default now). */
|
|
11
|
+
readonly now?: number;
|
|
12
|
+
}
|
|
13
|
+
export interface PruneBuildResult {
|
|
14
|
+
/** The build folders looked at: those of the manifest's files (`dist/assets`), as served. */
|
|
15
|
+
readonly folders: readonly string[];
|
|
16
|
+
/** The files removed (with `dryRun`: that would be). */
|
|
17
|
+
readonly removed: readonly string[];
|
|
18
|
+
/** How many files of those folders stay. */
|
|
19
|
+
readonly kept: number;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Removes the old build files from the folders a Vite manifest's files sit in — the folders
|
|
23
|
+
* @dokebi/template-inertia serves for rolling deploys (`dist/assets`). A file stays when the
|
|
24
|
+
* manifest names it (or it is the source map of one), when its name is not content-hashed (not a
|
|
25
|
+
* build output: `app-3fa8c1d2.js` is one, `robots.txt` is not), when it is hidden, a link or
|
|
26
|
+
* changed within `olderThanMs`. Files directly in the output folder (the manifest's `.vite/`, the
|
|
27
|
+
* SSR build's `ssr/`) are never looked at. A manifest that cannot be read, is not an object of
|
|
28
|
+
* chunks or names no file is an error and nothing is removed.
|
|
29
|
+
*/
|
|
30
|
+
export declare function pruneBuildFiles(options: PruneBuildOptions): Promise<PruneBuildResult>;
|
package/dist/prune.js
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { lstat, readdir, readFile, rm, stat } from 'node:fs/promises';
|
|
2
|
+
import { basename, dirname, join, resolve, sep } from 'node:path';
|
|
3
|
+
import { HASHED } from './built-files.js';
|
|
4
|
+
import { InertiaError, InertiaUsageError } from './errors.js';
|
|
5
|
+
/*
|
|
6
|
+
* Removing the chunks of builds that no page uses any more (audit r5 review). A rolling deploy
|
|
7
|
+
* keeps the previous builds' files (vite `build.emptyOutDir: false`, D-0160 §5), so the build
|
|
8
|
+
* folders grow with every deploy. A cleanup by age alone (`find -mtime +7 -delete`) also removed
|
|
9
|
+
* the current build's files when no deploy came for a week. Here the current manifest decides:
|
|
10
|
+
* every file it names stays whatever its age; only content-hashed files it does not name, unchanged
|
|
11
|
+
* for `olderThanMs`, go.
|
|
12
|
+
*/
|
|
13
|
+
/** A week: the pages of a previous build are long gone by then (a tab left open for days). */
|
|
14
|
+
const DEFAULT_OLDER_THAN_MS = 7 * 86_400_000;
|
|
15
|
+
/** The default of the plugin's `viteManifestMaxBytes`. */
|
|
16
|
+
const DEFAULT_MAX_MANIFEST_BYTES = 4_194_304;
|
|
17
|
+
/**
|
|
18
|
+
* Removes the old build files from the folders a Vite manifest's files sit in — the folders
|
|
19
|
+
* @dokebi/template-inertia serves for rolling deploys (`dist/assets`). A file stays when the
|
|
20
|
+
* manifest names it (or it is the source map of one), when its name is not content-hashed (not a
|
|
21
|
+
* build output: `app-3fa8c1d2.js` is one, `robots.txt` is not), when it is hidden, a link or
|
|
22
|
+
* changed within `olderThanMs`. Files directly in the output folder (the manifest's `.vite/`, the
|
|
23
|
+
* SSR build's `ssr/`) are never looked at. A manifest that cannot be read, is not an object of
|
|
24
|
+
* chunks or names no file is an error and nothing is removed.
|
|
25
|
+
*/
|
|
26
|
+
export async function pruneBuildFiles(options) {
|
|
27
|
+
const olderThanMs = options.olderThanMs ?? DEFAULT_OLDER_THAN_MS;
|
|
28
|
+
const maxBytes = options.maxManifestBytes ?? DEFAULT_MAX_MANIFEST_BYTES;
|
|
29
|
+
const now = options.now ?? Date.now();
|
|
30
|
+
if (typeof options.manifest !== 'string' || !options.manifest)
|
|
31
|
+
throw new InertiaUsageError('INVALID_PRUNE', 'pruneBuildFiles: manifest must be a file path');
|
|
32
|
+
for (const [name, value] of [
|
|
33
|
+
['olderThanMs', olderThanMs],
|
|
34
|
+
['maxManifestBytes', maxBytes],
|
|
35
|
+
['now', now],
|
|
36
|
+
])
|
|
37
|
+
if (!Number.isSafeInteger(value) || value < 0)
|
|
38
|
+
throw new InertiaUsageError('INVALID_PRUNE', `pruneBuildFiles: ${name} must be whole milliseconds or bytes from 0, not ${String(value)}`);
|
|
39
|
+
const path = resolve(options.manifest);
|
|
40
|
+
const named = await namedFiles(path, maxBytes);
|
|
41
|
+
// Vite writes <outDir>/.vite/manifest.json; a custom manifest name sits in outDir itself.
|
|
42
|
+
const outDir = basename(dirname(path)) === '.vite' ? dirname(dirname(path)) : dirname(path);
|
|
43
|
+
const folders = buildFolders(outDir, named);
|
|
44
|
+
const cutoff = now - olderThanMs;
|
|
45
|
+
const removed = [];
|
|
46
|
+
let kept = 0;
|
|
47
|
+
const walk = async (folder) => {
|
|
48
|
+
let entries;
|
|
49
|
+
try {
|
|
50
|
+
entries = await readdir(folder, { withFileTypes: true });
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
// A folder the manifest names but the build did not write (or removed meanwhile).
|
|
54
|
+
if (error.code === 'ENOENT')
|
|
55
|
+
return;
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
58
|
+
for (const entry of entries) {
|
|
59
|
+
const file = join(folder, entry.name);
|
|
60
|
+
if (entry.name.startsWith('.')) {
|
|
61
|
+
kept++;
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
// A link (to a file or folder) is never followed or removed: a Dirent of a link is neither.
|
|
65
|
+
if (entry.isDirectory()) {
|
|
66
|
+
await walk(file);
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
if (!entry.isFile() || current(file, named) || !built(entry.name)) {
|
|
70
|
+
kept++;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
if ((await lstat(file)).mtimeMs > cutoff) {
|
|
74
|
+
kept++;
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
removed.push(file);
|
|
78
|
+
// force: another cleanup that removed it first is no failure.
|
|
79
|
+
if (options.dryRun !== true)
|
|
80
|
+
await rm(file, { force: true });
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
for (const folder of folders)
|
|
84
|
+
await walk(folder);
|
|
85
|
+
return { folders, removed, kept };
|
|
86
|
+
}
|
|
87
|
+
/** The files the manifest names (every chunk's file, CSS and assets), as absolute paths. */
|
|
88
|
+
async function namedFiles(path, maxBytes) {
|
|
89
|
+
let size;
|
|
90
|
+
try {
|
|
91
|
+
const info = await stat(path);
|
|
92
|
+
if (!info.isFile())
|
|
93
|
+
throw new InertiaError('VITE_MANIFEST_INVALID', `Vite manifest ${path} is not a file`);
|
|
94
|
+
size = info.size;
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
if (error.code === 'ENOENT')
|
|
98
|
+
throw new InertiaError('VITE_MANIFEST_MISSING', `Vite manifest not found: ${path}`, {
|
|
99
|
+
cause: error,
|
|
100
|
+
});
|
|
101
|
+
throw error;
|
|
102
|
+
}
|
|
103
|
+
if (size > maxBytes)
|
|
104
|
+
throw new InertiaError('VITE_MANIFEST_INVALID', `Vite manifest ${path} is larger than maxManifestBytes (${maxBytes})`);
|
|
105
|
+
let manifest;
|
|
106
|
+
try {
|
|
107
|
+
manifest = JSON.parse(await readFile(path, 'utf8'));
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
throw new InertiaError('VITE_MANIFEST_INVALID', `Vite manifest ${path} is not JSON`, {
|
|
111
|
+
cause: error,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
if (manifest === null || typeof manifest !== 'object' || Array.isArray(manifest))
|
|
115
|
+
throw new InertiaError('VITE_MANIFEST_INVALID', `Vite manifest ${path} is not an object of chunks`);
|
|
116
|
+
const outDir = basename(dirname(path)) === '.vite' ? dirname(dirname(path)) : dirname(path);
|
|
117
|
+
const named = new Set();
|
|
118
|
+
const add = (file) => {
|
|
119
|
+
if (typeof file !== 'string' || file.split('/').some((part) => part === '..' || !part))
|
|
120
|
+
return;
|
|
121
|
+
named.add(join(outDir, ...file.split('/')));
|
|
122
|
+
};
|
|
123
|
+
for (const chunk of Object.values(manifest)) {
|
|
124
|
+
if (chunk === null || typeof chunk !== 'object')
|
|
125
|
+
continue;
|
|
126
|
+
const { file, css, assets } = chunk;
|
|
127
|
+
add(file);
|
|
128
|
+
for (const item of Array.isArray(css) ? css : [])
|
|
129
|
+
add(item);
|
|
130
|
+
for (const item of Array.isArray(assets) ? assets : [])
|
|
131
|
+
add(item);
|
|
132
|
+
}
|
|
133
|
+
if (named.size === 0)
|
|
134
|
+
throw new InertiaError('VITE_MANIFEST_INVALID', `Vite manifest ${path} names no file: nothing is removed`);
|
|
135
|
+
return named;
|
|
136
|
+
}
|
|
137
|
+
/** The folders of the named files under outDir (not outDir itself, no hidden one), outermost. */
|
|
138
|
+
function buildFolders(outDir, named) {
|
|
139
|
+
const folders = new Set();
|
|
140
|
+
for (const file of named) {
|
|
141
|
+
const folder = dirname(file);
|
|
142
|
+
const inside = folder.slice(outDir.length + 1);
|
|
143
|
+
if (!folder.startsWith(outDir + sep) || inside.split(sep).some((part) => part.startsWith('.')))
|
|
144
|
+
continue;
|
|
145
|
+
folders.add(folder);
|
|
146
|
+
}
|
|
147
|
+
// A folder inside another is walked with it.
|
|
148
|
+
return [...folders]
|
|
149
|
+
.sort()
|
|
150
|
+
.filter((folder, _index, all) => !all.some((other) => folder.startsWith(other + sep)));
|
|
151
|
+
}
|
|
152
|
+
/** A file the manifest names, or the source map of one (`build.sourcemap: 'hidden'`). */
|
|
153
|
+
function current(file, named) {
|
|
154
|
+
return named.has(file) || (/\.map$/i.test(file) && named.has(file.slice(0, -4)));
|
|
155
|
+
}
|
|
156
|
+
/** A build output by its name: content-hashed (`app-3fa8c1d2.js`), or the source map of one. */
|
|
157
|
+
function built(name) {
|
|
158
|
+
return HASHED.test(/\.map$/i.test(name) ? name.slice(0, -4) : name);
|
|
159
|
+
}
|
package/dist/responder.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { Awaitable, Logger } from '@dokebi/core';
|
|
2
2
|
import { diagnostics } from '@dokebi/core/libraries';
|
|
3
|
-
import type
|
|
3
|
+
import { type WebErrorResponse } from '@dokebi/web';
|
|
4
4
|
import type { FastifyReply, FastifyRequest } from 'fastify';
|
|
5
|
+
import { type InertiaBootFallback, type NormalizedBootFallback } from './boot-fallback.js';
|
|
5
6
|
import { type InertiaProp } from './props.js';
|
|
6
7
|
export type Json = null | boolean | number | string | readonly Json[] | {
|
|
7
8
|
readonly [key: string]: Json;
|
|
@@ -25,6 +26,12 @@ export interface InertiaOptions {
|
|
|
25
26
|
* unset: JSON for visits, dokebi-web's small HTML page for page loads.
|
|
26
27
|
*/
|
|
27
28
|
errorPage?: string | false;
|
|
29
|
+
/**
|
|
30
|
+
* Text in the root of the first HTML while the app loads, then "could not load — reload" after
|
|
31
|
+
* `timeoutMs` or at once when the entry script fails to load (Studio v2); `false` turns it off.
|
|
32
|
+
* Not on a server-rendered page.
|
|
33
|
+
*/
|
|
34
|
+
bootFallback?: InertiaBootFallback | NormalizedBootFallback | false;
|
|
28
35
|
}
|
|
29
36
|
/** What `answerError` reads of a dokebi-web error answer (`WebErrorResponse`). */
|
|
30
37
|
export type InertiaErrorResponse = Pick<WebErrorResponse, 'request' | 'reply' | 'status' | 'body' | 'format' | 'sealed'>;
|
|
@@ -116,7 +123,9 @@ export interface InertiaApi {
|
|
|
116
123
|
answerError(response: InertiaErrorResponse): Promise<boolean>;
|
|
117
124
|
}
|
|
118
125
|
export declare function vary(reply: FastifyReply): void;
|
|
119
|
-
export type NormalizedInertiaOptions = Readonly<Required<InertiaOptions
|
|
126
|
+
export type NormalizedInertiaOptions = Readonly<Required<Omit<InertiaOptions, 'bootFallback'>> & {
|
|
127
|
+
bootFallback: NormalizedBootFallback;
|
|
128
|
+
}>;
|
|
120
129
|
/** Validates and fills defaults; the result is accepted again unchanged. */
|
|
121
130
|
export declare function normalizeInertiaOptions(options?: InertiaOptions): NormalizedInertiaOptions;
|
|
122
131
|
export declare class InertiaResponder implements InertiaApi {
|
|
@@ -133,8 +142,9 @@ export declare class InertiaResponder implements InertiaApi {
|
|
|
133
142
|
/**
|
|
134
143
|
* The error answer of an Inertia request or page load (D-0074 §3), for dokebi-web's error
|
|
135
144
|
* responders: the validation redirect (an Inertia request refused with 422 VALIDATION_FAILED,
|
|
136
|
-
* with
|
|
137
|
-
*
|
|
145
|
+
* or with another 4xx whose `data.formErrors` asks for it — D-0076 §2.5, dokebi-csrf's expired
|
|
146
|
+
* token — with a session), then the `errorPage` component. False leaves the answer to
|
|
147
|
+
* dokebi-web (JSON for visits, a small HTML page for page loads).
|
|
138
148
|
*/
|
|
139
149
|
answerError(response: InertiaErrorResponse): Promise<boolean>;
|
|
140
150
|
redirect(request: FastifyRequest, reply: FastifyReply, url: string): FastifyReply<import("fastify").RouteGenericInterface, import("fastify").RawServerDefault, import("http").IncomingMessage, import("http").ServerResponse<import("http").IncomingMessage>, unknown, import("fastify").FastifySchema, import("fastify").FastifyTypeProviderDefault, unknown>;
|