tw-framework 0.2.6__tar.gz → 0.2.8__tar.gz

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.
Files changed (63) hide show
  1. {tw_framework-0.2.6 → tw_framework-0.2.8}/PKG-INFO +1 -1
  2. {tw_framework-0.2.6 → tw_framework-0.2.8}/pyproject.toml +4 -1
  3. {tw_framework-0.2.6 → tw_framework-0.2.8}/tw_framework/cli.py +1 -0
  4. {tw_framework-0.2.6 → tw_framework-0.2.8}/tw_framework/compiler.py +21 -3
  5. tw_framework-0.2.8/tw_framework/twm_api_runner.js +469 -0
  6. {tw_framework-0.2.6 → tw_framework-0.2.8}/tw_framework.egg-info/PKG-INFO +1 -1
  7. {tw_framework-0.2.6 → tw_framework-0.2.8}/tw_framework.egg-info/SOURCES.txt +1 -0
  8. {tw_framework-0.2.6 → tw_framework-0.2.8}/README.md +0 -0
  9. {tw_framework-0.2.6 → tw_framework-0.2.8}/setup.cfg +0 -0
  10. {tw_framework-0.2.6 → tw_framework-0.2.8}/tests/test_deployment.py +0 -0
  11. {tw_framework-0.2.6 → tw_framework-0.2.8}/tw_framework/__init__.py +0 -0
  12. {tw_framework-0.2.6 → tw_framework-0.2.8}/tw_framework/adapters/__init__.py +0 -0
  13. {tw_framework-0.2.6 → tw_framework-0.2.8}/tw_framework/adapters/cloudflare.py +0 -0
  14. {tw_framework-0.2.6 → tw_framework-0.2.8}/tw_framework/adapters/netlify.py +0 -0
  15. {tw_framework-0.2.6 → tw_framework-0.2.8}/tw_framework/adapters/vercel.py +0 -0
  16. {tw_framework-0.2.6 → tw_framework-0.2.8}/tw_framework/adapters/vercel_functions.py +0 -0
  17. {tw_framework-0.2.6 → tw_framework-0.2.8}/tw_framework/advanced_diagnostics.py +0 -0
  18. {tw_framework-0.2.6 → tw_framework-0.2.8}/tw_framework/asset_optimizer.py +0 -0
  19. {tw_framework-0.2.6 → tw_framework-0.2.8}/tw_framework/ast_nodes.py +0 -0
  20. {tw_framework-0.2.6 → tw_framework-0.2.8}/tw_framework/build.py +0 -0
  21. {tw_framework-0.2.6 → tw_framework-0.2.8}/tw_framework/build_performance.py +0 -0
  22. {tw_framework-0.2.6 → tw_framework-0.2.8}/tw_framework/build_report.py +0 -0
  23. {tw_framework-0.2.6 → tw_framework-0.2.8}/tw_framework/code_splitting.py +0 -0
  24. {tw_framework-0.2.6 → tw_framework-0.2.8}/tw_framework/common.py +0 -0
  25. {tw_framework-0.2.6 → tw_framework-0.2.8}/tw_framework/compiler_stats.py +0 -0
  26. {tw_framework-0.2.6 → tw_framework-0.2.8}/tw_framework/dead_code.py +0 -0
  27. {tw_framework-0.2.6 → tw_framework-0.2.8}/tw_framework/dependency_graph.py +0 -0
  28. {tw_framework-0.2.6 → tw_framework-0.2.8}/tw_framework/deploy.py +0 -0
  29. {tw_framework-0.2.6 → tw_framework-0.2.8}/tw_framework/dev.py +0 -0
  30. {tw_framework-0.2.6 → tw_framework-0.2.8}/tw_framework/diagnostics.py +0 -0
  31. {tw_framework-0.2.6 → tw_framework-0.2.8}/tw_framework/dynamic_imports.py +0 -0
  32. {tw_framework-0.2.6 → tw_framework-0.2.8}/tw_framework/error_formatter.py +0 -0
  33. {tw_framework-0.2.6 → tw_framework-0.2.8}/tw_framework/framework.py +0 -0
  34. {tw_framework-0.2.6 → tw_framework-0.2.8}/tw_framework/hydration.py +0 -0
  35. {tw_framework-0.2.6 → tw_framework-0.2.8}/tw_framework/incremental_cache.py +0 -0
  36. {tw_framework-0.2.6 → tw_framework-0.2.8}/tw_framework/interpreter.py +0 -0
  37. {tw_framework-0.2.6 → tw_framework-0.2.8}/tw_framework/ir.py +0 -0
  38. {tw_framework-0.2.6 → tw_framework-0.2.8}/tw_framework/lexer.py +0 -0
  39. {tw_framework-0.2.6 → tw_framework-0.2.8}/tw_framework/lowering.py +0 -0
  40. {tw_framework-0.2.6 → tw_framework-0.2.8}/tw_framework/parser.py +0 -0
  41. {tw_framework-0.2.6 → tw_framework-0.2.8}/tw_framework/partial_rebuild.py +0 -0
  42. {tw_framework-0.2.6 → tw_framework-0.2.8}/tw_framework/performance_analyzer.py +0 -0
  43. {tw_framework-0.2.6 → tw_framework-0.2.8}/tw_framework/plugin_runtime.py +0 -0
  44. {tw_framework-0.2.6 → tw_framework-0.2.8}/tw_framework/production_optimizer.py +0 -0
  45. {tw_framework-0.2.6 → tw_framework-0.2.8}/tw_framework/reactivity.py +0 -0
  46. {tw_framework-0.2.6 → tw_framework-0.2.8}/tw_framework/render_css.py +0 -0
  47. {tw_framework-0.2.6 → tw_framework-0.2.8}/tw_framework/render_html.py +0 -0
  48. {tw_framework-0.2.6 → tw_framework-0.2.8}/tw_framework/route_optimizer.py +0 -0
  49. {tw_framework-0.2.6 → tw_framework-0.2.8}/tw_framework/router.py +0 -0
  50. {tw_framework-0.2.6 → tw_framework-0.2.8}/tw_framework/runtime_model.py +0 -0
  51. {tw_framework-0.2.6 → tw_framework-0.2.8}/tw_framework/runtime_values.py +0 -0
  52. {tw_framework-0.2.6 → tw_framework-0.2.8}/tw_framework/semantic.py +0 -0
  53. {tw_framework-0.2.6 → tw_framework-0.2.8}/tw_framework/server.py +0 -0
  54. {tw_framework-0.2.6 → tw_framework-0.2.8}/tw_framework/signature.py +0 -0
  55. {tw_framework-0.2.6 → tw_framework-0.2.8}/tw_framework/static_dynamic_auto.py +0 -0
  56. {tw_framework-0.2.6 → tw_framework-0.2.8}/tw_framework/streaming.py +0 -0
  57. {tw_framework-0.2.6 → tw_framework-0.2.8}/tw_framework/test_build_cli_pytest.py +0 -0
  58. {tw_framework-0.2.6 → tw_framework-0.2.8}/tw_framework/tree_shaking.py +0 -0
  59. {tw_framework-0.2.6 → tw_framework-0.2.8}/tw_framework/twm_parser.py +0 -0
  60. {tw_framework-0.2.6 → tw_framework-0.2.8}/tw_framework.egg-info/dependency_links.txt +0 -0
  61. {tw_framework-0.2.6 → tw_framework-0.2.8}/tw_framework.egg-info/entry_points.txt +0 -0
  62. {tw_framework-0.2.6 → tw_framework-0.2.8}/tw_framework.egg-info/requires.txt +0 -0
  63. {tw_framework-0.2.6 → tw_framework-0.2.8}/tw_framework.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tw-framework
