Skip to content

Commit 2396bb1

Browse files
committed
fixes
1 parent 3d1a0f7 commit 2396bb1

10 files changed

Lines changed: 157 additions & 29 deletions

File tree

backend/src/modules/attachment/helpers/signed-url.ts

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,22 @@ import { getSignedUrl as s3SignedUrl } from '@aws-sdk/s3-request-presigner';
33
import { appConfig } from 'shared';
44
import { env } from '#/env';
55

6-
const s3Client = new S3Client({
7-
region: appConfig.s3.region,
8-
endpoint: `https://${appConfig.s3.host}`,
9-
credentials: {
10-
accessKeyId: env.S3_ACCESS_KEY_ID,
11-
secretAccessKey: env.S3_ACCESS_KEY_SECRET,
12-
},
13-
});
6+
let s3Client: S3Client | undefined;
7+
8+
function getS3Client(): S3Client {
9+
if (s3Client) return s3Client;
10+
11+
s3Client = new S3Client({
12+
region: appConfig.s3.region,
13+
endpoint: `https://${appConfig.s3.host}`,
14+
credentials: {
15+
accessKeyId: env.S3_ACCESS_KEY_ID,
16+
secretAccessKey: env.S3_ACCESS_KEY_SECRET,
17+
},
18+
});
19+
20+
return s3Client;
21+
}
1422

