Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
41972eb
feat: bundle Full Stack template source
dDevAhmed Sep 8, 2026
9b98ec1
feat: add Full Stack root package template
dDevAhmed Sep 8, 2026
2894ced
docs: add safe Full Stack environment example
dDevAhmed Sep 8, 2026
1aa5c52
chore: add Full Stack ignore rules
dDevAhmed Sep 8, 2026
fa7ac55
feat: add Full Stack frontend boundary
dDevAhmed Sep 8, 2026
561444c
test: add Full Stack frontend smoke test
dDevAhmed Sep 8, 2026
fa26449
feat: add Full Stack backend boundary
dDevAhmed Sep 8, 2026
5c43f72
feat: add Full Stack backend runtime entry
dDevAhmed Sep 8, 2026
a4d72a4
test: add Full Stack backend smoke test
dDevAhmed Sep 8, 2026
f01ebd8
docs: define Full Stack contract workspace
dDevAhmed Sep 8, 2026
c02aa6d
docs: add Full Stack generated README
dDevAhmed Sep 8, 2026
1054830
fix: wire Full Stack backend runtime entry
dDevAhmed Sep 8, 2026
fb51a6e
test: smoke test generated Full Stack project
dDevAhmed Sep 8, 2026
845de10
chore: diagnose CLI-028 formatting
dDevAhmed Sep 8, 2026
0b0d621
style: format Full Stack smoke test
dDevAhmed Sep 8, 2026
3c5f460
chore: remove CLI-028 formatting diagnostic
dDevAhmed Sep 8, 2026
be5bec6
fix: make Full Stack test targets explicit
dDevAhmed Sep 8, 2026
1eeab40
test: allow doctor integration checks sufficient runtime
dDevAhmed Sep 8, 2026
2b0c63d
chore: diagnose executable test formatting
dDevAhmed Sep 8, 2026
7b7b67a
style: format doctor integration timeout
dDevAhmed Sep 8, 2026
887d096
chore: remove CLI-028 executable formatting diagnostic
dDevAhmed Sep 8, 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
1 change: 1 addition & 0 deletions src/generator/template-sources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ValidationCliError } from '../errors/errors.js';

const BUNDLED_TEMPLATE_DIRECTORIES = {
'basic-app': 'basic-app',
'full-stack': 'full-stack',
} as const;

type BundledTemplateId = keyof typeof BUNDLED_TEMPLATE_DIRECTORIES;
Expand Down
4 changes: 4 additions & 0 deletions templates/full-stack/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
STELLAR_NETWORK=TESTNET
STELLAR_HORIZON_URL=https://horizon-testnet.stellar.org
STELLAR_RPC_URL=https://soroban-testnet.stellar.org
BACKEND_PORT=3000
7 changes: 7 additions & 0 deletions templates/full-stack/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
node_modules/
.env
.env.*
!.env.example
.DS_Store
target/
dist/
44 changes: 44 additions & 0 deletions templates/full-stack/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# {{projectName}}

Generated with the StellarForge `full-stack` template.

## Structure

```text
frontend/ Browser-facing application boundary and tests
backend/ Node.js API/runtime boundary and tests
contracts/ Soroban contract workspace boundary
```

The starter keeps these layers separate so application UI, server-side Stellar integration, and smart-contract code can evolve independently.

## Requirements

- Node.js 22.13 or newer

## Validate the starter

```bash
npm run check
npm test
```

These checks are dependency-free and do not require network access.

## Run the backend

```bash
npm run start:backend
```

The backend exposes `GET /health` on `127.0.0.1:3000` by default.

## Environment

Copy `.env.example` into your preferred local environment-management flow. The example contains public Stellar Testnet endpoints only and no credentials.

Never commit wallet seeds, secret keys, signing credentials, production tokens, or private infrastructure credentials.

## Contracts

`contracts/` is intentionally a boundary rather than a fake production contract. Add reviewed Soroban code when the application domain is defined, or generate a dedicated StellarForge `smart-contract` starter for contract-focused work.
19 changes: 19 additions & 0 deletions templates/full-stack/backend/src/server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export function getBackendConfig(env = globalThis.process.env) {
return {
network: env.STELLAR_NETWORK ?? 'TESTNET',
horizonUrl:
env.STELLAR_HORIZON_URL ?? 'https://horizon-testnet.stellar.org',
rpcUrl: env.STELLAR_RPC_URL ?? 'https://soroban-testnet.stellar.org',
port: Number.parseInt(env.BACKEND_PORT ?? '3000', 10),
};
}

export function createHealthPayload(env = {}) {
const config = getBackendConfig(env);

return {
status: 'ok',
app: '{{projectName}}',
network: config.network,
};
}
16 changes: 16 additions & 0 deletions templates/full-stack/backend/src/start.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { createServer } from 'node:http';
import { createHealthPayload, getBackendConfig } from './server.js';

const config = getBackendConfig();
const server = createServer((request, response) => {
if (request.method === 'GET' && request.url === '/health') {
response.writeHead(200, { 'content-type': 'application/json' });
response.end(JSON.stringify(createHealthPayload(globalThis.process.env)));
return;
}

response.writeHead(404, { 'content-type': 'application/json' });
response.end(JSON.stringify({ error: 'not_found' }));
});