3
- Version: 0.2.6
3
+ Version: 0.2.8
4
4
  Summary: TW framework CLI with create, dev, build, and deploy commands
5
5
  Author: TW Framework
6
6
  Requires-Python: >=3.9
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "tw-framework"
7
- version = "0.2.6"
7
+ version = "0.2.8"
8
8
  description = "TW framework CLI with create, dev, build, and deploy commands"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -24,3 +24,6 @@ tw = "tw_framework.cli:main"
24
24
 
25
25
  [tool.setuptools]
26
26
  packages = ["tw_framework", "tw_framework.adapters"]
27
+
28
+ [tool.setuptools.package-data]
29
+ tw_framework = ["*.js"]
@@ -27,6 +27,7 @@ theme: system
27
27
  pretty_urls: true
28
28
  search: true
29
29
  modular_pipeline: true
30
+ allow_raw_script: true
30
31
  """,
31
32
  ".env": """SITE_NAME=My TW Site
32
33
  API_TOKEN=change-me
@@ -1136,9 +1136,11 @@ def tokenize(code, allow_inline_scripts=False):
1136
1136
  start_line, start_col = line, col
1137
1137
  word = []
1138
1138
  # Read until whitespace, braces, quotes, or common operators.
1139
- # Exception: a `-` surrounded by alnum chars on both sides is kept as
1140
- # part of the word (kebab-case identifiers like `aria-label`,
1141
- # `data-foo`), since HTML/ARIA attribute names rely on this.
1139
+ # Exceptions kept as part of the word:
1140
+ # - `-` between two alnum chars: kebab-case identifiers like
1141
+ # `aria-label`, `data-foo`.
1142
+ # - `:` right after `on` or `bind`: reactive directives like
1143
+ # `on:click`, `bind:value` (see reactivity.py).
1142
1144
  while i < n:
