Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
ad1266b
feat(integration): switch long-running test apps from dev to build+serve
jacekradko Feb 6, 2026
f15afe6
fix(e2e): fix template build scripts for build+serve mode
jacekradko Feb 6, 2026
00f15ca
fix(e2e): add node adapter to astro-hybrid template for build mode
jacekradko Feb 6, 2026
8ee0693
fix(e2e): assign serve result to outer-scoped variables in longRunnin…
jacekradko Feb 6, 2026
689f475
fix(e2e): use @astrojs/node@8 for astro-hybrid template (astro 4 compat)
jacekradko Feb 6, 2026
f18383e
fix(e2e): add debug logging to long-running app init, remove tsc from…
jacekradko Feb 7, 2026
0378aff
fix(e2e): add build timeout and cap serve polling attempts
jacekradko Feb 7, 2026
ba2ebe6
fix(e2e): add serve log file dump on polling failure for debugging
jacekradko Feb 7, 2026
d8d3001
fix(e2e): add lsof/ps debugging when serve polling fails
jacekradko Feb 7, 2026
7917eea
fix(e2e): check serve process liveness and port binding after 3s
jacekradko Feb 7, 2026
be51c2e
fix(e2e): better serve debug: lsof port check, pstree, 10s wait
jacekradko Feb 7, 2026
d963ae6
Merge remote-tracking branch 'origin/main' into jr/integration-build-…
jacekradko Feb 7, 2026
0c9111d
style: format application.ts
jacekradko Feb 7, 2026
33c0699
refactor(e2e): remove debug instrumentation from serve()
jacekradko Feb 7, 2026
a963ee2
fix(e2e): fix serverUrl port detection using URL constructor
jacekradko Feb 7, 2026
1eaf924
fix(e2e): fix production serve commands for tanstack-start and react-…
jacekradko Feb 8, 2026
5516aa4
fix(e2e): correct tanstack-start production entry to dist/server/serv…
jacekradko Feb 8, 2026
74ba697
fix(e2e): use srvx to serve tanstack-start production build
jacekradko Feb 8, 2026
0f3475d
style: format tanstack-start package.json
jacekradko Feb 8, 2026
7cc55ae
fix(integration): fix srvx static path resolution for tanstack-start
jacekradko Feb 8, 2026
978e231
fix(integration): pass .env vars to serve process
jacekradko Feb 8, 2026
dcec915
Merge branch 'main' into jr/integration-build-serve
jacekradko Feb 9, 2026
b9571bf
Merge branch 'main' into jr/integration-build-serve
jacekradko Feb 9, 2026
11395e2
Merge branch 'main' into jr/integration-build-serve
jacekradko Feb 10, 2026
2fb8426
Merge branch 'main' into jr/integration-build-serve
jacekradko Feb 10, 2026
0407556
Merge branch 'main' into jr/integration-build-serve
jacekradko Feb 11, 2026
2be0f2d
Merge branch 'main' into jr/integration-build-serve
jacekradko Feb 18, 2026
45f91c9
Merge branch 'main' into jr/integration-build-serve
jacekradko Feb 20, 2026
81168d2
Merge branch 'main' into jr/integration-build-serve
jacekradko Feb 20, 2026
1fa37c7
Merge branch 'main' into jr/integration-build-serve
jacekradko Feb 23, 2026
6fc7426
Merge branch 'main' into jr/integration-build-serve
jacekradko Feb 24, 2026
2dceb1e
Merge branch 'main' into jr/integration-build-serve
jacekradko Feb 27, 2026
c56dca4
Merge branch 'main' into jr/integration-build-serve
jacekradko Mar 2, 2026
b462f45
Merge branch 'main' into jr/integration-build-serve
jacekradko Mar 2, 2026
7a448c1
Merge branch 'main' into jr/integration-build-serve
jacekradko Mar 4, 2026
a5fbb12
Merge branch 'main' into jr/integration-build-serve
jacekradko Mar 5, 2026
0cc5ee2
Merge branch 'main' into jr/integration-build-serve
jacekradko Mar 5, 2026
857cc83
Merge branch 'main' into jr/integration-build-serve
jacekradko Mar 6, 2026
ba96727
Merge branch 'main' into jr/integration-build-serve
jacekradko Mar 9, 2026
cc6871e
Merge branch 'main' into jr/integration-build-serve
jacekradko Mar 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 70 additions & 12 deletions integration/models/application.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { execSync } from 'node:child_process';
Comment thread
jacekradko marked this conversation as resolved.
Outdated
import * as path from 'node:path';

