@cogenta/cli 0.1.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/dist/bin.d.ts +3 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +31 -0
- package/dist/bin.js.map +1 -0
- package/dist/commands/doctor.d.ts +45 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +147 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/generate.d.ts +19 -0
- package/dist/commands/generate.d.ts.map +1 -0
- package/dist/commands/generate.js +60 -0
- package/dist/commands/generate.js.map +1 -0
- package/dist/commands/import.d.ts +22 -0
- package/dist/commands/import.d.ts.map +1 -0
- package/dist/commands/import.js +77 -0
- package/dist/commands/import.js.map +1 -0
- package/dist/commands/migrate.d.ts +38 -0
- package/dist/commands/migrate.d.ts.map +1 -0
- package/dist/commands/migrate.js +272 -0
- package/dist/commands/migrate.js.map +1 -0
- package/dist/commands/serve.d.ts +81 -0
- package/dist/commands/serve.d.ts.map +1 -0
- package/dist/commands/serve.js +515 -0
- package/dist/commands/serve.js.map +1 -0
- package/dist/commands/skin.d.ts +24 -0
- package/dist/commands/skin.d.ts.map +1 -0
- package/dist/commands/skin.js +199 -0
- package/dist/commands/skin.js.map +1 -0
- package/dist/commands/users.d.ts +22 -0
- package/dist/commands/users.d.ts.map +1 -0
- package/dist/commands/users.js +107 -0
- package/dist/commands/users.js.map +1 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +244 -0
- package/dist/index.js.map +1 -0
- package/dist/output.d.ts +17 -0
- package/dist/output.d.ts.map +1 -0
- package/dist/output.js +39 -0
- package/dist/output.js.map +1 -0
- package/package.json +53 -0
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { readFile, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { dirname, join, resolve as resolvePath } from 'node:path';
|
|
3
|
+
import process from 'node:process';
|
|
4
|
+
import { createAnthropicClient, createGoogleClient, createOpenAiClient, generateSkin, } from '@cogenta/agents';
|
|
5
|
+
import { CogentaError, isCogentaError, loadConfig } from '@cogenta/core';
|
|
6
|
+
import { TOKEN_GROUPS, validateSkin } from '@cogenta/render';
|
|
7
|
+
const TOKENS_FILE = 'theme.tokens.json';
|
|
8
|
+
const USAGE = `Usage
|
|
9
|
+
cogenta skin list Show the site's active skin
|
|
10
|
+
cogenta skin validate <tokens.json> Check a token file against contract D
|
|
11
|
+
cogenta skin apply <tokens.json> Validate, then make it the active skin
|
|
12
|
+
cogenta skin generate --description "…" Generate a skin from a description
|
|
13
|
+
|
|
14
|
+
"generate" needs an LLM provider configured (cogenta.config's llm block, or
|
|
15
|
+
COGENTA_LLM_* environment variables) — the CMS works without one (R2), so
|
|
16
|
+
"skin generate" is the one skin subcommand that refuses without it.
|
|
17
|
+
`;
|
|
18
|
+
async function resolveProjectRoot(options) {
|
|
19
|
+
const env = options.env ?? process.env;
|
|
20
|
+
const loaded = await loadConfig({
|
|
21
|
+
...(options.cwd === undefined ? {} : { cwd: options.cwd }),
|
|
22
|
+
env,
|
|
23
|
+
});
|
|
24
|
+
return loaded.path === null ? resolvePath(options.cwd ?? process.cwd()) : dirname(loaded.path);
|
|
25
|
+
}
|
|
26
|
+
function reportCogentaError(error, stderr) {
|
|
27
|
+
if (isCogentaError(error)) {
|
|
28
|
+
stderr(`${error.code}: ${error.message}\n`);
|
|
29
|
+
if (error.hint !== undefined)
|
|
30
|
+
stderr(`${error.hint}\n`);
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
stderr(`${error instanceof Error ? error.stack : String(error)}\n`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
async function readTokensFile(path) {
|
|
37
|
+
const raw = await readFile(path, 'utf8');
|
|
38
|
+
return JSON.parse(raw);
|
|
39
|
+
}
|
|
40
|
+
/** Validates, then writes — a skin is never applied without passing contract D first. */
|
|
41
|
+
async function applyTokens(tokens, projectRoot) {
|
|
42
|
+
const path = join(projectRoot, TOKENS_FILE);
|
|
43
|
+
await writeFile(path, `${JSON.stringify(tokens, null, 2)}\n`, 'utf8');
|
|
44
|
+
return path;
|
|
45
|
+
}
|
|
46
|
+
async function runList(options) {
|
|
47
|
+
const { out, stderr } = options;
|
|
48
|
+
const projectRoot = await resolveProjectRoot(options);
|
|
49
|
+
const path = join(projectRoot, TOKENS_FILE);
|
|
50
|
+
let candidate;
|
|
51
|
+
try {
|
|
52
|
+
candidate = await readTokensFile(path);
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
stderr(`Could not read ${path}: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
56
|
+
return 1;
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
const tokens = validateSkin(candidate);
|
|
60
|
+
out.heading('Active skin');
|
|
61
|
+
out.line(path);
|
|
62
|
+
for (const group of TOKEN_GROUPS) {
|
|
63
|
+
out.line(`${group}: ${JSON.stringify(tokens[group])}`);
|
|
64
|
+
}
|
|
65
|
+
return 0;
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
reportCogentaError(error, stderr);
|
|
69
|
+
return 1;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
async function runValidate(options) {
|
|
73
|
+
const { out, stderr } = options;
|
|
74
|
+
if (options.file === undefined || options.file.trim().length === 0) {
|
|
75
|
+
stderr(`A file path is required.\n\n${USAGE}`);
|
|
76
|
+
return 2;
|
|
77
|
+
}
|
|
78
|
+
let candidate;
|
|
79
|
+
try {
|
|
80
|
+
candidate = await readTokensFile(options.file);
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
stderr(`Could not read "${options.file}": ${error instanceof Error ? error.message : String(error)}\n`);
|
|
84
|
+
return 1;
|
|
85
|
+
}
|
|
86
|
+
try {
|
|
87
|
+
validateSkin(candidate);
|
|
88
|
+
out.ok(`${options.file} is a valid skin.`);
|
|
89
|
+
return 0;
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
reportCogentaError(error, stderr);
|
|
93
|
+
return 1;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
async function runApply(options) {
|
|
97
|
+
const { out, stderr } = options;
|
|
98
|
+
if (options.file === undefined || options.file.trim().length === 0) {
|
|
99
|
+
stderr(`A file path is required.\n\n${USAGE}`);
|
|
100
|
+
return 2;
|
|
101
|
+
}
|
|
102
|
+
let candidate;
|
|
103
|
+
try {
|
|
104
|
+
candidate = await readTokensFile(options.file);
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
stderr(`Could not read "${options.file}": ${error instanceof Error ? error.message : String(error)}\n`);
|
|
108
|
+
return 1;
|
|
109
|
+
}
|
|
110
|
+
try {
|
|
111
|
+
const tokens = validateSkin(candidate);
|
|
112
|
+
const projectRoot = await resolveProjectRoot(options);
|
|
113
|
+
const written = await applyTokens(tokens, projectRoot);
|
|
114
|
+
out.ok(`Applied. ${written}`);
|
|
115
|
+
return 0;
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
reportCogentaError(error, stderr);
|
|
119
|
+
return 1;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/** The same adapter construction `create-cogenta`'s `llm-setup.ts` uses for its own provider client — kept as a small, local switch rather than a shared dependency, since the CLI's only need is "one client for the configured provider," with its own test seam. */
|
|
123
|
+
function clientFor(provider, apiKey, model, fetchImpl) {
|
|
124
|
+
const config = { apiKey, model, ...(fetchImpl === undefined ? {} : { fetchImpl }) };
|
|
125
|
+
if (provider === 'anthropic')
|
|
126
|
+
return createAnthropicClient(config);
|
|
127
|
+
if (provider === 'openai')
|
|
128
|
+
return createOpenAiClient(config);
|
|
129
|
+
if (provider === 'google')
|
|
130
|
+
return createGoogleClient(config);
|
|
131
|
+
throw new CogentaError({
|
|
132
|
+
code: 'PROVIDER_UNKNOWN',
|
|
133
|
+
message: `No provider named "${provider}" is configured for this site.`,
|
|
134
|
+
hint: 'Set llm.provider in cogenta.config to "anthropic", "openai" or "google".',
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
async function runGenerateSkin(options) {
|
|
138
|
+
const { out, stderr } = options;
|
|
139
|
+
if (options.description === undefined || options.description.trim().length === 0) {
|
|
140
|
+
stderr(`--description is required.\n\n${USAGE}`);
|
|
141
|
+
return 2;
|
|
142
|
+
}
|
|
143
|
+
const env = options.env ?? process.env;
|
|
144
|
+
const loaded = await loadConfig({
|
|
145
|
+
...(options.cwd === undefined ? {} : { cwd: options.cwd }),
|
|
146
|
+
env,
|
|
147
|
+
});
|
|
148
|
+
if (loaded.config.llm === undefined || loaded.config.llm.apiKey === undefined) {
|
|
149
|
+
stderr('No LLM provider is configured for this site — the CMS works without one (R2).\n');
|
|
150
|
+
stderr('Set llm.provider/llm.model in cogenta.config and COGENTA_LLM_API_KEY to use this.\n');
|
|
151
|
+
return 1;
|
|
152
|
+
}
|
|
153
|
+
const projectRoot = loaded.path === null ? resolvePath(options.cwd ?? process.cwd()) : dirname(loaded.path);
|
|
154
|
+
try {
|
|
155
|
+
const client = clientFor(loaded.config.llm.provider, loaded.config.llm.apiKey, loaded.config.llm.model, options.fetchImpl);
|
|
156
|
+
out.detail(`Generating a skin from your description…`);
|
|
157
|
+
const result = await generateSkin({
|
|
158
|
+
client,
|
|
159
|
+
model: loaded.config.llm.model,
|
|
160
|
+
description: options.description,
|
|
161
|
+
blueprintLabel: loaded.config.site.name,
|
|
162
|
+
});
|
|
163
|
+
if (!result.ok) {
|
|
164
|
+
stderr(`${result.attempts} attempt${result.attempts === 1 ? '' : 's'} all failed validation: ${result.reason}\n`);
|
|
165
|
+
stderr('The site keeps its current skin — nothing was written.\n');
|
|
166
|
+
return 1;
|
|
167
|
+
}
|
|
168
|
+
const written = await applyTokens(result.tokens, projectRoot);
|
|
169
|
+
out.ok(`Skin generated and validated in ${result.attempts} attempt${result.attempts === 1 ? '' : 's'}. Applied. ${written}`);
|
|
170
|
+
return 0;
|
|
171
|
+
}
|
|
172
|
+
catch (error) {
|
|
173
|
+
reportCogentaError(error, stderr);
|
|
174
|
+
return 1;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* `cogenta skin` — 0 the operation succeeded (including a valid `validate`).
|
|
179
|
+
* 1 an invalid skin, a missing file, or a real failure. 2 the command line
|
|
180
|
+
* was wrong.
|
|
181
|
+
*/
|
|
182
|
+
export async function runSkin(options) {
|
|
183
|
+
const { stderr } = options;
|
|
184
|
+
if (options.subcommand === undefined) {
|
|
185
|
+
stderr(`cogenta skin needs a subcommand.\n\n${USAGE}`);
|
|
186
|
+
return 2;
|
|
187
|
+
}
|
|
188
|
+
if (options.subcommand === 'list')
|
|
189
|
+
return runList(options);
|
|
190
|
+
if (options.subcommand === 'validate')
|
|
191
|
+
return runValidate(options);
|
|
192
|
+
if (options.subcommand === 'apply')
|
|
193
|
+
return runApply(options);
|
|
194
|
+
if (options.subcommand === 'generate')
|
|
195
|
+
return runGenerateSkin(options);
|
|
196
|
+
stderr(`Unknown subcommand "${options.subcommand}".\n\n${USAGE}`);
|
|
197
|
+
return 2;
|
|
198
|
+
}
|
|
199
|
+
//# sourceMappingURL=skin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skin.js","sourceRoot":"","sources":["../../src/commands/skin.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type Logger } from '@cogenta/core';
|
|
2
|
+
import type { Output, Writer } from '../output.js';
|
|
3
|
+
export type UsersSubcommand = 'create';
|
|
4
|
+
export interface UsersOptions {
|
|
5
|
+
readonly subcommand: string | undefined;
|
|
6
|
+
readonly cwd?: string;
|
|
7
|
+
readonly env?: Record<string, string | undefined>;
|
|
8
|
+
readonly logger?: Logger;
|
|
9
|
+
readonly out: Output;
|
|
10
|
+
readonly stderr: Writer;
|
|
11
|
+
readonly email?: string;
|
|
12
|
+
readonly roles?: string;
|
|
13
|
+
readonly admin?: boolean;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Runs one `users` subcommand and returns its exit code.
|
|
17
|
+
*
|
|
18
|
+
* 0 succeeded, 1 the database or the store said no, 2 the command line was
|
|
19
|
+
* wrong — same convention as `migrate` and `doctor`.
|
|
20
|
+
*/
|
|
21
|
+
export declare function runUsers(options: UsersOptions): Promise<number>;
|
|
22
|
+
//# sourceMappingURL=users.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"users.d.ts","sourceRoot":"","sources":["../../src/commands/users.ts"],"names":[],"mappings":"AAGA,OAAO,EAKL,KAAK,MAAM,EAEZ,MAAM,eAAe,CAAA;AACtB,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AAElD,MAAM,MAAM,eAAe,GAAG,QAAQ,CAAA;AAEtC,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,CAAA;IACvC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAA;IACjD,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CACzB;AAuDD;;;;;GAKG;AACH,wBAAsB,QAAQ,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,CAsDrE"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { randomBytes } from 'node:crypto';
|
|
2
|
+
import process from 'node:process';
|
|
3
|
+
import { createCredentialStore, createUserStore, ensureAuthTables } from '@cogenta/auth';
|
|
4
|
+
import { createDatabaseRegistry, createLogger, isCogentaError, loadConfig, } from '@cogenta/core';
|
|
5
|
+
const USAGE = `Usage
|
|
6
|
+
cogenta users create --email <email> [--roles <role,role>] [--admin]
|
|
7
|
+
|
|
8
|
+
The very first admin account is created this way — there is no other way in,
|
|
9
|
+
since the admin UI needs one to sign in to. A random password is generated and
|
|
10
|
+
printed once; it is never stored anywhere but the credential table's hash.
|
|
11
|
+
|
|
12
|
+
Options
|
|
13
|
+
--admin Shorthand for --roles admin
|
|
14
|
+
`;
|
|
15
|
+
/** Base64url, so it is safe to read off a terminal and paste back with no escaping. */
|
|
16
|
+
function generatePassword() {
|
|
17
|
+
return randomBytes(24).toString('base64url');
|
|
18
|
+
}
|
|
19
|
+
function parseRoles(options) {
|
|
20
|
+
if (options.admin === true && options.roles !== undefined) {
|
|
21
|
+
return { error: '--admin and --roles are mutually exclusive — --admin already means admin.' };
|
|
22
|
+
}
|
|
23
|
+
if (options.admin === true)
|
|
24
|
+
return ['admin'];
|
|
25
|
+
if (options.roles === undefined) {
|
|
26
|
+
return { error: 'Name at least one role with --roles, or pass --admin for the first account.' };
|
|
27
|
+
}
|
|
28
|
+
const roles = options.roles
|
|
29
|
+
.split(',')
|
|
30
|
+
.map((role) => role.trim())
|
|
31
|
+
.filter((role) => role.length > 0);
|
|
32
|
+
if (roles.length === 0) {
|
|
33
|
+
return { error: '--roles was given but named no role.' };
|
|
34
|
+
}
|
|
35
|
+
return roles;
|
|
36
|
+
}
|
|
37
|
+
async function withDatabase(options, logger, use) {
|
|
38
|
+
const env = options.env ?? process.env;
|
|
39
|
+
const loaded = await loadConfig({
|
|
40
|
+
...(options.cwd === undefined ? {} : { cwd: options.cwd }),
|
|
41
|
+
env,
|
|
42
|
+
});
|
|
43
|
+
const selection = await createDatabaseRegistry({ logger }).select(loaded.config.database);
|
|
44
|
+
try {
|
|
45
|
+
return await use(selection.instance);
|
|
46
|
+
}
|
|
47
|
+
finally {
|
|
48
|
+
await selection.dispose();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Runs one `users` subcommand and returns its exit code.
|
|
53
|
+
*
|
|
54
|
+
* 0 succeeded, 1 the database or the store said no, 2 the command line was
|
|
55
|
+
* wrong — same convention as `migrate` and `doctor`.
|
|
56
|
+
*/
|
|
57
|
+
export async function runUsers(options) {
|
|
58
|
+
const { out, stderr } = options;
|
|
59
|
+
if (options.subcommand === undefined) {
|
|
60
|
+
stderr(`cogenta users needs a subcommand.\n\n${USAGE}`);
|
|
61
|
+
return 2;
|
|
62
|
+
}
|
|
63
|
+
if (options.subcommand !== 'create') {
|
|
64
|
+
stderr(`Unknown subcommand "${options.subcommand}".\n\n${USAGE}`);
|
|
65
|
+
return 2;
|
|
66
|
+
}
|
|
67
|
+
if (options.email === undefined || options.email.trim().length === 0) {
|
|
68
|
+
stderr(`--email is required.\n\n${USAGE}`);
|
|
69
|
+
return 2;
|
|
70
|
+
}
|
|
71
|
+
const roles = parseRoles(options);
|
|
72
|
+
if ('error' in roles) {
|
|
73
|
+
stderr(`${roles.error}\n\n${USAGE}`);
|
|
74
|
+
return 2;
|
|
75
|
+
}
|
|
76
|
+
const logger = options.logger ?? createLogger({ level: 'silent' });
|
|
77
|
+
const password = generatePassword();
|
|
78
|
+
try {
|
|
79
|
+
return await withDatabase(options, logger, async (db) => {
|
|
80
|
+
await ensureAuthTables(db);
|
|
81
|
+
const users = createUserStore(db);
|
|
82
|
+
const credentials = createCredentialStore(db);
|
|
83
|
+
const user = await users.create({ email: options.email, roles });
|
|
84
|
+
await credentials.setPassword(user.id, password);
|
|
85
|
+
out.heading('User created');
|
|
86
|
+
out.ok(`${user.email} — ${user.roles.join(', ')}`);
|
|
87
|
+
out.line();
|
|
88
|
+
out.line(`Password: ${password}`);
|
|
89
|
+
out.line();
|
|
90
|
+
out.warn('This password is shown once. It is stored only as a salted hash.');
|
|
91
|
+
out.detail('A role that can publish, or admin, will be asked to set up a second factor at first sign-in.');
|
|
92
|
+
return 0;
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
if (isCogentaError(error)) {
|
|
97
|
+
stderr(`${error.code}: ${error.message}\n`);
|
|
98
|
+
if (error.hint !== undefined)
|
|
99
|
+
stderr(`${error.hint}\n`);
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
stderr(`${error instanceof Error ? error.stack : String(error)}\n`);
|
|
103
|
+
}
|
|
104
|
+
return 1;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=users.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"users.js","sourceRoot":"","sources":["../../src/commands/users.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { type Writer } from './output.js';
|
|
2
|
+
export type { DoctorCheck, DoctorOptions, DoctorReport } from './commands/doctor.js';
|
|
3
|
+
export { formatDoctorReport, runDoctor } from './commands/doctor.js';
|
|
4
|
+
export type { GenerateOptions, GenerateSubcommand } from './commands/generate.js';
|
|
5
|
+
export { runGenerate } from './commands/generate.js';
|
|
6
|
+
export type { ImportOptions, ImportSubcommand } from './commands/import.js';
|
|
7
|
+
export { runImport } from './commands/import.js';
|
|
8
|
+
export type { MigrateOptions, MigrateSubcommand } from './commands/migrate.js';
|
|
9
|
+
export { loadMigrations, MIGRATIONS_DIRECTORY, runMigrate } from './commands/migrate.js';
|
|
10
|
+
export type { ServeOptions } from './commands/serve.js';
|
|
11
|
+
export { loadCollections, runServe } from './commands/serve.js';
|
|
12
|
+
export type { SkinOptions, SkinSubcommand } from './commands/skin.js';
|
|
13
|
+
export { runSkin } from './commands/skin.js';
|
|
14
|
+
export type { UsersOptions, UsersSubcommand } from './commands/users.js';
|
|
15
|
+
export { runUsers } from './commands/users.js';
|
|
16
|
+
export type { Output, Writer } from './output.js';
|
|
17
|
+
export { createOutput, shouldUseColour } from './output.js';
|
|
18
|
+
export interface RunOptions {
|
|
19
|
+
readonly argv: readonly string[];
|
|
20
|
+
readonly stdout?: Writer;
|
|
21
|
+
readonly stderr?: Writer;
|
|
22
|
+
readonly env?: Record<string, string | undefined>;
|
|
23
|
+
readonly isTty?: boolean;
|
|
24
|
+
readonly version?: string;
|
|
25
|
+
/** Stops `serve` when aborted. Ignored by every other command. */
|
|
26
|
+
readonly signal?: AbortSignal;
|
|
27
|
+
/** `serve` only: reports the bound address once listening (tests need the OS-assigned port). */
|
|
28
|
+
readonly onListening?: (address: {
|
|
29
|
+
port: number;
|
|
30
|
+
host: string;
|
|
31
|
+
}) => void;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Runs one command and returns its exit code.
|
|
35
|
+
*
|
|
36
|
+
* Nothing here calls `process.exit` or writes to a stream directly: the streams
|
|
37
|
+
* are injected, so the whole CLI is testable without spawning a process or
|
|
38
|
+
* capturing stdout.
|
|
39
|
+
*/
|
|
40
|
+
export declare function run(options: RunOptions): Promise<number>;
|
|
41
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAUA,OAAO,EAAiC,KAAK,MAAM,EAAE,MAAM,aAAa,CAAA;AAExE,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AACpF,OAAO,EAAE,kBAAkB,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAA;AACpE,YAAY,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAA;AACjF,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAA;AACpD,YAAY,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AAC3E,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAA;AAChD,YAAY,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;AAC9E,OAAO,EAAE,cAAc,EAAE,oBAAoB,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAA;AACxF,YAAY,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AACvD,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAA;AAC/D,YAAY,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AACrE,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAA;AAC5C,YAAY,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAA;AACxE,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAA;AAC9C,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACjD,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAkD3D,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAA;IAChC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAA;IACjD,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAA;IACxB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;IACzB,kEAAkE;IAClE,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAA;IAC7B,gGAAgG;IAChG,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAA;CACzE;AAED;;;;;;GAMG;AACH,wBAAsB,GAAG,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAuL9D"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import process from 'node:process';
|
|
2
|
+
import { parseArgs } from 'node:util';
|
|
3
|
+
import { createLogger, isCogentaError } from '@cogenta/core';
|
|
4
|
+
import { formatDoctorReport, runDoctor } from './commands/doctor.js';
|
|
5
|
+
import { runGenerate } from './commands/generate.js';
|
|
6
|
+
import { runImport } from './commands/import.js';
|
|
7
|
+
import { runMigrate } from './commands/migrate.js';
|
|
8
|
+
import { runServe } from './commands/serve.js';
|
|
9
|
+
import { runSkin } from './commands/skin.js';
|
|
10
|
+
import { runUsers } from './commands/users.js';
|
|
11
|
+
import { createOutput, shouldUseColour } from './output.js';
|
|
12
|
+
export { formatDoctorReport, runDoctor } from './commands/doctor.js';
|
|
13
|
+
export { runGenerate } from './commands/generate.js';
|
|
14
|
+
export { runImport } from './commands/import.js';
|
|
15
|
+
export { loadMigrations, MIGRATIONS_DIRECTORY, runMigrate } from './commands/migrate.js';
|
|
16
|
+
export { loadCollections, runServe } from './commands/serve.js';
|
|
17
|
+
export { runSkin } from './commands/skin.js';
|
|
18
|
+
export { runUsers } from './commands/users.js';
|
|
19
|
+
export { createOutput, shouldUseColour } from './output.js';
|
|
20
|
+
const USAGE = `cogenta — the command line for a Cogenta site
|
|
21
|
+
|
|
22
|
+
Usage
|
|
23
|
+
cogenta <command> [options]
|
|
24
|
+
|
|
25
|
+
Commands
|
|
26
|
+
doctor Report which driver is running for each need, and why
|
|
27
|
+
migrate status List every migration and whether it ran here
|
|
28
|
+
migrate up Apply the pending migrations
|
|
29
|
+
migrate down Revert applied migrations
|
|
30
|
+
users create Create a user — the first admin account is made this way
|
|
31
|
+
import wordpress <file.xml> Import a WordPress WXR export, with a report
|
|
32
|
+
generate types Write TypeScript declarations for the content schema
|
|
33
|
+
skin list Show the site's active skin
|
|
34
|
+
skin validate <tokens.json> Check a token file against contract D
|
|
35
|
+
skin apply <tokens.json> Validate, then make it the active skin
|
|
36
|
+
skin generate --description "…" Generate a skin from a description
|
|
37
|
+
serve, dev Run the content and auth API over HTTP
|
|
38
|
+
help Show this message
|
|
39
|
+
version Print the version
|
|
40
|
+
|
|
41
|
+
Not built yet: build, backup, upgrade, deploy, theme, agent, and
|
|
42
|
+
generate schema/generate migrations — see CLAUDE.md for why each is
|
|
43
|
+
deferred rather than stubbed.
|
|
44
|
+
|
|
45
|
+
Options
|
|
46
|
+
--cwd <path> Run as if from this directory
|
|
47
|
+
--no-color Never colour the output (NO_COLOR is honoured too)
|
|
48
|
+
--verbose Send structured driver logs to stderr
|
|
49
|
+
--out <path> generate types: where to write the declarations
|
|
50
|
+
--description <text> skin generate: free text describing the site
|
|
51
|
+
|
|
52
|
+
Migration options
|
|
53
|
+
--to <id> Stop at this migration, inclusive
|
|
54
|
+
--steps <n> How many migrations "migrate down" reverts (default 1)
|
|
55
|
+
--confirm-destructive The impact of every destructive migration has been read
|
|
56
|
+
--backup-verified A backup was taken and verified to restore
|
|
57
|
+
|
|
58
|
+
User options
|
|
59
|
+
--email <email> The new user's email
|
|
60
|
+
--roles <role,role> Comma-separated role names
|
|
61
|
+
--admin Shorthand for --roles admin
|
|
62
|
+
|
|
63
|
+
Serve options
|
|
64
|
+
--port <n> Port to listen on (default 4000)
|
|
65
|
+
--host <host> Host to bind to (default 127.0.0.1)
|
|
66
|
+
`;
|
|
67
|
+
/**
|
|
68
|
+
* Runs one command and returns its exit code.
|
|
69
|
+
*
|
|
70
|
+
* Nothing here calls `process.exit` or writes to a stream directly: the streams
|
|
71
|
+
* are injected, so the whole CLI is testable without spawning a process or
|
|
72
|
+
* capturing stdout.
|
|
73
|
+
*/
|
|
74
|
+
export async function run(options) {
|
|
75
|
+
const env = options.env ?? process.env;
|
|
76
|
+
const stdout = options.stdout ?? ((text) => void process.stdout.write(text));
|
|
77
|
+
const stderr = options.stderr ?? ((text) => void process.stderr.write(text));
|
|
78
|
+
let parsed;
|
|
79
|
+
try {
|
|
80
|
+
parsed = parseArgs({
|
|
81
|
+
args: [...options.argv],
|
|
82
|
+
allowPositionals: true,
|
|
83
|
+
strict: true,
|
|
84
|
+
options: {
|
|
85
|
+
cwd: { type: 'string' },
|
|
86
|
+
'no-color': { type: 'boolean' },
|
|
87
|
+
verbose: { type: 'boolean' },
|
|
88
|
+
help: { type: 'boolean', short: 'h' },
|
|
89
|
+
version: { type: 'boolean', short: 'v' },
|
|
90
|
+
to: { type: 'string' },
|
|
91
|
+
steps: { type: 'string' },
|
|
92
|
+
'confirm-destructive': { type: 'boolean' },
|
|
93
|
+
'backup-verified': { type: 'boolean' },
|
|
94
|
+
email: { type: 'string' },
|
|
95
|
+
roles: { type: 'string' },
|
|
96
|
+
admin: { type: 'boolean' },
|
|
97
|
+
port: { type: 'string' },
|
|
98
|
+
host: { type: 'string' },
|
|
99
|
+
out: { type: 'string' },
|
|
100
|
+
description: { type: 'string' },
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
stderr(`${error instanceof Error ? error.message : String(error)}\n\n${USAGE}`);
|
|
106
|
+
return 2;
|
|
107
|
+
}
|
|
108
|
+
const colour = parsed.values['no-color'] === true ? false : shouldUseColour(env, options.isTty ?? false);
|
|
109
|
+
const out = createOutput(stdout, colour);
|
|
110
|
+
const command = parsed.positionals[0] ?? (parsed.values.version === true ? 'version' : 'help');
|
|
111
|
+
if (parsed.values.help === true || command === 'help') {
|
|
112
|
+
stdout(USAGE);
|
|
113
|
+
return 0;
|
|
114
|
+
}
|
|
115
|
+
if (command === 'version') {
|
|
116
|
+
stdout(`${options.version ?? '0.0.0'}\n`);
|
|
117
|
+
return 0;
|
|
118
|
+
}
|
|
119
|
+
const verboseLogger = parsed.values.verbose === true
|
|
120
|
+
? createLogger({ level: 'debug', destination: stderr })
|
|
121
|
+
: undefined;
|
|
122
|
+
if (command === 'migrate') {
|
|
123
|
+
// `--steps` is a string here because parseArgs has no number type. A
|
|
124
|
+
// non-number is a usage error, not a silent 0 that would revert nothing.
|
|
125
|
+
let steps;
|
|
126
|
+
if (typeof parsed.values.steps === 'string') {
|
|
127
|
+
steps = Number(parsed.values.steps);
|
|
128
|
+
if (!Number.isInteger(steps) || steps < 1) {
|
|
129
|
+
stderr(`--steps must be a whole number of migrations, not "${parsed.values.steps}".\n`);
|
|
130
|
+
return 2;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return runMigrate({
|
|
134
|
+
subcommand: parsed.positionals[1],
|
|
135
|
+
out,
|
|
136
|
+
stderr,
|
|
137
|
+
env,
|
|
138
|
+
...(typeof parsed.values.cwd === 'string' ? { cwd: parsed.values.cwd } : {}),
|
|
139
|
+
...(typeof parsed.values.to === 'string' ? { to: parsed.values.to } : {}),
|
|
140
|
+
...(steps === undefined ? {} : { steps }),
|
|
141
|
+
...(parsed.values['confirm-destructive'] === true ? { confirmDestructive: true } : {}),
|
|
142
|
+
...(parsed.values['backup-verified'] === true ? { backupVerified: true } : {}),
|
|
143
|
+
...(verboseLogger === undefined ? {} : { logger: verboseLogger }),
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
if (command === 'users') {
|
|
147
|
+
return runUsers({
|
|
148
|
+
subcommand: parsed.positionals[1],
|
|
149
|
+
out,
|
|
150
|
+
stderr,
|
|
151
|
+
env,
|
|
152
|
+
...(typeof parsed.values.cwd === 'string' ? { cwd: parsed.values.cwd } : {}),
|
|
153
|
+
...(typeof parsed.values.email === 'string' ? { email: parsed.values.email } : {}),
|
|
154
|
+
...(typeof parsed.values.roles === 'string' ? { roles: parsed.values.roles } : {}),
|
|
155
|
+
...(parsed.values.admin === true ? { admin: true } : {}),
|
|
156
|
+
...(verboseLogger === undefined ? {} : { logger: verboseLogger }),
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
if (command === 'import') {
|
|
160
|
+
return runImport({
|
|
161
|
+
subcommand: parsed.positionals[1],
|
|
162
|
+
file: parsed.positionals[2],
|
|
163
|
+
out,
|
|
164
|
+
stderr,
|
|
165
|
+
env,
|
|
166
|
+
...(typeof parsed.values.cwd === 'string' ? { cwd: parsed.values.cwd } : {}),
|
|
167
|
+
...(verboseLogger === undefined ? {} : { logger: verboseLogger }),
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
if (command === 'generate') {
|
|
171
|
+
return runGenerate({
|
|
172
|
+
subcommand: parsed.positionals[1],
|
|
173
|
+
out,
|
|
174
|
+
stderr,
|
|
175
|
+
env,
|
|
176
|
+
...(typeof parsed.values.cwd === 'string' ? { cwd: parsed.values.cwd } : {}),
|
|
177
|
+
...(typeof parsed.values.out === 'string' ? { outFile: parsed.values.out } : {}),
|
|
178
|
+
...(verboseLogger === undefined ? {} : { logger: verboseLogger }),
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
if (command === 'skin') {
|
|
182
|
+
return runSkin({
|
|
183
|
+
subcommand: parsed.positionals[1],
|
|
184
|
+
file: parsed.positionals[2],
|
|
185
|
+
out,
|
|
186
|
+
stderr,
|
|
187
|
+
env,
|
|
188
|
+
...(typeof parsed.values.cwd === 'string' ? { cwd: parsed.values.cwd } : {}),
|
|
189
|
+
...(typeof parsed.values.description === 'string'
|
|
190
|
+
? { description: parsed.values.description }
|
|
191
|
+
: {}),
|
|
192
|
+
...(verboseLogger === undefined ? {} : { logger: verboseLogger }),
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
if (command === 'serve' || command === 'dev') {
|
|
196
|
+
let port;
|
|
197
|
+
if (typeof parsed.values.port === 'string') {
|
|
198
|
+
port = Number(parsed.values.port);
|
|
199
|
+
if (!Number.isInteger(port) || port < 0 || port > 65535) {
|
|
200
|
+
stderr(`--port must be a whole number between 0 and 65535, not "${parsed.values.port}".\n`);
|
|
201
|
+
return 2;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return runServe({
|
|
205
|
+
out,
|
|
206
|
+
stderr,
|
|
207
|
+
env,
|
|
208
|
+
...(typeof parsed.values.cwd === 'string' ? { cwd: parsed.values.cwd } : {}),
|
|
209
|
+
...(port === undefined ? {} : { port }),
|
|
210
|
+
...(typeof parsed.values.host === 'string' ? { host: parsed.values.host } : {}),
|
|
211
|
+
...(verboseLogger === undefined ? {} : { logger: verboseLogger }),
|
|
212
|
+
...(options.signal === undefined ? {} : { signal: options.signal }),
|
|
213
|
+
...(options.onListening === undefined ? {} : { onListening: options.onListening }),
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
if (command !== 'doctor') {
|
|
217
|
+
stderr(`Unknown command "${command}".\n\n${USAGE}`);
|
|
218
|
+
return 2;
|
|
219
|
+
}
|
|
220
|
+
try {
|
|
221
|
+
const report = await runDoctor({
|
|
222
|
+
env,
|
|
223
|
+
...(typeof parsed.values.cwd === 'string' ? { cwd: parsed.values.cwd } : {}),
|
|
224
|
+
// Structured logs go to stderr so the report on stdout stays readable
|
|
225
|
+
// when it is piped.
|
|
226
|
+
...(verboseLogger === undefined ? {} : { logger: verboseLogger }),
|
|
227
|
+
});
|
|
228
|
+
formatDoctorReport(report, out);
|
|
229
|
+
return report.problems.length === 0 ? 0 : 1;
|
|
230
|
+
}
|
|
231
|
+
catch (error) {
|
|
232
|
+
// Anything that reaches here is a bug in doctor itself, not a diagnosis.
|
|
233
|
+
if (isCogentaError(error)) {
|
|
234
|
+
stderr(`${error.code}: ${error.message}\n`);
|
|
235
|
+
if (error.hint !== undefined)
|
|
236
|
+
stderr(`${error.hint}\n`);
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
stderr(`${error instanceof Error ? error.stack : String(error)}\n`);
|
|
240
|
+
}
|
|
241
|
+
return 1;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
//# sourceMappingURL=index.js.map
|