@ducking-mind/proxy-app-cli 0.1.2 → 0.2.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/README.md +50 -13
- package/agent-contract.mjs +13 -6
- package/backend-container.mjs +199 -0
- package/local-dev-credentials.mjs +5 -5
- package/local-dev-relay.mjs +130 -524
- package/package.json +4 -2
- package/platform-settings.mjs +1 -0
- package/proxy-app.mjs +376 -49
- package/runtime-sources.mjs +83 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { lstat, readdir, readFile } from 'node:fs/promises'
|
|
2
|
+
import { extname, join, resolve } from 'node:path'
|
|
3
|
+
import { assertSafeArtifactFile } from './artifact-security.mjs'
|
|
4
|
+
|
|
5
|
+
const MAX_FUNCTION_SOURCE_BYTES = 4 * 1024 * 1024
|
|
6
|
+
const MAX_FUNCTION_SOURCE_FILES = 2000
|
|
7
|
+
|
|
8
|
+
function functionEntries(manifest) {
|
|
9
|
+
const functions = manifest?.functions
|
|
10
|
+
if (!functions || typeof functions !== 'object' || Array.isArray(functions)) return []
|
|
11
|
+
return ['request', 'task']
|
|
12
|
+
.map(kind => functions[kind]?.entry)
|
|
13
|
+
.filter(entry => typeof entry === 'string' && entry.length > 0)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Read the same runtime module files that proxy-app pack uploads. Development
|
|
17
|
+
// sends these sources to Admin so Gateway can execute the versioned draft; the
|
|
18
|
+
// browser continues to call Gateway and never receives function source.
|
|
19
|
+
export async function readLocalFunctionSources(appDir, manifest) {
|
|
20
|
+
const requiredEntries = functionEntries(manifest)
|
|
21
|
+
if (requiredEntries.length === 0) return []
|
|
22
|
+
|
|
23
|
+
const root = resolve(appDir)
|
|
24
|
+
const runtimeRoot = join(root, 'runtime')
|
|
25
|
+
const files = []
|
|
26
|
+
let totalBytes = 0
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
const runtimeMetadata = await lstat(runtimeRoot)
|
|
30
|
+
if (runtimeMetadata.isSymbolicLink() || !runtimeMetadata.isDirectory()) {
|
|
31
|
+
throw new Error('runtime function sources must be inside a real runtime/ directory')
|
|
32
|
+
}
|
|
33
|
+
} catch (error) {
|
|
34
|
+
if (error?.code !== 'ENOENT') throw error
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const visit = async (directory, prefix) => {
|
|
38
|
+
let entries
|
|
39
|
+
try {
|
|
40
|
+
entries = await readdir(directory, { withFileTypes: true })
|
|
41
|
+
} catch (error) {
|
|
42
|
+
if (error?.code === 'ENOENT' && directory === runtimeRoot) return
|
|
43
|
+
throw error
|
|
44
|
+
}
|
|
45
|
+
entries.sort((left, right) => left.name.localeCompare(right.name))
|
|
46
|
+
for (const entry of entries) {
|
|
47
|
+
if (entry.name.includes('\\') || entry.name === '.' || entry.name === '..') {
|
|
48
|
+
throw new Error(`invalid runtime source path: ${entry.name}`)
|
|
49
|
+
}
|
|
50
|
+
const relativePath = `${prefix}/${entry.name}`
|
|
51
|
+
const absolutePath = join(directory, entry.name)
|
|
52
|
+
if (entry.isSymbolicLink()) throw new Error(`runtime function sources cannot be symbolic links: ${relativePath}`)
|
|
53
|
+
if (entry.isDirectory()) {
|
|
54
|
+
await visit(absolutePath, relativePath)
|
|
55
|
+
continue
|
|
56
|
+
}
|
|
57
|
+
if (!entry.isFile() || !['.js', '.mjs'].includes(extname(entry.name).toLowerCase())) continue
|
|
58
|
+
if (Buffer.byteLength(relativePath, 'utf8') > 255) throw new Error(`runtime function path is too long: ${relativePath}`)
|
|
59
|
+
if (files.length >= MAX_FUNCTION_SOURCE_FILES) throw new Error('runtime function source contains too many files')
|
|
60
|
+
const metadata = await lstat(absolutePath)
|
|
61
|
+
if (!metadata.isFile() || metadata.isSymbolicLink()) throw new Error(`runtime function sources cannot be symbolic links: ${relativePath}`)
|
|
62
|
+
if (metadata.size === 0) throw new Error(`runtime function source is empty: ${relativePath}`)
|
|
63
|
+
totalBytes += metadata.size
|
|
64
|
+
if (totalBytes > MAX_FUNCTION_SOURCE_BYTES) throw new Error('runtime function source exceeds 4 MiB')
|
|
65
|
+
const sourceBytes = await readFile(absolutePath)
|
|
66
|
+
let source
|
|
67
|
+
try {
|
|
68
|
+
source = new TextDecoder('utf-8', { fatal: true }).decode(sourceBytes)
|
|
69
|
+
} catch {
|
|
70
|
+
throw new Error(`runtime function source is not valid UTF-8: ${relativePath}`)
|
|
71
|
+
}
|
|
72
|
+
assertSafeArtifactFile(relativePath, source)
|
|
73
|
+
files.push({ path: relativePath, source })
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
await visit(runtimeRoot, 'runtime')
|
|
78
|
+
const available = new Set(files.map(file => file.path))
|
|
79
|
+
for (const entry of requiredEntries) {
|
|
80
|
+
if (!available.has(entry)) throw new Error(`manifest function entry is missing from runtime/: ${entry}`)
|
|
81
|
+
}
|
|
82
|
+
return files
|
|
83
|
+
}
|