@emeryld/manager 0.4.0 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,58 +1,42 @@
1
1
  # @emeryld/manager
2
2
 
3
- Interactive release helper for pnpm monorepos. Install it as a local dev dependency, then run `pnpm manager-cli` (or call a `scripts.manager-cli` alias) from your workspace root to walk through selecting packages, running updates/tests/builds, and publishing.
4
-
5
- ## Installation
6
-
7
- 1. Ensure your workspace has a `packages/` directory (or configure one via `packages.mjs`).
8
- 2. Add the manager to your dev dependencies so its CLI and helpers are hoisted into the consuming repo:
9
- ```sh
10
- pnpm add -D @emeryld/manager
11
- ```
12
- 3. Include a script in your `package.json` so you can invoke it consistently:
13
- ```json
14
- {
15
- "scripts": {
16
- "manager-cli": "manager-cli"
17
- }
18
- }
19
- ```
20
- 4. Run `pnpm install` to fetch `ts-node`, `semver`, and other runtime deps.
21
-
22
- ## Running the CLI
23
-
24
- - Always run `pnpm manager-cli` (or `pnpm run manager-cli`) from the **workspace root** where your `packages/` folder lives. The manager operates against the workspace you are in, not the package that ships the tool.
25
- - Each TypeScript helper script registers the bundled `ts-node/esm` loader with the exact script path, so the CLI works even from pnpm workspaces that hoist dependencies.
26
- - To avoid the warning/error you saw earlier, make sure:
27
- - the workspace has `packages/` (or a configured manifest) so `loadPackages()` can find targets,
28
- - `pnpm install` has already written `ts-node` into `node_modules`,
29
- - you execute the CLI from the workspace that owns those packages.
30
-
31
- ## Testing the loader registration
32
-
33
- Run `pnpm test` to ensure the helper CLI always generates a `--import data:text...` snippet that registers `ts-node/esm.mjs` with **the actual script file path**. This guards against regressions that would make Node reject the loader and crash before the interactive menu appears.
34
-
35
- ## Package creator overview
36
-
37
- Use `Create package` inside the CLI to scaffold starter packages. The flow will prompt for a variant, target path, and package name, then generate the files and attempt `pnpm install` and `pnpm run build` when a build script exists.
38
-
39
- ### How to scaffold
40
- - From the workspace root, run `pnpm manager-cli` → choose `Create package`.
41
- - Pick a variant, confirm the target directory, and override the package name if desired.
42
- - After scaffolding, the new package’s README covers variant-specific details and scripts.
43
-
44
- ### Variant quick view
45
-
46
- | Variant | Default dir | Purpose | Key scripts |
3
+ Dev dependency built for Codex agents: scaffold RRRoutes packages, read the generated structure, and ship changes without rebuilding boilerplate. Install it in a pnpm workspace and call `pnpm manager-cli` from the workspace root.
4
+
5
+ ## Quick start (agents)
6
+ - Install: `pnpm add -D @emeryld/manager`
7
+ - Add a script: `"manager-cli": "manager-cli"` in the workspace `package.json`
8
+ - Discover templates fast: `pnpm manager-cli templates` (alias `pnpm manager-cli create --list`)
9
+ - Scaffold without prompts:
10
+ `pnpm manager-cli create --variant rrr-server --dir packages/api --name @scope/api --contract @scope/contract --skip-install`
11
+ - Prefer prompts: `pnpm manager-cli create` and follow the menu
12
+ - After scaffolding, open the new package `README.md` for variant-specific scripts and usage
13
+
14
+ ## Templates at a glance
15
+ | id | default dir | summary | key files |
47
16
  | --- | --- | --- | --- |
