almostnode 0.2.6 → 0.2.8
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 +1 -1
- package/dist/__sw__.js +80 -84
- package/dist/assets/{runtime-worker-B8_LZkBX.js → runtime-worker-D8VYeuKv.js} +1448 -1121
- package/dist/assets/runtime-worker-D8VYeuKv.js.map +1 -0
- package/dist/frameworks/code-transforms.d.ts +53 -0
- package/dist/frameworks/code-transforms.d.ts.map +1 -0
- package/dist/frameworks/next-config-parser.d.ts +16 -0
- package/dist/frameworks/next-config-parser.d.ts.map +1 -0
- package/dist/frameworks/next-dev-server.d.ts +29 -18
- package/dist/frameworks/next-dev-server.d.ts.map +1 -1
- package/dist/frameworks/next-html-generator.d.ts +35 -0
- package/dist/frameworks/next-html-generator.d.ts.map +1 -0
- package/dist/frameworks/next-shims.d.ts +79 -0
- package/dist/frameworks/next-shims.d.ts.map +1 -0
- package/dist/frameworks/vite-dev-server.d.ts +0 -4
- package/dist/frameworks/vite-dev-server.d.ts.map +1 -1
- package/dist/index.cjs +30392 -9523
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +27296 -8797
- package/dist/index.mjs.map +1 -1
- package/dist/runtime.d.ts +20 -0
- package/dist/runtime.d.ts.map +1 -1
- package/dist/server-bridge.d.ts +2 -0
- package/dist/server-bridge.d.ts.map +1 -1
- package/dist/shims/crypto.d.ts +2 -0
- package/dist/shims/crypto.d.ts.map +1 -1
- package/dist/shims/esbuild.d.ts.map +1 -1
- package/dist/shims/fs.d.ts.map +1 -1
- package/dist/shims/http.d.ts +29 -0
- package/dist/shims/http.d.ts.map +1 -1
- package/dist/shims/path.d.ts.map +1 -1
- package/dist/shims/stream.d.ts.map +1 -1
- package/dist/shims/vfs-adapter.d.ts.map +1 -1
- package/dist/shims/ws.d.ts +2 -0
- package/dist/shims/ws.d.ts.map +1 -1
- package/dist/utils/binary-encoding.d.ts +13 -0
- package/dist/utils/binary-encoding.d.ts.map +1 -0
- package/dist/virtual-fs.d.ts.map +1 -1
- package/package.json +8 -4
- package/src/convex-app-demo-entry.ts +231 -35
- package/src/frameworks/code-transforms.ts +581 -0
- package/src/frameworks/next-config-parser.ts +140 -0
- package/src/frameworks/next-dev-server.ts +561 -1641
- package/src/frameworks/next-html-generator.ts +597 -0
- package/src/frameworks/next-shims.ts +1050 -0
- package/src/frameworks/tailwind-config-loader.ts +1 -1
- package/src/frameworks/vite-dev-server.ts +2 -61
- package/src/index.ts +2 -0
- package/src/runtime.ts +94 -15
- package/src/server-bridge.ts +61 -28
- package/src/shims/crypto.ts +13 -0
- package/src/shims/esbuild.ts +4 -1
- package/src/shims/fs.ts +9 -11
- package/src/shims/http.ts +309 -3
- package/src/shims/path.ts +6 -13
- package/src/shims/stream.ts +12 -26
- package/src/shims/vfs-adapter.ts +5 -2
- package/src/shims/ws.ts +92 -2
- package/src/utils/binary-encoding.ts +43 -0
- package/src/virtual-fs.ts +7 -15
- package/dist/assets/runtime-worker-B8_LZkBX.js.map +0 -1
|
@@ -516,8 +516,11 @@ async function deployToConvex(adminKey: string): Promise<void> {
|
|
|
516
516
|
vfs.writeFileSync('/package.json', packageJson);
|
|
517
517
|
|
|
518
518
|
// Create convex.json in /project
|
|
519
|
+
// codegen.fileType: "ts" ensures codegen creates .ts files that esbuild can resolve
|
|
520
|
+
// (default is "js/dts" which creates .js + .d.ts, but our source imports expect .ts)
|
|
519
521
|
vfs.writeFileSync('/project/convex.json', JSON.stringify({
|
|
520
|
-
functions: "convex/"
|
|
522
|
+
functions: "convex/",
|
|
523
|
+
codegen: { fileType: "ts" }
|
|
521
524
|
}, null, 2));
|
|
522
525
|
|
|
523
526
|
// Clean up /project/convex/ completely to ensure fresh state
|
|
@@ -550,19 +553,12 @@ async function deployToConvex(adminKey: string): Promise<void> {
|
|
|
550
553
|
}
|
|
551
554
|
vfs.mkdirSync('/project/convex', { recursive: true });
|
|
552
555
|
|
|
553
|
-
//
|
|
554
|
-
if (vfs.existsSync('/
|
|
555
|
-
|
|
556
|
-
try {
|
|
557
|
-
const files = vfs.readdirSync('/convex/_generated');
|
|
558
|
-
for (const file of files) {
|
|
559
|
-
vfs.unlinkSync(`/convex/_generated/${file}`);
|
|
560
|
-
}
|
|
561
|
-
vfs.rmdirSync('/convex/_generated');
|
|
562
|
-
} catch (e) {
|
|
563
|
-
log(`Warning: Could not remove /convex/_generated: ${e}`, 'warn');
|
|
564
|
-
}
|
|
556
|
+
// Remove /project/.env.local before CLI runs so we can detect when the new deployment creates it.
|
|
557
|
+
if (vfs.existsSync('/project/.env.local')) {
|
|
558
|
+
vfs.unlinkSync('/project/.env.local');
|
|
565
559
|
}
|
|
560
|
+
// NOTE: Do NOT delete /project/convex/_generated/ — esbuild needs those files to resolve
|
|
561
|
+
// imports like `from "./_generated/server"` in user code. The CLI's codegen will update them.
|
|
566
562
|
|
|
567
563
|
// Create convex config files (BOTH .ts and .js required!)
|
|
568
564
|
const convexConfig = `import { defineApp } from "convex/server";
|
|
@@ -580,14 +576,17 @@ export default app;
|
|
|
580
576
|
for (const file of convexFiles) {
|
|
581
577
|
const srcPath = `/convex/${file}`;
|
|
582
578
|
const destPath = `/project/convex/${file}`;
|
|
583
|
-
// Skip _generated directory and only copy files (not directories)
|
|
584
|
-
if (file === '_generated') continue;
|
|
585
579
|
try {
|
|
586
580
|
const stat = vfs.statSync(srcPath);
|
|
587
581
|
if (stat.isFile()) {
|
|
588
582
|
const content = vfs.readFileSync(srcPath, 'utf8');
|
|
589
583
|
vfs.writeFileSync(destPath, content);
|
|
590
|
-
log(` Copied ${file}`);
|
|
584
|
+
log(` Copied ${file} (${content.length}b)`);
|
|
585
|
+
// For todos.ts, show the mutation handler to verify modifications
|
|
586
|
+
if (file === 'todos.ts') {
|
|
587
|
+
const handlerMatch = content.match(/title:\s*args\.title[^\n]*/);
|
|
588
|
+
log(` → todos.ts mutation: ${handlerMatch ? handlerMatch[0] : 'handler not found'}`);
|
|
589
|
+
}
|
|
591
590
|
}
|
|
592
591
|
} catch (e) {
|
|
593
592
|
log(` Warning: Could not copy ${srcPath}: ${e}`, 'warn');
|
|
@@ -635,11 +634,14 @@ export default app;
|
|
|
635
634
|
];
|
|
636
635
|
for (const file of requiredFiles) {
|
|
637
636
|
if (vfs.existsSync(file)) {
|
|
638
|
-
// For convex source files, show content preview to verify it's fresh
|
|
639
637
|
if (file.includes('/project/convex/') && (file.endsWith('.ts') || file.endsWith('.js'))) {
|
|
640
638
|
const content = vfs.readFileSync(file, 'utf8');
|
|
641
|
-
|
|
642
|
-
|
|
639
|
+
log(` ✓ ${file} (${content.length}b)`, 'success');
|
|
640
|
+
// For todos.ts, verify the mutation content the CLI will push
|
|
641
|
+
if (file.endsWith('todos.ts')) {
|
|
642
|
+
const titleLine = content.match(/title:\s*args\.title[^\n]*/);
|
|
643
|
+
log(` → CLI will push: ${titleLine ? titleLine[0].trim() : 'title line not found'}`, 'info');
|
|
644
|
+
}
|
|
643
645
|
} else {
|
|
644
646
|
log(` ✓ ${file}`, 'success');
|
|
645
647
|
}
|
|
@@ -648,7 +650,159 @@ export default app;
|
|
|
648
650
|
}
|
|
649
651
|
}
|
|
650
652
|
|
|
651
|
-
//
|
|
653
|
+
// Patch CLI bundle: stub fetchDeploymentCanonicalSiteUrl
|
|
654
|
+
// This function was added in convex v1.31.7 and calls envGetInDeployment() to fetch CONVEX_SITE_URL
|
|
655
|
+
// from the deployment. The envGetInDeployment call hangs in our browser runtime because it uses
|
|
656
|
+
// a deployment API endpoint we can't handle. We derive the site URL from the deployment URL instead.
|
|
657
|
+
{
|
|
658
|
+
const cliBundlePath = '/project/node_modules/convex/dist/cli.bundle.cjs';
|
|
659
|
+
let cliSrc = vfs.readFileSync(cliBundlePath, 'utf8');
|
|
660
|
+
|
|
661
|
+
const fetchCanonSearch = [
|
|
662
|
+
'async function fetchDeploymentCanonicalSiteUrl(ctx, options) {',
|
|
663
|
+
' const result = await envGetInDeployment(ctx, options, "CONVEX_SITE_URL");',
|
|
664
|
+
' if (typeof result !== "string") {',
|
|
665
|
+
' return await ctx.crash({',
|
|
666
|
+
' exitCode: 1,',
|
|
667
|
+
' errorType: "invalid filesystem or env vars",',
|
|
668
|
+
' printedMessage: "Invalid process.env.CONVEX_SITE_URL"',
|
|
669
|
+
' });',
|
|
670
|
+
' }',
|
|
671
|
+
' return result;',
|
|
672
|
+
'}',
|
|
673
|
+
].join('\n');
|
|
674
|
+
const fetchCanonReplace = [
|
|
675
|
+
'async function fetchDeploymentCanonicalSiteUrl(ctx, options) {',
|
|
676
|
+
' // Stubbed: derive site URL from deployment URL (.convex.cloud -> .convex.site)',
|
|
677
|
+
' var siteUrl = (options?.deploymentUrl || "").replace(".convex.cloud", ".convex.site");',
|
|
678
|
+
' return siteUrl || "https://placeholder.convex.site";',
|
|
679
|
+
'}',
|
|
680
|
+
].join('\n');
|
|
681
|
+
|
|
682
|
+
let patched = cliSrc.replace(fetchCanonSearch, fetchCanonReplace);
|
|
683
|
+
const patch1Applied = patched !== cliSrc;
|
|
684
|
+
log(` Patch 1 (fetchDeploymentCanonicalSiteUrl): ${patch1Applied ? 'APPLIED' : 'already patched or not found'}`);
|
|
685
|
+
|
|
686
|
+
// Patch 2: Stub Sentry5.close() in flushAndExit so process.exit() actually fires.
|
|
687
|
+
// Without this patch, Sentry5.close() hangs forever and process.exit() is never called,
|
|
688
|
+
// making it impossible to detect when the CLI has finished pushing functions.
|
|
689
|
+
const flushAndExitSearch = [
|
|
690
|
+
'async function flushAndExit(exitCode, err) {',
|
|
691
|
+
' if (err) {',
|
|
692
|
+
' Sentry5.captureException(err);',
|
|
693
|
+
' }',
|
|
694
|
+
' await Sentry5.close();',
|
|
695
|
+
' return process.exit(exitCode);',
|
|
696
|
+
'}',
|
|
697
|
+
].join('\n');
|
|
698
|
+
const flushAndExitReplace = [
|
|
699
|
+
'async function flushAndExit(exitCode, err) {',
|
|
700
|
+
' // Patched: skip Sentry5.close() which hangs in browser runtime',
|
|
701
|
+
' var callerStack = new Error("flushAndExit-trace").stack || "";',
|
|
702
|
+
' globalThis.__cliExitInfo = { code: exitCode, msg: err ? (err.message || String(err)) : null, stack: err ? (err.stack || "").substring(0, 2000) : null, callerStack: callerStack.substring(0, 3000) };',
|
|
703
|
+
' return process.exit(exitCode);',
|
|
704
|
+
'}',
|
|
705
|
+
].join('\n');
|
|
706
|
+
|
|
707
|
+
const beforePatch2 = patched;
|
|
708
|
+
patched = patched.replace(flushAndExitSearch, flushAndExitReplace);
|
|
709
|
+
let patch2Applied = patched !== beforePatch2;
|
|
710
|
+
|
|
711
|
+
// Also handle re-patching: if already patched with an older version
|
|
712
|
+
if (!patch2Applied) {
|
|
713
|
+
const oldPatchMarker = '// Patched: skip Sentry5.close()';
|
|
714
|
+
const markerIdx = patched.indexOf(oldPatchMarker);
|
|
715
|
+
if (markerIdx > -1) {
|
|
716
|
+
// Find the function boundaries around our marker
|
|
717
|
+
const funcStart = patched.lastIndexOf('async function flushAndExit(exitCode, err) {', markerIdx);
|
|
718
|
+
const funcEnd = patched.indexOf('\n}', markerIdx);
|
|
719
|
+
if (funcStart > -1 && funcEnd > -1) {
|
|
720
|
+
const oldFunc = patched.substring(funcStart, funcEnd + 2);
|
|
721
|
+
if (oldFunc !== flushAndExitReplace) {
|
|
722
|
+
patched = patched.replace(oldFunc, flushAndExitReplace);
|
|
723
|
+
patch2Applied = patched !== beforePatch2;
|
|
724
|
+
if (patch2Applied) log(' Patch 2: re-patched (upgraded old patch)');
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
log(` Patch 2 (flushAndExit/Sentry5): ${patch2Applied ? 'APPLIED' : 'already up-to-date'}`);
|
|
731
|
+
|
|
732
|
+
// Patch 3: Capture Crash error details in watchAndPush catch block before flushAndExit
|
|
733
|
+
// The Crash object is thrown by runPush() and caught in watchAndPush. It has errorType,
|
|
734
|
+
// printedMessage, message, and stack. We save these to __cliCrashInfo before exiting.
|
|
735
|
+
const watchPushSearch = ' if (cmdOptions.once) {\n await outerCtx.flushAndExit(1, e.errorType);\n }';
|
|
736
|
+
const watchPushReplace = ' if (cmdOptions.once) {\n globalThis.__cliCrashInfo = { errorType: e.errorType, printedMessage: e.printedMessage || null, message: e.message || null, stack: (e.stack || "").substring(0, 2000) };\n await outerCtx.flushAndExit(1, e.errorType);\n }';
|
|
737
|
+
const beforePatch3 = patched;
|
|
738
|
+
patched = patched.replace(watchPushSearch, watchPushReplace);
|
|
739
|
+
const patch3Applied = patched !== beforePatch3;
|
|
740
|
+
log(` Patch 3 (watchAndPush crash details): ${patch3Applied ? 'APPLIED' : 'already patched or not found'}`);
|
|
741
|
+
|
|
742
|
+
// Patch 4: Skip post-esbuild file size mismatch check in doEsbuild
|
|
743
|
+
// The CLI checks that each bundled file's size hasn't changed after esbuild runs.
|
|
744
|
+
// In our browser VFS, stat().size may differ from esbuild's metafile byte count
|
|
745
|
+
// (e.g., UTF-8 encoding differences), causing a false "transient" crash.
|
|
746
|
+
// We skip the size comparison since VFS files can't change from external sources.
|
|
747
|
+
const sizeCheckSearch = ' if (st.size !== input.bytes) {\n logWarning(\n `Bundled file ${absPath} changed right after esbuild invocation`\n );\n return await ctx.crash({\n exitCode: 1,\n errorType: "transient",\n printedMessage: null\n });\n }';
|
|
748
|
+
const sizeCheckReplace = ' // Patched: skip file size check (VFS stat size may differ from esbuild byte count)';
|
|
749
|
+
const beforePatch4 = patched;
|
|
750
|
+
patched = patched.replace(sizeCheckSearch, sizeCheckReplace);
|
|
751
|
+
const patch4Applied = patched !== beforePatch4;
|
|
752
|
+
log(` Patch 4 (skip file size check): ${patch4Applied ? 'APPLIED' : 'already patched or not found'}`);
|
|
753
|
+
|
|
754
|
+
|
|
755
|
+
if (patched !== cliSrc) {
|
|
756
|
+
vfs.writeFileSync(cliBundlePath, patched);
|
|
757
|
+
log('CLI bundle patched and saved');
|
|
758
|
+
} else {
|
|
759
|
+
log('CLI bundle already patched (no changes needed)');
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
// Set up process.exit listener BEFORE running CLI.
|
|
764
|
+
// The CLI calls flushAndExit → process.exit(0) when deployment completes.
|
|
765
|
+
// This is the most reliable completion signal since it fires AFTER the push POST finishes.
|
|
766
|
+
const cliExitPromise = new Promise<number>((resolve) => {
|
|
767
|
+
const proc = cliRuntime!.getProcess();
|
|
768
|
+
proc.on('exit', (code: unknown) => {
|
|
769
|
+
log(`CLI process exited with code ${code}`);
|
|
770
|
+
const crashInfo = (globalThis as any).__cliCrashInfo;
|
|
771
|
+
if (crashInfo) {
|
|
772
|
+
log(`CLI CRASH: [${crashInfo.errorType}] msg=${crashInfo.printedMessage || crashInfo.message || '(no message)'}`, 'error');
|
|
773
|
+
if (crashInfo.stack) log(`CLI CRASH STACK: ${crashInfo.stack.substring(0, 500)}`, 'error');
|
|
774
|
+
}
|
|
775
|
+
const exitInfo = (globalThis as any).__cliExitInfo;
|
|
776
|
+
if (exitInfo) {
|
|
777
|
+
log(`CLI exit: code=${exitInfo.code} err=${exitInfo.msg || 'none'}`, exitInfo.code === 0 ? 'success' : 'error');
|
|
778
|
+
}
|
|
779
|
+
resolve(code as number);
|
|
780
|
+
});
|
|
781
|
+
});
|
|
782
|
+
|
|
783
|
+
// Intercept fetch to log Convex API push calls
|
|
784
|
+
const origFetch = globalThis.fetch;
|
|
785
|
+
globalThis.fetch = async function(input: RequestInfo | URL, init?: RequestInit) {
|
|
786
|
+
const url = typeof input === 'string' ? input : input instanceof URL ? input.toString() : input.url;
|
|
787
|
+
if (url.includes('convex.cloud') || url.includes('convex.site')) {
|
|
788
|
+
const method = init?.method || 'GET';
|
|
789
|
+
log(`[fetch] ${method} ${url.substring(0, 120)}`);
|
|
790
|
+
try {
|
|
791
|
+
const resp = await origFetch.call(globalThis, input, init);
|
|
792
|
+
log(`[fetch] ${method} ${url.substring(0, 80)} → ${resp.status} ${resp.statusText}`);
|
|
793
|
+
if (!resp.ok) {
|
|
794
|
+
const text = await resp.clone().text();
|
|
795
|
+
log(`[fetch] ERROR body: ${text.substring(0, 500)}`, 'error');
|
|
796
|
+
}
|
|
797
|
+
return resp;
|
|
798
|
+
} catch (e) {
|
|
799
|
+
log(`[fetch] ${method} ${url.substring(0, 80)} → NETWORK ERROR: ${(e as Error).message}`, 'error');
|
|
800
|
+
throw e;
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
return origFetch.call(globalThis, input, init);
|
|
804
|
+
} as typeof fetch;
|
|
805
|
+
|
|
652
806
|
const cliCode = `
|
|
653
807
|
// Set environment for Convex CLI
|
|
654
808
|
process.env.CONVEX_DEPLOY_KEY = '${adminKey}';
|
|
@@ -656,35 +810,51 @@ export default app;
|
|
|
656
810
|
// Set CLI arguments
|
|
657
811
|
process.argv = ['node', 'convex', 'dev', '--once'];
|
|
658
812
|
|
|
659
|
-
//
|
|
813
|
+
// Load and execute the CLI bundle
|
|
660
814
|
require('./node_modules/convex/dist/cli.bundle.cjs');
|
|
661
815
|
`;
|
|
662
816
|
|
|
817
|
+
// Capture unhandled rejections from CLI async code (void main())
|
|
818
|
+
const rejectionHandler = (event: PromiseRejectionEvent) => {
|
|
819
|
+
const err = event.reason;
|
|
820
|
+
const msg = err?.message || String(err);
|
|
821
|
+
if (!msg.includes('Process exited with code')) {
|
|
822
|
+
log(`[CLI ASYNC ERROR] ${msg}`, 'error');
|
|
823
|
+
if (err?.stack) log(`[CLI ASYNC STACK] ${err.stack.substring(0, 500)}`, 'error');
|
|
824
|
+
}
|
|
825
|
+
};
|
|
826
|
+
globalThis.addEventListener('unhandledrejection', rejectionHandler);
|
|
827
|
+
|
|
663
828
|
try {
|
|
664
829
|
cliRuntime.execute(cliCode, '/project/cli-runner.js');
|
|
830
|
+
log('CLI synchronous execution completed (async work continues in background)');
|
|
665
831
|
} catch (cliError) {
|
|
666
832
|
// Some errors are expected (like process.exit or stack overflow in watcher)
|
|
667
833
|
// The important work (deployment) happens before these errors
|
|
668
834
|
log(`CLI completed with: ${(cliError as Error).message}`, 'warn');
|
|
669
835
|
}
|
|
670
836
|
|
|
671
|
-
// Wait for
|
|
672
|
-
// Poll for .env.local creation instead of fixed timeout
|
|
837
|
+
// Wait for CLI to finish: either process.exit fires (reliable) or fall back to polling
|
|
673
838
|
logStatus('WAITING', 'Waiting for deployment to complete...');
|
|
674
|
-
const deploymentSucceeded = await waitForDeployment(vfs, 30000, 500);
|
|
675
839
|
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
840
|
+
const timeoutPromise = new Promise<'timeout'>((resolve) =>
|
|
841
|
+
setTimeout(() => resolve('timeout'), 90000)
|
|
842
|
+
);
|
|
843
|
+
|
|
844
|
+
const result = await Promise.race([cliExitPromise, timeoutPromise]);
|
|
845
|
+
|
|
846
|
+
// Restore original fetch and remove rejection handler
|
|
847
|
+
globalThis.fetch = origFetch;
|
|
848
|
+
globalThis.removeEventListener('unhandledrejection', rejectionHandler);
|
|
849
|
+
|
|
850
|
+
if (result === 'timeout') {
|
|
851
|
+
log('CLI did not exit within 90s, falling back to file polling...', 'warn');
|
|
852
|
+
const envCreated = await waitForDeployment(vfs, 15000, 500);
|
|
853
|
+
if (envCreated) {
|
|
854
|
+
await waitForGenerated(vfs, 15000, 500);
|
|
687
855
|
}
|
|
856
|
+
} else {
|
|
857
|
+
log(`CLI exited with code ${result}, deployment complete`);
|
|
688
858
|
}
|
|
689
859
|
|
|
690
860
|
// Check if deployment succeeded by reading .env.local (CLI creates it in /project)
|
|
@@ -732,6 +902,29 @@ export default app;
|
|
|
732
902
|
log(' WARNING: _generated directory not created - functions may not be deployed!', 'error');
|
|
733
903
|
}
|
|
734
904
|
|
|
905
|
+
// Ensure /convex/_generated/api.ts always exists (fallback if CLI didn't generate it)
|
|
906
|
+
if (!vfs.existsSync('/convex/_generated/api.ts')) {
|
|
907
|
+
log(' Restoring fallback api.ts (CLI did not generate one)');
|
|
908
|
+
vfs.mkdirSync('/convex/_generated', { recursive: true });
|
|
909
|
+
vfs.writeFileSync('/convex/_generated/api.ts', `// Convex API - fallback for browser demo
|
|
910
|
+
export const api = {
|
|
911
|
+
todos: {
|
|
912
|
+
list: "todos:list",
|
|
913
|
+
create: "todos:create",
|
|
914
|
+
toggle: "todos:toggle",
|
|
915
|
+
remove: "todos:remove",
|
|
916
|
+
},
|
|
917
|
+
} as const;
|
|
918
|
+
`);
|
|
919
|
+
}
|
|
920
|
+
if (!vfs.existsSync('/convex/_generated/server.ts')) {
|
|
921
|
+
vfs.mkdirSync('/convex/_generated', { recursive: true });
|
|
922
|
+
vfs.writeFileSync('/convex/_generated/server.ts', `// Server stubs for browser demo
|
|
923
|
+
export function query(config) { return config; }
|
|
924
|
+
export function mutation(config) { return config; }
|
|
925
|
+
`);
|
|
926
|
+
}
|
|
927
|
+
|
|
735
928
|
// Parse the Convex URL from .env.local
|
|
736
929
|
const match = envContent.match(/CONVEX_URL=(.+)/);
|
|
737
930
|
if (match) {
|
|
@@ -756,6 +949,7 @@ export default app;
|
|
|
756
949
|
convexUrl = parsed.url;
|
|
757
950
|
log(`Using fallback URL: ${convexUrl}`, 'warn');
|
|
758
951
|
}
|
|
952
|
+
logStatus('COMPLETE', `Connected to ${convexUrl} (fallback)`);
|
|
759
953
|
}
|
|
760
954
|
|
|
761
955
|
// Set the env var on the dev server (idiomatic Next.js pattern)
|
|
@@ -907,6 +1101,8 @@ async function main() {
|
|
|
907
1101
|
log('Creating Convex App project structure...');
|
|
908
1102
|
createConvexAppProject(vfs);
|
|
909
1103
|
log('Project files created', 'success');
|
|
1104
|
+
log(' /convex/schema.ts');
|
|
1105
|
+
log(' /convex/todos.ts');
|
|
910
1106
|
|
|
911
1107
|
// Expose VFS to window and build file tree
|
|
912
1108
|
exposeVfsToWindow();
|