@kuratchi/js 0.0.15 → 0.0.17
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 +160 -1
- package/dist/cli.js +78 -45
- package/dist/compiler/api-route-pipeline.d.ts +8 -0
- package/dist/compiler/api-route-pipeline.js +23 -0
- package/dist/compiler/asset-pipeline.d.ts +7 -0
- package/dist/compiler/asset-pipeline.js +33 -0
- package/dist/compiler/client-module-pipeline.d.ts +25 -0
- package/dist/compiler/client-module-pipeline.js +257 -0
- package/dist/compiler/compiler-shared.d.ts +73 -0
- package/dist/compiler/compiler-shared.js +4 -0
- package/dist/compiler/component-pipeline.d.ts +15 -0
- package/dist/compiler/component-pipeline.js +158 -0
- package/dist/compiler/config-reading.d.ts +12 -0
- package/dist/compiler/config-reading.js +380 -0
- package/dist/compiler/convention-discovery.d.ts +9 -0
- package/dist/compiler/convention-discovery.js +83 -0
- package/dist/compiler/durable-object-pipeline.d.ts +9 -0
- package/dist/compiler/durable-object-pipeline.js +255 -0
- package/dist/compiler/error-page-pipeline.d.ts +1 -0
- package/dist/compiler/error-page-pipeline.js +16 -0
- package/dist/compiler/import-linking.d.ts +36 -0
- package/dist/compiler/import-linking.js +140 -0
- package/dist/compiler/index.d.ts +7 -7
- package/dist/compiler/index.js +181 -3321
- package/dist/compiler/layout-pipeline.d.ts +31 -0
- package/dist/compiler/layout-pipeline.js +155 -0
- package/dist/compiler/page-route-pipeline.d.ts +16 -0
- package/dist/compiler/page-route-pipeline.js +62 -0
- package/dist/compiler/parser.d.ts +4 -0
- package/dist/compiler/parser.js +436 -55
- package/dist/compiler/root-layout-pipeline.d.ts +10 -0
- package/dist/compiler/root-layout-pipeline.js +532 -0
- package/dist/compiler/route-discovery.d.ts +7 -0
- package/dist/compiler/route-discovery.js +87 -0
- package/dist/compiler/route-pipeline.d.ts +57 -0
- package/dist/compiler/route-pipeline.js +291 -0
- package/dist/compiler/route-state-pipeline.d.ts +26 -0
- package/dist/compiler/route-state-pipeline.js +139 -0
- package/dist/compiler/routes-module-feature-blocks.d.ts +2 -0
- package/dist/compiler/routes-module-feature-blocks.js +330 -0
- package/dist/compiler/routes-module-pipeline.d.ts +2 -0
- package/dist/compiler/routes-module-pipeline.js +6 -0
- package/dist/compiler/routes-module-runtime-shell.d.ts +2 -0
- package/dist/compiler/routes-module-runtime-shell.js +91 -0
- package/dist/compiler/routes-module-types.d.ts +45 -0
- package/dist/compiler/routes-module-types.js +1 -0
- package/dist/compiler/script-transform.d.ts +16 -0
- package/dist/compiler/script-transform.js +218 -0
- package/dist/compiler/server-module-pipeline.d.ts +13 -0
- package/dist/compiler/server-module-pipeline.js +124 -0
- package/dist/compiler/template.d.ts +13 -1
- package/dist/compiler/template.js +337 -71
- package/dist/compiler/worker-output-pipeline.d.ts +13 -0
- package/dist/compiler/worker-output-pipeline.js +37 -0
- package/dist/compiler/wrangler-sync.d.ts +14 -0
- package/dist/compiler/wrangler-sync.js +185 -0
- package/dist/runtime/app.js +15 -3
- package/dist/runtime/context.d.ts +4 -0
- package/dist/runtime/context.js +40 -2
- package/dist/runtime/do.js +21 -6
- package/dist/runtime/generated-worker.d.ts +55 -0
- package/dist/runtime/generated-worker.js +543 -0
- package/dist/runtime/index.d.ts +4 -1
- package/dist/runtime/index.js +2 -0
- package/dist/runtime/router.d.ts +6 -1
- package/dist/runtime/router.js +125 -31
- package/dist/runtime/security.d.ts +101 -0
- package/dist/runtime/security.js +298 -0
- package/dist/runtime/types.d.ts +29 -2
- package/package.json +5 -1
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
function skipWhitespace(source, start) {
|
|
4
|
+
let i = start;
|
|
5
|
+
while (i < source.length && /\s/.test(source[i]))
|
|
6
|
+
i++;
|
|
7
|
+
return i;
|
|
8
|
+
}
|
|
9
|
+
function extractBalancedBody(source, start, openChar, closeChar) {
|
|
10
|
+
if (source[start] !== openChar)
|
|
11
|
+
return null;
|
|
12
|
+
let depth = 0;
|
|
13
|
+
for (let i = start; i < source.length; i++) {
|
|
14
|
+
if (source[i] === openChar)
|
|
15
|
+
depth++;
|
|
16
|
+
else if (source[i] === closeChar) {
|
|
17
|
+
depth--;
|
|
18
|
+
if (depth === 0)
|
|
19
|
+
return source.slice(start + 1, i);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
function readConfigBlock(source, key) {
|
|
25
|
+
const keyRegex = new RegExp(`\\b${key}\\s*:`);
|
|
26
|
+
const keyMatch = keyRegex.exec(source);
|
|
27
|
+
if (!keyMatch)
|
|
28
|
+
return null;
|
|
29
|
+
const colonIdx = source.indexOf(':', keyMatch.index);
|
|
30
|
+
if (colonIdx === -1)
|
|
31
|
+
return null;
|
|
32
|
+
const valueIdx = skipWhitespace(source, colonIdx + 1);
|
|
33
|
+
if (valueIdx >= source.length)
|
|
34
|
+
return null;
|
|
35
|
+
if (source[valueIdx] === '{') {
|
|
36
|
+
throw new Error(`[kuratchi] "${key}" config must use an adapter call (e.g. ${key}: kuratchi${key[0].toUpperCase()}${key.slice(1)}Config({...})).`);
|
|
37
|
+
}
|
|
38
|
+
const callOpen = source.indexOf('(', valueIdx);
|
|
39
|
+
if (callOpen === -1)
|
|
40
|
+
return null;
|
|
41
|
+
const argIdx = skipWhitespace(source, callOpen + 1);
|
|
42
|
+
if (argIdx >= source.length)
|
|
43
|
+
return null;
|
|
44
|
+
if (source[argIdx] === ')')
|
|
45
|
+
return { kind: 'call-empty', body: '' };
|
|
46
|
+
if (source[argIdx] === '{') {
|
|
47
|
+
const body = extractBalancedBody(source, argIdx, '{', '}');
|
|
48
|
+
if (body == null)
|
|
49
|
+
return null;
|
|
50
|
+
return { kind: 'call-object', body };
|
|
51
|
+
}
|
|
52
|
+
return { kind: 'call-empty', body: '' };
|
|
53
|
+
}
|
|
54
|
+
export function readUiTheme(projectDir) {
|
|
55
|
+
const configPath = path.join(projectDir, 'kuratchi.config.ts');
|
|
56
|
+
if (!fs.existsSync(configPath))
|
|
57
|
+
return null;
|
|
58
|
+
const source = fs.readFileSync(configPath, 'utf-8');
|
|
59
|
+
const uiBlock = readConfigBlock(source, 'ui');
|
|
60
|
+
if (!uiBlock)
|
|
61
|
+
return null;
|
|
62
|
+
const themeMatch = uiBlock.body.match(/theme\s*:\s*['"]([^'"]+)['"]/);
|
|
63
|
+
const themeValue = themeMatch?.[1] ?? 'default';
|
|
64
|
+
if (themeValue === 'default' || themeValue === 'dark' || themeValue === 'light' || themeValue === 'system') {
|
|
65
|
+
const candidates = [
|
|
66
|
+
path.join(projectDir, 'node_modules', '@kuratchi/ui', 'src', 'styles', 'theme.css'),
|
|
67
|
+
path.join(path.resolve(projectDir, '../..'), 'packages', 'kuratchi-ui', 'src', 'styles', 'theme.css'),
|
|
68
|
+
path.join(path.resolve(projectDir, '../..'), 'node_modules', '@kuratchi/ui', 'src', 'styles', 'theme.css'),
|
|
69
|
+
];
|
|
70
|
+
for (const candidate of candidates) {
|
|
71
|
+
if (fs.existsSync(candidate)) {
|
|
72
|
+
return fs.readFileSync(candidate, 'utf-8');
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
console.warn(`[kuratchi] ui.theme: "${themeValue}" configured but @kuratchi/ui theme.css not found`);
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
const customPath = path.resolve(projectDir, themeValue);
|
|
79
|
+
if (fs.existsSync(customPath)) {
|
|
80
|
+
return fs.readFileSync(customPath, 'utf-8');
|
|
81
|
+
}
|
|
82
|
+
console.warn(`[kuratchi] ui.theme: "${themeValue}" not found at ${customPath}`);
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
export function readUiConfigValues(projectDir) {
|
|
86
|
+
const configPath = path.join(projectDir, 'kuratchi.config.ts');
|
|
87
|
+
if (!fs.existsSync(configPath))
|
|
88
|
+
return null;
|
|
89
|
+
const source = fs.readFileSync(configPath, 'utf-8');
|
|
90
|
+
const uiBlock = readConfigBlock(source, 'ui');
|
|
91
|
+
if (!uiBlock)
|
|
92
|
+
return null;
|
|
93
|
+
const themeMatch = uiBlock.body.match(/theme\s*:\s*['"]([^'"]+)['"]/);
|
|
94
|
+
const radiusMatch = uiBlock.body.match(/radius\s*:\s*['"]([^'"]+)['"]/);
|
|
95
|
+
return {
|
|
96
|
+
theme: themeMatch?.[1] ?? 'dark',
|
|
97
|
+
radius: radiusMatch?.[1] ?? 'default',
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
export function readOrmConfig(projectDir) {
|
|
101
|
+
const configPath = path.join(projectDir, 'kuratchi.config.ts');
|
|
102
|
+
if (!fs.existsSync(configPath))
|
|
103
|
+
return [];
|
|
104
|
+
const source = fs.readFileSync(configPath, 'utf-8');
|
|
105
|
+
const ormBlock = readConfigBlock(source, 'orm');
|
|
106
|
+
if (!ormBlock)
|
|
107
|
+
return [];
|
|
108
|
+
const importMap = new Map();
|
|
109
|
+
const importRegex = /import\s*\{\s*([^}]+)\s*\}\s*from\s*['"]([^'"]+)['"]/g;
|
|
110
|
+
let match;
|
|
111
|
+
while ((match = importRegex.exec(source)) !== null) {
|
|
112
|
+
const names = match[1].split(',').map((name) => name.trim()).filter(Boolean);
|
|
113
|
+
const importPath = match[2];
|
|
114
|
+
for (const name of names) {
|
|
115
|
+
importMap.set(name, importPath);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
const databasesIdx = ormBlock.body.search(/databases\s*:\s*\{/);
|
|
119
|
+
if (databasesIdx === -1)
|
|
120
|
+
return [];
|
|
121
|
+
const dbBraceStart = ormBlock.body.indexOf('{', databasesIdx);
|
|
122
|
+
if (dbBraceStart === -1)
|
|
123
|
+
return [];
|
|
124
|
+
const databasesBody = extractBalancedBody(ormBlock.body, dbBraceStart, '{', '}');
|
|
125
|
+
if (databasesBody == null)
|
|
126
|
+
return [];
|
|
127
|
+
const entries = [];
|
|
128
|
+
const entryRegex = /(\w+)\s*:\s*\{\s*schema\s*:\s*(\w+)([^}]*)\}/g;
|
|
129
|
+
while ((match = entryRegex.exec(databasesBody)) !== null) {
|
|
130
|
+
const binding = match[1];
|
|
131
|
+
const schemaExportName = match[2];
|
|
132
|
+
const rest = match[3] || '';
|
|
133
|
+
const skipMatch = rest.match(/skipMigrations\s*:\s*(true|false)/);
|
|
134
|
+
const skipMigrations = skipMatch?.[1] === 'true';
|
|
135
|
+
const typeMatch = rest.match(/type\s*:\s*['"]?(d1|do)['"]?/);
|
|
136
|
+
const type = typeMatch?.[1] ?? 'd1';
|
|
137
|
+
const schemaImportPath = importMap.get(schemaExportName);
|
|
138
|
+
if (!schemaImportPath)
|
|
139
|
+
continue;
|
|
140
|
+
entries.push({ binding, schemaImportPath, schemaExportName, skipMigrations, type });
|
|
141
|
+
}
|
|
142
|
+
return entries;
|
|
143
|
+
}
|
|
144
|
+
export function readAuthConfig(projectDir) {
|
|
145
|
+
const configPath = path.join(projectDir, 'kuratchi.config.ts');
|
|
146
|
+
if (!fs.existsSync(configPath))
|
|
147
|
+
return null;
|
|
148
|
+
const source = fs.readFileSync(configPath, 'utf-8');
|
|
149
|
+
const authBlockMatch = readConfigBlock(source, 'auth');
|
|
150
|
+
if (!authBlockMatch)
|
|
151
|
+
return null;
|
|
152
|
+
const authBlock = authBlockMatch.body;
|
|
153
|
+
const cookieMatch = authBlock.match(/cookieName\s*:\s*['"]([^'"]+)['"]/);
|
|
154
|
+
const secretMatch = authBlock.match(/secretEnvKey\s*:\s*['"]([^'"]+)['"]/);
|
|
155
|
+
const sessionMatch = authBlock.match(/sessionEnabled\s*:\s*(true|false)/);
|
|
156
|
+
return {
|
|
157
|
+
cookieName: cookieMatch?.[1] ?? 'kuratchi_session',
|
|
158
|
+
secretEnvKey: secretMatch?.[1] ?? 'AUTH_SECRET',
|
|
159
|
+
sessionEnabled: sessionMatch?.[1] !== 'false',
|
|
160
|
+
hasCredentials: /credentials\s*:/.test(authBlock),
|
|
161
|
+
hasActivity: /activity\s*:/.test(authBlock),
|
|
162
|
+
hasRoles: /roles\s*:/.test(authBlock),
|
|
163
|
+
hasOAuth: /oauth\s*:/.test(authBlock),
|
|
164
|
+
hasGuards: /guards\s*:/.test(authBlock),
|
|
165
|
+
hasRateLimit: /rateLimit\s*:/.test(authBlock),
|
|
166
|
+
hasTurnstile: /turnstile\s*:/.test(authBlock),
|
|
167
|
+
hasOrganization: /organizations\s*:/.test(authBlock),
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
export function readDoConfig(projectDir) {
|
|
171
|
+
const configPath = path.join(projectDir, 'kuratchi.config.ts');
|
|
172
|
+
if (!fs.existsSync(configPath))
|
|
173
|
+
return [];
|
|
174
|
+
const source = fs.readFileSync(configPath, 'utf-8');
|
|
175
|
+
const doIdx = source.search(/durableObjects\s*:\s*\{/);
|
|
176
|
+
if (doIdx === -1)
|
|
177
|
+
return [];
|
|
178
|
+
const braceStart = source.indexOf('{', doIdx);
|
|
179
|
+
if (braceStart === -1)
|
|
180
|
+
return [];
|
|
181
|
+
let depth = 0;
|
|
182
|
+
let braceEnd = braceStart;
|
|
183
|
+
for (let i = braceStart; i < source.length; i++) {
|
|
184
|
+
if (source[i] === '{')
|
|
185
|
+
depth++;
|
|
186
|
+
else if (source[i] === '}') {
|
|
187
|
+
depth--;
|
|
188
|
+
if (depth === 0) {
|
|
189
|
+
braceEnd = i;
|
|
190
|
+
break;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
const doBlock = source.slice(braceStart + 1, braceEnd);
|
|
195
|
+
const entries = [];
|
|
196
|
+
const objRegex = /(\w+)\s*:\s*\{([^}]*(?:\{[^}]*\}[^}]*)*)\}/g;
|
|
197
|
+
let match;
|
|
198
|
+
while ((match = objRegex.exec(doBlock)) !== null) {
|
|
199
|
+
const binding = match[1];
|
|
200
|
+
const body = match[2];
|
|
201
|
+
const cnMatch = body.match(/className\s*:\s*['"](\w+)['"]/);
|
|
202
|
+
if (!cnMatch)
|
|
203
|
+
continue;
|
|
204
|
+
const entry = { binding, className: cnMatch[1] };
|
|
205
|
+
const stubIdMatch = body.match(/stubId\s*:\s*['"]([^'"]+)['"]/);
|
|
206
|
+
if (stubIdMatch)
|
|
207
|
+
entry.stubId = stubIdMatch[1];
|
|
208
|
+
const filesMatch = body.match(/files\s*:\s*\[([\s\S]*?)\]/);
|
|
209
|
+
if (filesMatch) {
|
|
210
|
+
const list = [];
|
|
211
|
+
const itemRegex = /['"]([^'"]+)['"]/g;
|
|
212
|
+
let fileMatch;
|
|
213
|
+
while ((fileMatch = itemRegex.exec(filesMatch[1])) !== null) {
|
|
214
|
+
list.push(fileMatch[1]);
|
|
215
|
+
}
|
|
216
|
+
if (list.length > 0)
|
|
217
|
+
entry.files = list;
|
|
218
|
+
}
|
|
219
|
+
entries.push(entry);
|
|
220
|
+
}
|
|
221
|
+
const foundBindings = new Set(entries.map((entry) => entry.binding));
|
|
222
|
+
const pairRegex = /(\w+)\s*:\s*['"](\w+)['"]\s*[,}\n]/g;
|
|
223
|
+
while ((match = pairRegex.exec(doBlock)) !== null) {
|
|
224
|
+
if (foundBindings.has(match[1]))
|
|
225
|
+
continue;
|
|
226
|
+
if (['className', 'stubId'].includes(match[1]))
|
|
227
|
+
continue;
|
|
228
|
+
entries.push({ binding: match[1], className: match[2] });
|
|
229
|
+
}
|
|
230
|
+
return entries;
|
|
231
|
+
}
|
|
232
|
+
export function readWorkerClassConfig(projectDir, key) {
|
|
233
|
+
const configPath = path.join(projectDir, 'kuratchi.config.ts');
|
|
234
|
+
if (!fs.existsSync(configPath))
|
|
235
|
+
return [];
|
|
236
|
+
const source = fs.readFileSync(configPath, 'utf-8');
|
|
237
|
+
const keyIdx = source.search(new RegExp(`\\b${key}\\s*:\\s*\\{`));
|
|
238
|
+
if (keyIdx === -1)
|
|
239
|
+
return [];
|
|
240
|
+
const braceStart = source.indexOf('{', keyIdx);
|
|
241
|
+
if (braceStart === -1)
|
|
242
|
+
return [];
|
|
243
|
+
const body = extractBalancedBody(source, braceStart, '{', '}');
|
|
244
|
+
if (body == null)
|
|
245
|
+
return [];
|
|
246
|
+
const entries = [];
|
|
247
|
+
const expectedSuffix = key === 'containers' ? '.container' : '.workflow';
|
|
248
|
+
const allowedExt = /\.(ts|js|mjs|cjs)$/i;
|
|
249
|
+
const requiredFilePattern = new RegExp(`\\${expectedSuffix}\\.(ts|js|mjs|cjs)$`, 'i');
|
|
250
|
+
const resolveClassFromFile = (binding, filePath) => {
|
|
251
|
+
if (!requiredFilePattern.test(filePath)) {
|
|
252
|
+
throw new Error(`[kuratchi] ${key}.${binding} must reference a file ending in "${expectedSuffix}.ts|js|mjs|cjs". Received: ${filePath}`);
|
|
253
|
+
}
|
|
254
|
+
if (!allowedExt.test(filePath)) {
|
|
255
|
+
throw new Error(`[kuratchi] ${key}.${binding} file must be a TypeScript or JavaScript module. Received: ${filePath}`);
|
|
256
|
+
}
|
|
257
|
+
const absPath = path.isAbsolute(filePath) ? filePath : path.join(projectDir, filePath);
|
|
258
|
+
if (!fs.existsSync(absPath)) {
|
|
259
|
+
throw new Error(`[kuratchi] ${key}.${binding} file not found: ${filePath}`);
|
|
260
|
+
}
|
|
261
|
+
const fileSource = fs.readFileSync(absPath, 'utf-8');
|
|
262
|
+
const defaultClass = fileSource.match(/export\s+default\s+class\s+(\w+)/);
|
|
263
|
+
if (defaultClass) {
|
|
264
|
+
return { className: defaultClass[1], exportKind: 'default' };
|
|
265
|
+
}
|
|
266
|
+
const namedClass = fileSource.match(/export\s+class\s+(\w+)/);
|
|
267
|
+
if (namedClass) {
|
|
268
|
+
return { className: namedClass[1], exportKind: 'named' };
|
|
269
|
+
}
|
|
270
|
+
throw new Error(`[kuratchi] ${key}.${binding} must export a class via "export class X" or "export default class X". File: ${filePath}`);
|
|
271
|
+
};
|
|
272
|
+
const objRegex = /(\w+)\s*:\s*\{([^}]*(?:\{[^}]*\}[^}]*)*)\}/g;
|
|
273
|
+
let match;
|
|
274
|
+
while ((match = objRegex.exec(body)) !== null) {
|
|
275
|
+
const binding = match[1];
|
|
276
|
+
const entryBody = match[2];
|
|
277
|
+
const fileMatch = entryBody.match(/file\s*:\s*['"]([^'"]+)['"]/);
|
|
278
|
+
if (!fileMatch)
|
|
279
|
+
continue;
|
|
280
|
+
const inferred = resolveClassFromFile(binding, fileMatch[1]);
|
|
281
|
+
const classMatch = entryBody.match(/className\s*:\s*['"](\w+)['"]/);
|
|
282
|
+
const className = classMatch?.[1] ?? inferred.className;
|
|
283
|
+
entries.push({
|
|
284
|
+
binding,
|
|
285
|
+
className,
|
|
286
|
+
file: fileMatch[1],
|
|
287
|
+
exportKind: inferred.exportKind,
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
const foundBindings = new Set(entries.map((entry) => entry.binding));
|
|
291
|
+
const pairRegex = /(\w+)\s*:\s*['"]([^'"]+)['"]\s*[,}\n]/g;
|
|
292
|
+
while ((match = pairRegex.exec(body)) !== null) {
|
|
293
|
+
const binding = match[1];
|
|
294
|
+
const file = match[2];
|
|
295
|
+
if (foundBindings.has(binding))
|
|
296
|
+
continue;
|
|
297
|
+
if (binding === 'file' || binding === 'className')
|
|
298
|
+
continue;
|
|
299
|
+
const inferred = resolveClassFromFile(binding, file);
|
|
300
|
+
entries.push({
|
|
301
|
+
binding,
|
|
302
|
+
className: inferred.className,
|
|
303
|
+
file,
|
|
304
|
+
exportKind: inferred.exportKind,
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
return entries;
|
|
308
|
+
}
|
|
309
|
+
export function readAssetsPrefix(projectDir) {
|
|
310
|
+
const configPath = path.join(projectDir, 'kuratchi.config.ts');
|
|
311
|
+
if (!fs.existsSync(configPath))
|
|
312
|
+
return '/assets/';
|
|
313
|
+
const source = fs.readFileSync(configPath, 'utf-8');
|
|
314
|
+
const match = source.match(/assetsPrefix\s*:\s*['"]([^'"]+)['"]/);
|
|
315
|
+
if (!match)
|
|
316
|
+
return '/assets/';
|
|
317
|
+
let prefix = match[1];
|
|
318
|
+
if (!prefix.startsWith('/'))
|
|
319
|
+
prefix = '/' + prefix;
|
|
320
|
+
if (!prefix.endsWith('/'))
|
|
321
|
+
prefix += '/';
|
|
322
|
+
return prefix;
|
|
323
|
+
}
|
|
324
|
+
export function readSecurityConfig(projectDir) {
|
|
325
|
+
const defaults = {
|
|
326
|
+
csrfEnabled: true,
|
|
327
|
+
csrfCookieName: '__kuratchi_csrf',
|
|
328
|
+
csrfHeaderName: 'x-kuratchi-csrf',
|
|
329
|
+
rpcRequireAuth: false,
|
|
330
|
+
actionRequireAuth: false,
|
|
331
|
+
contentSecurityPolicy: null,
|
|
332
|
+
strictTransportSecurity: null,
|
|
333
|
+
permissionsPolicy: null,
|
|
334
|
+
};
|
|
335
|
+
const configPath = path.join(projectDir, 'kuratchi.config.ts');
|
|
336
|
+
if (!fs.existsSync(configPath))
|
|
337
|
+
return defaults;
|
|
338
|
+
const source = fs.readFileSync(configPath, 'utf-8');
|
|
339
|
+
const securityBlock = readConfigBlock(source, 'security');
|
|
340
|
+
if (!securityBlock)
|
|
341
|
+
return defaults;
|
|
342
|
+
const body = securityBlock.body;
|
|
343
|
+
// Parse CSRF settings
|
|
344
|
+
const csrfEnabledMatch = body.match(/csrfEnabled\s*:\s*(true|false)/);
|
|
345
|
+
if (csrfEnabledMatch) {
|
|
346
|
+
defaults.csrfEnabled = csrfEnabledMatch[1] === 'true';
|
|
347
|
+
}
|
|
348
|
+
const csrfCookieMatch = body.match(/csrfCookieName\s*:\s*['"]([^'"]+)['"]/);
|
|
349
|
+
if (csrfCookieMatch) {
|
|
350
|
+
defaults.csrfCookieName = csrfCookieMatch[1];
|
|
351
|
+
}
|
|
352
|
+
const csrfHeaderMatch = body.match(/csrfHeaderName\s*:\s*['"]([^'"]+)['"]/);
|
|
353
|
+
if (csrfHeaderMatch) {
|
|
354
|
+
defaults.csrfHeaderName = csrfHeaderMatch[1];
|
|
355
|
+
}
|
|
356
|
+
// Parse RPC settings
|
|
357
|
+
const rpcAuthMatch = body.match(/rpcRequireAuth\s*:\s*(true|false)/);
|
|
358
|
+
if (rpcAuthMatch) {
|
|
359
|
+
defaults.rpcRequireAuth = rpcAuthMatch[1] === 'true';
|
|
360
|
+
}
|
|
361
|
+
// Parse action settings
|
|
362
|
+
const actionAuthMatch = body.match(/actionRequireAuth\s*:\s*(true|false)/);
|
|
363
|
+
if (actionAuthMatch) {
|
|
364
|
+
defaults.actionRequireAuth = actionAuthMatch[1] === 'true';
|
|
365
|
+
}
|
|
366
|
+
// Parse security headers
|
|
367
|
+
const cspMatch = body.match(/contentSecurityPolicy\s*:\s*['"`]([^'"`]+)['"`]/);
|
|
368
|
+
if (cspMatch) {
|
|
369
|
+
defaults.contentSecurityPolicy = cspMatch[1];
|
|
370
|
+
}
|
|
371
|
+
const hstsMatch = body.match(/strictTransportSecurity\s*:\s*['"`]([^'"`]+)['"`]/);
|
|
372
|
+
if (hstsMatch) {
|
|
373
|
+
defaults.strictTransportSecurity = hstsMatch[1];
|
|
374
|
+
}
|
|
375
|
+
const permMatch = body.match(/permissionsPolicy\s*:\s*['"`]([^'"`]+)['"`]/);
|
|
376
|
+
if (permMatch) {
|
|
377
|
+
defaults.permissionsPolicy = permMatch[1];
|
|
378
|
+
}
|
|
379
|
+
return defaults;
|
|
380
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type ConventionClassEntry, type WorkerClassConfigEntry } from './compiler-shared.js';
|
|
2
|
+
export declare function resolveClassExportFromFile(absPath: string, errorLabel: string): {
|
|
3
|
+
className: string;
|
|
4
|
+
exportKind: 'named' | 'default';
|
|
5
|
+
};
|
|
6
|
+
export declare function discoverConventionClassFiles(projectDir: string, dir: string, suffix: string, errorLabel: string): ConventionClassEntry[];
|
|
7
|
+
export declare function discoverFilesWithSuffix(dir: string, suffix: string): string[];
|
|
8
|
+
export declare function discoverWorkflowFiles(projectDir: string): WorkerClassConfigEntry[];
|
|
9
|
+
export declare function discoverContainerFiles(projectDir: string): WorkerClassConfigEntry[];
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
export function resolveClassExportFromFile(absPath, errorLabel) {
|
|
4
|
+
if (!fs.existsSync(absPath)) {
|
|
5
|
+
throw new Error(`[kuratchi] ${errorLabel} file not found: ${absPath}`);
|
|
6
|
+
}
|
|
7
|
+
const fileSource = fs.readFileSync(absPath, 'utf-8');
|
|
8
|
+
const defaultClass = fileSource.match(/export\s+default\s+class\s+(\w+)/);
|
|
9
|
+
if (defaultClass) {
|
|
10
|
+
return { className: defaultClass[1], exportKind: 'default' };
|
|
11
|
+
}
|
|
12
|
+
const namedClass = fileSource.match(/export\s+class\s+(\w+)/);
|
|
13
|
+
if (namedClass) {
|
|
14
|
+
return { className: namedClass[1], exportKind: 'named' };
|
|
15
|
+
}
|
|
16
|
+
throw new Error(`[kuratchi] ${errorLabel} must export a class via "export class X" or "export default class X". File: ${absPath}`);
|
|
17
|
+
}
|
|
18
|
+
export function discoverConventionClassFiles(projectDir, dir, suffix, errorLabel) {
|
|
19
|
+
const absDir = path.join(projectDir, dir);
|
|
20
|
+
const files = discoverFilesWithSuffix(absDir, suffix);
|
|
21
|
+
if (files.length === 0)
|
|
22
|
+
return [];
|
|
23
|
+
return files.map((absPath) => {
|
|
24
|
+
const resolved = resolveClassExportFromFile(absPath, errorLabel);
|
|
25
|
+
return {
|
|
26
|
+
className: resolved.className,
|
|
27
|
+
file: path.relative(projectDir, absPath).replace(/\\/g, '/'),
|
|
28
|
+
exportKind: resolved.exportKind,
|
|
29
|
+
};
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
export function discoverFilesWithSuffix(dir, suffix) {
|
|
33
|
+
if (!fs.existsSync(dir))
|
|
34
|
+
return [];
|
|
35
|
+
const out = [];
|
|
36
|
+
const walk = (absDir) => {
|
|
37
|
+
for (const entry of fs.readdirSync(absDir, { withFileTypes: true })) {
|
|
38
|
+
const abs = path.join(absDir, entry.name);
|
|
39
|
+
if (entry.isDirectory()) {
|
|
40
|
+
walk(abs);
|
|
41
|
+
}
|
|
42
|
+
else if (entry.isFile() && abs.endsWith(suffix)) {
|
|
43
|
+
out.push(abs);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
walk(dir);
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
50
|
+
export function discoverWorkflowFiles(projectDir) {
|
|
51
|
+
const serverDir = path.join(projectDir, 'src', 'server');
|
|
52
|
+
const files = discoverFilesWithSuffix(serverDir, '.workflow.ts');
|
|
53
|
+
if (files.length === 0)
|
|
54
|
+
return [];
|
|
55
|
+
return files.map((absPath) => {
|
|
56
|
+
const fileName = path.basename(absPath, '.workflow.ts');
|
|
57
|
+
const binding = fileName.toUpperCase().replace(/-/g, '_') + '_WORKFLOW';
|
|
58
|
+
const resolved = resolveClassExportFromFile(absPath, '.workflow');
|
|
59
|
+
return {
|
|
60
|
+
binding,
|
|
61
|
+
className: resolved.className,
|
|
62
|
+
file: path.relative(projectDir, absPath).replace(/\\/g, '/'),
|
|
63
|
+
exportKind: resolved.exportKind,
|
|
64
|
+
};
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
export function discoverContainerFiles(projectDir) {
|
|
68
|
+
const serverDir = path.join(projectDir, 'src', 'server');
|
|
69
|
+
const files = discoverFilesWithSuffix(serverDir, '.container.ts');
|
|
70
|
+
if (files.length === 0)
|
|
71
|
+
return [];
|
|
72
|
+
return files.map((absPath) => {
|
|
73
|
+
const fileName = path.basename(absPath, '.container.ts');
|
|
74
|
+
const binding = fileName.toUpperCase().replace(/-/g, '_') + '_CONTAINER';
|
|
75
|
+
const resolved = resolveClassExportFromFile(absPath, '.container');
|
|
76
|
+
return {
|
|
77
|
+
binding,
|
|
78
|
+
className: resolved.className,
|
|
79
|
+
file: path.relative(projectDir, absPath).replace(/\\/g, '/'),
|
|
80
|
+
exportKind: resolved.exportKind,
|
|
81
|
+
};
|
|
82
|
+
});
|
|
83
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type DoConfigEntry, type DoHandlerEntry, type OrmDatabaseEntry } from './compiler-shared.js';
|
|
2
|
+
export declare function discoverDurableObjects(srcDir: string, configDoEntries: DoConfigEntry[], ormDatabases: OrmDatabaseEntry[]): {
|
|
3
|
+
config: DoConfigEntry[];
|
|
4
|
+
handlers: DoHandlerEntry[];
|
|
5
|
+
};
|
|
6
|
+
export declare function generateHandlerProxy(handler: DoHandlerEntry, opts: {
|
|
7
|
+
projectDir: string;
|
|
8
|
+
runtimeDoImport: string;
|
|
9
|
+
}): string;
|