import { parsePublishableKey } from '@clerk/shared/keys';
Expand Down Expand Up @@ -144,25 +145,82 @@ export const application = (
get serveOutput() {
return serveOutput;
},
serve: async (opts: { port?: number; manualStart?: boolean } = {}) => {
serve: async (opts: { port?: number; manualStart?: boolean; detached?: boolean; serverUrl?: string } = {}) => {
const log = logger.child({ prefix: 'serve' }).info;
const port = opts.port || (await getPort());
// TODO: get serverUrl as in dev()
const serverUrl = `http://localhost:${port}`;
// If this is ever used as a background process, we need to make sure
// it's not using the log function. See the dev() method above
const getServerUrl = () => {
if (opts.serverUrl) {
return opts.serverUrl.includes(':') ? opts.serverUrl : `${opts.serverUrl}:${port}`;
}
return serverUrl || `http://localhost:${port}`;
};
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
const runtimeServerUrl = getServerUrl();
log(`Will try to serve app at ${runtimeServerUrl}`);

if (opts.manualStart) {
state.serverUrl = runtimeServerUrl;
return { port, serverUrl: runtimeServerUrl };
}

log(`Running serve command: "${scripts.serve}" with PORT=${port}, detached=${opts.detached}`);
const proc = run(scripts.serve, {
cwd: appDirPath,
env: { PORT: port.toString() },
log: (msg: string) => {
serveOutput += `\n${msg}`;
log(msg);
},
detached: opts.detached,
stdout: opts.detached ? fs.openSync(stdoutFilePath, 'a') : undefined,
stderr: opts.detached ? fs.openSync(stderrFilePath, 'a') : undefined,
log: opts.detached
? undefined
: (msg: string) => {
serveOutput += `\n${msg}`;
log(msg);
},
});
log(`Serve process spawned: pid=${proc.pid}`);

if (opts.detached) {
// Give the process a moment to start, then check its state
await new Promise(res => setTimeout(res, 3000));
try {
const procExists = execSync(`kill -0 ${proc.pid} 2>&1 && echo "alive" || echo "dead"`, {
encoding: 'utf-8',
}).trim();
log(`Serve process ${proc.pid} status after 3s: ${procExists}`);
const ssOut = execSync(`ss -tlnp 2>&1 | head -20 || true`, { encoding: 'utf-8' });
log(`Listening ports:\n${ssOut}`);
} catch {
log('Could not check serve process status');
}

const shouldExit = () => {
if (proc.exitCode != null) {
log(`Serve process has exitCode=${proc.exitCode}`);
return true;
}
return false;
};
try {
await waitForServer(runtimeServerUrl, { log, maxAttempts: 120, shouldExit });
} catch (e) {
try {
const stdoutContent = await fs.readFile(stdoutFilePath, 'utf-8');
const stderrContent = await fs.readFile(stderrFilePath, 'utf-8');
log(`Serve stdout:\n${stdoutContent || '(empty)'}`);
log(`Serve stderr:\n${stderrContent || '(empty)'}`);
} catch {
log('Could not read serve log files');
}
log(`Serve process exitCode=${proc.exitCode}, killed=${proc.killed}, pid=${proc.pid}`);
throw e;
}
} else {
await waitForIdleProcess(proc);
}

log(`Server started at ${runtimeServerUrl}, pid: ${proc.pid}`);
cleanupFns.push(() => awaitableTreekill(proc.pid, 'SIGKILL'));
await waitForIdleProcess(proc);
state.serverUrl = serverUrl;
return { port, serverUrl, pid: proc };
state.serverUrl = runtimeServerUrl;
return { port, serverUrl: runtimeServerUrl, pid: proc.pid };
},
stop: async () => {
logger.info('Stopping...');
Expand Down
37 changes: 30 additions & 7 deletions integration/models/longRunningApplication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,15 +59,18 @@ export const longRunningApplication = (params: LongRunningApplicationParams) =>
// will be called by global.setup.ts and by the test runner
// the first time this is called, the app starts and the state is persisted in the state file
init: async () => {
const log = (msg: string) => console.log(`[${name}] ${msg}`);
log('Starting init...');
try {
const publishableKey = params.env.publicVariables.get('CLERK_PUBLISHABLE_KEY');
const secretKey = params.env.privateVariables.get('CLERK_SECRET_KEY');
const apiUrl = params.env.privateVariables.get('CLERK_API_URL');
const { instanceType, frontendApi: frontendApiUrl } = parsePublishableKey(publishableKey);

if (instanceType !== 'development') {
console.log('Clerk: skipping setup of testing tokens for non-development instance');
log('Skipping setup of testing tokens for non-development instance');
} else {
log('Setting up testing tokens...');
await clerkSetup({
publishableKey,
frontendApiUrl,
Expand All @@ -76,13 +79,16 @@ export const longRunningApplication = (params: LongRunningApplicationParams) =>
apiUrl,
dotenv: false,
});
log('Testing tokens setup complete');
}
} catch (error) {
console.error('Error setting up testing tokens:', error);
throw error;
}
try {
log('Committing config...');
app = await config.commit();
log(`Config committed, appDir: ${app.appDir}`);
} catch (error) {
console.error('Error committing config:', error);
throw error;
Expand All @@ -94,16 +100,35 @@ export const longRunningApplication = (params: LongRunningApplicationParams) =>
throw error;
}
try {
log('Running setup (pnpm install)...');
await app.setup();
log('Setup complete');
} catch (error) {
console.error('Error during app setup:', error);
throw error;
}
try {
const { port, serverUrl, pid } = await app.dev({ detached: true });
stateFile.addLongRunningApp({ port, serverUrl, pid, id, appDir: app.appDir, env: params.env.toJson() });
log('Building app...');
const buildTimeout = new Promise((_, reject) =>
setTimeout(() => reject(new Error(`Build timed out after 120s for ${name}`)), 120_000),
);
await Promise.race([app.build(), buildTimeout]);
log('Build complete');
} catch (error) {
console.error('Error during app dev:', error);
console.error('Error during app build:', error);
throw error;
}
try {
log('Starting serve (detached)...');
const serveResult = await app.serve({ detached: true });
port = serveResult.port;
serverUrl = serveResult.serverUrl;
pid = serveResult.pid;
appDir = app.appDir;
log(`Serve complete: port=${port}, serverUrl=${serverUrl}, pid=${pid}`);
stateFile.addLongRunningApp({ port, serverUrl, pid, id, appDir, env: params.env.toJson() });
} catch (error) {
console.error('Error during app serve:', error);
throw error;
}
},
Expand All @@ -126,9 +151,7 @@ export const longRunningApplication = (params: LongRunningApplicationParams) =>
setup: () => Promise.resolve(),
withEnv: () => Promise.resolve(),
teardown: () => Promise.resolve(),
build: () => {
throw new Error('build for long running apps is not supported yet');
},
build: () => Promise.resolve(),
get name() {
return name;
},
Expand Down
6 changes: 5 additions & 1 deletion integration/templates/astro-hybrid/astro.config.mjs
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import { defineConfig } from 'astro/config';
import node from '@astrojs/node';
import clerk from '@clerk/astro';
import react from '@astrojs/react';

export default defineConfig({
output: 'hybrid',
adapter: node({
mode: 'standalone',
}),
integrations: [
clerk({
appearance: {
Expand All @@ -15,6 +19,6 @@ export default defineConfig({
react(),
],
server: {
port: Number(process.env.PORT),
port: process.env.PORT ? Number(process.env.PORT) : undefined,
},
});
4 changes: 2 additions & 2 deletions integration/templates/astro-hybrid/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@
"type": "module",
"scripts": {
"astro": "astro",
"build": "astro check && astro build",
"build": "astro build",
"dev": "astro dev",
"preview": "astro preview --port $PORT",
"start": "astro dev --port $PORT"
},
"dependencies": {
"@astrojs/check": "^0.9.4",
"@astrojs/node": "^9.4.2",
"@astrojs/node": "^8.0.0",
"@astrojs/react": "^3.6.2",
"@types/react": "18.3.12",
"@types/react-dom": "18.3.1",
Expand Down
2 changes: 1 addition & 1 deletion integration/templates/astro-node/astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,6 @@ export default defineConfig({
tailwind(),
],
server: {
port: Number(process.env.PORT),
port: process.env.PORT ? Number(process.env.PORT) : undefined,
},
});
2 changes: 1 addition & 1 deletion integration/templates/astro-node/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"type": "module",
"scripts": {
"astro": "astro",
"build": "astro check && astro build",
"build": "astro build",
"dev": "astro dev",
"preview": "astro preview --port $PORT",
"start": "astro dev --port $PORT"
Expand Down
2 changes: 1 addition & 1 deletion integration/templates/react-router-node/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ export default defineConfig({
}),
],
server: {
port: Number(process.env.PORT),
port: process.env.PORT ? Number(process.env.PORT) : undefined,
},
});
2 changes: 1 addition & 1 deletion integration/templates/react-vite/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"private": true,
"type": "module",
"scripts": {
"build": "tsc && vite build",
"build": "vite build",
"dev": "vite --port $PORT --no-open",
"lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview --port $PORT --no-open"
Expand Down
2 changes: 1 addition & 1 deletion integration/templates/tanstack-react-start/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"private": true,
"type": "module",
"scripts": {
"build": "vite build && tsc --noEmit",
"build": "vite build",
"dev": "vite dev --port=$PORT",
"start": "vite start --port=$PORT"
},
Expand Down
2 changes: 1 addition & 1 deletion integration/templates/vue-vite/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"private": true,
"type": "module",
"scripts": {
"build": "vue-tsc -b && vite build",
"build": "vite build",
"dev": "vite --port $PORT",
"preview": "vite preview --port $PORT"
},
Expand Down
2 changes: 1 addition & 1 deletion integration/tests/global.setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { appConfigs } from '../presets';
import { fs, parseEnvOptions, startClerkJsHttpServer, startClerkUiHttpServer } from '../scripts';

setup('start long running apps', async () => {
setup.setTimeout(90_000);
setup.setTimeout(300_000);

await fs.ensureDir(constants.TMP_DIR);

Expand Down
Loading