@skgo/sveltekit-adapter 0.10.1 → 0.11.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 -1
- package/skgo-adapter/env-private.js +48 -0
- package/skgo-adapter/env.js +87 -2
- package/skgo-adapter.js +4 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skgo/sveltekit-adapter",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "SvelteKit adapter for skgo: builds a frontend that a Go binary renders and answers",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"adapter",
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
"skgo-adapter/app-paths.js",
|
|
33
33
|
"skgo-adapter/app-server.js",
|
|
34
34
|
"skgo-adapter/entry.js",
|
|
35
|
+
"skgo-adapter/env-private.js",
|
|
35
36
|
"skgo-adapter/env.js",
|
|
36
37
|
"skgo-adapter/generated.js",
|
|
37
38
|
"skgo-adapter/identity.js",
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { resolve } from 'node:path';
|
|
2
|
+
import { realpathSync } from 'node:fs';
|
|
3
|
+
|
|
4
|
+
function canonical(file) {
|
|
5
|
+
try { return realpathSync.native(file); } catch { return file; }
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// Vite's raw/transform middleware can serve generated server files directly.
|
|
9
|
+
// Deny them before those middleware run, including absolute /@fs requests.
|
|
10
|
+
export function protectEnvironmentFiles({ root, kitOut, output, base = '/', token }) {
|
|
11
|
+
const files = [
|
|
12
|
+
resolve(root, '.svelte-kit/skgo-env-values.js'),
|
|
13
|
+
resolve(root, '.svelte-kit/skgo-env-runtime.json'),
|
|
14
|
+
resolve(root, kitOut, 'generated/build/env/config.js'),
|
|
15
|
+
resolve(root, kitOut, 'generated/dev/env/config.js'),
|
|
16
|
+
resolve(root, output, 'env.json')
|
|
17
|
+
].map(canonical);
|
|
18
|
+
const directories = [
|
|
19
|
+
resolve(root, kitOut, 'output/server'),
|
|
20
|
+
resolve(root, kitOut, 'generated/build/env/private'),
|
|
21
|
+
resolve(root, kitOut, 'generated/dev/env/private'),
|
|
22
|
+
resolve(root, output, 'ssr')
|
|
23
|
+
].map(canonical);
|
|
24
|
+
return (req, res, next) => {
|
|
25
|
+
let path;
|
|
26
|
+
try { path = decodeURIComponent((req.url || '').split('?')[0]); }
|
|
27
|
+
catch { res.statusCode = 400; res.end('Bad request'); return; }
|
|
28
|
+
if (base !== '/' && path.startsWith(base)) path = '/' + path.slice(base.length);
|
|
29
|
+
if (token && /^\/__skgo_dev\/(?:module|styles)(?:\/|$)/.test(path) && req.headers['x-skgo-dev-token'] !== token) {
|
|
30
|
+
res.statusCode = 403; res.end('Forbidden'); return;
|
|
31
|
+
}
|
|
32
|
+
let file;
|
|
33
|
+
if (path.startsWith('/@id/')) {
|
|
34
|
+
const id = path.slice(5).replace('__x00__', '\0');
|
|
35
|
+
if (id === '$app/env/private' || id === '#app/env/private' || /<sveltekit:generated>\/env\/(?:config\.js|private(?:\/|$))/.test(id)) {
|
|
36
|
+
res.statusCode = 403; res.end('Forbidden'); return;
|
|
37
|
+
}
|
|
38
|
+
file = resolve(id);
|
|
39
|
+
} else {
|
|
40
|
+
file = path.startsWith('/@fs/') ? resolve(path.slice(4)) : resolve(root, '.' + path);
|
|
41
|
+
}
|
|
42
|
+
file = canonical(file);
|
|
43
|
+
if (files.includes(file) || directories.some(dir => file === dir || file.startsWith(dir + '/'))) {
|
|
44
|
+
res.statusCode = 403; res.end('Forbidden'); return;
|
|
45
|
+
}
|
|
46
|
+
next();
|
|
47
|
+
};
|
|
48
|
+
}
|
package/skgo-adapter/env.js
CHANGED
|
@@ -25,7 +25,10 @@
|
|
|
25
25
|
*/
|
|
26
26
|
|
|
27
27
|
import { createRequire } from 'node:module';
|
|
28
|
-
import {
|
|
28
|
+
import { execFileSync } from 'node:child_process';
|
|
29
|
+
import { randomBytes } from 'node:crypto';
|
|
30
|
+
import { protectEnvironmentFiles } from './env-private.js';
|
|
31
|
+
import { existsSync, readdirSync, readFileSync, realpathSync, rmSync, statSync, mkdirSync, writeFileSync, chmodSync } from 'node:fs';
|
|
29
32
|
import { dirname, isAbsolute, join, relative, resolve } from 'node:path';
|
|
30
33
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
31
34
|
|
|
@@ -64,7 +67,7 @@ const { transformSync } = await fromApp('vite/rolldown/experimental');
|
|
|
64
67
|
// `fetchModule` operates on the app's own `DevEnvironment`, so it has to be
|
|
65
68
|
// the app's copy for the same reason rolldown is: a second module realm's
|
|
66
69
|
// vite does not recognise this one's environments.
|
|
67
|
-
const { fetchModule, isCSSRequest } = await fromApp('vite');
|
|
70
|
+
const { fetchModule, isCSSRequest, loadEnv } = await fromApp('vite');
|
|
68
71
|
|
|
69
72
|
// These are the same two Kit functions its own dev server calls in
|
|
70
73
|
// `update_manifest`: one derives the route/node graph from the authored tree,
|
|
@@ -324,6 +327,11 @@ export function gojaEnvironment() {
|
|
|
324
327
|
load: {
|
|
325
328
|
order: 'pre',
|
|
326
329
|
handler(id) {
|
|
330
|
+
if (/\/\.svelte-kit\/skgo-env-(?:values\.js|runtime\.json)(?:\?|$)/.test(id)) {
|
|
331
|
+
throw new Error('skgo: environment build inputs are private');
|
|
332
|
+
}
|
|
333
|
+
const environment = goEnvironmentModule(id);
|
|
334
|
+
if (environment !== null) return environment;
|
|
327
335
|
if (!id.startsWith(PREFIX)) return null;
|
|
328
336
|
const make = sources[id.slice(PREFIX.length)];
|
|
329
337
|
if (!make) throw new Error('skgo: no virtual module ' + id.slice(PREFIX.length));
|
|
@@ -926,6 +934,11 @@ export function gojaDevEnvironment({ outDir = '.svelte-kit' } = {}) {
|
|
|
926
934
|
load: {
|
|
927
935
|
order: 'pre',
|
|
928
936
|
handler(id) {
|
|
937
|
+
if (/\/\.svelte-kit\/skgo-env-(?:values\.js|runtime\.json)(?:\?|$)/.test(id)) {
|
|
938
|
+
throw new Error('skgo: environment build inputs are private');
|
|
939
|
+
}
|
|
940
|
+
const environment = goEnvironmentModule(id);
|
|
941
|
+
if (environment !== null) return environment;
|
|
929
942
|
if (!id.startsWith(PREFIX)) return null;
|
|
930
943
|
const make = sources[id.slice(PREFIX.length)];
|
|
931
944
|
if (!make) throw new Error('skgo: no virtual module ' + id.slice(PREFIX.length));
|
|
@@ -1056,3 +1069,75 @@ function errorText(e) {
|
|
|
1056
1069
|
if (e instanceof Error) return e.stack ?? e.message;
|
|
1057
1070
|
return String(e);
|
|
1058
1071
|
}
|
|
1072
|
+
|
|
1073
|
+
|
|
1074
|
+
// Values are validated by Go once before Kit loads its environment declaration.
|
|
1075
|
+
// This must be a real file: Kit imports env.ts in a separate Vite server with
|
|
1076
|
+
// configFile:false, where this adapter's virtual modules do not exist.
|
|
1077
|
+
export function goEnvironmentValues(output = 'build') {
|
|
1078
|
+
let token = '';
|
|
1079
|
+
let kitOut = '.svelte-kit';
|
|
1080
|
+
let fields = [];
|
|
1081
|
+
let values = {};
|
|
1082
|
+
return {
|
|
1083
|
+
plugin: {
|
|
1084
|
+
name: 'skgo-environment-values',
|
|
1085
|
+
configureServer(server) {
|
|
1086
|
+
// A server-only build input can contain private values. Vite's
|
|
1087
|
+
// filesystem endpoint must not make it downloadable in development.
|
|
1088
|
+
server.middlewares.use(protectEnvironmentFiles({
|
|
1089
|
+
root: server.config.root, kitOut, output, base: server.config.base, token
|
|
1090
|
+
}));
|
|
1091
|
+
},
|
|
1092
|
+
configResolved(config) {
|
|
1093
|
+
const schema = join(config.root, 'skgo.env.json');
|
|
1094
|
+
if (!existsSync(schema)) return;
|
|
1095
|
+
fields = JSON.parse(readFileSync(schema, 'utf8')).fields;
|
|
1096
|
+
const setup = config.plugins.find((p) => p.name === 'vite-plugin-sveltekit-setup');
|
|
1097
|
+
const kit = setup?.api?.options;
|
|
1098
|
+
if (!kit) throw new Error('skgo: Kit environment configuration is unavailable');
|
|
1099
|
+
kitOut = kit.outDir;
|
|
1100
|
+
const raw = loadEnv(config.mode, resolve(config.root, kit.env.dir), '');
|
|
1101
|
+
// Pass only declared values; diagnostics never echo the full process env.
|
|
1102
|
+
const input = Object.fromEntries(fields.filter((f) => f.name in raw).map((f) => [f.name, raw[f.name]]));
|
|
1103
|
+
try {
|
|
1104
|
+
values = JSON.parse(execFileSync('go', ['tool', 'skgo', 'env', '--schema', schema], {
|
|
1105
|
+
cwd: config.root, input: JSON.stringify(input), encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe']
|
|
1106
|
+
}));
|
|
1107
|
+
} catch (error) {
|
|
1108
|
+
throw new Error('skgo: Go environment validation failed\n' + String(error.stderr || error.message));
|
|
1109
|
+
}
|
|
1110
|
+
const file = join(config.root, '.svelte-kit/skgo-env-values.js');
|
|
1111
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
1112
|
+
writeFileSync(file, 'export const values = ' + JSON.stringify(values) + ';\n', { mode: 0o600 });
|
|
1113
|
+
chmodSync(file, 0o600);
|
|
1114
|
+
|
|
1115
|
+
if (config.command === 'serve') {
|
|
1116
|
+
token = randomBytes(32).toString('hex');
|
|
1117
|
+
const artifact = { token, fields, static: Object.fromEntries(fields.filter((f) => f.static && f.name in values).map((f) => [f.name, values[f.name]])), values: input };
|
|
1118
|
+
const runtimeFile = join(config.root, '.svelte-kit/skgo-env-runtime.json');
|
|
1119
|
+
writeFileSync(runtimeFile, JSON.stringify(artifact), { mode: 0o600 });
|
|
1120
|
+
chmodSync(runtimeFile, 0o600);
|
|
1121
|
+
}
|
|
1122
|
+
},
|
|
1123
|
+
},
|
|
1124
|
+
artifact() {
|
|
1125
|
+
return { fields, static: Object.fromEntries(fields.filter((f) => f.static && f.name in values).map((f) => [f.name, values[f.name]])) };
|
|
1126
|
+
}
|
|
1127
|
+
};
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
// Kit's public/private modules snapshot these properties during evaluation.
|
|
1131
|
+
// Go installs the snapshot before loading any application module. In particular,
|
|
1132
|
+
// importing Kit's original config here would re-run JavaScript validators and
|
|
1133
|
+
// bake the development/build environment into newly-created renderer runtimes.
|
|
1134
|
+
function goEnvironmentModule(id) {
|
|
1135
|
+
if (!existsSync(join(process.cwd(), 'skgo.env.json'))) return null;
|
|
1136
|
+
if (!/\/generated\/(?:build|dev)\/env\/config\.js$/.test(id.replaceAll('\\', '/'))) return null;
|
|
1137
|
+
return `const snapshot = globalThis.__skgo_environment || {};
|
|
1138
|
+
export const dynamic_private_env = snapshot.private || {};
|
|
1139
|
+
export const explicit_public_env = snapshot.public || {};
|
|
1140
|
+
export const rendered_env = snapshot.dynamicPublic || {};
|
|
1141
|
+
export const variables = {};
|
|
1142
|
+
export function set_env() {}`;
|
|
1143
|
+
}
|
package/skgo-adapter.js
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
} from 'node:fs';
|
|
11
11
|
import { basename, dirname, join, relative, resolve } from 'node:path';
|
|
12
12
|
import { pathToFileURL } from 'node:url';
|
|
13
|
-
import { gojaDevEnvironment, gojaEnvironment, nodeTable, SSR_TARGET } from './skgo-adapter/env.js';
|
|
13
|
+
import { gojaDevEnvironment, gojaEnvironment, goEnvironmentValues, nodeTable, SSR_TARGET } from './skgo-adapter/env.js';
|
|
14
14
|
import { identity } from './skgo-adapter/identity.js';
|
|
15
15
|
import { checkEndpoints, validateGenerated } from './skgo-adapter/generated.js';
|
|
16
16
|
|
|
@@ -56,6 +56,7 @@ export default function skgo({ out = 'build', precompress = true } = {}) {
|
|
|
56
56
|
// runs, so the plugin that declares the environment has to exist here; the
|
|
57
57
|
// build itself waits until adapt() knows the node table.
|
|
58
58
|
const goja = gojaEnvironment();
|
|
59
|
+
const environment = goEnvironmentValues(out);
|
|
59
60
|
|
|
60
61
|
return {
|
|
61
62
|
name: 'skgo',
|
|
@@ -65,10 +66,11 @@ export default function skgo({ out = 'build', precompress = true } = {}) {
|
|
|
65
66
|
// compiles the SSR bundle in is also declared in `vite dev`, where Go
|
|
66
67
|
// pulls one transformed module at a time out of it instead. Each plugin
|
|
67
68
|
// states its own `apply`, so only one of them is ever live.
|
|
68
|
-
vite: { plugins: { post: [goja.plugin, gojaDevEnvironment()] } },
|
|
69
|
+
vite: { plugins: { post: [environment.plugin, goja.plugin, gojaDevEnvironment()] } },
|
|
69
70
|
async adapt(builder) {
|
|
70
71
|
rmSync(out, { force: true, recursive: true });
|
|
71
72
|
|
|
73
|
+
write(`${out}/env.json`, JSON.stringify(environment.artifact()));
|
|
72
74
|
const generated = readGenerated();
|
|
73
75
|
const { manifest: kit, source } = await readKitManifest(builder);
|
|
74
76
|
const hashes = checkRemoteHashes(kit, generated.remotes);
|