1523
interface GetUrlOptions {
1624
isPublic: boolean;
@@ -42,5 +50,5 @@ export async function getSignedUrlFromKey(
4250
if (isPublic) return `https://${bucketName}.s3.nl-ams.scw.cloud/${Key}`;
4351

4452
// Private, sign URL
45-
return s3SignedUrl(s3Client, new GetObjectCommand({ Bucket: bucketName, Key }), { expiresIn });
53+
return s3SignedUrl(getS3Client(), new GetObjectCommand({ Bucket: bucketName, Key }), { expiresIn });
4654
}

cli/create-cella/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@cellajs/create-cella",
3-
"version": "0.2.1",
3+
"version": "0.2.2",
44
"private": false,
55
"license": "MIT",
66
"description": "Cella is a TypeScript template to create web apps with sync and offline capabilities.",
@@ -42,7 +42,7 @@
4242
"lint": "biome check .",
4343
"lint:fix": "biome check --write .",
4444
"test": "vitest run",
45-
"test:release": "pnpm run build && vitest run tests/release-artifact.test.ts",
45+
"test:release": "pnpm run build && vitest run tests/release-artifact.test.ts tests/release-smoke.test.ts",
4646
"test:watch": "vitest"
4747
},
4848
"dependencies": {

cli/create-cella/src/create-cella-cli.ts

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -70,17 +70,19 @@ async function main(): Promise<void> {
7070
}
7171

7272
// Scan sibling directories and prompt for port offset
73-
const portOffset = await promptPortOffset(targetFolder, promptTheme, promptContext);
73+
const portOffset = cli.options.portOffset ?? (await promptPortOffset(targetFolder, promptTheme, promptContext));
7474

7575
// Prompt for admin email
76-
const adminEmail = await input(
77-
{
78-
message: 'Admin email for initial seed user',
79-
default: `admin@${projectName}.com`,
80-
theme: promptTheme,
81-
},
82-
promptContext,
83-
);
76+
const adminEmail =
77+
cli.options.adminEmail ??
78+
(await input(
79+
{
80+
message: 'Admin email for initial seed user',
81+
default: `admin@${projectName}.com`,
82+
theme: promptTheme,
83+
},
84+
promptContext,
85+
));
8486

8587
// Proceed with the project creation
8688
const createOptions: CreateOptions = {

cli/create-cella/src/create.ts

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ function isLocalPath(path: string): boolean {
1515
return path.startsWith('/') || path.startsWith('./') || path.startsWith('../');
1616
}
1717

18+
function shouldSkipStep(name: 'install' | 'generate' | 'git' | 'remote'): boolean {
19+
return process.env[`CREATE_CELLA_SKIP_${name.toUpperCase()}`] === 'true';
20+
}
21+
1822
export async function create({
1923
projectName,
2024
targetFolder,
@@ -73,16 +77,20 @@ export async function create({
7377
await cleanTemplate({ targetFolder, projectName, displayName, portOffset, adminEmail });
7478

7579
// Install dependencies
76-
progress.step('installing dependencies');
77-
await install(packageManager);
80+
if (!shouldSkipStep('install')) {
81+
progress.step('installing dependencies');
82+
await install(packageManager);
83+
}
7884

7985
// Generate SQL files
80-
progress.step('generating migrations');
81-
await generate(packageManager);
86+
if (!shouldSkipStep('generate')) {
87+
progress.step('generating migrations');
88+
await generate(packageManager);
89+
}
8290

8391
// Initialize git repository
8492
const gitFolderPath = join(targetFolder, '.git');
85-
if (!existsSync(gitFolderPath)) {
93+
if (!shouldSkipStep('git') && !existsSync(gitFolderPath)) {
8694
progress.step('initializing git');
8795
await gitInit(targetFolder);
8896
await gitAddAll(targetFolder);
@@ -96,8 +104,10 @@ export async function create({
96104
}
97105

98106
// Add upstream remote
99-
progress.step('adding upstream remote');
100-
await addRemote({ targetFolder, silent: true });
107+
if (!shouldSkipStep('remote')) {
108+
progress.step('adding upstream remote');
109+
await addRemote({ targetFolder, silent: true });
110+
}
101111

102112
// Done
103113
progress.done(`created ${projectName}`);

cli/create-cella/src/modules/cli/commands.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,20 @@ import { NAME, VERSION } from '#/constants';
44
import { validateProjectName } from '#/utils/validate-project-name';
55
import type { CLIConfig, CLIOptions } from './types';
66

7+
function parsePortOffset(value: string): number {
8+
const parsed = Number(value);
9+
10+
if (!Number.isInteger(parsed) || parsed < 0 || parsed > 490) {
11+
throw new InvalidArgumentError('Port offset must be an integer between 0 and 490');
12+
}
13+
14+
if (parsed % 10 !== 0) {
15+
throw new InvalidArgumentError('Port offset must be a multiple of 10');
16+
}
17+
18+
return parsed;
19+
}
20+
721
// Initialize CLI variables
822
let directory: string | null = null;
923
const newBranchName: string | null = null;
@@ -19,6 +33,8 @@ export const command = new Command(NAME)
1933
.usage('[directory] [options]')
2034
.helpOption('-h, --help', 'display this help message')
2135
.option('--template <path>', 'use a custom template (local path or github:user/repo)')
36+
.option('--port-offset <number>', 'set the port offset (0-490 in steps of 10)', parsePortOffset)
37+
.option('--admin-email <email>', 'set the admin email for the initial seed user')
2238
.action((name: string) => {
2339
const trimmedName = typeof name === 'string' ? name.trim() : name;
2440

cli/create-cella/src/modules/cli/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
/** CLI options parsed from command line arguments */
22
export interface CLIOptions {
33
template?: string;
4+
portOffset?: number;
5+
adminEmail?: string;
46
}
57

68
/** CLI configuration state */

cli/create-cella/src/utils/git/command.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export async function gitAddAll(repoPath: string): Promise<string> {
5050
* Creates a commit with the specified message.
5151
*/
5252
export async function gitCommit(repoPath: string, message: string): Promise<string> {
53-
return runGitCommand(['commit', '-m', message], repoPath);
53+
return runGitCommand(['commit', '--quiet', '--no-verify', '-m', message], repoPath);
5454
}
5555

5656
/**

cli/create-cella/tests/env-config.test.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs';
22
import { tmpdir } from 'node:os';
3-
import { join } from 'node:path';
3+
import { join, resolve } from 'node:path';
44
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
55
import { generateEnvConfigs, generateEnvFromExample, getBackendEnvReplacements } from '#/constants';
66

@@ -60,6 +60,22 @@ describe('generateEnvFromExample', () => {
6060
const result = await generateEnvFromExample(join(dir, 'missing.env.example'), {});
6161
expect(result).toBeNull();
6262
});
63+
64+
it('applies project slug and port offsets to the real backend env template', async () => {
65+
const result = await generateEnvFromExample(
66+
resolve(import.meta.dirname, '../../../backend/.env.example'),
67+
getBackendEnvReplacements('my-app', 'admin@my-app.example.com', 10),
68+
);
69+
70+
expect(result).toContain('PROJECT_SLUG=my-app');
71+
expect(result).toContain('DB_PORT=5442');
72+
expect(result).toContain('DB_TEST_PORT=5444');
73+
expect(result).toContain('DATABASE_URL=postgres://runtime_role:dev_password@0.0.0.0:5442/postgres');
74+
expect(result).toContain('DATABASE_ADMIN_URL=postgres://postgres:postgres@0.0.0.0:5442/postgres');
75+
expect(result).toContain('DATABASE_CDC_URL=postgres://admin_role:dev_password@0.0.0.0:5442/postgres');
76+
expect(result).toContain('ADMIN_EMAIL=admin@my-app.example.com');
77+
expect(result).toContain('PORT=4010');
78+
});
6379
});
6480

6581
describe('generateEnvConfigs', () => {

cli/create-cella/tests/release-artifact.test.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { execFileSync } from 'node:child_process';
2+
import { readFileSync } from 'node:fs';
23
import { describe, expect, it } from 'vitest';
34
import packageJson from '../package.json' with { type: 'json' };
45

@@ -23,4 +24,12 @@ describe('release artifact', () => {
2324
expect(packedFiles).toContain('dist/index.js');
2425
expect(packedFiles).toContain('package.json');
2526
});
27+
28+
it('keeps container isolation and port offset replacements in the built CLI', () => {
29+
const builtEntry = readFileSync(new URL('../dist/index.js', import.meta.url), 'utf8');
30+
31+
expect(builtEntry).toContain('PROJECT_SLUG: slug');
32+
expect(builtEntry).toContain('DB_PORT: String(db)');
33+
expect(builtEntry).toContain('DB_TEST_PORT: String(5434 + portOffset)');
34+
});
2635
});
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { execFileSync } from 'node:child_process';
2+
import { existsSync, mkdtempSync, readFileSync, rmSync } from 'node:fs';
3+
import { tmpdir } from 'node:os';
4+
import { join, resolve } from 'node:path';
5+
import { afterAll, describe, expect, it } from 'vitest';
6+
7+
describe('release smoke', () => {
8+
const workspaceRoot = resolve(import.meta.dirname, '../../..');
9+
const packageRoot = resolve(import.meta.dirname, '..');
10+
const tempRoot = mkdtempSync(join(tmpdir(), 'create-cella-release-'));
11+
const targetFolder = join(tempRoot, 'smoke-app');
12+
13+
afterAll(() => {
14+
rmSync(tempRoot, { recursive: true, force: true });
15+
});
16+
17+
it('creates a project with slug and offset propagated into backend env and dev config', () => {
18+
execFileSync(
19+
'node',
20+
[
21+
'index.js',
22+
targetFolder,
23+
'--template',
24+
workspaceRoot,
25+
'--port-offset',
26+
'10',
27+
'--admin-email',
28+
'admin@smoke-app.com',
29+
],
30+
{
31+
cwd: packageRoot,
32+
encoding: 'utf8',
33+
env: {
34+
...process.env,
35+
CREATE_CELLA_SKIP_INSTALL: 'true',
36+
CREATE_CELLA_SKIP_GENERATE: 'true',
37+
CREATE_CELLA_SKIP_GIT: 'true',
38+
CREATE_CELLA_SKIP_REMOTE: 'true',
39+
},
40+
maxBuffer: 10 * 1024 * 1024,
41+
},
42+
);
43+
44+
const backendEnvPath = join(targetFolder, 'backend', '.env');
45+
const developmentConfigPath = join(targetFolder, 'shared', 'config', 'config.development.ts');
46+
47+
expect(existsSync(backendEnvPath)).toBe(true);
48+
expect(existsSync(developmentConfigPath)).toBe(true);
49+
50+
const backendEnv = readFileSync(backendEnvPath, 'utf8');
51+
const developmentConfig = readFileSync(developmentConfigPath, 'utf8');
52+
53+
expect(backendEnv).toContain('PROJECT_SLUG=smoke-app');
54+
expect(backendEnv).toContain('DB_PORT=5442');
55+
expect(backendEnv).toContain('DB_TEST_PORT=5444');
56+
expect(backendEnv).toContain('DATABASE_URL=postgres://runtime_role:dev_password@0.0.0.0:5442/postgres');
57+
expect(backendEnv).toContain('ADMIN_EMAIL=admin@smoke-app.com');
58+
expect(backendEnv).toContain('PORT=4010');
59+
60+
expect(developmentConfig).toContain("slug: 'smoke-app-development'");
61+
expect(developmentConfig).toContain("frontendUrl: 'http://localhost:3010'");
62+
expect(developmentConfig).toContain("backendUrl: 'http://localhost:4010'");
63+
expect(developmentConfig).toContain("backendAuthUrl: 'http://localhost:4010/auth'");
64+
}, 240000);
65+
});

0 commit comments

Comments
 (0)