fastscript 1.0.0 → 3.0.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.
Files changed (119) hide show
  1. package/CHANGELOG.md +38 -7
  2. package/LICENSE +33 -21
  3. package/README.md +605 -73
  4. package/node_modules/@fastscript/core-private/BOUNDARY.json +15 -0
  5. package/node_modules/@fastscript/core-private/README.md +5 -0
  6. package/node_modules/@fastscript/core-private/package.json +34 -0
  7. package/node_modules/@fastscript/core-private/src/asset-optimizer.mjs +67 -0
  8. package/node_modules/@fastscript/core-private/src/audit-log.mjs +50 -0
  9. package/node_modules/@fastscript/core-private/src/auth-flows.mjs +29 -0
  10. package/node_modules/@fastscript/core-private/src/auth.mjs +115 -0
  11. package/node_modules/@fastscript/core-private/src/bench.mjs +45 -0
  12. package/node_modules/@fastscript/core-private/src/build.mjs +670 -0
  13. package/node_modules/@fastscript/core-private/src/cache.mjs +248 -0
  14. package/node_modules/@fastscript/core-private/src/check.mjs +22 -0
  15. package/node_modules/@fastscript/core-private/src/cli.mjs +95 -0
  16. package/node_modules/@fastscript/core-private/src/compat.mjs +128 -0
  17. package/node_modules/@fastscript/core-private/src/create.mjs +278 -0
  18. package/node_modules/@fastscript/core-private/src/csp.mjs +26 -0
  19. package/node_modules/@fastscript/core-private/src/db-cli.mjs +185 -0
  20. package/node_modules/@fastscript/core-private/src/db-postgres-collection.mjs +110 -0
  21. package/node_modules/@fastscript/core-private/src/db-postgres.mjs +40 -0
  22. package/node_modules/@fastscript/core-private/src/db.mjs +103 -0
  23. package/node_modules/@fastscript/core-private/src/deploy.mjs +662 -0
  24. package/node_modules/@fastscript/core-private/src/dev.mjs +5 -0
  25. package/node_modules/@fastscript/core-private/src/docs-search.mjs +35 -0
  26. package/node_modules/@fastscript/core-private/src/env.mjs +118 -0
  27. package/node_modules/@fastscript/core-private/src/export.mjs +83 -0
  28. package/node_modules/@fastscript/core-private/src/fs-diagnostics.mjs +70 -0
  29. package/node_modules/@fastscript/core-private/src/fs-error-codes.mjs +141 -0
  30. package/node_modules/@fastscript/core-private/src/fs-formatter.mjs +66 -0
  31. package/node_modules/@fastscript/core-private/src/fs-linter.mjs +274 -0
  32. package/node_modules/@fastscript/core-private/src/fs-normalize.mjs +121 -0
  33. package/node_modules/@fastscript/core-private/src/fs-parser.mjs +1120 -0
  34. package/node_modules/@fastscript/core-private/src/generated/docs-search-index.mjs +3182 -0
  35. package/node_modules/@fastscript/core-private/src/i18n.mjs +25 -0
  36. package/node_modules/@fastscript/core-private/src/interop.mjs +16 -0
  37. package/node_modules/@fastscript/core-private/src/jobs.mjs +378 -0
  38. package/node_modules/@fastscript/core-private/src/logger.mjs +27 -0
  39. package/node_modules/@fastscript/core-private/src/metrics.mjs +45 -0
  40. package/node_modules/@fastscript/core-private/src/middleware.mjs +14 -0
  41. package/node_modules/@fastscript/core-private/src/migrate.mjs +81 -0
  42. package/node_modules/@fastscript/core-private/src/migration-wizard.mjs +16 -0
  43. package/node_modules/@fastscript/core-private/src/module-loader.mjs +46 -0
  44. package/node_modules/@fastscript/core-private/src/oauth-providers.mjs +103 -0
  45. package/node_modules/@fastscript/core-private/src/observability.mjs +21 -0
  46. package/node_modules/@fastscript/core-private/src/plugins.mjs +194 -0
  47. package/node_modules/@fastscript/core-private/src/retention.mjs +57 -0
  48. package/node_modules/@fastscript/core-private/src/routes.mjs +178 -0
  49. package/node_modules/@fastscript/core-private/src/scheduler.mjs +104 -0
  50. package/node_modules/@fastscript/core-private/src/security.mjs +233 -0
  51. package/node_modules/@fastscript/core-private/src/server-runtime.mjs +849 -0
  52. package/node_modules/@fastscript/core-private/src/serverless-handler.mjs +20 -0
  53. package/node_modules/@fastscript/core-private/src/session-policy.mjs +38 -0
  54. package/node_modules/@fastscript/core-private/src/start.mjs +10 -0
  55. package/node_modules/@fastscript/core-private/src/storage.mjs +155 -0
  56. package/node_modules/@fastscript/core-private/src/style-primitives.mjs +538 -0
  57. package/node_modules/@fastscript/core-private/src/style-system.mjs +461 -0
  58. package/node_modules/@fastscript/core-private/src/tenant.mjs +55 -0
  59. package/node_modules/@fastscript/core-private/src/typecheck.mjs +1466 -0
  60. package/node_modules/@fastscript/core-private/src/validate.mjs +22 -0
  61. package/node_modules/@fastscript/core-private/src/validation.mjs +88 -0
  62. package/node_modules/@fastscript/core-private/src/webhook.mjs +81 -0
  63. package/node_modules/@fastscript/core-private/src/worker.mjs +24 -0
  64. package/package.json +108 -14
  65. package/src/asset-optimizer.mjs +67 -0
  66. package/src/audit-log.mjs +50 -0
  67. package/src/auth.mjs +1 -115
  68. package/src/bench.mjs +20 -7
  69. package/src/benchmark-discipline.mjs +39 -0
  70. package/src/build.mjs +1 -234
  71. package/src/cache.mjs +210 -20
  72. package/src/cli.mjs +65 -6
  73. package/src/compat.mjs +8 -10
  74. package/src/conversion-manifest.mjs +101 -0
  75. package/src/create.mjs +71 -17
  76. package/src/csp.mjs +26 -0
  77. package/src/db-cli.mjs +152 -8
  78. package/src/db-postgres-collection.mjs +110 -0
  79. package/src/deploy.mjs +1 -65
  80. package/src/diagnostics.mjs +100 -0
  81. package/src/docs-search.mjs +35 -0
  82. package/src/env.mjs +34 -5
  83. package/src/fs-diagnostics.mjs +70 -0
  84. package/src/fs-error-codes.mjs +126 -0
  85. package/src/fs-formatter.mjs +66 -0
  86. package/src/fs-linter.mjs +274 -0
  87. package/src/fs-normalize.mjs +52 -239
  88. package/src/fs-parser.mjs +1 -0
  89. package/src/generated/docs-search-index.mjs +3591 -0
  90. package/src/i18n.mjs +25 -0
  91. package/src/jobs.mjs +283 -32
  92. package/src/metrics.mjs +45 -0
  93. package/src/migrate-rollback.mjs +144 -0
  94. package/src/migrate.mjs +1275 -47
  95. package/src/migration-wizard.mjs +42 -0
  96. package/src/module-loader.mjs +22 -11
  97. package/src/oauth-providers.mjs +103 -0
  98. package/src/permissions-cli.mjs +112 -0
  99. package/src/plugins.mjs +194 -0
  100. package/src/profile.mjs +95 -0
  101. package/src/regression-guard.mjs +245 -0
  102. package/src/retention.mjs +57 -0
  103. package/src/routes.mjs +178 -0
  104. package/src/runtime-permissions.mjs +299 -0
  105. package/src/scheduler.mjs +104 -0
  106. package/src/security.mjs +197 -19
  107. package/src/server-runtime.mjs +1 -339
  108. package/src/serverless-handler.mjs +20 -0
  109. package/src/session-policy.mjs +38 -0
  110. package/src/storage.mjs +1 -56
  111. package/src/style-system.mjs +461 -0
  112. package/src/tenant.mjs +55 -0
  113. package/src/trace.mjs +95 -0
  114. package/src/typecheck.mjs +1 -0
  115. package/src/validate.mjs +13 -1
  116. package/src/validation.mjs +14 -5
  117. package/src/webhook.mjs +1 -71
  118. package/src/worker.mjs +23 -4
  119. package/src/language-spec.mjs +0 -58