server.listen(config.port, '127.0.0.1');
20 changes: 20 additions & 0 deletions templates/full-stack/backend/test/server.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import { createHealthPayload, getBackendConfig } from '../src/server.js';

test('uses safe Testnet defaults without secrets', () => {
const config = getBackendConfig({});

assert.equal(config.network, 'TESTNET');
assert.equal(config.horizonUrl, 'https://horizon-testnet.stellar.org');
assert.equal(config.rpcUrl, 'https://soroban-testnet.stellar.org');
assert.equal(config.port, 3000);
});

test('returns a generated application health payload', () => {
assert.deepEqual(createHealthPayload({}), {
status: 'ok',
app: '{{projectName}}',
network: 'TESTNET',
});
});
16 changes: 16 additions & 0 deletions templates/full-stack/contracts/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Contracts workspace

This directory is the contract boundary for `{{projectName}}`.

The Full Stack starter deliberately does not invent a production Soroban contract or embed signing credentials. Add reviewed Soroban contracts here when the application domain is defined, or start from StellarForge's dedicated `smart-contract` template.

Recommended structure when contract development begins:

```text
contracts/
Cargo.toml
src/
test/
```

Keep network configuration and signing secrets outside source control. Contract build and deployment workflows should target Stellar Testnet until explicitly promoted through the project's release process.
7 changes: 7 additions & 0 deletions templates/full-stack/frontend/src/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export function createAppModel() {
return {
name: '{{projectName}}',
network: 'Stellar Testnet',
backendHealthPath: '/health',
};
}
11 changes: 11 additions & 0 deletions templates/full-stack/frontend/test/app.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import { createAppModel } from '../src/app.js';

test('exposes the generated app identity and Testnet boundary', () => {
const model = createAppModel();

assert.equal(model.name, '{{projectName}}');
assert.equal(model.network, 'Stellar Testnet');
assert.equal(model.backendHealthPath, '/health');
});
14 changes: 14 additions & 0 deletions templates/full-stack/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"name": "{{projectName}}",
"version": "0.1.0",
"private": true,
"type": "module",
"scripts": {
"check": "node --check frontend/src/app.js && node --check backend/src/server.js && node --check backend/src/start.js",
"test": "node --test frontend/test/app.test.js backend/test/server.test.js",
"start:backend": "node backend/src/start.js"
},
"engines": {
"node": ">=22.13.0"
}
}
2 changes: 1 addition & 1 deletion tests/executable.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ describe('StellarForge CLI executable', () => {
expect(result.stdout).toMatch(
/Summary: \d+ passed, \d+ warnings, \d+ failed\./,
);
});
}, 15_000);

it('prints the package version from the built executable', () => {
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8')) as {
Expand Down
70 changes: 70 additions & 0 deletions tests/full-stack-template.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { existsSync, readFileSync } from 'node:fs';
import { join } from 'node:path';
import { spawnSync } from 'node:child_process';
import { describe, expect, it } from 'vitest';
import { createNewCommand } from '../src/commands/new.js';
import {
createCapturedTerminalOutput,
withTempDirectory,
} from './helpers/index.js';

function runNpmScript(projectRoot: string, script: string) {
return spawnSync(
process.platform === 'win32' ? 'npm.cmd' : 'npm',
['run', script],
{
cwd: projectRoot,
encoding: 'utf8',
shell: false,
},
);
}

describe('Full Stack template', () => {
it('generates the documented frontend, backend, and contracts boundaries and passes smoke checks', () =>
withTempDirectory((root) => {
const captured = createCapturedTerminalOutput();
const command = createNewCommand({
cwd: () => root,
output: captured.output,
});

command.parse(['node', 'new', 'demo-stack', '--template', 'full-stack']);

const projectRoot = join(root, 'demo-stack');
expect(existsSync(join(projectRoot, 'frontend', 'src', 'app.js'))).toBe(
true,
);
expect(existsSync(join(projectRoot, 'backend', 'src', 'server.js'))).toBe(
true,
);
expect(existsSync(join(projectRoot, 'contracts', 'README.md'))).toBe(
true,
);
expect(captured.stdoutText()).toContain(
'Created demo-stack from full-stack',
);

const environmentExample = readFileSync(
join(projectRoot, '.env.example'),
'utf8',
);
expect(environmentExample).toContain('STELLAR_NETWORK=TESTNET');
expect(environmentExample).not.toMatch(/SECRET|SEED|PRIVATE_KEY/);

const check = runNpmScript(projectRoot, 'check');
expect(check.status, check.stderr).toBe(0);

const test = runNpmScript(projectRoot, 'test');
expect(test.status, test.stderr).toBe(0);

const generatedReadme = readFileSync(
join(projectRoot, 'README.md'),
'utf8',
);
expect(generatedReadme).toContain('frontend/');
expect(generatedReadme).toContain('backend/');
expect(generatedReadme).toContain('contracts/');
expect(generatedReadme).toContain('no credentials');
}));
});
Loading