48
- | rrr contract | `packages/rrr-contract` | Shared RRRoutes contract registry and socket config | `dev`, `build`, `typecheck`, `lint`, `format` |
49
- | rrr server | `packages/rrr-server` | Express + RRRoutes API wired to a contract import | `dev`, `build`, `start`, `lint`, `format` |
50
- | rrr client | `packages/rrr-client` | RRRoutes client helpers + React Query setup | `dev`, `build`, `lint`, `format` |
51
- | empty package | `packages/rrr-empty` | Minimal TypeScript starter | `dev`, `build`, `lint`, `format` |
52
- | dockerized service | `packages/rrr-docker` | Express service with Dockerfile and helper CLI | `dev`, `build`, `start`, `docker:*` |
53
- | full stack stack | `rrrfull-stack` | Workspace with contract + server + client + docker | `dev`, `build`, `lint`, `docker:*` (root scripts orchestrate) |
54
-
55
- ### Common scripts and helpers
56
- - `dev` (watch), `build`, `typecheck`, `lint`/`lint:fix`, `format`/`format:check`, `clean`, `test` are scaffolded where relevant.
57
- - Docker-focused variants expose `docker:build`, `docker:up`, `docker:dev`, `docker:logs`, `docker:stop`, `docker:clean`, `docker:reset`.
58
- - Husky + lint-staged and ESLint/Prettier configs are included in every package for consistent DX.
17
+ | rrr-contract | `packages/rrr-contract` | Shared RRRoutes contract + sockets for clients/servers | `src/index.ts`, `README.md` |
18
+ | rrr-server | `packages/rrr-server` | Express + RRRoutes API wired to a contract import | `src/index.ts`, `.env.example`, `README.md` |
19
+ | rrr-client | `packages/rrr-client` | React Query-ready RRRoutes client bound to a contract import | `src/index.ts`, `README.md` |
20
+ | rrr-empty | `packages/rrr-empty` | Minimal TypeScript package with lint/format/test wiring | `src/index.ts`, `README.md` |
21
+ | rrr-docker | `packages/rrr-docker` | Express service with Dockerfile + helper CLI | `src/index.ts`, `scripts/docker.ts`, `Dockerfile`, `README.md` |
22
+ | rrr-fullstack | `rrrfull-stack` | pnpm workspace with contract, server, client, and docker helper | root `package.json`, `pnpm-workspace.yaml`, `packages/*` |
23
+
24
+ Use `pnpm manager-cli create --describe <variant>` to print the scripts, key files, and notes for any template before scaffolding.
25
+
26
+ ## Create command reference
27
+ - `pnpm manager-cli create --list` show templates
28
+ - `pnpm manager-cli create --describe rrr-server` — see scripts/files/notes for one template
29
+ - `pnpm manager-cli create --variant rrr-client --dir packages/web-client --name @scope/web-client` — scaffold directly (adds `--contract <id>` for server/client)
30
+ - `--skip-install` / `--skip-build` — bypass post-create steps when you want to install/build later
31
+
32
+ ## Release helper (existing packages)
33
+ - Run from the workspace root where `packages/` lives.
34
+ - `pnpm manager-cli` launches an interactive menu: pick packages, then run update → test → build → publish (or any single step).
35
+ - Non-interactive publish: `pnpm manager-cli <pkg|all> --non-interactive --bump patch` (see CLI prompts for flags like `--sync` and `--tag`).
36
+
37
+ ## Notes
38
+ - Each helper script registers the bundled `ts-node/esm` loader with the correct path so the CLI works even when dependencies are hoisted.
39
+ - The create flow runs `pnpm install` and `pnpm run build` when a build script exists; skip with `--skip-install`/`--skip-build` if you want to control timing.
40
+
41
+ ## Tests
42
+ - `pnpm test` verifies the helper CLI loader registration.
@@ -4,7 +4,7 @@ import path from 'node:path';
4
4
  import { stdin as input } from 'node:process';
5
5
  import { askLine, promptSingleKey } from '../prompts.js';
6
6
  import { colors, logGlobal } from '../utils/log.js';
7
- import { workspaceRoot } from './shared.js';
7
+ import { SCRIPT_DESCRIPTIONS, workspaceRoot, } from './shared.js';
8
8
  import { clientVariant } from './variants/client.js';
9
9
  import { contractVariant } from './variants/contract.js';
10
10
  import { dockerVariant } from './variants/docker.js';
@@ -19,10 +19,122 @@ const VARIANTS = [
19
19
  dockerVariant,
20
20
  fullstackVariant,
21
21
  ];
22
+ const VARIANT_LOOKUP = new Map(VARIANTS.map((variant) => [variant.id, variant]));
22
23
  function derivePackageName(targetDir) {
23
24
  const base = path.basename(targetDir) || 'rrr-package';
24
25
  return base;
25
26
  }