@@ -0,0 +1,248 @@
1
+ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
2
+ import { join, resolve } from "node:path";
3
+
4
+ function now() {
5
+ return Date.now();
6
+ }
7
+
8
+ function normalizeTags(tags) {
9
+ return [...new Set((Array.isArray(tags) ? tags : []).map((tag) => String(tag).trim()).filter(Boolean))];
10
+ }
11
+
12
+ function expired(row) {
13
+ return Boolean(row && row.exp && row.exp < now());
14
+ }
15
+
16
+ export function createMemoryCache() {
17
+ const rows = new Map();
18
+ const tagIndex = new Map();
19
+
20
+ function attachTags(key, tags) {
21
+ for (const tag of normalizeTags(tags)) {
22
+ if (!tagIndex.has(tag)) tagIndex.set(tag, new Set());
23
+ tagIndex.get(tag).add(key);
24
+ }
25
+ }
26
+
27
+ function detachTags(key, tags) {
28
+ for (const tag of normalizeTags(tags)) {
29
+ const set = tagIndex.get(tag);
30
+ if (!set) continue;
31
+ set.delete(key);
32
+ if (set.size === 0) tagIndex.delete(tag);
33
+ }
34
+ }
35
+
36
+ return {
37
+ async get(key) {
38
+ const row = rows.get(key);
39
+ if (!row) return null;
40
+ if (expired(row)) {
41
+ detachTags(key, row.tags);
42
+ rows.delete(key);
43
+ return null;
44
+ }
45
+ return row.value;
46
+ },
47
+ async set(key, value, ttlMs = 0) {
48
+ const prev = rows.get(key);
49
+ if (prev) detachTags(key, prev.tags);
50
+ rows.set(key, { value, exp: ttlMs ? now() + ttlMs : 0, tags: [] });
51
+ },
52
+ async setWithTags(key, value, { ttlMs = 0, tags = [] } = {}) {
53
+ const prev = rows.get(key);
54
+ if (prev) detachTags(key, prev.tags);
55
+ const normalizedTags = normalizeTags(tags);
56
+ rows.set(key, { value, exp: ttlMs ? now() + ttlMs : 0, tags: normalizedTags });
57
+ attachTags(key, normalizedTags);
58
+ },
59
+ async del(key) {
60
+ const prev = rows.get(key);
61
+ if (prev) detachTags(key, prev.tags);
62
+ rows.delete(key);
63
+ },
64
+ async invalidateTag(tag) {
65
+ const keys = [...(tagIndex.get(tag) || [])];
66
+ for (const key of keys) {
67
+ const row = rows.get(key);
68
+ if (row) detachTags(key, row.tags);
69
+ rows.delete(key);
70
+ }
71
+ tagIndex.delete(tag);
72
+ return keys.length;
73
+ },
74
+ async clear() {
75
+ rows.clear();
76
+ tagIndex.clear();
77
+ },
78
+ };
79
+ }
80
+
81
+ export function createFileCache({ dir = ".fastscript/cache" } = {}) {
82
+ const root = resolve(dir);
83
+ mkdirSync(root, { recursive: true });
84
+ const p = (key) => join(root, `${encodeURIComponent(key)}.json`);
85
+ const tagsFile = join(root, "_tags.json");
86
+
87
+ function readTags() {
88
+ if (!existsSync(tagsFile)) return {};
89
+ try {
90
+ return JSON.parse(readFileSync(tagsFile, "utf8")) || {};
91
+ } catch {
92
+ return {};
93
+ }
94
+ }
95
+
96
+ function writeTags(index) {
97
+ writeFileSync(tagsFile, JSON.stringify(index, null, 2), "utf8");
98
+ }
99
+
100
+ function detach(key) {
101
+ const index = readTags();
102
+ let changed = false;
103
+ for (const [tag, keys] of Object.entries(index)) {
104
+ const next = (Array.isArray(keys) ? keys : []).filter((item) => item !== key);
105
+ if (next.length !== keys.length) changed = true;
106
+ if (next.length) index[tag] = next;
107
+ else delete index[tag];
108
+ }
109
+ if (changed) writeTags(index);
110
+ }
111
+
112
+ function attach(key, tags) {
113
+ const normalized = normalizeTags(tags);
114
+ if (!normalized.length) return;
115
+ const index = readTags();
116
+ for (const tag of normalized) {
117
+ const set = new Set(Array.isArray(index[tag]) ? index[tag] : []);
118
+ set.add(key);
119
+ index[tag] = [...set];
120
+ }
121
+ writeTags(index);
122
+ }
123
+
124
+ return {
125
+ async get(key) {
126
+ const file = p(key);
127
+ if (!existsSync(file)) return null;
128
+ const row = JSON.parse(readFileSync(file, "utf8"));
129
+ if (expired(row)) {
130
+ rmSync(file, { force: true });
131
+ detach(key);
132
+ return null;
133
+ }
134
+ return row.value;
135
+ },
136
+ async set(key, value, ttlMs = 0) {
137
+ writeFileSync(p(key), JSON.stringify({ value, exp: ttlMs ? now() + ttlMs : 0, tags: [] }), "utf8");
138
+ detach(key);
139
+ },
140
+ async setWithTags(key, value, { ttlMs = 0, tags = [] } = {}) {
141
+ const normalized = normalizeTags(tags);
142
+ writeFileSync(p(key), JSON.stringify({ value, exp: ttlMs ? now() + ttlMs : 0, tags: normalized }), "utf8");
143
+ detach(key);
144
+ attach(key, normalized);
145
+ },
146
+ async del(key) {
147
+ rmSync(p(key), { force: true });
148
+ detach(key);
149
+ },
150
+ async invalidateTag(tag) {
151
+ const index = readTags();
152
+ const keys = Array.isArray(index[tag]) ? index[tag] : [];
153
+ for (const key of keys) rmSync(p(key), { force: true });
154
+ delete index[tag];
155
+ writeTags(index);
156
+ return keys.length;
157
+ },
158
+ async clear() {
159
+ rmSync(root, { recursive: true, force: true });
160
+ mkdirSync(root, { recursive: true });
161
+ },
162
+ };
163
+ }
164
+
165
+ function stringifyValue(value) {
166
+ if (typeof value === "string") return value;
167
+ return JSON.stringify(value);
168
+ }
169
+
170
+ function parseValue(value) {
171
+ if (value === null || value === undefined) return null;
172
+ try {
173
+ return JSON.parse(value);
174
+ } catch {
175
+ return value;
176
+ }
177
+ }
178
+
179
+ export async function createRedisCache({ url = process.env.REDIS_URL, prefix = "fastscript:cache" } = {}) {
180
+ const mod = await import("redis");
181
+ const client = mod.createClient({ url });
182
+ await client.connect();
183
+
184
+ function rowKey(key) {
185
+ return `${prefix}:row:${key}`;
186
+ }
187
+ function tagKey(tag) {
188
+ return `${prefix}:tag:${tag}`;
189
+ }
190
+
191
+ async function detachFromTags(key) {
192
+ const tags = await client.sMembers(`${prefix}:rowtags:${key}`);
193
+ if (!tags.length) return;
194
+ for (const tag of tags) await client.sRem(tagKey(tag), key);
195
+ await client.del(`${prefix}:rowtags:${key}`);
196
+ }
197
+
198
+ return {
199
+ async get(key) {
200
+ const raw = await client.get(rowKey(key));
201
+ if (raw === null) return null;
202
+ const row = parseValue(raw);
203
+ if (!row || typeof row !== "object") return row;
204
+ if (expired(row)) {
205
+ await this.del(key);
206
+ return null;
207
+ }
208
+ return row.value;
209
+ },
210
+ async set(key, value, ttlMs = 0) {
211
+ await detachFromTags(key);
212
+ const row = stringifyValue({ value, exp: ttlMs ? now() + ttlMs : 0, tags: [] });
213
+ if (ttlMs > 0) await client.set(rowKey(key), row, { PX: ttlMs });
214
+ else await client.set(rowKey(key), row);
215
+ },
216
+ async setWithTags(key, value, { ttlMs = 0, tags = [] } = {}) {
217
+ await detachFromTags(key);
218
+ const normalized = normalizeTags(tags);
219
+ const row = stringifyValue({ value, exp: ttlMs ? now() + ttlMs : 0, tags: normalized });
220
+ if (ttlMs > 0) await client.set(rowKey(key), row, { PX: ttlMs });
221
+ else await client.set(rowKey(key), row);
222
+ for (const tag of normalized) await client.sAdd(tagKey(tag), key);
223
+ if (normalized.length) await client.sAdd(`${prefix}:rowtags:${key}`, ...normalized);
224
+ },
225
+ async del(key) {
226
+ await detachFromTags(key);
227
+ await client.del(rowKey(key));
228
+ },
229
+ async invalidateTag(tag) {
230
+ const keys = await client.sMembers(tagKey(tag));
231
+ for (const key of keys) await this.del(key);
232
+ await client.del(tagKey(tag));
233
+ return keys.length;
234
+ },
235
+ async clear() {
236
+ const cursor = "0";
237
+ let next = cursor;
238
+ do {
239
+ const data = await client.scan(next, { MATCH: `${prefix}:*`, COUNT: 200 });
240
+ next = data.cursor;
241
+ if (data.keys && data.keys.length) await client.del(data.keys);
242
+ } while (next !== "0");
243
+ },
244
+ async close() {
245
+ await client.quit();
246
+ },
247
+ };
248
+ }
@@ -0,0 +1,22 @@
1
+ import { existsSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+
4
+ export async function runCheck() {
5
+ const indexExists = existsSync(resolve("app/pages/index.fs")) || existsSync(resolve("app/pages/index.js"));
6
+ const layoutExists = existsSync(resolve("app/pages/_layout.fs")) || existsSync(resolve("app/pages/_layout.js"));
7
+
8
+ const required = [
9
+ resolve("app"),
10
+ resolve("app/pages"),
11
+ ];
12
+
13
+ const missing = required.filter((path) => !existsSync(path));
14
+ if (!indexExists) missing.push("app/pages/index.fs (or index.js)");
15
+ if (!layoutExists) missing.push("app/pages/_layout.fs (or _layout.js)");
16
+
17
+ if (missing.length > 0) {
18
+ throw new Error(`Missing required FastScript files:\n${missing.join("\n")}`);
19
+ }
20
+
21
+ console.log("check passed: FastScript app structure is valid");
22
+ }
@@ -0,0 +1,95 @@
1
+ #!/usr/bin/env node
2
+ import { createApp } from "./create.mjs";
3
+ import { runDev } from "./dev.mjs";
4
+ import { runBuild } from "./build.mjs";
5
+ import { runCheck } from "./check.mjs";
6
+ import { runMigrate } from "./migrate.mjs";
7
+ import { runBench } from "./bench.mjs";
8
+ import { runExport } from "./export.mjs";
9
+ import { runCompat } from "./compat.mjs";
10
+ import { runValidate } from "./validate.mjs";
11
+ import { runDbMigrate, runDbRollback, runDbSeed } from "./db-cli.mjs";
12
+ import { runStart } from "./start.mjs";
13
+ import { runDeploy } from "./deploy.mjs";
14
+ import { runWorkerCommand } from "./worker.mjs";
15
+ import { runTypeCheck } from "./typecheck.mjs";
16
+ import { runFormat } from "./fs-formatter.mjs";
17
+ import { runLint } from "./fs-linter.mjs";
18
+ import { runMigrationWizard } from "./migration-wizard.mjs";
19
+
20
+ const [, , command, ...args] = process.argv;
21
+
22
+ async function main() {
23
+ switch (command) {
24
+ case "create":
25
+ await createApp(args[0] ?? "app", {
26
+ template: args.includes("--template") ? args[args.indexOf("--template") + 1] || "default" : "default",
27
+ });
28
+ break;
29
+ case "dev":
30
+ await runDev();
31
+ break;
32
+ case "start":
33
+ await runStart();
34
+ break;
35
+ case "build":
36
+ await runBuild({ mode: args.includes("--mode") ? args[args.indexOf("--mode") + 1] || "build" : "build" });
37
+ break;
38
+ case "ssg":
39
+ await runBuild({ mode: "ssg" });
40
+ break;
41
+ case "check":
42
+ await runCheck();
43
+ break;
44
+ case "migrate":
45
+ await runMigrate(args[0] ?? "app/pages");
46
+ break;
47
+ case "wizard:migrate":
48
+ await runMigrationWizard(args);
49
+ break;
50
+ case "bench":
51
+ await runBench();
52
+ break;
53
+ case "export":
54
+ await runExport(args);
55
+ break;
56
+ case "compat":
57
+ await runCompat();
58
+ break;
59
+ case "validate":
60
+ await runValidate();
61
+ break;
62
+ case "typecheck":
63
+ await runTypeCheck(args);
64
+ break;
65
+ case "format":
66
+ await runFormat(args);
67
+ break;
68
+ case "lint":
69
+ await runLint(args);
70
+ break;
71
+ case "db:migrate":
72
+ await runDbMigrate();
73
+ break;
74
+ case "db:seed":
75
+ await runDbSeed();
76
+ break;
77
+ case "db:rollback":
78
+ await runDbRollback(args);
79
+ break;
80
+ case "deploy":
81
+ await runDeploy(args);
82
+ break;
83
+ case "worker":
84
+ await runWorkerCommand(args);
85
+ break;
86
+ default:
87
+ console.log("FastScript CLI");
88
+ console.log("Commands: create, dev, start, build, ssg, check, migrate, wizard:migrate, bench, export, compat, validate, typecheck, format, lint, db:migrate, db:seed, db:rollback, deploy, worker");
89
+ }
90
+ }
91
+
92
+ main().catch((error) => {
93
+ console.error("fastscript error:", error.message);
94
+ process.exit(1);
95
+ });
@@ -0,0 +1,128 @@
1
+ import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
2
+ import { join, resolve } from "node:path";
3
+ import esbuild from "esbuild";
4
+ import { normalizeFastScript } from "./fs-normalize.mjs";
5
+ import { assertFastScript } from "./fs-diagnostics.mjs";
6
+
7
+ const TMP_DIR = resolve(".fastscript-tmp-compat");
8
+
9
+ function fsLoaderPlugin() {
10
+ const compilerMode = (process.env.FASTSCRIPT_COMPILER_MODE || "strict").toLowerCase() === "lenient" ? "lenient" : "strict";
11
+ return {
12
+ name: "fastscript-fs-loader",
13
+ setup(build) {
14
+ build.onLoad({ filter: /\.fs$/ }, async (args) => {
15
+ const { readFile } = await import("node:fs/promises");
16
+ const raw = await readFile(args.path, "utf8");
17
+ assertFastScript(raw, { file: args.path, mode: compilerMode });
18
+ return {
19
+ contents: normalizeFastScript(raw, { file: args.path, mode: compilerMode, sourceMap: "inline" }),
20
+ loader: "js",
21
+ };
22
+ });
23
+ },
24
+ };
25
+ }
26
+
27
+ async function bundle(entry) {
28
+ await esbuild.build({
29
+ entryPoints: [entry],
30
+ bundle: true,
31
+ platform: "browser",
32
+ format: "esm",
33
+ write: false,
34
+ logLevel: "silent",
35
+ plugins: [fsLoaderPlugin()],
36
+ loader: { ".fs": "js" },
37
+ resolveExtensions: [".fs", ".js", ".mjs", ".cjs", ".json"],
38
+ });
39
+ }
40
+
41
+ export async function runCompat() {
42
+ rmSync(TMP_DIR, { recursive: true, force: true });
43
+ mkdirSync(TMP_DIR, { recursive: true });
44
+
45
+ // CJS fixture
46
+ writeFileSync(
47
+ join(TMP_DIR, "cjs-lib.cjs"),
48
+ `module.exports = { answer: 42, greet(name){ return "hi " + name; } };`,
49
+ "utf8",
50
+ );
51
+
52
+ // ESM fixture
53
+ writeFileSync(
54
+ join(TMP_DIR, "esm-lib.js"),
55
+ `export const value = 7; export default function twice(n){ return n*2; }`,
56
+ "utf8",
57
+ );
58
+
59
+ // FS fixture using lenient syntax + mixed imports.
60
+ writeFileSync(
61
+ join(TMP_DIR, "entry.fs"),
62
+ `import cjs from "./cjs-lib.cjs"
63
+ import twice, { value } from "./esm-lib.js"
64
+
65
+ state name = "fastscript"
66
+ fn run(n) {
67
+ return cjs.greet(name) + ":" + String(twice(n) + value + cjs.answer)
68
+ }
69
+
70
+ export default run
71
+ `,
72
+ "utf8",
73
+ );
74
+
75
+ // JS fixture importing FS module.
76
+ writeFileSync(
77
+ join(TMP_DIR, "entry-js.js"),
78
+ `import run from "./entry.fs"; export default run(1);`,
79
+ "utf8",
80
+ );
81
+
82
+ // Dynamic import + JSON compatibility fixture.
83
+ writeFileSync(join(TMP_DIR, "data.json"), JSON.stringify({ ok: true, n: 3 }), "utf8");
84
+ writeFileSync(
85
+ join(TMP_DIR, "dynamic.js"),
86
+ `export async function getN(){ const mod = await import("./data.json"); return mod.default.n; }`,
87
+ "utf8",
88
+ );
89
+ writeFileSync(
90
+ join(TMP_DIR, "dynamic-entry.fs"),
91
+ `fn boot() { return "ok" }
92
+ export async function run() {
93
+ const mod = await import("./dynamic.js");
94
+ return mod.getN();
95
+ }
96
+ export default boot
97
+ `,
98
+ "utf8",
99
+ );
100
+
101
+ // TS-like migrated syntax fixture.
102
+ writeFileSync(
103
+ join(TMP_DIR, "ts-like.fs"),
104
+ `state total = 0
105
+ fn add(n) { total = total + n; return total }
106
+ export default add
107
+ `,
108
+ "utf8",
109
+ );
110
+
111
+ const checks = [
112
+ { name: "fs-entry-bundle", entry: join(TMP_DIR, "entry.fs") },
113
+ { name: "js-imports-fs-bundle", entry: join(TMP_DIR, "entry-js.js") },
114
+ { name: "dynamic-json-import-bundle", entry: join(TMP_DIR, "dynamic-entry.fs") },
115
+ { name: "ts-like-fs-bundle", entry: join(TMP_DIR, "ts-like.fs") },
116
+ ];
117
+
118
+ for (const check of checks) {
119
+ try {
120
+ await bundle(check.entry);
121
+ console.log(`compat pass: ${check.name}`);
122
+ } catch (error) {
123
+ throw new Error(`compat fail: ${check.name}\n${error.message}`);
124
+ }
125
+ }
126
+
127
+ console.log("compat summary: core ESM/CJS/FS interop checks passed");
128
+ }