Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
13 changes: 9 additions & 4 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ name: Release
# Release pipeline:
# 1) smoke: pack, install the tarball, scaffold Node and Express apps, boot their
# servers and verify a live HTTP response. Kept out of the per-PR ci.yml job.
# 2) publish: on a version tag, publish to npm using OIDC trusted publishing
# (no long-lived token; provenance is attached automatically), then create the
# matching GitHub Release so npm and GitHub show the same latest version.
# 2) publish: on a version tag, publish the CLI and npm-create wrapper to npm
# using OIDC trusted publishing (no long-lived token; provenance is attached
# automatically), then create the matching GitHub Release so npm and GitHub
# show the same latest version.
on:
push:
tags:
Expand Down Expand Up @@ -54,9 +55,13 @@ jobs:

# Explicit --tag latest: 2.1.0+ is the active release line and supersedes
# the historical 2021 servergen@2.0.0 package on the npm registry.
- name: Publish to npm
- name: Publish servergen to npm
run: npm publish --tag latest

- name: Publish create-servergen to npm
run: npm publish --tag latest
working-directory: packages/create-servergen

- name: Create GitHub release
env:
GH_TOKEN: ${{ github.token }}
Expand Down
9 changes: 6 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@

## Unreleased

- Add an Express-only `--openapi` option that generates `docs/openapi.yaml` and documents it in the generated README.
- Add `--typescript` support for generated Express apps with `src/index.ts`, `tsconfig.json`, `tsx` development, `tsc` builds to `dist`, generated TypeScript tests, and Docker support.
- Keep JavaScript output unchanged by default and reject `--typescript` with the plain Node framework.
## 2.3.0 - 2026-06-24

- Add TypeScript support for generated Express apps with `src/index.ts`, `tsconfig.json`, `tsx` development, `tsc` builds to `dist`, generated TypeScript tests, and Docker support.
- Add OpenAPI spec support with an Express-only `--openapi` option that generates `docs/openapi.yaml` and documents it in the generated README.
- Add interactive mode so new projects can be configured through prompts instead of requiring every option up front.
- Add npm create support so the recommended first-run path can use `npm create servergen@latest`.

## 2.2.3 - 2026-06-23

Expand Down
22 changes: 18 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ npm scripts, optional Express views, and optional Mongoose/MongoDB config.
Requires Node.js 20 or higher.