1143
1145
  ch = code[i]
1144
1146
  if ch in ' \t\r\n{}"\'' or ch in ONE_CHAR_OPS:
@@ -1151,6 +1153,14 @@ def tokenize(code, allow_inline_scripts=False):
1151
1153
  ):
1152
1154
  word.append(advance_one())
1153
1155
  continue
1156
+ if (
1157
+ ch == ":"
1158
+ and "".join(word) in ("on", "bind")
1159
+ and i + 1 < n
1160
+ and (code[i + 1].isalnum() or code[i + 1] == "_")
1161
+ ):
1162
+ word.append(advance_one())
1163
+ continue
1154
1164
  break
1155
1165
  word.append(advance_one())
1156
1166
  word = "".join(word)
@@ -1182,6 +1192,14 @@ def classify_known_prop(name):
1182
1192
  nl = name.lower()
1183
1193
  if nl in ROUTER_KEYS:
1184
1194
  return "router"
1195
+ if (
1196
+ nl.startswith("bind:")
1197
+ or nl.startswith("on:")
1198
+ or nl.startswith("show:")
1199
+ or nl.startswith("tw-")
1200
+ or nl.startswith("tw:")
1201
+ ):
1202
+ return "attr"
1185
1203
  if nl in EVENTS or (nl.startswith("on") and nl[2:] in EVENTS):
1186
1204
  return "event"
1187
1205
  if nl in HTML_ATTRIBUTES or nl.startswith("data-") or nl.startswith("aria-"):
