@roblawn/devtool-runtime 0.1.0-alpha.0 → 0.1.0-alpha.1
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/LICENSE.md +11 -11
- package/README.md +7 -7
- package/dist/content/envelope.d.ts +54 -0
- package/dist/content/envelope.js +10 -0
- package/dist/content/envelopeResolver.d.ts +14 -0
- package/dist/content/envelopeResolver.js +77 -0
- package/dist/content/identity.d.ts +16 -0
- package/dist/content/identity.js +1 -0
- package/dist/content/index.d.ts +10 -0
- package/dist/content/index.js +10 -0
- package/dist/content/normalize.d.ts +8 -0
- package/dist/content/normalize.js +53 -0
- package/dist/content/profile.d.ts +11 -0
- package/dist/content/profile.js +29 -0
- package/dist/content/provider.d.ts +17 -0
- package/dist/content/provider.js +1 -0
- package/dist/content/safeContent.d.ts +2 -0
- package/dist/content/safeContent.js +44 -0
- package/dist/content/scope.d.ts +6 -0
- package/dist/content/scope.js +41 -0
- package/dist/content/slotItems.d.ts +13 -0
- package/dist/content/slotItems.js +53 -0
- package/dist/content/targets.d.ts +24 -0
- package/dist/content/targets.js +9 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/schema/LayoutDefinitionMeta.d.ts +6 -0
- package/dist/shared/legacyNames.d.ts +6 -0
- package/dist/shared/legacyNames.js +8 -0
- package/dist/static/buildSsg.d.ts +11 -0
- package/dist/static/buildSsg.js +93 -0
- package/dist/static/index.d.ts +4 -0
- package/dist/static/index.js +4 -0
- package/dist/static/packageNetlify.d.ts +28 -0
- package/dist/static/packageNetlify.js +141 -0
- package/dist/static/packageStaticSite.d.ts +19 -0
- package/dist/static/packageStaticSite.js +144 -0
- package/dist/static/serveStaticSite.d.ts +9 -0
- package/dist/static/serveStaticSite.js +143 -0
- package/dist/theme-contract/ops/coreOps.js +5 -47
- package/dist/theme-contract/ops/emitters.d.ts +2 -0
- package/dist/theme-contract/ops/emitters.js +38 -2
- package/dist/theme-contract/tokens/schemas/spacing.token.schema.d.ts +9 -2
- package/dist/theme-contract/tokens/schemas/spacing.token.schema.js +9 -2
- package/dist/theme-contract/tokens/tokens.schema.d.ts +9 -2
- package/dist/vite/embeddedJsonPlugin.d.ts +8 -0
- package/dist/vite/embeddedJsonPlugin.js +109 -0
- package/dist/vite/index.d.ts +4 -0
- package/dist/vite/index.js +4 -0
- package/dist/vite/localJsonPlugin.d.ts +4 -0
- package/dist/vite/localJsonPlugin.js +70 -0
- package/dist/vite/ssgEntryPlugin.d.ts +7 -0
- package/dist/vite/ssgEntryPlugin.js +21 -0
- package/dist/vite/staticFallbackPlugin.d.ts +8 -0
- package/dist/vite/staticFallbackPlugin.js +77 -0
- package/package.json +47 -27
|
@@ -13,10 +13,12 @@ export declare class Emit {
|
|
|
13
13
|
static colorRuntimeVar(cssVarName: string, tokenPrefix: string, value: unknown): string;
|
|
14
14
|
static shadowCompose(value: unknown): string;
|
|
15
15
|
static border(value: string): string;
|
|
16
|
+
static borderSide(side: 't' | 'r' | 'b' | 'l', value: string): string;
|
|
16
17
|
static borderComposite(value: unknown): string;
|
|
17
18
|
static borderCompositeSide(side: 't' | 'r' | 'b' | 'l', value: unknown): string;
|
|
18
19
|
static borderCompositeAxis(axis: 'x' | 'y', value: unknown): string;
|
|
19
20
|
static divideBorder(value: string): string;
|
|
21
|
+
static leading(value: unknown): string;
|
|
20
22
|
static divideComposite(value: unknown): string;
|
|
21
23
|
static zIndex(value: unknown): string;
|
|
22
24
|
static aspect(value: unknown): string;
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
const CONTAINER_PASSTHROUGH_SPECIALS = new Set(['full', 'screen', 'fit', 'min', 'max']);
|
|
2
2
|
const CONTAINER_ALIAS_PASSTHROUGH = new Set(['full', 'screen', 'fit', 'min', 'max', 'none']);
|
|
3
3
|
const ASPECT_ALIAS_PASSTHROUGH = new Set(['auto', 'square', 'video', 'none']);
|
|
4
|
+
const LEADING_NUMERIC_ALIASES = new Map([
|
|
5
|
+
[1, 'leading-none'],
|
|
6
|
+
[1.25, 'leading-tight'],
|
|
7
|
+
[1.375, 'leading-snug'],
|
|
8
|
+
[1.5, 'leading-normal'],
|
|
9
|
+
[1.625, 'leading-relaxed'],
|
|
10
|
+
[2, 'leading-loose'],
|
|
11
|
+
]);
|
|
4
12
|
export class Emit {
|
|
5
13
|
static isEmpty(value) {
|
|
6
14
|
return value === undefined || value === null || value === '' || value === false;
|
|
@@ -96,13 +104,29 @@ export class Emit {
|
|
|
96
104
|
static border(value) {
|
|
97
105
|
const numericValue = Number(String(value));
|
|
98
106
|
if (!Number.isFinite(numericValue)) {
|
|
99
|
-
return Emit.isEmpty(value) ? 'border
|
|
107
|
+
return Emit.isEmpty(value) ? 'border' : `border-(length:--border-${String(value)})`;
|
|
108
|
+
}
|
|
109
|
+
if (numericValue === 1) {
|
|
110
|
+
return 'border';
|
|
100
111
|
}
|
|
101
112
|
if (Number.isInteger(numericValue)) {
|
|
102
113
|
return `border-${numericValue}`;
|
|
103
114
|
}
|
|
104
115
|
return `border-[${numericValue}px]`;
|
|
105
116
|
}
|
|
117
|
+
static borderSide(side, value) {
|
|
118
|
+
const numericValue = Number(String(value));
|
|
119
|
+
if (!Number.isFinite(numericValue)) {
|
|
120
|
+
return Emit.isEmpty(value) ? `border-${side}` : `border-${side}-(length:--border-${String(value)})`;
|
|
121
|
+
}
|
|
122
|
+
if (numericValue === 1) {
|
|
123
|
+
return `border-${side}`;
|
|
124
|
+
}
|
|
125
|
+
if (Number.isInteger(numericValue)) {
|
|
126
|
+
return `border-${side}-${numericValue}`;
|
|
127
|
+
}
|
|
128
|
+
return `border-${side}-[${numericValue}px]`;
|
|
129
|
+
}
|
|
106
130
|
static borderComposite(value) {
|
|
107
131
|
return Emit.isEmpty(value) ? '' : `border-${String(value)}`;
|
|
108
132
|
}
|
|
@@ -115,13 +139,25 @@ export class Emit {
|
|
|
115
139
|
static divideBorder(value) {
|
|
116
140
|
const numericValue = Number(String(value));
|
|
117
141
|
if (!Number.isFinite(numericValue)) {
|
|
118
|
-
return Emit.isEmpty(value) ? '' : `divide-x-(
|
|
142
|
+
return Emit.isEmpty(value) ? '' : `divide-x-(length:--border-${String(value)})`;
|
|
143
|
+
}
|
|
144
|
+
if (numericValue === 1) {
|
|
145
|
+
return 'divide-x';
|
|
119
146
|
}
|
|
120
147
|
if (Number.isInteger(numericValue)) {
|
|
121
148
|
return `divide-x-${numericValue}`;
|
|
122
149
|
}
|
|
123
150
|
return `divide-x-[${numericValue}px]`;
|
|
124
151
|
}
|
|
152
|
+
static leading(value) {
|
|
153
|
+
if (Emit.isEmpty(value))
|
|
154
|
+
return '';
|
|
155
|
+
const numericValue = Number(String(value));
|
|
156
|
+
if (Number.isFinite(numericValue)) {
|
|
157
|
+
return LEADING_NUMERIC_ALIASES.get(numericValue) ?? `leading-[${numericValue}]`;
|
|
158
|
+
}
|
|
159
|
+
return Emit.prefix('leading', value);
|
|
160
|
+
}
|
|
125
161
|
static divideComposite(value) {
|
|
126
162
|
return Emit.isEmpty(value) ? '' : `divide-border-${String(value)}`;
|
|
127
163
|
}
|
|
@@ -8,13 +8,20 @@ export declare const spacingTokenSchema: {
|
|
|
8
8
|
readonly direction: "asc";
|
|
9
9
|
};
|
|
10
10
|
readonly initialTokens: {
|
|
11
|
-
readonly zero: "0px";
|
|
12
11
|
readonly '2xs': "2px";
|
|
13
12
|
readonly xs: "4px";
|
|
14
13
|
readonly sm: "8px";
|
|
15
14
|
readonly md: "12px";
|
|
16
15
|
readonly lg: "16px";
|
|
17
|
-
readonly xl: "
|
|
16
|
+
readonly xl: "20px";
|
|
17
|
+
readonly '2xl': "24px";
|
|
18
|
+
readonly '3xl': "32px";
|
|
19
|
+
readonly '4xl': "40px";
|
|
20
|
+
readonly '5xl': "48px";
|
|
21
|
+
readonly '6xl': "64px";
|
|
22
|
+
readonly '7xl': "80px";
|
|
23
|
+
readonly '8xl': "96px";
|
|
24
|
+
readonly '9xl': "128px";
|
|
18
25
|
};
|
|
19
26
|
readonly toCssVarName: (tokenKey: string) => string;
|
|
20
27
|
readonly toCssVarValue: (_tokenKey: string, tokenValue: string) => string;
|
|
@@ -7,13 +7,20 @@ export const spacingTokenSchema = defineTokenFamilySchema({
|
|
|
7
7
|
responsive: true,
|
|
8
8
|
order: { type: 'numeric', direction: 'asc' },
|
|
9
9
|
initialTokens: {
|
|
10
|
-
zero: '0px',
|
|
11
10
|
'2xs': '2px',
|
|
12
11
|
xs: '4px',
|
|
13
12
|
sm: '8px',
|
|
14
13
|
md: '12px',
|
|
15
14
|
lg: '16px',
|
|
16
|
-
xl: '
|
|
15
|
+
xl: '20px',
|
|
16
|
+
'2xl': '24px',
|
|
17
|
+
'3xl': '32px',
|
|
18
|
+
'4xl': '40px',
|
|
19
|
+
'5xl': '48px',
|
|
20
|
+
'6xl': '64px',
|
|
21
|
+
'7xl': '80px',
|
|
22
|
+
'8xl': '96px',
|
|
23
|
+
'9xl': '128px',
|
|
17
24
|
},
|
|
18
25
|
toCssVarName: (tokenKey) => `--spacing-${tokenKey}`,
|
|
19
26
|
toCssVarValue: (_tokenKey, tokenValue) => pxTokenToRemValue(tokenValue),
|
|
@@ -331,13 +331,20 @@ export declare const TOKEN_SCHEMAS: readonly [{
|
|
|
331
331
|
readonly direction: "asc";
|
|
332
332
|
};
|
|
333
333
|
readonly initialTokens: {
|
|
334
|
-
readonly zero: "0px";
|
|
335
334
|
readonly '2xs': "2px";
|
|
336
335
|
readonly xs: "4px";
|
|
337
336
|
readonly sm: "8px";
|
|
338
337
|
readonly md: "12px";
|
|
339
338
|
readonly lg: "16px";
|
|
340
|
-
readonly xl: "
|
|
339
|
+
readonly xl: "20px";
|
|
340
|
+
readonly '2xl': "24px";
|
|
341
|
+
readonly '3xl': "32px";
|
|
342
|
+
readonly '4xl': "40px";
|
|
343
|
+
readonly '5xl': "48px";
|
|
344
|
+
readonly '6xl': "64px";
|
|
345
|
+
readonly '7xl': "80px";
|
|
346
|
+
readonly '8xl': "96px";
|
|
347
|
+
readonly '9xl': "128px";
|
|
341
348
|
};
|
|
342
349
|
readonly toCssVarName: (tokenKey: string) => string;
|
|
343
350
|
readonly toCssVarValue: (_tokenKey: string, tokenValue: string) => string;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Plugin } from 'vite';
|
|
2
|
+
export interface EmbeddedJsonAsset {
|
|
3
|
+
fileName: string;
|
|
4
|
+
sourcePath: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function listEmbeddedJsonAssets(projectRoot: string): Promise<EmbeddedJsonAsset[]>;
|
|
7
|
+
export declare function embeddedJsonPlugin(): Plugin;
|
|
8
|
+
export default embeddedJsonPlugin;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { promises as fs } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { LEGACY_CONFIG_FILE, LEGACY_NAME_LOWER, LEGACY_PRIVATE_DIR, LEGACY_PUBLIC_DIR, } from '../shared/legacyNames.js';
|
|
4
|
+
function toOutputPath(...parts) {
|
|
5
|
+
return parts.join('/').replace(/\\/g, '/');
|
|
6
|
+
}
|
|
7
|
+
function isPlainObject(value) {
|
|
8
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
9
|
+
}
|
|
10
|
+
function normalizeRelativePath(value, fallback) {
|
|
11
|
+
if (typeof value !== 'string')
|
|
12
|
+
return fallback;
|
|
13
|
+
const next = value.trim();
|
|
14
|
+
return next.length > 0 ? next : fallback;
|
|
15
|
+
}
|
|
16
|
+
async function listDirs(abs) {
|
|
17
|
+
try {
|
|
18
|
+
const entries = await fs.readdir(abs, { withFileTypes: true });
|
|
19
|
+
return entries
|
|
20
|
+
.filter((entry) => entry.isDirectory())
|
|
21
|
+
.map((entry) => entry.name)
|
|
22
|
+
.sort();
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return [];
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
async function fileExists(abs) {
|
|
29
|
+
try {
|
|
30
|
+
await fs.access(abs);
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
async function readEmbeddedJsonConfig(projectRoot) {
|
|
38
|
+
const fallback = {
|
|
39
|
+
pagePublishRoot: './src/pages',
|
|
40
|
+
privateRoot: `./${LEGACY_PRIVATE_DIR}`,
|
|
41
|
+
};
|
|
42
|
+
try {
|
|
43
|
+
const raw = JSON.parse(await fs.readFile(path.resolve(projectRoot, LEGACY_CONFIG_FILE), 'utf8'));
|
|
44
|
+
if (!isPlainObject(raw))
|
|
45
|
+
return fallback;
|
|
46
|
+
const paths = isPlainObject(raw.paths) ? raw.paths : {};
|
|
47
|
+
return {
|
|
48
|
+
pagePublishRoot: normalizeRelativePath(paths.pagePublishRoot, fallback.pagePublishRoot),
|
|
49
|
+
privateRoot: normalizeRelativePath(paths[`${LEGACY_NAME_LOWER}PrivateRoot`], fallback.privateRoot),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return fallback;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
export async function listEmbeddedJsonAssets(projectRoot) {
|
|
57
|
+
const config = await readEmbeddedJsonConfig(projectRoot);
|
|
58
|
+
const privateRoot = path.resolve(projectRoot, config.privateRoot);
|
|
59
|
+
const pagePublishRoot = path.resolve(projectRoot, config.pagePublishRoot);
|
|
60
|
+
const assets = [];
|
|
61
|
+
const pagesRoot = path.resolve(privateRoot, 'pages');
|
|
62
|
+
for (const slug of await listDirs(pagesRoot)) {
|
|
63
|
+
const sourcePath = path.resolve(pagesRoot, slug, 'content.json');
|
|
64
|
+
const pageEntry = path.resolve(pagePublishRoot, slug, 'index.vue');
|
|
65
|
+
if (!(await fileExists(sourcePath)) || !(await fileExists(pageEntry)))
|
|
66
|
+
continue;
|
|
67
|
+
assets.push({
|
|
68
|
+
fileName: toOutputPath(LEGACY_PUBLIC_DIR, 'pages', slug, 'content.json'),
|
|
69
|
+
sourcePath,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
const modulesRoot = path.resolve(privateRoot, 'modules');
|
|
73
|
+
for (const subType of await listDirs(modulesRoot)) {
|
|
74
|
+
const subRoot = path.resolve(modulesRoot, subType);
|
|
75
|
+
for (const slug of await listDirs(subRoot)) {
|
|
76
|
+
const slugRoot = path.resolve(subRoot, slug);
|
|
77
|
+
const rootJson = path.resolve(slugRoot, 'content.json');
|
|
78
|
+
if (await fileExists(rootJson)) {
|
|
79
|
+
assets.push({
|
|
80
|
+
fileName: toOutputPath(LEGACY_PUBLIC_DIR, 'modules', subType, slug, 'content.json'),
|
|
81
|
+
sourcePath: rootJson,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
for (const instance of await listDirs(slugRoot)) {
|
|
85
|
+
const instanceJson = path.resolve(slugRoot, instance, 'content.json');
|
|
86
|
+
if (!(await fileExists(instanceJson)))
|
|
87
|
+
continue;
|
|
88
|
+
assets.push({
|
|
89
|
+
fileName: toOutputPath(LEGACY_PUBLIC_DIR, 'modules', subType, slug, instance, 'content.json'),
|
|
90
|
+
sourcePath: instanceJson,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return assets;
|
|
96
|
+
}
|
|
97
|
+
export function embeddedJsonPlugin() {
|
|
98
|
+
return {
|
|
99
|
+
name: 'runtime-embedded-json',
|
|
100
|
+
apply: 'build',
|
|
101
|
+
async generateBundle() {
|
|
102
|
+
for (const asset of await listEmbeddedJsonAssets(process.cwd())) {
|
|
103
|
+
const source = await fs.readFile(asset.sourcePath, 'utf8');
|
|
104
|
+
this.emitFile({ type: 'asset', fileName: asset.fileName, source });
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
export default embeddedJsonPlugin;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { promises as fs } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { LEGACY_PRIVATE_DIR, LEGACY_PUBLIC_DIR } from '../shared/legacyNames.js';
|
|
4
|
+
function sendJson(res, statusCode, body) {
|
|
5
|
+
res.statusCode = statusCode;
|
|
6
|
+
res.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
7
|
+
res.end(body);
|
|
8
|
+
}
|
|
9
|
+
function decodeRequestPath(requestPath) {
|
|
10
|
+
try {
|
|
11
|
+
return decodeURIComponent(requestPath).replace(/\\/g, '/');
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function isSafeSegment(value) {
|
|
18
|
+
return value.length > 0 && value !== '.' && value !== '..' && !value.includes('/');
|
|
19
|
+
}
|
|
20
|
+
function isPublicRuntimeJsonPath(rel) {
|
|
21
|
+
const parts = rel.split('/');
|
|
22
|
+
if (!parts.every(isSafeSegment))
|
|
23
|
+
return false;
|
|
24
|
+
if (parts.length === 3 && parts[0] === 'pages' && parts[2] === 'content.json')
|
|
25
|
+
return true;
|
|
26
|
+
if (parts.length === 4 && parts[0] === 'modules' && parts[3] === 'content.json')
|
|
27
|
+
return true;
|
|
28
|
+
return parts.length === 5 && parts[0] === 'modules' && parts[4] === 'content.json';
|
|
29
|
+
}
|
|
30
|
+
export function resolveRuntimeJsonPath(privateRoot, requestPath) {
|
|
31
|
+
const normalized = decodeRequestPath(requestPath);
|
|
32
|
+
if (!normalized)
|
|
33
|
+
return null;
|
|
34
|
+
const prefixes = [`/${LEGACY_PRIVATE_DIR}/`, `/${LEGACY_PUBLIC_DIR}/`];
|
|
35
|
+
const prefix = prefixes.find((candidate) => normalized.startsWith(candidate));
|
|
36
|
+
if (!prefix)
|
|
37
|
+
return null;
|
|
38
|
+
const rel = normalized.slice(prefix.length);
|
|
39
|
+
if (!isPublicRuntimeJsonPath(rel))
|
|
40
|
+
return null;
|
|
41
|
+
const resolved = path.resolve(privateRoot, rel);
|
|
42
|
+
const rootWithSep = privateRoot.endsWith(path.sep) ? privateRoot : `${privateRoot}${path.sep}`;
|
|
43
|
+
if (resolved !== privateRoot && !resolved.startsWith(rootWithSep))
|
|
44
|
+
return null;
|
|
45
|
+
return resolved;
|
|
46
|
+
}
|
|
47
|
+
export function localJsonPlugin() {
|
|
48
|
+
return {
|
|
49
|
+
name: 'runtime-local-json',
|
|
50
|
+
apply: 'serve',
|
|
51
|
+
configureServer(server) {
|
|
52
|
+
const privateRoot = path.resolve(process.cwd(), LEGACY_PRIVATE_DIR);
|
|
53
|
+
server.middlewares.use(async (req, res, next) => {
|
|
54
|
+
const url = req.url?.split('?')[0] ?? '';
|
|
55
|
+
const filePath = resolveRuntimeJsonPath(privateRoot, url);
|
|
56
|
+
if (!filePath) {
|
|
57
|
+
next();
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
sendJson(res, 200, await fs.readFile(filePath, 'utf8'));
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
sendJson(res, 404, JSON.stringify({ error: 'Local JSON not found' }));
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
export default localJsonPlugin;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export function ssgEntryPlugin(options = {}) {
|
|
2
|
+
const enabled = options.enabled ?? true;
|
|
3
|
+
const entry = options.entry ?? '/src/main.ssg.ts';
|
|
4
|
+
return {
|
|
5
|
+
name: 'runtime-ssg-entry',
|
|
6
|
+
apply: 'build',
|
|
7
|
+
transformIndexHtml: {
|
|
8
|
+
order: 'pre',
|
|
9
|
+
handler(html) {
|
|
10
|
+
if (!enabled)
|
|
11
|
+
return html;
|
|
12
|
+
const next = html.replace(/(<script\b[^>]*\btype=["']module["'][^>]*\bsrc=["'])\/src\/main\.ts(["'][^>]*><\/script>)/, `$1${entry}$2`);
|
|
13
|
+
if (next === html && !html.includes(`src="${entry}"`) && !html.includes(`src='${entry}'`)) {
|
|
14
|
+
throw new Error(`[runtime-ssg-entry] Could not replace the HTML module entry with ${entry}.`);
|
|
15
|
+
}
|
|
16
|
+
return next;
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export default ssgEntryPlugin;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Plugin } from 'vite';
|
|
2
|
+
interface StaticFallbackConfig {
|
|
3
|
+
notFound: string;
|
|
4
|
+
pagePublishRoot: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function resolveStaticFallbackConfig(projectRoot: string): Promise<StaticFallbackConfig>;
|
|
7
|
+
export declare function staticFallbackPlugin(): Plugin;
|
|
8
|
+
export default staticFallbackPlugin;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { promises as fs } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { LEGACY_CONFIG_FILE } from '../shared/legacyNames.js';
|
|
4
|
+
function isPlainObject(value) {
|
|
5
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
6
|
+
}
|
|
7
|
+
function normalizePageSlug(value, fallback) {
|
|
8
|
+
if (typeof value !== 'string')
|
|
9
|
+
return fallback;
|
|
10
|
+
const slug = value.trim().replace(/^\/+|\/+$/g, '');
|
|
11
|
+
if (!slug || slug.includes('\\') || slug.split('/').some((part) => part === '..' || part === '.')) {
|
|
12
|
+
return fallback;
|
|
13
|
+
}
|
|
14
|
+
return slug;
|
|
15
|
+
}
|
|
16
|
+
function normalizeRelativePath(value, fallback) {
|
|
17
|
+
if (typeof value !== 'string')
|
|
18
|
+
return fallback;
|
|
19
|
+
const next = value.trim();
|
|
20
|
+
return next.length > 0 ? next : fallback;
|
|
21
|
+
}
|
|
22
|
+
async function fileExists(abs) {
|
|
23
|
+
try {
|
|
24
|
+
await fs.access(abs);
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export async function resolveStaticFallbackConfig(projectRoot) {
|
|
32
|
+
const fallback = {
|
|
33
|
+
notFound: '404',
|
|
34
|
+
pagePublishRoot: './src/pages',
|
|
35
|
+
};
|
|
36
|
+
try {
|
|
37
|
+
const raw = JSON.parse(await fs.readFile(path.resolve(projectRoot, LEGACY_CONFIG_FILE), 'utf8'));
|
|
38
|
+
if (!isPlainObject(raw))
|
|
39
|
+
return fallback;
|
|
40
|
+
const paths = isPlainObject(raw.paths) ? raw.paths : {};
|
|
41
|
+
const site = isPlainObject(raw.site) ? raw.site : {};
|
|
42
|
+
const systemPages = isPlainObject(site.systemPages) ? site.systemPages : {};
|
|
43
|
+
return {
|
|
44
|
+
notFound: normalizePageSlug(systemPages.notFound, fallback.notFound),
|
|
45
|
+
pagePublishRoot: normalizeRelativePath(paths.pagePublishRoot, fallback.pagePublishRoot),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return fallback;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
export function staticFallbackPlugin() {
|
|
53
|
+
let projectRoot = process.cwd();
|
|
54
|
+
let outDir = path.resolve(projectRoot, 'dist');
|
|
55
|
+
let mode = 'production';
|
|
56
|
+
return {
|
|
57
|
+
name: 'runtime-static-fallbacks',
|
|
58
|
+
apply: 'build',
|
|
59
|
+
configResolved(config) {
|
|
60
|
+
projectRoot = config.root;
|
|
61
|
+
outDir = path.resolve(config.root, config.build.outDir);
|
|
62
|
+
mode = config.mode;
|
|
63
|
+
},
|
|
64
|
+
async closeBundle() {
|
|
65
|
+
const config = await resolveStaticFallbackConfig(projectRoot);
|
|
66
|
+
const notFoundPage = path.resolve(projectRoot, config.pagePublishRoot, config.notFound, 'index.vue');
|
|
67
|
+
if (mode === 'ssg' && !(await fileExists(notFoundPage)))
|
|
68
|
+
return;
|
|
69
|
+
const indexHtml = path.resolve(outDir, 'index.html');
|
|
70
|
+
const notFoundHtml = path.resolve(outDir, '404.html');
|
|
71
|
+
if (!(await fileExists(indexHtml)) || (await fileExists(notFoundHtml)))
|
|
72
|
+
return;
|
|
73
|
+
await fs.copyFile(indexHtml, notFoundHtml);
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
export default staticFallbackPlugin;
|
package/package.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@roblawn/devtool-runtime",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
4
|
-
"description": "Repo-safe runtime primitives for visual site authoring.",
|
|
5
|
-
"license": "SEE LICENSE IN LICENSE.md",
|
|
6
|
-
"type": "module",
|
|
3
|
+
"version": "0.1.0-alpha.1",
|
|
4
|
+
"description": "Repo-safe runtime primitives for visual site authoring.",
|
|
5
|
+
"license": "SEE LICENSE IN LICENSE.md",
|
|
6
|
+
"type": "module",
|
|
7
7
|
"files": ["dist"],
|
|
8
|
-
"publishConfig": {
|
|
9
|
-
"access": "public",
|
|
10
|
-
"registry": "https://registry.npmjs.org"
|
|
11
|
-
},
|
|
8
|
+
"publishConfig": {
|
|
9
|
+
"access": "public",
|
|
10
|
+
"registry": "https://registry.npmjs.org"
|
|
11
|
+
},
|
|
12
12
|
"exports": {
|
|
13
13
|
".": {
|
|
14
14
|
"types": "./dist/index.d.ts",
|
|
@@ -22,31 +22,43 @@
|
|
|
22
22
|
"types": "./dist/ak/index.d.ts",
|
|
23
23
|
"import": "./dist/ak/index.js"
|
|
24
24
|
},
|
|
25
|
-
"./theme-contract": {
|
|
26
|
-
"types": "./dist/theme-contract/index.d.ts",
|
|
27
|
-
"import": "./dist/theme-contract/index.js"
|
|
28
|
-
},
|
|
25
|
+
"./theme-contract": {
|
|
26
|
+
"types": "./dist/theme-contract/index.d.ts",
|
|
27
|
+
"import": "./dist/theme-contract/index.js"
|
|
28
|
+
},
|
|
29
29
|
"./cms": {
|
|
30
30
|
"types": "./dist/cms/index.d.ts",
|
|
31
31
|
"import": "./dist/cms/index.js"
|
|
32
32
|
},
|
|
33
|
+
"./content": {
|
|
34
|
+
"types": "./dist/content/index.d.ts",
|
|
35
|
+
"import": "./dist/content/index.js"
|
|
36
|
+
},
|
|
37
|
+
"./vite": {
|
|
38
|
+
"types": "./dist/vite/index.d.ts",
|
|
39
|
+
"import": "./dist/vite/index.js"
|
|
40
|
+
},
|
|
41
|
+
"./static": {
|
|
42
|
+
"types": "./dist/static/index.d.ts",
|
|
43
|
+
"import": "./dist/static/index.js"
|
|
44
|
+
},
|
|
33
45
|
"./types": {
|
|
34
46
|
"types": "./dist/types/index.d.ts",
|
|
35
47
|
"import": "./dist/types/index.js"
|
|
36
48
|
},
|
|
37
|
-
"./authoring": {
|
|
38
|
-
"types": "./dist/authoring/index.d.ts",
|
|
39
|
-
"import": "./dist/authoring/index.js"
|
|
40
|
-
},
|
|
41
|
-
"./layout": {
|
|
42
|
-
"types": "./dist/layout/index.d.ts",
|
|
43
|
-
"import": "./dist/layout/index.js"
|
|
44
|
-
},
|
|
45
|
-
"./schema": {
|
|
46
|
-
"types": "./dist/schema/index.d.ts",
|
|
47
|
-
"import": "./dist/schema/index.js"
|
|
48
|
-
}
|
|
49
|
-
},
|
|
49
|
+
"./authoring": {
|
|
50
|
+
"types": "./dist/authoring/index.d.ts",
|
|
51
|
+
"import": "./dist/authoring/index.js"
|
|
52
|
+
},
|
|
53
|
+
"./layout": {
|
|
54
|
+
"types": "./dist/layout/index.d.ts",
|
|
55
|
+
"import": "./dist/layout/index.js"
|
|
56
|
+
},
|
|
57
|
+
"./schema": {
|
|
58
|
+
"types": "./dist/schema/index.d.ts",
|
|
59
|
+
"import": "./dist/schema/index.js"
|
|
60
|
+
}
|
|
61
|
+
},
|
|
50
62
|
"scripts": {
|
|
51
63
|
"clean": "node ../scripts/clean-runtime-dist.mjs",
|
|
52
64
|
"build": "pnpm run clean && tsc -p tsconfig.json && pnpm run check:package-boundary",
|
|
@@ -54,12 +66,20 @@
|
|
|
54
66
|
"dev": "tsc -p tsconfig.json -w --preserveWatchOutput"
|
|
55
67
|
},
|
|
56
68
|
"peerDependencies": {
|
|
57
|
-
"
|
|
69
|
+
"vite": "^6.0.0 || ^7.0.0",
|
|
70
|
+
"vue": "^3.5.34",
|
|
58
71
|
"zod": ">=3.24.0 <4"
|
|
59
72
|
},
|
|
73
|
+
"peerDependenciesMeta": {
|
|
74
|
+
"vite": {
|
|
75
|
+
"optional": true
|
|
76
|
+
}
|
|
77
|
+
},
|
|
60
78
|
"devDependencies": {
|
|
79
|
+
"@types/node": "^22.14.0",
|
|
61
80
|
"typescript": "^5.8.3",
|
|
62
|
-
"
|
|
81
|
+
"vite": "^6.2.0",
|
|
82
|
+
"vue": "^3.5.34",
|
|
63
83
|
"zod": "3.24.3"
|
|
64
84
|
}
|
|
65
85
|
}
|