```bash
npx servergen@latest my-api
npm create servergen@latest
cd my-api
npm start
```
Expand All @@ -33,8 +33,15 @@ Expected response:
{"status":"ok"}
```

ServerGen creates an Express app by default and runs `npm install` in the
generated app unless `--skip-install` is used.
The create flow guides you through the app name and options interactively,
creates an Express app by default, and runs `npm install` in the generated app
unless install is skipped.

Prefer the direct CLI path when you already know the app name and options:

```bash
npx servergen@latest my-api
```

## What Gets Generated

Expand Down Expand Up @@ -77,6 +84,7 @@ servergen [options] [name]
### Examples

```bash
npm create servergen@latest
npx servergen@latest my-api
npx servergen@latest my-api --framework node
npx servergen@latest my-api --view ejs
Expand All @@ -93,7 +101,13 @@ steps, see the [scaffold examples](https://github.com/theinfosecguy/ServerGen/tr

## Install Options

Use `npx` when you want to create an app without adding ServerGen to another
Use the interactive create flow for the shortest first run:

```bash
npm create servergen@latest
```

Use `npx` when you want the direct CLI path without adding ServerGen to another
project:

```bash
Expand Down
115 changes: 75 additions & 40 deletions bin/servergen.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ import { validateOptions } from '../lib/validator.js';
import { createGenerator } from '../index.js';
import * as fileName from '../lib/fileName.js';
import * as logger from '../lib/logger.js';
import {
isInteractiveTerminal,
runInteractiveWizard,
} from '../lib/interactive.js';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
Expand Down Expand Up @@ -48,7 +52,8 @@ Examples:
$ servergen my-api --typescript Express app with TypeScript
$ servergen my-api -p 8080 use a custom port
$ servergen my-api --skip-install scaffold without running npm install
$ servergen --name my-api name via flag (equivalent to positional)`
$ servergen --name my-api name via flag (equivalent to positional)
$ servergen start the interactive wizard`
)
.parse(process.argv);

Expand All @@ -61,56 +66,86 @@ if (options.debug) {

logger.debug('CLI options received', { ...options, positionalName });

const validationResult = validateOptions(options, config.validation);
if (!validationResult.isValid) {
validationResult.errors.forEach((error) => logger.error(error));
process.exit(1);
}

// Resolve the app name from the positional argument or the --name flag.
if (positionalName && options.name && positionalName !== options.name) {
logger.error(
`Conflicting app names: "${positionalName}" (positional) and "${options.name}" (--name). Provide only one.`
);
process.exit(1);
}
/**
* Main function to run the application generator.
*/
const main = async () => {
if (positionalName && options.name && positionalName !== options.name) {
logger.error(
`Conflicting app names: "${positionalName}" (positional) and "${options.name}" (--name). Provide only one.`
);
process.exit(1);
}

const rawName = positionalName || options.name;
let rawName = positionalName || options.name;
const resolvedOptions = { ...options };

if (!rawName) {
if (!isInteractiveTerminal(process.stdin, process.stdout)) {
logger.error(
'Missing app name. Provide it as a positional argument (servergen my-api) or with --name.'
);
process.exit(1);
}

let wizardOptions;
try {
wizardOptions = await runInteractiveWizard({
input: process.stdin,
output: process.stdout,
defaults: {
port: resolvedOptions.port,
},
});
} catch (err) {
logger.error('Interactive wizard cancelled.');
logger.debug('Interactive wizard error', err);
process.exit(1);
}

rawName = wizardOptions.name;
Object.assign(resolvedOptions, wizardOptions, {
port: String(wizardOptions.port),
});

logger.debug('Interactive wizard answers received', wizardOptions);
}

if (!rawName) {
logger.error(
'Missing app name. Provide it as a positional argument (servergen my-api) or with --name.'
);
process.exit(1);
}
const validationResult = validateOptions(resolvedOptions, config.validation);
if (!validationResult.isValid) {
validationResult.errors.forEach((error) => logger.error(error));
process.exit(1);
}

const appName = fileName.cleanAppName(rawName);
const appName = fileName.cleanAppName(rawName);

if (!appName) {
logger.error(
'App name must contain at least one alphanumeric character. Please provide a valid name.'
);
process.exit(1);
}
if (!appName) {
logger.error(
'App name must contain at least one alphanumeric character. Please provide a valid name.'
);
process.exit(1);
}

const port = parseInt(options.port, 10) || 3000;
const skipInstall = options.skipInstall || false;
const port = parseInt(resolvedOptions.port, 10) || 3000;
const skipInstall = resolvedOptions.skipInstall || false;

logger.debug('Parsed configuration', { appName, port, framework: options.framework, skipInstall, typescript: options.typescript });
logger.debug('Parsed configuration', {
appName,
port,
framework: resolvedOptions.framework,
skipInstall,
typescript: resolvedOptions.typescript,
});

/**
* Main function to run the application generator.
*/
const main = async () => {
const generator = createGenerator({
appName,
framework: options.framework,
view: options.view,
db: options.db,
openapi: options.openapi,
framework: resolvedOptions.framework,
view: resolvedOptions.view,
db: resolvedOptions.db,
openapi: resolvedOptions.openapi,
port,
skipInstall,
typescript: options.typescript,
typescript: resolvedOptions.typescript,
config,
});

Expand Down
11 changes: 9 additions & 2 deletions docs/examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@

Copy-paste examples for current ServerGen flows.

These examples use `npx --yes servergen@latest` to follow the current published CLI. If you need reproducible output, replace `latest` with a pinned version. If you have ServerGen installed globally, you can replace that prefix with `servergen`.
For a guided first run, use the interactive create flow:

```sh
npm create servergen@latest
```

The command-by-command examples use `npx --yes servergen@latest` to follow the current published CLI. If you need reproducible output, replace `latest` with a pinned version. If you have ServerGen installed globally, you can replace that prefix with `servergen`.

## Examples

Expand All @@ -16,9 +22,10 @@ These examples use `npx --yes servergen@latest` to follow the current published
## Shared Notes

- ServerGen creates a new app directory in your current working directory.
- `npm create servergen@latest` starts the interactive flow; the `npx --yes servergen@latest ...` examples show direct CLI equivalents.
- The default framework is Express.
- Generated apps require Node.js 20 or newer.
- Generation runs `npm install` unless you pass `--skip-install`.
- When install is not skipped, npm also creates `node_modules/` and `package-lock.json` inside the generated app.
- Express-only options: `--view ejs|pug|hbs`, `--db`, and `--typescript`.
- Express-only options: `--view ejs|pug|hbs`, `--db`, `--openapi`, and `--typescript`.
- Generated apps include Docker support files. Express apps also include `.env.example`; Node apps do not.
45 changes: 42 additions & 3 deletions docs/release.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ the files consumers need:
- `CHANGELOG.md`
- `LICENSE`

This checkout includes the root `servergen` package and the
`packages/create-servergen` npm-create wrapper. Confirm the wrapper publishes
the `create-servergen` package name and delegates to the released `servergen`
CLI without duplicating generation logic.

The generated app smoke expectations are:

- The packaged CLI installs from the packed tarball, not the working tree.
Expand Down Expand Up @@ -82,6 +87,11 @@ git add package.json CHANGELOG.md
git commit -m "chore: release x.y.z"
```

Update `packages/create-servergen/package.json` in the same release change. The
`servergen` and `create-servergen` package versions should normally match for
adoption releases so `npm create servergen@latest` and `npx servergen@latest`
resolve to the same documented behavior.

## Tag and release workflow

Create an annotated version tag from the exact commit that should be released:
Expand All @@ -97,15 +107,44 @@ the following:

1. Installs dependencies on Node.js 22.
2. Runs `npm run test:package`.
3. Publishes the package to npm with `npm publish --tag latest`.
4. Uses npm trusted publishing through GitHub OIDC, so no long-lived npm token is
3. Publishes `servergen` to npm with `npm publish --tag latest`.
4. Publishes `create-servergen` from `packages/create-servergen`.
5. Uses npm trusted publishing through GitHub OIDC, so no long-lived npm token is
required and npm provenance is attached to the package.
5. Creates the matching GitHub Release, or marks the existing matching release as
6. Creates the matching GitHub Release, or marks the existing matching release as
latest.

Do not run a separate local `npm publish` for normal releases. Let the tag
workflow publish once.

### Publishing `create-servergen`

The release workflow publishes the root `servergen` package first, then
publishes `packages/create-servergen`. Before tagging, configure npm trusted
publishing for both npm packages against this GitHub repository and the
`Release` workflow so both publishes use OIDC provenance.

If `create-servergen` has never been published before, bootstrap the package
name before relying on trusted publishing. npm trusted publisher settings are
configured from an existing package's settings page, so the first publish may
require an npm owner to publish the wrapper manually or with a short-lived token.
After the package exists, configure its trusted publisher for the `Release`
workflow before using tag-based releases.

The package smoke test packs and installs both local tarballs, then verifies the
wrapper can create an app through the installed `create-servergen` bin. The
wrapper publish is ordered after the root CLI publish because it depends on the
just-published `servergen` version.

Post-release verification should check both package names:

```sh
npm view servergen version dist-tags --json
npm view create-servergen version dist-tags --json
npm view "servergen@$VERSION" dist.attestations --json
npm view "create-servergen@$VERSION" dist.attestations --json
```

## Post-release verification

Set the version once and use it in the checks:
Expand Down
Loading
Loading