@@ -0,0 +1,469 @@
1
+ /* eslint-disable no-console */
2
+ // TWLang server-side `.twm` API runner.
3
+ //
4
+ // Usage:
5
+ // node twm_api_runner.js /abs/path/to/compiled_module.cjs
6
+ //
7
+ // Reads request JSON from stdin and prints ONE JSON response to stdout.
8
+
9
+ "use strict";
10
+
11
+ const fs = require("fs");
12
+ const path = require("path");
13
+ const { createRequire } = require("module");
14
+
15
+ function findProjectRoot(startPath) {
16
+ let current = path.resolve(startPath || process.cwd());
17
+ if (!fs.existsSync(current)) {
18
+ current = path.dirname(current);
19
+ }
20
+ if (fs.existsSync(current) && fs.statSync(current).isFile()) {
21
+ current = path.dirname(current);
22
+ }
23
+ while (true) {
24
+ if (
25
+ fs.existsSync(path.join(current, "package.json")) ||
26
+ fs.existsSync(path.join(current, "tw.config")) ||
27
+ fs.existsSync(path.join(current, "[home]"))
28
+ ) {
29
+ return current;
30
+ }
31
+ const parent = path.dirname(current);
32
+ if (parent === current) {
33
+ return process.cwd();
34
+ }
35
+ current = parent;
36
+ }
37
+ }
38
+
39
+ function readJsonFile(filePath, fallbackValue) {
40
+ try {
41
+ return JSON.parse(fs.readFileSync(filePath, "utf8"));
42
+ } catch (error) {
43
+ return fallbackValue;
44
+ }
45
+ }
46
+
47
+ function normalizeHeaderValue(headers, key) {
48
+ if (!headers || typeof headers !== "object") return undefined;
49
+ const target = String(key || "").toLowerCase();
50
+ for (const [name, value] of Object.entries(headers)) {
51
+ if (String(name).toLowerCase() === target) return value;
52
+ }
53
+ return undefined;
54
+ }
55
+
56
+ function createTimeoutSignal(timeoutMs) {
57
+ const timeout = Number(timeoutMs || 0);
58
+ if (!Number.isFinite(timeout) || timeout <= 0) return {};
59
+ if (typeof AbortSignal !== "undefined" && typeof AbortSignal.timeout === "function") {
60
+ return { signal: AbortSignal.timeout(timeout) };
61
+ }
62
+ if (typeof AbortController === "undefined") {
63
+ return {};
64
+ }
65
+ const controller = new AbortController();
66
+ const timer = setTimeout(() => controller.abort(new Error(`Request timeout after ${timeout}ms`)), timeout);
67
+ return {
68
+ signal: controller.signal,
69
+ cleanup() {
70
+ clearTimeout(timer);
71
+ },
72
+ };
73
+ }
74
+
75
+ function normalizeHttpData(rawText, headers) {
76
+ const contentType = String(normalizeHeaderValue(headers, "content-type") || "").toLowerCase();
77
+ if (contentType.includes("application/json")) {
78
+ try {
79
+ return JSON.parse(rawText);
80
+ } catch (error) {
81
+ return rawText;
82
+ }
83
+ }
84
+ return rawText;
85
+ }
86
+
87
+ function buildHttpHelpers(defaultHeaders) {
88
+ async function request(url, options = {}) {
89
+ if (typeof fetch !== "function") {
90
+ throw new Error("Global `fetch` is not available. Use Node.js 18+ for built-in http helpers.");
91
+ }
92
+ const opts = options && typeof options === "object" ? { ...options } : {};
93
+ const method = String(opts.method || "GET").toUpperCase();
94
+ const headers = { ...(defaultHeaders || {}), ...(opts.headers || {}) };
95
+ let body = opts.body;
96
+ const hasContentType = Object.keys(headers).some((name) => String(name).toLowerCase() === "content-type");
97
+ if (
98
+ body !== undefined &&
99
+ body !== null &&
100
+ typeof body === "object" &&
101
+ !Buffer.isBuffer(body) &&
102
+ !(body instanceof URLSearchParams) &&
103
+ typeof body !== "string"
104
+ ) {
105
+ if (!hasContentType) headers["Content-Type"] = "application/json";
106
+ body = JSON.stringify(body);
107
+ }
108
+ const timeoutCtl = createTimeoutSignal(opts.timeout || opts.timeoutMs || 0);
109
+ try {
110
+ const response = await fetch(url, {
111
+ ...opts,
112
+ method,
113
+ headers,
114
+ body,
115
+ signal: timeoutCtl.signal || opts.signal,
116
+ });
117
+ const text = await response.text();
118
+ const responseHeaders = Object.fromEntries(response.headers.entries());
119
+ return {
120
+ ok: response.ok,
121
+ status: response.status,
122
+ statusText: response.statusText,
123
+ url: response.url,
124
+ headers: responseHeaders,
125
+ text,
126
+ data: normalizeHttpData(text, responseHeaders),
127
+ };
128
+ } finally {
129
+ if (typeof timeoutCtl.cleanup === "function") timeoutCtl.cleanup();
130
+ }
131
+ }
132
+
133
+ return {
134
+ request,
135
+ get(url, options = {}) {
136
+ return request(url, { ...options, method: "GET" });
137
+ },
138
+ post(url, body, options = {}) {
139
+ return request(url, { ...options, method: "POST", body });
140
+ },
141
+ put(url, body, options = {}) {
142
+ return request(url, { ...options, method: "PUT", body });
143
+ },
144
+ patch(url, body, options = {}) {
145
+ return request(url, { ...options, method: "PATCH", body });
146
+ },
147
+ delete(url, options = {}) {
148
+ return request(url, { ...options, method: "DELETE" });
149
+ },
150
+ };
151
+ }
152
+
153
+ function buildValueHelper(values, label) {
154
+ const source = values && typeof values === "object" ? { ...values } : {};
155
+ const api = {
156
+ get(name, fallbackValue = undefined) {
157
+ return Object.prototype.hasOwnProperty.call(source, name) ? source[name] : fallbackValue;
158
+ },
159
+ has(name) {
160
+ return Object.prototype.hasOwnProperty.call(source, name);
161
+ },
162
+ require(name) {
163
+ if (!Object.prototype.hasOwnProperty.call(source, name) || source[name] === "") {
164
+ throw new Error(`Missing ${label}: ${name}`);
165
+ }
166
+ return source[name];
167
+ },
168
+ all() {
169
+ return { ...source };
170
+ },
171
+ };
172
+ return new Proxy(api, {
173
+ get(target, prop) {
174
+ if (prop in target) return target[prop];
175
+ if (typeof prop === "string" && Object.prototype.hasOwnProperty.call(source, prop)) {
176
+ return source[prop];
177
+ }
178
+ return undefined;
179
+ },
180
+ has(target, prop) {
181
+ return prop in target || (typeof prop === "string" && Object.prototype.hasOwnProperty.call(source, prop));
182
+ },
183
+ });
184
+ }
185
+
186
+ function createProjectRequire(projectRoot) {
187
+ const packageJsonPath = path.join(projectRoot, "package.json");
188
+ if (fs.existsSync(packageJsonPath)) {
189
+ return createRequire(packageJsonPath);
190
+ }
191
+ return createRequire(__filename);
192
+ }
193
+
194
+ function createPackageHelper(projectRoot) {
195
+ const projectRequire = createProjectRequire(projectRoot);
196
+ const packageJson = readJsonFile(path.join(projectRoot, "package.json"), {});
197
+ return {
198
+ json() {
199
+ return { ...packageJson };
200
+ },
201
+ has(name) {
202
+ const deps = {
203
+ ...((packageJson && packageJson.dependencies) || {}),
204
+ ...((packageJson && packageJson.devDependencies) || {}),
205
+ };
206
+ return Object.prototype.hasOwnProperty.call(deps, name);
207
+ },
208
+ require(name) {
209
+ try {
210
+ return projectRequire(name);
211
+ } catch (error) {
212
+ if (error && (error.code === "MODULE_NOT_FOUND" || String(error.message || "").includes("Cannot find module"))) {
213
+ throw new Error(
214
+ `Package \`${name}\` nahi mila. Isko project ke package.json me add karke \`npm install ${name}\` chalao.`
215
+ );
216
+ }
217
+ throw error;
218
+ }
219
+ },
220
+ resolve(name) {
221
+ return projectRequire.resolve(name);
222
+ },
223
+ };
224
+ }
225
+
226
+ function parseServiceAccount(rawValue, projectRoot) {
227
+ if (!rawValue) return null;
228
+ if (typeof rawValue === "object") return rawValue;
229
+ const text = String(rawValue).trim();
230
+ if (!text) return null;
231
+ if (text.startsWith("{")) {
232
+ return JSON.parse(text);
233
+ }
234
+ const absolutePath = path.isAbsolute(text) ? text : path.join(projectRoot, text);
235
+ return readJsonFile(absolutePath, null);
236
+ }
237
+
238
+ function createFirebaseHelper(projectRoot, envHelper, packageHelper) {
239
+ function loadAdmin() {
240
+ return packageHelper.require("firebase-admin");
241
+ }
242
+
243
+ function resolveCredential(admin) {
244
+ const inlineJson =
245
+ envHelper.get("FIREBASE_SERVICE_ACCOUNT_JSON") ||
246
+ envHelper.get("FIREBASE_ADMIN_CREDENTIALS_JSON") ||
247
+ envHelper.get("GOOGLE_APPLICATION_CREDENTIALS_JSON");
248
+ const parsedInline = parseServiceAccount(inlineJson, projectRoot);
249
+ if (parsedInline) {
250
+ return admin.credential.cert(parsedInline);
251
+ }
252
+ const filePath =
253
+ envHelper.get("FIREBASE_SERVICE_ACCOUNT_PATH") ||
254
+ envHelper.get("FIREBASE_ADMIN_CREDENTIALS_PATH") ||
255
+ envHelper.get("GOOGLE_APPLICATION_CREDENTIALS");
256
+ const parsedFile = parseServiceAccount(filePath, projectRoot);
257
+ if (parsedFile) {
258
+ return admin.credential.cert(parsedFile);
259
+ }
260
+ return undefined;
261
+ }
262
+
263
+ function app(options = {}) {
264
+ const admin = loadAdmin();
265
+ if (Array.isArray(admin.apps) && admin.apps.length > 0) {
266
+ return admin.app();
267
+ }
268
+ const initOptions = { ...(options || {}) };
269
+ if (!initOptions.credential) {
270
+ const credential = resolveCredential(admin);
271
+ if (credential) initOptions.credential = credential;
272
+ }
273
+ if (!initOptions.projectId) {
274
+ initOptions.projectId = envHelper.get("FIREBASE_PROJECT_ID") || envHelper.get("GOOGLE_CLOUD_PROJECT");
275
+ }
276
+ return admin.initializeApp(initOptions);
277
+ }
278
+
279
+ return {
280
+ admin: loadAdmin,
281
+ app,
282
+ firestore() {
283
+ return app().firestore();
284
+ },
285
+ auth() {
286
+ return app().auth();
287
+ },
288
+ messaging() {
289
+ return app().messaging();
290
+ },
291
+ storage() {
292
+ return app().storage();
293
+ },
294
+ };
295
+ }
296
+
297
+ function installRuntimeHelpers(request, compiledPath) {
298
+ const projectRoot = findProjectRoot(
299
+ (request && request.project_root) || compiledPath || process.cwd()
300
+ );
301
+ const envHelper = buildValueHelper((request && request.env) || {}, "env value");
302
+ const secretsHelper = buildValueHelper((request && request.env) || {}, "secret");
303
+ const httpHelper = buildHttpHelpers({
304
+ Accept: "application/json, text/plain;q=0.9, */*;q=0.8",
305
+ });
306
+ const packageHelper = createPackageHelper(projectRoot);
307
+ const firebaseHelper = createFirebaseHelper(projectRoot, envHelper, packageHelper);
308
+ globalThis.http = httpHelper;
309
+ globalThis.env = envHelper;
310
+ globalThis.secrets = secretsHelper;
311
+ globalThis.pkg = packageHelper;
312
+ globalThis.firebase = firebaseHelper;
313
+ globalThis.helpers = {
314
+ http: httpHelper,
315
+ env: envHelper,
316
+ secrets: secretsHelper,
317
+ pkg: packageHelper,
318
+ firebase: firebaseHelper,
319
+ projectRoot,
320
+ };
321
+ }
322
+
323
+ function toPairs(headers) {
324
+ if (!headers) return [];
325
+ if (Array.isArray(headers)) return headers;
326
+ if (typeof headers === "object") return Object.entries(headers);
327
+ return [];
328
+ }
329
+
330
+ function normalizeResult(result) {
331
+ // Allowed return shapes:
332
+ // - string
333
+ // - object (auto JSON), or object with {json|text|html|body,status,headers,cookies}
334
+ // - tuple-like array: [body, status] or [body, status, headers]
335
+ let status = 200;
336
+ let headers = [];
337
+ let cookies = [];
338
+
339
+ // Tuple: [body, status, headers]
340
+ if (Array.isArray(result)) {
341
+ const body = result[0];
342
+ status = Number(result[1] ?? 200) || 200;
343
+ headers = toPairs(result[2]);
344
+ if (typeof body === "string") {
345
+ return { status, content_type: "text/plain; charset=utf-8", body, headers, cookies };
346
+ }
347
+ return {
348
+ status,
349
+ content_type: "application/json; charset=utf-8",
350
+ body: JSON.stringify(body),
351
+ headers,
352
+ cookies,
353
+ };
354
+ }
355
+
356
+ if (typeof result === "string") {
357
+ return { status: 200, content_type: "text/plain; charset=utf-8", body: result, headers: [], cookies: [] };
358
+ }
359
+
360
+ if (result && typeof result === "object") {
361
+ status = Number(result.status ?? 200) || 200;
362
+ headers = toPairs(result.headers);
363
+ cookies = Array.isArray(result.cookies) ? result.cookies : toPairs(result.cookies);
364
+
365
+ if (Object.prototype.hasOwnProperty.call(result, "json")) {
366
+ const payload = result.json;
367
+ return {
368
+ status,
369
+ content_type: "application/json; charset=utf-8",
370
+ body: typeof payload === "string" ? payload : JSON.stringify(payload),
371
+ headers,
372
+ cookies,
373
+ };
374
+ }
375
+ if (Object.prototype.hasOwnProperty.call(result, "html")) {
376
+ return { status, content_type: "text/html; charset=utf-8", body: String(result.html ?? ""), headers, cookies };
377
+ }
378
+ if (Object.prototype.hasOwnProperty.call(result, "text")) {
379
+ return { status, content_type: "text/plain; charset=utf-8", body: String(result.text ?? ""), headers, cookies };
380
+ }
381
+ if (Object.prototype.hasOwnProperty.call(result, "body")) {
382
+ const bodyVal = result.body;
383
+ const ct = String(result.content_type || result.contentType || "text/plain; charset=utf-8");
384
+ if (bodyVal && typeof bodyVal === "object" && ct.includes("application/json")) {
385
+ return { status, content_type: ct, body: JSON.stringify(bodyVal), headers, cookies };
386
+ }
387
+ return { status, content_type: ct, body: typeof bodyVal === "string" ? bodyVal : JSON.stringify(bodyVal), headers, cookies };
388
+ }
389
+
390
+ // Default: treat object as JSON
391
+ return {
392
+ status,
393
+ content_type: "application/json; charset=utf-8",
394
+ body: JSON.stringify(result),
395
+ headers,
396
+ cookies,
397
+ };
398
+ }
399
+
400
+ return { status: 200, content_type: "text/plain; charset=utf-8", body: String(result ?? ""), headers: [], cookies: [] };
401
+ }
402
+
403
+ function methodList(mod) {
404
+ const methods = ["get", "post", "put", "patch", "delete", "options"];
405
+ return methods.filter((m) => typeof mod[m] === "function").map((m) => m.toUpperCase());
406
+ }
407
+
408
+ async function main() {
409
+ const compiledPath = process.argv[2];
410
+ if (!compiledPath) {
411
+ process.stderr.write("Missing compiled module path\n");
412
+ process.exit(2);
413
+ }
414
+
415
+ const mod = require(path.resolve(compiledPath));
416
+
417
+ let input = "";
418
+ process.stdin.setEncoding("utf8");
419
+ process.stdin.on("data", (chunk) => (input += chunk));
420
+ process.stdin.on("end", async () => {
421
+ let request = {};
422
+ try {
423
+ request = JSON.parse(input || "{}");
424
+ } catch (e) {
425
+ const resp = { status: 400, content_type: "application/json; charset=utf-8", body: JSON.stringify({ error: "Invalid JSON request" }), headers: [], cookies: [] };
426
+ process.stdout.write(JSON.stringify(resp));
427
+ return;
428
+ }
429
+
430
+ const method = String(request.method || "GET").toLowerCase();
431
+ const fn = (typeof mod[method] === "function" ? mod[method] : null) || (typeof mod.handler === "function" ? mod.handler : null);
432
+ if (!fn) {
433
+ const allowed = methodList(mod);
434
+ const resp = {
435
+ status: 405,
436
+ content_type: "application/json; charset=utf-8",
437
+ body: JSON.stringify({ error: "Method not allowed", allowed }),
438
+ headers: [["Allow", allowed.join(", ")]],
439
+ cookies: [],
440
+ };
441
+ process.stdout.write(JSON.stringify(resp));
442
+ return;
443
+ }
444
+
445
+ try {
446
+ installRuntimeHelpers(request, compiledPath);
447
+ const result = await fn(request);
448
+ const resp = normalizeResult(result);
449
+ process.stdout.write(JSON.stringify(resp));
450
+ } catch (e) {
451
+ const resp = {
452
+ status: 500,
453
+ content_type: "application/json; charset=utf-8",
454
+ body: JSON.stringify({ error: "TWM handler error", name: e && e.name ? e.name : "Error", message: e && e.message ? e.message : String(e) }),
455
+ headers: [],
456
+ cookies: [],
457
+ };
458
+ process.stdout.write(JSON.stringify(resp));
459
+ }
460
+ });
461
+
462
+ // Important: start consuming stdin
463
+ process.stdin.resume();
464
+ }
465
+
466
+ main().catch((e) => {
467
+ process.stderr.write(String(e && e.stack ? e.stack : e) + "\n");
468
+ process.exit(1);
469
+ });
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tw-framework
3
- Version: 0.2.6
3
+ Version: 0.2.8
4
4
  Summary: TW framework CLI with create, dev, build, and deploy commands
5
5
  Author: TW Framework
6
6
  Requires-Python: >=3.9
@@ -46,6 +46,7 @@ tw_framework/static_dynamic_auto.py
46
46
  tw_framework/streaming.py
47
47
  tw_framework/test_build_cli_pytest.py
48
48
  tw_framework/tree_shaking.py
49
+ tw_framework/twm_api_runner.js
49
50
  tw_framework/twm_parser.py
50
51
  tw_framework.egg-info/PKG-INFO
51
52
  tw_framework.egg-info/SOURCES.txt
File without changes
File without changes