Skip to content

Commit 782cc9c

Browse files
committed
feat(create-deepcrawl): enhance environment file handling and add API_URL support in configuration
1 parent 2308713 commit 782cc9c

7 files changed

Lines changed: 98 additions & 2 deletions

File tree

apps/app/components/www/create-deepcrawl-terminal.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ const TERMINAL_LINES: TerminalLine[] = [
5050
delay: 120,
5151
tone: 'default',
5252
},
53+
// "Faircrawl" is intentional here. This demo deliberately leans into a
54+
// Firecrawl-adjacent naming joke for the terminal sequence.
5355
{ text: '│ Faircrawl', delay: 140, tone: 'accent' },
5456
{ text: '', delay: 90 },
5557
{ text: '◇ Choose what to deploy', delay: 120, tone: 'default' },
@@ -60,6 +62,7 @@ const TERMINAL_LINES: TerminalLine[] = [
6062
{ text: '', delay: 90 },
6163
{ text: 'wait... creating your project', delay: 180, tone: 'muted' },
6264
{
65+
// Keep the product name aligned with the intentional "Faircrawl" demo copy above.
6366
text: '🎉 Your Faircrawl is ready. Do you want to test your API now?',
6467
delay: 120,
6568
tone: 'accent',

apps/app/components/www/installer.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ export const CommandInstaller = ({
7070
{code}
7171
</pre>
7272
<Button
73+
aria-label={
74+
isCopied ? 'Installer command copied' : 'Copy installer command'
75+
}
7376
className="absolute right-1 rounded-sm bg-transparent! text-muted-foreground group-hover:text-foreground"
7477
onClick={(event) => {
7578
event.stopPropagation();
@@ -89,6 +92,9 @@ export const CommandInstaller = ({
8992
{code}
9093
</pre>
9194
<Button
95+
aria-label={
96+
isCopied ? 'Installer command copied' : 'Copy installer command'
97+
}
9298
className="justify-self-center rounded-sm bg-transparent! text-muted-foreground group-hover:text-foreground"
9399
onClick={(event) => {
94100
event.stopPropagation();

packages/create-deepcrawl/src/__tests__/env-file.test.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,15 @@ test('upsertEnvFile updates JWT_SECRET and keeps other lines', () => {
3636
assert.match(output, /JWT_SECRET=second-secret/);
3737
assert.equal(output.match(/JWT_SECRET=/g)?.length, 1);
3838
});
39+
40+
test('upsertEnvFile quotes values that would break dotenv parsing', () => {
41+
const root = mkdtempSync(join(tmpdir(), 'create-deepcrawl-env-'));
42+
const filePath = join(root, '.dev.vars');
43+
44+
upsertEnvFile(filePath, {
45+
JWT_SECRET: 'abc#def with spaces',
46+
});
47+
48+
const output = readFileSync(filePath, 'utf8');
49+
assert.match(output, /JWT_SECRET="abc#def with spaces"/);
50+
});

packages/create-deepcrawl/src/__tests__/patch-v0-wrangler.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ test('patchV0WranglerConfigForDeployment rebuilds production vars from allowlist
6262
assert.deepEqual(data.vars, {
6363
AUTH_MODE: 'jwt',
6464
ENABLE_ACTIVITY_LOGS: true,
65+
API_URL: 'http://localhost:8080',
6566
WORKER_NODE_ENV: 'development',
6667
JWT_ISSUER: 'issuer-1',
6768
JWT_AUDIENCE: 'audience-1',
@@ -104,6 +105,7 @@ test('patchV0WranglerConfigForDeployment keeps optional jwt vars discoverable wh
104105
assert.deepEqual(data.vars, {
105106
AUTH_MODE: 'none',
106107
ENABLE_ACTIVITY_LOGS: false,
108+
API_URL: 'http://localhost:8080',
107109
WORKER_NODE_ENV: 'development',
108110
JWT_ISSUER: '',
109111
JWT_AUDIENCE: '',

packages/create-deepcrawl/src/__tests__/prepare-v0-local-project.test.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ test('prepareV0LocalProject aligns local wrangler config and JWT secret files',
6363
assert.deepEqual(data.vars, {
6464
AUTH_MODE: 'jwt',
6565
ENABLE_ACTIVITY_LOGS: false,
66+
API_URL: 'http://localhost:8080',
6667
WORKER_NODE_ENV: 'development',
6768
JWT_ISSUER: 'issuer-1',
6869
JWT_AUDIENCE: 'audience-1',
@@ -87,3 +88,60 @@ test('prepareV0LocalProject aligns local wrangler config and JWT secret files',
8788
/JWT_SECRET=generated-secret/,
8889
);
8990
});
91+
92+
test('prepareV0LocalProject keeps no-auth local config aligned without secret files', async () => {
93+
const projectDir = await createProjectFixture();
94+
95+
const result = await prepareV0LocalProject({
96+
projectDir,
97+
projectName: 'My Deepcrawl',
98+
authMode: 'none',
99+
enableActivityLogs: true,
100+
});
101+
102+
const next = await readFile(
103+
join(projectDir, 'apps', 'workers', 'v0', 'wrangler.jsonc'),
104+
'utf8',
105+
);
106+
const data = parse(next) as {
107+
vars?: Record<string, string | boolean>;
108+
env?: {
109+
production?: {
110+
vars?: Record<string, string | boolean>;
111+
};
112+
};
113+
};
114+
115+
assert.equal(result.localJwtSecretFiles, undefined);
116+
assert.deepEqual(data.vars, {
117+
AUTH_MODE: 'none',
118+
ENABLE_ACTIVITY_LOGS: true,
119+
API_URL: 'http://localhost:8080',
120+
WORKER_NODE_ENV: 'development',
121+
JWT_ISSUER: '',
122+
JWT_AUDIENCE: '',
123+
ENABLE_API_RATE_LIMIT: false,
124+
});
125+
assert.deepEqual(data.env?.production?.vars, {
126+
AUTH_MODE: 'none',
127+
ENABLE_ACTIVITY_LOGS: true,
128+
WORKER_NODE_ENV: 'production',
129+
JWT_ISSUER: '',
130+
JWT_AUDIENCE: '',
131+
ENABLE_API_RATE_LIMIT: false,
132+
});
133+
});
134+
135+
test('prepareV0LocalProject rejects jwt mode without a secret', async () => {
136+
const projectDir = await createProjectFixture();
137+
138+
await assert.rejects(
139+
prepareV0LocalProject({
140+
projectDir,
141+
projectName: 'My Deepcrawl',
142+
authMode: 'jwt',
143+
enableActivityLogs: false,
144+
}),
145+
/JWT secret is missing/,
146+
);
147+
});

packages/create-deepcrawl/src/lib/env-file.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ import { dirname } from 'node:path';
33

44
type EnvUpdates = Record<string, string>;
55

6+
function formatEnvValue(value: string): string {
7+
return /^[A-Za-z0-9_./:-]+$/.test(value) ? value : JSON.stringify(value);
8+
}
9+
610
function getDefaultHeader(filePath: string): string[] {
711
if (filePath.endsWith('.dev.vars.production')) {
812
return [
@@ -48,7 +52,7 @@ export function upsertEnvFile(filePath: string, updates: EnvUpdates): void {
4852
}
4953

5054
seen.add(key);
51-
return `${key}=${updates[key]}`;
55+
return `${key}=${formatEnvValue(updates[key])}`;
5256
});
5357

5458
for (const [key, value] of Object.entries(updates)) {
@@ -63,7 +67,7 @@ export function upsertEnvFile(filePath: string, updates: EnvUpdates): void {
6367
updatedLines.push('# JWT');
6468
}
6569

66-
updatedLines.push(`${key}=${value}`);
70+
updatedLines.push(`${key}=${formatEnvValue(value)}`);
6771
}
6872

6973
const output = updatedLines

packages/create-deepcrawl/src/steps/patch-v0-wrangler.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ type ParsedWranglerConfig = {
1010
d1_databases?: Array<{
1111
migrations_dir?: string;
1212
}>;
13+
vars?: Record<string, string | boolean>;
1314
};
1415

1516
function normalizeWorkerName(projectName: string): string {
@@ -30,6 +31,12 @@ function getExistingMigrationsDir(source: string): string | null {
3031
return typeof migrations === 'string' ? migrations : null;
3132
}
3233

34+
function getExistingLocalApiUrl(source: string): string | null {
35+
const data = parse(source) as ParsedWranglerConfig;
36+
const apiUrl = data?.vars?.API_URL;
37+
return typeof apiUrl === 'string' ? apiUrl : null;
38+
}
39+
3340
function buildSharedWorkerVars(input: {
3441
authMode: AuthMode;
3542
enableActivityLogs: boolean;
@@ -114,6 +121,10 @@ export async function patchV0WranglerConfigForDeployment({
114121
jwtIssuer,
115122
jwtAudience,
116123
});
124+
const localApiUrl = getExistingLocalApiUrl(source);
125+
if (localApiUrl) {
126+
rootVars.API_URL = localApiUrl;
127+
}
117128
const deploymentVars = buildProductionVars({
118129
authMode,
119130
enableActivityLogs,

0 commit comments

Comments
 (0)