27
+ function resolveVariant(key) {
28
+ if (!key)
29
+ return undefined;
30
+ const normalized = key.toLowerCase();
31
+ return (VARIANT_LOOKUP.get(normalized) ??
32
+ VARIANTS.find((variant) => {
33
+ const label = variant.label.toLowerCase();
34
+ const id = variant.id.toLowerCase();
35
+ return (id === normalized ||
36
+ id.replace(/^rrr-/, '') === normalized ||
37
+ label === normalized ||
38
+ label.includes(normalized));
39
+ }));
40
+ }
41
+ function printVariantList() {
42
+ logGlobal('Available templates', colors.magenta);
43
+ VARIANTS.forEach((variant) => {
44
+ const summary = variant.summary ? ` ${colors.dim(`– ${variant.summary}`)}` : '';
45
+ console.log(`- ${colors.green(variant.label)} ${colors.dim(`(${variant.id})`)} → ${colors.cyan(variant.defaultDir)}${summary}`);
46
+ });
47
+ console.log(colors.dim('Use "--describe <variant>" for details or "--variant <id> --dir <path>" to scaffold without prompts.'));
48
+ }
49
+ function printVariantDetails(variant) {
50
+ logGlobal(`${variant.label} (${variant.id})`, colors.green);
51
+ console.log(` default dir: ${colors.cyan(variant.defaultDir)}`);
52
+ if (variant.summary) {
53
+ console.log(` ${variant.summary}`);
54
+ }
55
+ if (variant.keyFiles?.length) {
56
+ console.log(' key files:');
57
+ variant.keyFiles.forEach((file) => console.log(` - ${file}`));
58
+ }
59
+ if (variant.scripts?.length) {
60
+ console.log(' scripts:');
61
+ variant.scripts.forEach((script) => {
62
+ const desc = SCRIPT_DESCRIPTIONS[script];
63
+ const detail = desc ? colors.dim(` – ${desc}`) : '';
64
+ console.log(` - ${script}${detail}`);
65
+ });
66
+ }
67
+ if (variant.notes?.length) {
68
+ console.log(' notes:');
69
+ variant.notes.forEach((note) => console.log(` - ${note}`));
70
+ }
71
+ }
72
+ function parseCreateCliArgs(argv) {
73
+ const options = {};
74
+ for (let i = 0; i < argv.length; i++) {
75
+ const arg = argv[i];
76
+ if (arg === '--list' || arg === '-l' || arg === 'list' || arg === 'ls') {
77
+ options.list = true;
78
+ continue;
79
+ }
80
+ if (arg === '--describe' || arg === '-d' || arg === 'describe') {
81
+ options.describe = argv[++i];
82
+ continue;
83
+ }
84
+ if (arg === '--variant' || arg === '-v') {
85
+ options.variant = argv[++i];
86
+ continue;
87
+ }
88
+ if (arg === '--dir' || arg === '--path' || arg === '-p') {
89
+ options.targetDir = argv[++i];
90
+ continue;
91
+ }
92
+ if (arg === '--name' || arg === '-n') {
93
+ options.pkgName = argv[++i];
94
+ continue;
95
+ }
96
+ if (arg === '--contract') {
97
+ options.contractName = argv[++i];
98
+ continue;
99
+ }
100
+ if (arg === '--skip-install') {
101
+ options.skipInstall = true;
102
+ continue;
103
+ }
104
+ if (arg === '--skip-build') {
105
+ options.skipBuild = true;
106
+ continue;
107
+ }
108
+ if (arg === '--help' || arg === '-h') {
109
+ options.help = true;
110
+ continue;
111
+ }
112
+ if (!arg.startsWith('-') && !options.variant) {
113
+ options.variant = arg;
114
+ }
115
+ }
116
+ return options;
117
+ }
118
+ function printCreateHelp() {
119
+ logGlobal('Create package help', colors.magenta);
120
+ console.log('Usage:');
121
+ console.log(' pnpm manager-cli create # interactive prompts');
122
+ console.log(' pnpm manager-cli create --list # list templates');
123
+ console.log(' pnpm manager-cli create --describe rrr-server');
124
+ console.log(' pnpm manager-cli create --variant rrr-client --dir packages/rrr-client --name @scope/client');
125
+ console.log(' pnpm manager-cli create --variant rrr-server --contract @scope/contract --skip-install');
126
+ console.log('');
127
+ console.log('Flags:');
128
+ console.log(' --list, -l Show available templates');
129
+ console.log(' --describe, -d Print details for a template');
130
+ console.log(' --variant, -v Pick a template by id/label (skips variant prompt)');
131
+ console.log(' --dir, --path, -p Target directory (skips path prompt)');
132
+ console.log(' --name, -n Package name (skips name prompt)');
133
+ console.log(' --contract Contract import to inject (server/client variants)');
134
+ console.log(' --skip-install Do not run pnpm install after scaffolding');
135
+ console.log(' --skip-build Skip build after scaffolding');
136
+ console.log(' --help, -h Show this help');
137
+ }
26
138
  async function ensureTargetDir(targetDir) {
27
139
  try {
28
140
  const stats = await stat(targetDir);
@@ -177,7 +289,11 @@ async function promptForTargetDir(fallback) {
177
289
  const normalized = answer || fallback;
178
290
  return path.resolve(workspaceRoot, normalized);
179
291
  }
180
- async function postCreateTasks(targetDir) {
292
+ async function postCreateTasks(targetDir, options) {
293
+ if (options?.skipInstall) {
294
+ logGlobal('Skipping pnpm install (flag).', colors.dim);
295
+ return;
296
+ }
181
297
  try {
182
298
  logGlobal('Running pnpm install…', colors.cyan);
183
299
  await runCommand('pnpm', ['install'], workspaceRoot);
@@ -186,6 +302,10 @@ async function postCreateTasks(targetDir) {
186
302
  logGlobal(`pnpm install failed: ${error instanceof Error ? error.message : String(error)}`, colors.yellow);
187
303
  return;
188
304
  }
305
+ if (options?.skipBuild) {
306
+ logGlobal('Skipping build (flag).', colors.dim);
307
+ return;
308
+ }
189
309
  try {
190
310
  const pkgJsonPath = path.join(targetDir, 'package.json');
191
311
  const pkgRaw = await readFile(pkgJsonPath, 'utf8');
@@ -199,22 +319,69 @@ async function postCreateTasks(targetDir) {
199
319
  logGlobal(`Skipping build (could not read package.json): ${error instanceof Error ? error.message : String(error)}`, colors.yellow);
200
320
  }
201
321
  }
202
- async function gatherTarget() {
203
- const variant = await promptForVariant();
204
- const targetDir = await promptForTargetDir(variant.defaultDir);
322
+ async function gatherTarget(initial = {}) {
323
+ const variant = initial.variant ?? (await promptForVariant());
324
+ const targetDir = initial.targetDir !== undefined
325
+ ? path.resolve(workspaceRoot, initial.targetDir)
326
+ : await promptForTargetDir(variant.defaultDir);
205
327
  const fallbackName = derivePackageName(targetDir);
206
- const nameAnswer = await askLine(`Package name? (${fallbackName}): `);
328
+ const nameAnswer = initial.pkgName === undefined
329
+ ? await askLine(`Package name? (${fallbackName}): `)
330
+ : initial.pkgName;
207
331
  const pkgName = (nameAnswer || fallbackName).trim() || fallbackName;
208
332
  await ensureTargetDir(targetDir);
209
- return { variant, targetDir, pkgName };
333
+ return {
334
+ variant,
335
+ targetDir,
336
+ pkgName,
337
+ contractName: initial.contractName,
338
+ };
210
339
  }
211
- export async function createRrrPackage() {
212
- const target = await gatherTarget();
340
+ export async function createRrrPackage(options = {}) {
341
+ const target = await gatherTarget(options);
213
342
  logGlobal(`Creating ${target.variant.label} in ${path.relative(workspaceRoot, target.targetDir) || '.'}`, colors.green);
214
343
  await target.variant.scaffold({
215
344
  targetDir: target.targetDir,
216
345
  pkgName: target.pkgName,
346
+ contractName: target.contractName ?? options.contractName,
347
+ });
348
+ await postCreateTasks(target.targetDir, {
349
+ skipInstall: options.skipInstall,
350
+ skipBuild: options.skipBuild ?? options.skipInstall,
217
351
  });
218
- await postCreateTasks(target.targetDir);
219
352
  logGlobal('Scaffold complete. Install/build steps were attempted; ready to run!', colors.green);
220
353
  }
354
+ export async function runCreatePackageCli(argv) {
355
+ const parsed = parseCreateCliArgs(argv);
356
+ if (parsed.help) {
357
+ printCreateHelp();
358
+ return;
359
+ }
360
+ if (parsed.list) {
361
+ printVariantList();
362
+ return;
363
+ }
364
+ if (parsed.describe) {
365
+ const variant = resolveVariant(parsed.describe);
366
+ if (!variant) {
367
+ throw new Error(`Unknown variant "${parsed.describe}". Use --list to see available templates.`);
368
+ }
369
+ printVariantDetails(variant);
370
+ return;
371
+ }
372
+ const variant = resolveVariant(parsed.variant);
373
+ if (parsed.variant && !variant) {
374
+ throw new Error(`Unknown variant "${parsed.variant}". Use --list to see available templates.`);
375
+ }
376
+ const targetDir = parsed.targetDir
377
+ ? path.resolve(workspaceRoot, parsed.targetDir)
378
+ : undefined;
379
+ await createRrrPackage({
380
+ variant,
381
+ targetDir,
382
+ pkgName: parsed.pkgName,
383
+ contractName: parsed.contractName,
384
+ skipInstall: parsed.skipInstall,
385
+ skipBuild: parsed.skipBuild ?? parsed.skipInstall,
386
+ });
387
+ }
@@ -155,7 +155,7 @@ export function basePackageFiles(options) {
155
155
  '.gitignore': gitignoreFrom(options?.gitignoreEntries),
156
156
  };
157
157
  }
158
- const SCRIPT_DESCRIPTIONS = {
158
+ export const SCRIPT_DESCRIPTIONS = {
159
159
  dev: 'Watch and rebuild on change',
160
160
  build: 'Type-check and emit to dist/',
161
161
  typecheck: 'Type-check only (no emit)',
@@ -1,4 +1,15 @@
1
1
  import { BASE_LINT_DEV_DEPENDENCIES, basePackageFiles, basePackageJson, buildReadme, baseScripts, baseTsConfig, writeFileIfMissing, } from '../shared.js';
2
+ const CLIENT_SCRIPTS = [
3
+ 'dev',
4
+ 'build',
5
+ 'typecheck',
6
+ 'lint',
7
+ 'lint:fix',
8
+ 'format',
9
+ 'format:check',
10
+ 'clean',
11
+ 'test',
12
+ ];
2
13
  const CONTRACT_IMPORT_PLACEHOLDER = '@your-scope/contract';
3
14
  export function clientIndexTs(contractImport) {
4
15
  return `import { QueryClient } from '@tanstack/react-query'
@@ -35,17 +46,6 @@ export function clientPackageJson(name, contractName = CONTRACT_IMPORT_PLACEHOLD
35
46
  });
36
47
  }
37
48
  function clientFiles(pkgName, contractImport) {
38
- const scriptsForReadme = [
39
- 'dev',
40
- 'build',
41
- 'typecheck',
42
- 'lint',
43
- 'lint:fix',
44
- 'format',
45
- 'format:check',
46
- 'clean',
47
- 'test',
48
- ];
49
49
  return {
50
50
  'package.json': clientPackageJson(pkgName, contractImport),
51
51
  'tsconfig.json': baseTsConfig({ lib: ['ES2020', 'DOM'], types: ['node'] }),
@@ -54,7 +54,7 @@ function clientFiles(pkgName, contractImport) {
54
54
  'README.md': buildReadme({
55
55
  name: pkgName,
56
56
  description: 'Starter RRRoutes client scaffold.',
57
- scripts: scriptsForReadme,
57
+ scripts: CLIENT_SCRIPTS,
58
58
  sections: [
59
59
  {
60
60
  title: 'Getting Started',
@@ -83,6 +83,13 @@ export const clientVariant = {
83
83
  id: 'rrr-client',
84
84
  label: 'rrr client',
85
85
  defaultDir: 'packages/rrr-client',
86
+ summary: 'React Query-ready RRRoutes client bound to a shared contract import.',
87
+ keyFiles: ['src/index.ts', 'README.md'],
88
+ scripts: CLIENT_SCRIPTS,
89
+ notes: [
90
+ 'Set the contract import via --contract or by editing src/index.ts.',
91
+ 'Exports query client + typed route builders to plug into React apps.',
92
+ ],
86
93
  async scaffold(ctx) {
87
94
  const contractImport = ctx.contractName ?? CONTRACT_IMPORT_PLACEHOLDER;
88
95
  const files = clientFiles(ctx.pkgName, contractImport);
@@ -1,4 +1,15 @@
1
1
  import { BASE_LINT_DEV_DEPENDENCIES, basePackageFiles, basePackageJson, buildReadme, baseScripts, baseTsConfig, writeFileIfMissing, } from '../shared.js';
2
+ const CONTRACT_SCRIPTS = [
3
+ 'dev',
4
+ 'build',
5
+ 'typecheck',
6
+ 'lint',
7
+ 'lint:fix',
8
+ 'format',
9
+ 'format:check',
10
+ 'clean',
11
+ 'test',
12
+ ];
2
13
  export const CONTRACT_TS = `import { defineSocketEvents, finalize, resource } from '@emeryld/rrroutes-contract'
3
14
  import { z } from 'zod'
4
15
 
@@ -90,17 +101,6 @@ function contractPackageJson(name) {
90
101
  });
91
102
  }
92
103
  function contractFiles(pkgName) {
93
- const scriptsForReadme = [
94
- 'dev',
95
- 'build',
96
- 'typecheck',
97
- 'lint',
98
- 'lint:fix',
99
- 'format',
100
- 'format:check',
101
- 'clean',
102
- 'test',
103
- ];
104
104
  return {
105
105
  'package.json': contractPackageJson(pkgName),
106
106
  'tsconfig.json': baseTsConfig(),
@@ -109,7 +109,7 @@ function contractFiles(pkgName) {
109
109
  'README.md': buildReadme({
110
110
  name: pkgName,
111
111
  description: 'Contract package scaffolded by manager-cli.',
112
- scripts: scriptsForReadme,
112
+ scripts: CONTRACT_SCRIPTS,
113
113
  sections: [
114
114
  {
115
115
  title: 'Getting Started',
@@ -134,6 +134,12 @@ export const contractVariant = {
134
134
  id: 'rrr-contract',
135
135
  label: 'rrr contract',
136
136
  defaultDir: 'packages/rrr-contract',
137
+ summary: 'Shared RRRoutes registry + sockets to be consumed by server/client packages.',
138
+ keyFiles: ['src/index.ts', 'README.md'],
139
+ scripts: CONTRACT_SCRIPTS,
140
+ notes: [
141
+ 'Edit src/index.ts to define routes and socket events; exports registry/socket config.',
142
+ ],
137
143
  async scaffold(ctx) {
138
144
  const files = contractFiles(ctx.pkgName);
139
145
  for (const [relative, contents] of Object.entries(files)) {
@@ -1,4 +1,23 @@
1
1
  import { BASE_LINT_DEV_DEPENDENCIES, basePackageFiles, basePackageJson, buildReadme, baseScripts, baseTsConfig, writeFileIfMissing, } from '../shared.js';
2
+ const DOCKER_SCRIPTS = [
3
+ 'dev',
4
+ 'build',
5
+ 'typecheck',
6
+ 'lint',
7
+ 'lint:fix',
8
+ 'format',
9
+ 'format:check',
10
+ 'clean',
11
+ 'test',
12
+ 'start',
13
+ 'docker:build',
14
+ 'docker:up',
15
+ 'docker:dev',
16
+ 'docker:logs',
17
+ 'docker:stop',
18
+ 'docker:clean',
19
+ 'docker:reset',
20
+ ];
2
21
  function dockerPackageJson(name) {
3
22
  return basePackageJson({
4
23
  name,
@@ -77,25 +96,6 @@ CMD ["node", "dist/index.js"]
77
96
  `;
78
97
  }
79
98
  function dockerFiles(pkgName) {
80
- const scriptsForReadme = [
81
- 'dev',
82
- 'build',
83
- 'typecheck',
84
- 'lint',
85
- 'lint:fix',
86
- 'format',
87
- 'format:check',
88
- 'clean',
89
- 'test',
90
- 'start',
91
- 'docker:build',
92
- 'docker:up',
93
- 'docker:dev',
94
- 'docker:logs',
95
- 'docker:stop',
96
- 'docker:clean',
97
- 'docker:reset',
98
- ];
99
99
  return {
100
100
  'package.json': dockerPackageJson(pkgName),
101
101
  'tsconfig.json': baseTsConfig({ types: ['node'] }),
@@ -107,7 +107,7 @@ function dockerFiles(pkgName) {
107
107
  'README.md': buildReadme({
108
108
  name: pkgName,
109
109
  description: 'Dockerized service scaffolded by manager-cli.',
110
- scripts: scriptsForReadme,
110
+ scripts: DOCKER_SCRIPTS,
111
111
  sections: [
112
112
  {
113
113
  title: 'Getting Started',
@@ -221,6 +221,18 @@ export const dockerVariant = {
221
221
  id: 'rrr-docker',
222
222
  label: 'dockerized service',
223
223
  defaultDir: 'packages/rrr-docker',
224
+ summary: 'Express service plus Dockerfile and a helper CLI for local runs.',
225
+ keyFiles: [
226
+ 'src/index.ts',
227
+ 'scripts/docker.ts',
228
+ 'Dockerfile',
229
+ 'README.md',
230
+ ],
231
+ scripts: DOCKER_SCRIPTS,
232
+ notes: [
233
+ 'Use docker:dev or docker:up to build and run the container quickly.',
234
+ 'scripts/docker.ts wraps common docker commands with consistent naming.',
235
+ ],
224
236
  async scaffold(ctx) {
225
237
  const files = dockerFiles(ctx.pkgName);
226
238
  for (const [relative, contents] of Object.entries(files)) {
@@ -1,4 +1,15 @@
1
1
  import { BASE_LINT_DEV_DEPENDENCIES, basePackageFiles, basePackageJson, buildReadme, baseScripts, baseTsConfig, writeFileIfMissing, } from '../shared.js';
2
+ const EMPTY_SCRIPTS = [
3
+ 'dev',
4
+ 'build',
5
+ 'typecheck',
6
+ 'lint',
7
+ 'lint:fix',
8
+ 'format',
9
+ 'format:check',
10
+ 'clean',
11
+ 'test',
12
+ ];
2
13
  function emptyPackageJson(name) {
3
14
  return basePackageJson({
4
15
  name,
@@ -9,17 +20,6 @@ function emptyPackageJson(name) {
9
20
  });
10
21
  }
11
22
  function emptyFiles(pkgName) {
12
- const scriptsForReadme = [
13
- 'dev',
14
- 'build',
15
- 'typecheck',
16
- 'lint',
17
- 'lint:fix',
18
- 'format',
19
- 'format:check',
20
- 'clean',
21
- 'test',
22
- ];
23
23
  return {
24
24
  'package.json': emptyPackageJson(pkgName),
25
25
  'tsconfig.json': baseTsConfig({ types: ['node'] }),
@@ -28,7 +28,7 @@ function emptyFiles(pkgName) {
28
28
  'README.md': buildReadme({
29
29
  name: pkgName,
30
30
  description: 'Empty package scaffolded by manager-cli.',
31
- scripts: scriptsForReadme,
31
+ scripts: EMPTY_SCRIPTS,
32
32
  sections: [
33
33
  {
34
34
  title: 'Getting Started',
@@ -50,6 +50,10 @@ export const emptyVariant = {
50
50
  id: 'rrr-empty',
51
51
  label: 'empty package',
52
52
  defaultDir: 'packages/rrr-empty',
53
+ summary: 'Minimal TypeScript package with lint/format/test scaffolding.',
54
+ keyFiles: ['src/index.ts', 'README.md'],
55
+ scripts: EMPTY_SCRIPTS,
56
+ notes: ['Start coding in src/index.ts; everything else is wired up.'],
53
57
  async scaffold(ctx) {
54
58
  const files = emptyFiles(ctx.pkgName);
55
59
  for (const [relative, contents] of Object.entries(files)) {
@@ -4,6 +4,25 @@ import { clientVariant } from './client.js';
4
4
  import { serverVariant } from './server.js';
5
5
  import { dockerVariant } from './docker.js';
6
6
  import { contractVariant } from './contract.js';
7
+ const FULLSTACK_SCRIPTS = [
8
+ 'setup',
9
+ 'dev',
10
+ 'build',
11
+ 'typecheck',
12
+ 'lint',
13
+ 'lint:fix',
14
+ 'lint-staged',
15
+ 'format',
16
+ 'format:check',
17
+ 'test',
18
+ 'clean',
19
+ 'prepare',
20
+ 'docker:up',
21
+ 'docker:dev',
22
+ 'docker:logs',
23
+ 'docker:stop',
24
+ 'docker:reset',
25
+ ];
7
26
  function deriveNames(baseName) {
8
27
  const normalized = baseName.trim();
9
28
  return {
@@ -97,6 +116,21 @@ export const fullstackVariant = {
97
116
  id: 'rrr-fullstack',
98
117
  label: 'rrr fullstack (contract + server + client + docker)',
99
118
  defaultDir: 'rrrfull-stack',
119
+ summary: 'End-to-end RRRoutes stack: pnpm workspace with contract, server, client, and docker helper packages.',
120
+ keyFiles: [
121
+ 'package.json',
122
+ 'pnpm-workspace.yaml',
123
+ 'docker-compose.yml',
124
+ 'packages/<name>-contract',
125
+ 'packages/<name>-server',
126
+ 'packages/<name>-client',
127
+ 'packages/<name>-docker',
128
+ ],
129
+ scripts: FULLSTACK_SCRIPTS,
130
+ notes: [
131
+ 'Generates four packages and a workspace root; great starting point when you need the whole stack.',
132
+ 'Use --name to control the workspace prefix (default is the target folder name).',
133
+ ],
100
134
  async scaffold(ctx) {
101
135
  const baseName = ctx.pkgName;
102
136
  const names = deriveNames(baseName);
@@ -1,4 +1,16 @@
1
1
  import { BASE_LINT_DEV_DEPENDENCIES, basePackageFiles, basePackageJson, buildReadme, baseScripts, baseTsConfig, writeFileIfMissing, } from '../shared.js';
2
+ const SERVER_SCRIPTS = [
3
+ 'dev',
4
+ 'build',
5
+ 'typecheck',
6
+ 'lint',
7
+ 'lint:fix',
8
+ 'format',
9
+ 'format:check',
10
+ 'clean',
11
+ 'test',
12
+ 'start',
13
+ ];
2
14
  const CONTRACT_IMPORT_PLACEHOLDER = '@your-scope/contract';
3
15
  export function serverIndexTs(contractImport) {
4
16
  return `import 'dotenv/config'
@@ -70,18 +82,6 @@ export function serverPackageJson(name, contractName = CONTRACT_IMPORT_PLACEHOLD
70
82
  });
71
83
  }
72
84
  function serverFiles(pkgName, contractImport) {
73
- const scriptsForReadme = [
74
- 'dev',
75
- 'build',
76
- 'typecheck',
77
- 'lint',
78
- 'lint:fix',
79
- 'format',
80
- 'format:check',
81
- 'clean',
82
- 'test',
83
- 'start',
84
- ];
85
85
  return {
86
86
  'package.json': serverPackageJson(pkgName, contractImport),
87
87
  'tsconfig.json': baseTsConfig({ types: ['node'] }),
@@ -91,7 +91,7 @@ function serverFiles(pkgName, contractImport) {
91
91
  'README.md': buildReadme({
92
92
  name: pkgName,
93
93
  description: 'Starter RRRoutes server scaffold.',
94
- scripts: scriptsForReadme,
94
+ scripts: SERVER_SCRIPTS,
95
95
  sections: [
96
96
  {
97
97
  title: 'Getting Started',
@@ -121,6 +121,13 @@ export const serverVariant = {
121
121
  id: 'rrr-server',
122
122
  label: 'rrr server',
123
123
  defaultDir: 'packages/rrr-server',
124
+ summary: 'Express + RRRoutes server wired to a contract import; ships a health route.',
125
+ keyFiles: ['src/index.ts', '.env.example', 'README.md'],
126
+ scripts: SERVER_SCRIPTS,
127
+ notes: [
128
+ 'Set the contract import via --contract or by editing src/index.ts.',
129
+ 'Includes start script for compiled output and dotenv-ready dev script.',
130
+ ],
124
131
  async scaffold(ctx) {
125
132
  const contractImport = ctx.contractName ?? CONTRACT_IMPORT_PLACEHOLDER;
126
133
  const files = serverFiles(ctx.pkgName, contractImport);
package/dist/publish.js CHANGED
@@ -5,7 +5,7 @@ import { getOrderedPackages, loadPackages, resolvePackage } from './packages.js'
5
5
  import { releaseMultiple, releaseSingle, } from './release.js';
6
6
  import { ensureWorkingTreeCommitted } from './preflight.js';
7
7
  import { publishCliState } from './prompts.js';
8
- import { createRrrPackage } from './create-package/index.js';
8
+ import { createRrrPackage, runCreatePackageCli, } from './create-package/index.js';
9
9
  import { colors, logGlobal } from './utils/log.js';
10
10
  function resolveTargetsFromArg(packages, arg) {
11
11
  if (arg.toLowerCase() === 'all')
@@ -121,6 +121,14 @@ async function runPackageSelectionLoop(packages, helperArgs) {
121
121
  }
122
122
  async function main() {
123
123
  const cliArgs = process.argv.slice(2);
124
+ if (cliArgs[0] === 'create') {
125
+ await runCreatePackageCli(cliArgs.slice(1));
126
+ return;
127
+ }
128
+ if (cliArgs[0] === 'templates') {
129
+ await runCreatePackageCli(['--list']);
130
+ return;
131
+ }
124
132
  const parsed = parseCliArgs(cliArgs);
125
133
  const packages = await loadPackages();
126
134
  // If user provided non-interactive flags, run headless path
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@emeryld/manager",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "description": "Interactive manager for pnpm monorepos (update/test/build/publish).",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -30,7 +30,8 @@
30
30
  "devDependencies": {
31
31
  "@types/node": "^20.17.0",
32
32
  "@types/semver": "^7.7.1",
33
- "cross-env": "^7.0.3"
33
+ "cross-env": "^7.0.3",
34
+ "@emeryld/manager": "^0.4.1"
34
35
  },
35
36
  "ts-node": {
36
37
  "esm": true,
@@ -41,6 +42,6 @@
41
42
  "scripts": {
42
43
  "build": "tsc -p tsconfig.base.json && node scripts/copy-manager-cli.mjs",
43
44
  "typecheck": "tsc -p tsconfig.base.json --noEmit",
44
- "test": "cross-env TS_NODE_PROJECT=tsconfig.test.json node --loader ts-node/esm test/helper-cli.test.ts"
45
+ "test": "cross-env TS_NODE_PROJECT=tsconfig.test.json node --loader ts-node/esm --test test/*.test.ts"
45
46
  }
46
47
  }