@emeryld/manager 0.5.0 → 0.6.0
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 +55 -19
- package/dist/create-package/index.js +67 -28
- package/dist/create-package/variants/client/client_expo_rn.js +530 -0
- package/dist/create-package/variants/client/client_vite_r.js +583 -0
- package/dist/create-package/variants/client/shared.js +56 -0
- package/dist/create-package/variants/client.js +1 -122
- package/dist/create-package/variants/docker.js +82 -37
- package/dist/create-package/variants/fullstack.js +262 -10
- package/dist/menu.js +76 -11
- package/dist/prompts.js +21 -15
- package/dist/publish.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,15 +1,52 @@
|
|
|
1
1
|
# @emeryld/manager
|
|
2
2
|
|
|
3
|
-
Dev dependency built for Codex agents:
|
|
3
|
+
Dev dependency built for Codex agents: scan a pnpm workspace, scaffold RRRoutes packages, and ship releases without rebuilding boilerplate. Install it in a workspace and call `pnpm manager-cli` from the root to drive everything interactively or via arguments for headless automation.
|
|
4
4
|
|
|
5
|
-
## Quick start
|
|
5
|
+
## Quick start
|
|
6
6
|
- Install: `pnpm add -D @emeryld/manager`
|
|
7
7
|
- Add a script: `"manager-cli": "manager-cli"` in the workspace `package.json`
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
|
|
11
|
-
-
|
|
12
|
-
|
|
8
|
+
- Launch interactive manager: `pnpm manager-cli` (package picker → action picker)
|
|
9
|
+
- List templates fast: `pnpm manager-cli templates` (alias `pnpm manager-cli create --list`)
|
|
10
|
+
- Headless scaffold (Codex/CI-ready): `pnpm manager-cli create --variant rrr-server --dir packages/api --name @scope/api --contract @scope/contract --skip-install`
|
|
11
|
+
- Headless publish: `pnpm manager-cli all --non-interactive --bump patch --tag next`
|
|
12
|
+
|
|
13
|
+
## Running `manager-cli` interactively
|
|
14
|
+
- **Package selection**: scans for every `package.json` (ignores node_modules/dist). Menu entries include each package (color-coded), "All packages", and "Create package". Navigate with ↑/↓/j/k or digits to run instantly.
|
|
15
|
+
- **Action menu** for the selection:
|
|
16
|
+
- `update dependencies` → runs `pnpm -r update` (or filtered update), stages only dependency files, prompts for a commit message, commits, and pushes.
|
|
17
|
+
- `test` → `pnpm test` (filtered to the package when possible).
|
|
18
|
+
- `build` → `pnpm build` (filtered when a single package is selected).
|
|
19
|
+
- `publish` → ensures the working tree is committed, checks registry auth, prompts a version strategy, commits the bump, tags, publishes with pnpm, and pushes tags.
|
|
20
|
+
- `full` → update → test → build → publish in order.
|
|
21
|
+
- `back` → return to package selection. When a single package is chosen, its `package.json` scripts also appear as runnable entries.
|
|
22
|
+
|
|
23
|
+
## Non-interactive release (Codex/CI)
|
|
24
|
+
- Syntax: `pnpm manager-cli <pkg|all> --non-interactive [publish flags]`
|
|
25
|
+
- Required: the selection (`<pkg>` or `all`) **and** one of `--bump <type>`, `--sync <version>`, or `--noop` (skip version change but still publish/tag).
|
|
26
|
+
- Flags:
|
|
27
|
+
- `--non-interactive`, `--ci`, `--yes`, `-y` → auto-confirm prompts.
|
|
28
|
+
- `--bump <patch|minor|major|prepatch|preminor|premajor|prerelease>` → bump versions (pre* keeps/previews prerelease ids).
|
|
29
|
+
- `--sync <version>` → set all selected packages to an exact version.
|
|
30
|
+
- `--tag <dist-tag>` → publish with a custom npm dist-tag (default inferred from prerelease suffix).
|
|
31
|
+
- `--dry-run` → pass through to `pnpm publish --dry-run`.
|
|
32
|
+
- `--provenance` → enable provenance on publish.
|
|
33
|
+
- `--noop` → skip version change but still tag/publish current versions.
|
|
34
|
+
- Behavior: commits any existing changes before running (prompting for a message), requires registry auth (`pnpm whoami`), and tags/pushes after publish.
|
|
35
|
+
|
|
36
|
+
## Scaffolding packages
|
|
37
|
+
- Interactive: `pnpm manager-cli create` → prompts for template, path, package name, contract (for server/client), and client kind (for client/fullstack). Uses arrow keys/digits when the terminal supports raw mode.
|
|
38
|
+
- Headless (Codex/CI) flags:
|
|
39
|
+
- `--list`, `-l` → show available templates.
|
|
40
|
+
- `--describe`, `-d <variant>` → print scripts/files/notes for a template.
|
|
41
|
+
- `--variant`, `-v <id>` → pick a template without a prompt (ids below).
|
|
42
|
+
- `--dir`, `--path`, `-p <path>` → target directory.
|
|
43
|
+
- `--name`, `-n <pkg>` → package name (defaults to basename of `--dir`).
|
|
44
|
+
- `--contract <pkg>` → contract import for server/client templates.
|
|
45
|
+
- `--client-kind <vite-react|expo-react-native>` → choose client stack (client/fullstack).
|
|
46
|
+
- `--reset` → delete the target directory before scaffolding.
|
|
47
|
+
- `--skip-install` / `--skip-build` → skip post-create install/build.
|
|
48
|
+
- `--help`, `-h` → print help.
|
|
49
|
+
- After effects: ensures the target directory exists (warns if non-empty, or wipes it when `--reset`), writes shared tooling files (tsconfig, workspace helpers), scaffolds the template, runs `pnpm install`, then builds the new package and its workspace deps (fallbacks: full workspace build, then package-only build). Each scaffolded package includes its own README with variant-specific scripts.
|
|
13
50
|
|
|
14
51
|
## Templates at a glance
|
|
15
52
|
| id | default dir | summary | key files |
|
|
@@ -23,20 +60,19 @@ Dev dependency built for Codex agents: scaffold RRRoutes packages, read the gene
|
|
|
23
60
|
|
|
24
61
|
Use `pnpm manager-cli create --describe <variant>` to print the scripts, key files, and notes for any template before scaffolding.
|
|
25
62
|
|
|
26
|
-
##
|
|
27
|
-
-
|
|
28
|
-
|
|
29
|
-
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
-
|
|
34
|
-
|
|
35
|
-
- Non-interactive publish: `pnpm manager-cli <pkg|all> --non-interactive --bump patch` (see CLI prompts for flags like `--sync` and `--tag`).
|
|
63
|
+
## Agent-ready command crib
|
|
64
|
+
- Scaffold server without prompts:
|
|
65
|
+
`pnpm manager-cli create --variant rrr-server --dir packages/api --name @scope/api --contract @scope/contract --skip-install`
|
|
66
|
+
- Scaffold Expo client:
|
|
67
|
+
`pnpm manager-cli create --variant rrr-client --dir packages/mobile --name @scope/mobile --contract @scope/contract --client-kind expo-react-native`
|
|
68
|
+
- Publish a single package headlessly:
|
|
69
|
+
`pnpm manager-cli @scope/api --non-interactive --bump minor --tag next`
|
|
70
|
+
- Publish everything with a synced version:
|
|
71
|
+
`pnpm manager-cli all --non-interactive --sync 1.2.3 --provenance`
|
|
36
72
|
|
|
37
73
|
## Notes
|
|
38
|
-
-
|
|
39
|
-
-
|
|
74
|
+
- Helper scripts register the bundled `ts-node/esm` loader so the CLI works even when dependencies are hoisted.
|
|
75
|
+
- Install/build after scaffolding can be skipped when you need full control (`--skip-install` / `--skip-build`).
|
|
40
76
|
|
|
41
77
|
## Tests
|
|
42
78
|
- `pnpm test` verifies the helper CLI loader registration.
|
|
@@ -4,9 +4,10 @@ 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 { runHelperCli } from '../helper-cli.js';
|
|
7
8
|
import { loadPackages } from '../packages.js';
|
|
8
9
|
import { SCRIPT_DESCRIPTIONS, workspaceRoot, ensureWorkspaceToolingFiles, } from './shared.js';
|
|
9
|
-
import { clientVariant } from './variants/client.js';
|
|
10
|
+
import { clientVariant, CLIENT_KIND_OPTIONS, DEFAULT_CLIENT_KIND, normalizeClientKind, } from './variants/client.js';
|
|
10
11
|
import { contractVariant } from './variants/contract.js';
|
|
11
12
|
import { dockerVariant } from './variants/docker.js';
|
|
12
13
|
import { emptyVariant } from './variants/empty.js';
|
|
@@ -25,6 +26,36 @@ function derivePackageName(targetDir) {
|
|
|
25
26
|
const base = path.basename(targetDir) || 'rrr-package';
|
|
26
27
|
return base;
|
|
27
28
|
}
|
|
29
|
+
async function promptForContractNameWithHelper(title, options) {
|
|
30
|
+
let selection;
|
|
31
|
+
const scripts = options.map((opt) => ({
|
|
32
|
+
name: opt.label,
|
|
33
|
+
description: opt.meta ?? '',
|
|
34
|
+
emoji: '📦',
|
|
35
|
+
handler: () => {
|
|
36
|
+
selection = opt.value;
|
|
37
|
+
},
|
|
38
|
+
}));
|
|
39
|
+
scripts.push({
|
|
40
|
+
name: 'Enter manually',
|
|
41
|
+
emoji: '⌨️',
|
|
42
|
+
description: 'Type a contract package name',
|
|
43
|
+
handler: async () => {
|
|
44
|
+
const manual = await askLine('Contract package name (e.g. @scope/contract): ');
|
|
45
|
+
selection = manual.trim() || undefined;
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
scripts.push({
|
|
49
|
+
name: 'Skip (no contract)',
|
|
50
|
+
emoji: '⏭️',
|
|
51
|
+
description: 'Continue without a contract dependency',
|
|
52
|
+
handler: () => {
|
|
53
|
+
selection = undefined;
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
await runHelperCli({ title, scripts, argv: [] });
|
|
57
|
+
return selection;
|
|
58
|
+
}
|
|
28
59
|
async function promptForContractName() {
|
|
29
60
|
let packages = [];
|
|
30
61
|
try {
|
|
@@ -34,39 +65,31 @@ async function promptForContractName() {
|
|
|
34
65
|
const message = error instanceof Error ? error.message : 'unknown error discovering packages';
|
|
35
66
|
logGlobal(`Could not auto-discover packages (${message}).`, colors.yellow);
|
|
36
67
|
}
|
|
37
|
-
const
|
|
68
|
+
const contractOptions = packages
|
|
38
69
|
.filter((pkg) => pkg.relativeDir !== '.')
|
|
39
|
-
.map((pkg
|
|
40
|
-
label: `${pkg.name}${pkg.relativeDir ? colors.dim(` (${pkg.relativeDir})`) : ''}`,
|
|
70
|
+
.map((pkg) => ({
|
|
41
71
|
value: pkg.name,
|
|
42
|
-
|
|
72
|
+
label: pkg.name,
|
|
73
|
+
meta: pkg.relativeDir,
|
|
43
74
|
}));
|
|
44
|
-
if (
|
|
75
|
+
if (contractOptions.length === 0) {
|
|
45
76
|
const manual = await askLine('Contract package name? (Enter to skip, e.g. @scope/contract): ');
|
|
46
77
|
return manual.trim() || undefined;
|
|
47
78
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
return manual.trim() || undefined;
|
|
63
|
-
}
|
|
64
|
-
const idx = Number.parseInt(choice, 10);
|
|
65
|
-
if (Number.isInteger(idx) && idx >= 1 && idx <= candidates.length) {
|
|
66
|
-
return candidates[idx - 1].value;
|
|
67
|
-
}
|
|
68
|
-
console.log(colors.yellow('Enter a number, "m" to type a name, or "s" to skip.'));
|
|
69
|
-
}
|
|
79
|
+
return promptForContractNameWithHelper('Select a contract package (or skip)', contractOptions);
|
|
80
|
+
}
|
|
81
|
+
async function promptForClientKind(existing) {
|
|
82
|
+
const defaultKind = normalizeClientKind(existing) ?? DEFAULT_CLIENT_KIND;
|
|
83
|
+
const optionLabels = CLIENT_KIND_OPTIONS.map((opt, idx) => `[${idx + 1}] ${opt.label}`).join(', ');
|
|
84
|
+
const answer = await askLine(`Client template? ${optionLabels} (${defaultKind}): `);
|
|
85
|
+
const normalized = normalizeClientKind(answer);
|
|
86
|
+
if (normalized)
|
|
87
|
+
return normalized;
|
|
88
|
+
if (answer === '1')
|
|
89
|
+
return CLIENT_KIND_OPTIONS[0]?.id ?? defaultKind;
|
|
90
|
+
if (answer === '2')
|
|
91
|
+
return CLIENT_KIND_OPTIONS[1]?.id ?? defaultKind;
|
|
92
|
+
return defaultKind;
|
|
70
93
|
}
|
|
71
94
|
function resolveVariant(key) {
|
|
72
95
|
if (!key)
|
|
@@ -141,6 +164,10 @@ function parseCreateCliArgs(argv) {
|
|
|
141
164
|
options.contractName = argv[++i];
|
|
142
165
|
continue;
|
|
143
166
|
}
|
|
167
|
+
if (arg === '--client-kind' || arg === '--client') {
|
|
168
|
+
options.clientKind = argv[++i];
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
144
171
|
if (arg === '--skip-install') {
|
|
145
172
|
options.skipInstall = true;
|
|
146
173
|
continue;
|
|
@@ -180,6 +207,7 @@ function printCreateHelp() {
|
|
|
180
207
|
console.log(' --dir, --path, -p Target directory (skips path prompt)');
|
|
181
208
|
console.log(' --name, -n Package name (skips name prompt)');
|
|
182
209
|
console.log(' --contract Contract import to inject (server/client variants)');
|
|
210
|
+
console.log(' --client-kind Client stack (vite-react | expo-react-native)');
|
|
183
211
|
console.log(' --reset Remove the target directory if it already exists');
|
|
184
212
|
console.log(' --skip-install Do not run pnpm install after scaffolding');
|
|
185
213
|
console.log(' --skip-build Skip build after scaffolding');
|
|
@@ -421,11 +449,20 @@ async function gatherTarget(initial = {}) {
|
|
|
421
449
|
contractName === undefined) {
|
|
422
450
|
contractName = await promptForContractName();
|
|
423
451
|
}
|
|
452
|
+
let clientKind = initial.clientKind;
|
|
453
|
+
if (variant.id === 'rrr-client' || variant.id === 'rrr-fullstack') {
|
|
454
|
+
const normalizedKind = normalizeClientKind(clientKind);
|
|
455
|
+
clientKind = normalizedKind ?? clientKind;
|
|
456
|
+
if (!clientKind) {
|
|
457
|
+
clientKind = await promptForClientKind();
|
|
458
|
+
}
|
|
459
|
+
}
|
|
424
460
|
return {
|
|
425
461
|
variant,
|
|
426
462
|
targetDir,
|
|
427
463
|
pkgName,
|
|
428
464
|
contractName,
|
|
465
|
+
clientKind,
|
|
429
466
|
reset: initial.reset,
|
|
430
467
|
};
|
|
431
468
|
}
|
|
@@ -438,6 +475,7 @@ export async function createRrrPackage(options = {}) {
|
|
|
438
475
|
targetDir: target.targetDir,
|
|
439
476
|
pkgName: target.pkgName,
|
|
440
477
|
contractName: target.contractName ?? options.contractName,
|
|
478
|
+
clientKind: target.clientKind ?? options.clientKind,
|
|
441
479
|
});
|
|
442
480
|
await postCreateTasks(target.targetDir, {
|
|
443
481
|
skipInstall: options.skipInstall,
|
|
@@ -476,6 +514,7 @@ export async function runCreatePackageCli(argv) {
|
|
|
476
514
|
targetDir,
|
|
477
515
|
pkgName: parsed.pkgName,
|
|
478
516
|
contractName: parsed.contractName,
|
|
517
|
+
clientKind: normalizeClientKind(parsed.clientKind),
|
|
479
518
|
reset: parsed.reset,
|
|
480
519
|
skipInstall: parsed.skipInstall,
|
|
481
520
|
skipBuild: parsed.skipBuild ?? parsed.skipInstall,
|