@assemora/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/LICENSE +202 -0
- package/README.md +272 -0
- package/bin.mjs +31 -0
- package/dist/args.d.ts +35 -0
- package/dist/args.d.ts.map +1 -0
- package/dist/args.js +104 -0
- package/dist/args.js.map +1 -0
- package/dist/bin.d.ts +3 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +11 -0
- package/dist/bin.js.map +1 -0
- package/dist/commands/agents.d.ts +50 -0
- package/dist/commands/agents.d.ts.map +1 -0
- package/dist/commands/agents.js +169 -0
- package/dist/commands/agents.js.map +1 -0
- package/dist/commands/artifacts.d.ts +5 -0
- package/dist/commands/artifacts.d.ts.map +1 -0
- package/dist/commands/artifacts.js +126 -0
- package/dist/commands/artifacts.js.map +1 -0
- package/dist/commands/console.d.ts +25 -0
- package/dist/commands/console.d.ts.map +1 -0
- package/dist/commands/console.js +99 -0
- package/dist/commands/console.js.map +1 -0
- package/dist/commands/db.d.ts +48 -0
- package/dist/commands/db.d.ts.map +1 -0
- package/dist/commands/db.js +585 -0
- package/dist/commands/db.js.map +1 -0
- package/dist/commands/index.d.ts +21 -0
- package/dist/commands/index.d.ts.map +1 -0
- package/dist/commands/index.js +21 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/commands/inspect.d.ts +8 -0
- package/dist/commands/inspect.d.ts.map +1 -0
- package/dist/commands/inspect.js +280 -0
- package/dist/commands/inspect.js.map +1 -0
- package/dist/commands/make.d.ts +29 -0
- package/dist/commands/make.d.ts.map +1 -0
- package/dist/commands/make.js +393 -0
- package/dist/commands/make.js.map +1 -0
- package/dist/commands/mcp.d.ts +19 -0
- package/dist/commands/mcp.d.ts.map +1 -0
- package/dist/commands/mcp.js +116 -0
- package/dist/commands/mcp.js.map +1 -0
- package/dist/commands/new.d.ts +20 -0
- package/dist/commands/new.d.ts.map +1 -0
- package/dist/commands/new.js +83 -0
- package/dist/commands/new.js.map +1 -0
- package/dist/commands/run.d.ts +20 -0
- package/dist/commands/run.d.ts.map +1 -0
- package/dist/commands/run.js +355 -0
- package/dist/commands/run.js.map +1 -0
- package/dist/commands/watchdog.d.ts +2 -0
- package/dist/commands/watchdog.d.ts.map +1 -0
- package/dist/commands/watchdog.js +74 -0
- package/dist/commands/watchdog.js.map +1 -0
- package/dist/config.d.ts +89 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +156 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +133 -0
- package/dist/index.js.map +1 -0
- package/dist/output.d.ts +96 -0
- package/dist/output.d.ts.map +1 -0
- package/dist/output.js +181 -0
- package/dist/output.js.map +1 -0
- package/dist/project.d.ts +44 -0
- package/dist/project.d.ts.map +1 -0
- package/dist/project.js +102 -0
- package/dist/project.js.map +1 -0
- package/dist/registry.d.ts +48 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +74 -0
- package/dist/registry.js.map +1 -0
- package/package.json +49 -0
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `make:*` — the six generators of SPEC.md §77.
|
|
3
|
+
*
|
|
4
|
+
* A generated file is the first Assemora code most developers will ever read, so the
|
|
5
|
+
* bar is not "it saves typing": it is that the file compiles, runs, and shows the
|
|
6
|
+
* declaration the way the framework's own examples write it. A generator that emits
|
|
7
|
+
* code the framework rejects is worse than no generator, and
|
|
8
|
+
* `make.test.ts` compiles what these templates produce against the real packages
|
|
9
|
+
* rather than trusting them.
|
|
10
|
+
*
|
|
11
|
+
* The CLI writes source files against APIs it deliberately does not depend on —
|
|
12
|
+
* `@assemora/resources`, `@assemora/pages`, `@assemora/auth` (ADR-0021). That is a
|
|
13
|
+
* template problem rather than an architectural one: nothing here imports those
|
|
14
|
+
* packages, it only writes text that names them.
|
|
15
|
+
*/
|
|
16
|
+
import { mkdir, stat, writeFile } from 'node:fs/promises';
|
|
17
|
+
import { dirname, join, relative } from 'node:path';
|
|
18
|
+
import { ConflictError } from '@assemora/core';
|
|
19
|
+
import { bool } from '../args.js';
|
|
20
|
+
import { loadConfig } from '../config.js';
|
|
21
|
+
import { detail, fail, ok, warn } from '../output.js';
|
|
22
|
+
import { defineCommand, register } from '../registry.js';
|
|
23
|
+
/**
|
|
24
|
+
* The words in a name, however it was typed.
|
|
25
|
+
*
|
|
26
|
+
* `BlogPost`, `blog_post`, `blog-post` and `blogPost` are the same three syllables,
|
|
27
|
+
* and a person typing at a terminal should not have to remember which spelling the
|
|
28
|
+
* generator wanted. Everything below is derived from these words, so all four
|
|
29
|
+
* spellings produce a byte-identical file.
|
|
30
|
+
*/
|
|
31
|
+
const wordsOf = (name) => name
|
|
32
|
+
// `BlogPost` → `Blog Post`, and `APIKey` → `API Key`: the second rule is what
|
|
33
|
+
// stops an acronym being split into one word per letter.
|
|
34
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1 $2')
|
|
35
|
+
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2')
|
|
36
|
+
.split(/[^a-zA-Z0-9]+/)
|
|
37
|
+
.filter((word) => word !== '')
|
|
38
|
+
.map((word) => word.toLowerCase());
|
|
39
|
+
const capitalised = (word) => `${word.slice(0, 1).toUpperCase()}${word.slice(1)}`;
|
|
40
|
+
/** `blog_post` → `BlogPost`. */
|
|
41
|
+
export const pascalCase = (name) => wordsOf(name).map(capitalised).join('');
|
|
42
|
+
/** `blog_post` → `blogPost`. */
|
|
43
|
+
export const camelCase = (name) => wordsOf(name)
|
|
44
|
+
.map((word, index) => (index === 0 ? word : capitalised(word)))
|
|
45
|
+
.join('');
|
|
46
|
+
/** `BlogPost` → `blog-post`. The form every generated file name takes. */
|
|
47
|
+
export const kebabCase = (name) => wordsOf(name).join('-');
|
|
48
|
+
/** `BlogPost` → `blog_post`. The form every generated table name takes. */
|
|
49
|
+
export const snakeCase = (name) => wordsOf(name).join('_');
|
|
50
|
+
/** `blog_post` → `Blog post`. What a label and a description read like. */
|
|
51
|
+
export const sentenceCase = (name) => capitalised(wordsOf(name).join(' '));
|
|
52
|
+
/**
|
|
53
|
+
* The plural of one lower-case word.
|
|
54
|
+
*
|
|
55
|
+
* Four ordinary English rules, plus one that leaves a word already ending in `s`
|
|
56
|
+
* alone so that `make:policy posts` is not a policy about `postses`. A genuine
|
|
57
|
+
* singular ending in `s` is caught by the first rule, which is why `status` becomes
|
|
58
|
+
* `statuses` rather than staying put.
|
|
59
|
+
*
|
|
60
|
+
* Nothing here knows that a person is people. An irregular plural is one word to fix
|
|
61
|
+
* in the generated file, and a dictionary of English exceptions inside a CLI is a
|
|
62
|
+
* promise it cannot keep — `model('people', …)` is the honest answer.
|
|
63
|
+
*/
|
|
64
|
+
export const pluralOf = (word) => {
|
|
65
|
+
if (/(?:ss|us|is)$/.test(word))
|
|
66
|
+
return `${word}es`;
|
|
67
|
+
if (word.endsWith('s'))
|
|
68
|
+
return word;
|
|
69
|
+
if (/(?:x|z|ch|sh)$/.test(word))
|
|
70
|
+
return `${word}es`;
|
|
71
|
+
if (/[^aeiou]y$/.test(word))
|
|
72
|
+
return `${word.slice(0, -1)}ies`;
|
|
73
|
+
return `${word}s`;
|
|
74
|
+
};
|
|
75
|
+
/** The inverse, to the same standard: it decides a file name, never a table name. */
|
|
76
|
+
export const singularOf = (word) => {
|
|
77
|
+
if (word.endsWith('ies'))
|
|
78
|
+
return `${word.slice(0, -3)}y`;
|
|
79
|
+
if (/(?:ss|us|is)$/.test(word))
|
|
80
|
+
return word;
|
|
81
|
+
if (/(?:x|z|ch|sh)es$/.test(word))
|
|
82
|
+
return word.slice(0, -2);
|
|
83
|
+
if (word.endsWith('ses'))
|
|
84
|
+
return word.slice(0, -2);
|
|
85
|
+
if (word.endsWith('s'))
|
|
86
|
+
return word.slice(0, -1);
|
|
87
|
+
return word;
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* Only the last word is inflected: `blog_post` pluralises to `blog posts`, never to
|
|
91
|
+
* `blogs posts`. The result is handed back space-separated, because every case
|
|
92
|
+
* helper re-reads it as words anyway.
|
|
93
|
+
*/
|
|
94
|
+
const inflectLast = (name, inflect) => {
|
|
95
|
+
const words = [...wordsOf(name)];
|
|
96
|
+
const last = words.at(-1);
|
|
97
|
+
if (last !== undefined)
|
|
98
|
+
words[words.length - 1] = inflect(last);
|
|
99
|
+
return words.join(' ');
|
|
100
|
+
};
|
|
101
|
+
const pluralName = (name) => inflectLast(name, pluralOf);
|
|
102
|
+
const singularName = (name) => inflectLast(name, singularOf);
|
|
103
|
+
/**
|
|
104
|
+
* Import lines are assembled rather than written out.
|
|
105
|
+
*
|
|
106
|
+
* `pnpm boundaries` reads source text and cannot tell a template apart from the code
|
|
107
|
+
* around it, so a template spelling out an import of the resource layer reads as this
|
|
108
|
+
* package importing it — the one thing the CLI must never do (ADR-0021). Composing
|
|
109
|
+
* the line keeps that check honest without changing a character of what reaches disk.
|
|
110
|
+
*/
|
|
111
|
+
const importFrom = (names, from) => `import { ${names.join(', ')} } from '${from}'`;
|
|
112
|
+
/**
|
|
113
|
+
* A relative import carries the real `.ts` extension.
|
|
114
|
+
*
|
|
115
|
+
* Node 24 runs the TypeScript file directly and does not rewrite specifiers, so
|
|
116
|
+
* `../models/post.js` would name a file that never exists. `apps/playground` is
|
|
117
|
+
* written this way for the same reason; a project's tsconfig needs
|
|
118
|
+
* `allowImportingTsExtensions`.
|
|
119
|
+
*/
|
|
120
|
+
const fromSource = (directory, file) => `../${directory}/${file}.ts`;
|
|
121
|
+
const isAlreadyExists = (error) => typeof error === 'object' && error !== null && error.code === 'EEXIST';
|
|
122
|
+
const exists = async (path) => {
|
|
123
|
+
try {
|
|
124
|
+
await stat(path);
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
const write = async (file, shown, contents, force) => {
|
|
132
|
+
await mkdir(dirname(file), { recursive: true });
|
|
133
|
+
try {
|
|
134
|
+
// `wx` is what actually refuses, rather than a check followed by a write: two
|
|
135
|
+
// generators racing for the same path cannot then both believe they won.
|
|
136
|
+
await writeFile(file, contents, { encoding: 'utf8', flag: force ? 'w' : 'wx' });
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
if (!isAlreadyExists(error))
|
|
140
|
+
throw error;
|
|
141
|
+
throw new ConflictError(`${shown} already exists. Pass --force to overwrite it.`);
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
const generate = async (spec, args, cwd) => {
|
|
145
|
+
const name = args.positionals[0];
|
|
146
|
+
// Checked before the config is loaded: a forgotten argument is a mistake in what
|
|
147
|
+
// was typed, and answering it with "no assemora.config.ts here" helps nobody.
|
|
148
|
+
if (name === undefined || wordsOf(name).length === 0) {
|
|
149
|
+
fail(`${spec.name} needs a name, for example: ${spec.example}`);
|
|
150
|
+
return 2;
|
|
151
|
+
}
|
|
152
|
+
const loaded = await loadConfig(cwd);
|
|
153
|
+
const generated = spec.plan(name);
|
|
154
|
+
const file = join(loaded.paths.source, generated.file);
|
|
155
|
+
const shown = relative(cwd, file) || file;
|
|
156
|
+
const force = bool(args, 'force');
|
|
157
|
+
// Asked before the write and reported after it, so that a write which failed does
|
|
158
|
+
// not leave behind a warning about work nobody did.
|
|
159
|
+
const replacing = force && (await exists(file));
|
|
160
|
+
await write(file, shown, generated.contents, force);
|
|
161
|
+
if (replacing)
|
|
162
|
+
warn(`${shown} existed already and was overwritten`);
|
|
163
|
+
// The path is the answer and goes to stdout, so `assemora make:model Post` can be
|
|
164
|
+
// piped into an editor; the next step is commentary and goes to stderr.
|
|
165
|
+
ok(shown);
|
|
166
|
+
detail(generated.nextStep);
|
|
167
|
+
return 0;
|
|
168
|
+
};
|
|
169
|
+
const generator = (spec) => defineCommand({
|
|
170
|
+
name: spec.name,
|
|
171
|
+
group: 'make',
|
|
172
|
+
summary: spec.summary,
|
|
173
|
+
usage: spec.usage,
|
|
174
|
+
handler: ({ args, cwd }) => generate(spec, args, cwd),
|
|
175
|
+
});
|
|
176
|
+
const modelFile = (name) => {
|
|
177
|
+
const model = pascalCase(singularName(name));
|
|
178
|
+
const table = snakeCase(pluralName(name));
|
|
179
|
+
return {
|
|
180
|
+
file: join('models', `${kebabCase(singularName(name))}.ts`),
|
|
181
|
+
nextStep: `Register it on a module with .models(${model}), then run \`assemora db:generate\`.`,
|
|
182
|
+
contents: `/**
|
|
183
|
+
* The ${model} model.
|
|
184
|
+
*
|
|
185
|
+
* One declaration is the record type, the database column, the runtime validation
|
|
186
|
+
* and every schema built from them — Studio's form, OpenAPI, the SDK and the MCP
|
|
187
|
+
* tool (SPEC.md §9, §17).
|
|
188
|
+
*/
|
|
189
|
+
${importFrom(['model', 'string', 'timestamp', 'uuid'], '@assemora/data')}
|
|
190
|
+
|
|
191
|
+
export const ${model} = model('${table}', {
|
|
192
|
+
id: uuid().primary().defaultRandom(),
|
|
193
|
+
title: string(),
|
|
194
|
+
createdAt: timestamp().created(),
|
|
195
|
+
updatedAt: timestamp().updated(),
|
|
196
|
+
})
|
|
197
|
+
`,
|
|
198
|
+
};
|
|
199
|
+
};
|
|
200
|
+
const resourceFile = (name) => {
|
|
201
|
+
const model = pascalCase(singularName(name));
|
|
202
|
+
const collection = pascalCase(pluralName(name));
|
|
203
|
+
const modelFileName = kebabCase(singularName(name));
|
|
204
|
+
return {
|
|
205
|
+
file: join('resources', `${kebabCase(pluralName(name))}.ts`),
|
|
206
|
+
nextStep: `Register it on a module with .resources(${collection}).`,
|
|
207
|
+
contents: `/**
|
|
208
|
+
* The ${collection} resource.
|
|
209
|
+
*
|
|
210
|
+
* A resource presents a model rather than inventing data, so every field here names
|
|
211
|
+
* a column of ${model}, and only what is declared is ever returned — a column the
|
|
212
|
+
* resource never mentioned cannot leak into an API response (SPEC.md §35).
|
|
213
|
+
*/
|
|
214
|
+
${importFrom(['resource', 'text'], '@assemora/resources')}
|
|
215
|
+
|
|
216
|
+
${importFrom([model], fromSource('models', modelFileName))}
|
|
217
|
+
|
|
218
|
+
export const ${collection} = resource(
|
|
219
|
+
${model},
|
|
220
|
+
{
|
|
221
|
+
title: text().required().searchable().sortable().label('Title'),
|
|
222
|
+
},
|
|
223
|
+
{ label: '${sentenceCase(pluralName(name))}' },
|
|
224
|
+
)
|
|
225
|
+
`,
|
|
226
|
+
};
|
|
227
|
+
};
|
|
228
|
+
const blockFile = (name) => {
|
|
229
|
+
const block = pascalCase(name);
|
|
230
|
+
const type = kebabCase(name);
|
|
231
|
+
return {
|
|
232
|
+
file: join('blocks', `${type}.ts`),
|
|
233
|
+
nextStep: `Register it with pages({ blocks: [${block}] }).`,
|
|
234
|
+
contents: `/**
|
|
235
|
+
* The ${block} block.
|
|
236
|
+
*
|
|
237
|
+
* These fields are the Studio form, the runtime validation, the JSON Schema and what
|
|
238
|
+
* an agent is allowed to set — one declaration, never four (SPEC.md §55, §56).
|
|
239
|
+
*/
|
|
240
|
+
${importFrom(['block'], '@assemora/pages')}
|
|
241
|
+
${importFrom(['text'], '@assemora/resources')}
|
|
242
|
+
|
|
243
|
+
export const ${block} = block(
|
|
244
|
+
'${type}',
|
|
245
|
+
{
|
|
246
|
+
title: text().required().label('Title'),
|
|
247
|
+
},
|
|
248
|
+
{ label: '${sentenceCase(name)}' },
|
|
249
|
+
)
|
|
250
|
+
`,
|
|
251
|
+
};
|
|
252
|
+
};
|
|
253
|
+
const moduleFile = (name) => {
|
|
254
|
+
const factory = camelCase(name);
|
|
255
|
+
const moduleName = kebabCase(name);
|
|
256
|
+
return {
|
|
257
|
+
file: join('modules', `${moduleName}.ts`),
|
|
258
|
+
// `assemora()`, not `createApplication()`: the project this same CLI scaffolds
|
|
259
|
+
// calls the umbrella in `src/app.ts` and contains the word `createApplication`
|
|
260
|
+
// nowhere at all, so the other name sends a reader looking for something that is
|
|
261
|
+
// not there (ADR-0022).
|
|
262
|
+
nextStep: `Register it with assemora({ modules: [${factory}()] }) in src/app.ts.`,
|
|
263
|
+
contents: `/**
|
|
264
|
+
* The ${moduleName} module.
|
|
265
|
+
*
|
|
266
|
+
* A module is the registration unit: models, resources, blocks, routes, commands and
|
|
267
|
+
* queries (SPEC.md §13). \`.models()\` and \`.resources()\` appear on the builder as
|
|
268
|
+
* soon as the packages that own them are imported — each contributes its own method
|
|
269
|
+
* rather than core learning what a model is (ADR-0009).
|
|
270
|
+
*/
|
|
271
|
+
${importFrom(['module'], '@assemora/core')}
|
|
272
|
+
|
|
273
|
+
export const ${factory} = () => module('${moduleName}')
|
|
274
|
+
`,
|
|
275
|
+
};
|
|
276
|
+
};
|
|
277
|
+
/**
|
|
278
|
+
* `posts.publish` names a command; the file is named after it.
|
|
279
|
+
*
|
|
280
|
+
* The last segment is the verb and the one before it is what the verb acts on, so
|
|
281
|
+
* `posts.publish` becomes `publish-post.ts` holding `PublishPost` — the file sorts
|
|
282
|
+
* beside the other things done to a post, and the export reads as the act. A name
|
|
283
|
+
* with no dot is taken as the verb alone.
|
|
284
|
+
*/
|
|
285
|
+
const commandFile = (name) => {
|
|
286
|
+
const segments = name
|
|
287
|
+
.split('.')
|
|
288
|
+
.map(camelCase)
|
|
289
|
+
.filter((segment) => segment !== '');
|
|
290
|
+
const verb = segments.at(-1) ?? camelCase(name);
|
|
291
|
+
const subject = segments.length > 1 ? (segments.at(-2) ?? '') : '';
|
|
292
|
+
const subjectWords = subject === '' ? '' : wordsOf(singularName(subject)).join(' ');
|
|
293
|
+
const definition = `${pascalCase(verb)}${subject === '' ? '' : pascalCase(singularName(subject))}`;
|
|
294
|
+
const fileName = subject === '' ? kebabCase(verb) : `${kebabCase(verb)}-${kebabCase(singularName(subject))}`;
|
|
295
|
+
const commandName = segments.join('.');
|
|
296
|
+
return {
|
|
297
|
+
file: join('commands', `${fileName}.ts`),
|
|
298
|
+
nextStep: `Register it on a module with .commands(${definition}).`,
|
|
299
|
+
contents: `/**
|
|
300
|
+
* The ${commandName} command.
|
|
301
|
+
*
|
|
302
|
+
* Every state change takes one path — validation, authorization, transaction,
|
|
303
|
+
* handler, revision, events, audit — and Studio, REST, the SDK, this CLI and an
|
|
304
|
+
* agent all arrive through it. The name is also the permission it requires
|
|
305
|
+
* (SPEC.md §14, ADR-0015).
|
|
306
|
+
*/
|
|
307
|
+
${importFrom(['command'], '@assemora/core')}
|
|
308
|
+
${importFrom(['uuid'], '@assemora/schema')}
|
|
309
|
+
|
|
310
|
+
export const ${definition} = command('${commandName}', {
|
|
311
|
+
description: '${sentenceCase(verb)}${subjectWords === '' ? '' : ` ${subjectWords}`}',
|
|
312
|
+
input: { id: uuid() },
|
|
313
|
+
output: { id: uuid() },
|
|
314
|
+
handle: async ({ id }, context) => {
|
|
315
|
+
context.logger.info('${commandName}', { id })
|
|
316
|
+
|
|
317
|
+
return { id }
|
|
318
|
+
},
|
|
319
|
+
})
|
|
320
|
+
`,
|
|
321
|
+
};
|
|
322
|
+
};
|
|
323
|
+
const policyFile = (name) => {
|
|
324
|
+
const subject = snakeCase(pluralName(name));
|
|
325
|
+
const definition = `${pascalCase(singularName(name))}Policy`;
|
|
326
|
+
return {
|
|
327
|
+
file: join('policies', `${kebabCase(pluralName(name))}.ts`),
|
|
328
|
+
nextStep: `Register it with auth({ policies: [${definition}] }).`,
|
|
329
|
+
contents: `/**
|
|
330
|
+
* Who may do what to ${wordsOf(subject).join(' ')}.
|
|
331
|
+
*
|
|
332
|
+
* The same answer is given to Studio, REST, the SDK, this CLI and an agent, because
|
|
333
|
+
* all of them arrive through the same buses: there is no trusted caller. A rule
|
|
334
|
+
* about a record is asked once the record has been read (SPEC.md §51, ADR-0015).
|
|
335
|
+
*/
|
|
336
|
+
${importFrom(['policy'], '@assemora/auth')}
|
|
337
|
+
|
|
338
|
+
export const ${definition} = policy('${subject}', {
|
|
339
|
+
read: ({ can }) => can('${subject}.read'),
|
|
340
|
+
create: ({ can }) => can('${subject}.create'),
|
|
341
|
+
update: ({ can }) => can('${subject}.update'),
|
|
342
|
+
delete: ({ can }) => can('${subject}.delete'),
|
|
343
|
+
})
|
|
344
|
+
`,
|
|
345
|
+
};
|
|
346
|
+
};
|
|
347
|
+
/** Registered in the order SPEC.md §77 lists them, which is the order they print in. */
|
|
348
|
+
export const makeCommands = [
|
|
349
|
+
generator({
|
|
350
|
+
name: 'make:model',
|
|
351
|
+
summary: 'a model and the table it maps to',
|
|
352
|
+
usage: 'assemora make:model <name> [--force]',
|
|
353
|
+
example: 'assemora make:model Post',
|
|
354
|
+
plan: modelFile,
|
|
355
|
+
}),
|
|
356
|
+
generator({
|
|
357
|
+
name: 'make:resource',
|
|
358
|
+
summary: 'a resource: how a model appears as content',
|
|
359
|
+
usage: 'assemora make:resource <name> [--force]',
|
|
360
|
+
example: 'assemora make:resource Post',
|
|
361
|
+
plan: resourceFile,
|
|
362
|
+
}),
|
|
363
|
+
generator({
|
|
364
|
+
name: 'make:block',
|
|
365
|
+
summary: 'a block for the page builder',
|
|
366
|
+
usage: 'assemora make:block <name> [--force]',
|
|
367
|
+
example: 'assemora make:block hero',
|
|
368
|
+
plan: blockFile,
|
|
369
|
+
}),
|
|
370
|
+
generator({
|
|
371
|
+
name: 'make:module',
|
|
372
|
+
summary: 'a module to register declarations in',
|
|
373
|
+
usage: 'assemora make:module <name> [--force]',
|
|
374
|
+
example: 'assemora make:module blog',
|
|
375
|
+
plan: moduleFile,
|
|
376
|
+
}),
|
|
377
|
+
generator({
|
|
378
|
+
name: 'make:command',
|
|
379
|
+
summary: 'a command for the Command Bus',
|
|
380
|
+
usage: 'assemora make:command <group.action> [--force]',
|
|
381
|
+
example: 'assemora make:command posts.publish',
|
|
382
|
+
plan: commandFile,
|
|
383
|
+
}),
|
|
384
|
+
generator({
|
|
385
|
+
name: 'make:policy',
|
|
386
|
+
summary: 'a policy for one subject',
|
|
387
|
+
usage: 'assemora make:policy <subject> [--force]',
|
|
388
|
+
example: 'assemora make:policy posts',
|
|
389
|
+
plan: policyFile,
|
|
390
|
+
}),
|
|
391
|
+
];
|
|
392
|
+
register(...makeCommands);
|
|
393
|
+
//# sourceMappingURL=make.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"make.js","sourceRoot":"","sources":["../../src/commands/make.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AACzD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAA;AAEnD,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAE9C,OAAO,EAAE,IAAI,EAAmB,MAAM,YAAY,CAAA;AAClD,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AACzC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,cAAc,CAAA;AACrD,OAAO,EAAmB,aAAa,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAEzE;;;;;;;GAOG;AACH,MAAM,OAAO,GAAG,CAAC,IAAY,EAAqB,EAAE,CAClD,IAAI;IACF,8EAA8E;IAC9E,yDAAyD;KACxD,OAAO,CAAC,oBAAoB,EAAE,OAAO,CAAC;KACtC,OAAO,CAAC,uBAAuB,EAAE,OAAO,CAAC;KACzC,KAAK,CAAC,eAAe,CAAC;KACtB,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,EAAE,CAAC;KAC7B,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAA;AAEtC,MAAM,WAAW,GAAG,CAAC,IAAY,EAAU,EAAE,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAA;AAEjG,gCAAgC;AAChC,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,IAAY,EAAU,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;AAE3F,gCAAgC;AAChC,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,IAAY,EAAU,EAAE,CAChD,OAAO,CAAC,IAAI,CAAC;KACV,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC;KAC9D,IAAI,CAAC,EAAE,CAAC,CAAA;AAEb,0EAA0E;AAC1E,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,IAAY,EAAU,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;AAE1E,2EAA2E;AAC3E,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,IAAY,EAAU,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;AAE1E,2EAA2E;AAC3E,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,IAAY,EAAU,EAAE,CAAC,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAA;AAE1F;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,IAAY,EAAU,EAAE;IAC/C,IAAI,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,GAAG,IAAI,IAAI,CAAA;IAClD,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAA;IACnC,IAAI,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,GAAG,IAAI,IAAI,CAAA;IACnD,IAAI,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAA;IAE7D,OAAO,GAAG,IAAI,GAAG,CAAA;AACnB,CAAC,CAAA;AAED,qFAAqF;AACrF,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,IAAY,EAAU,EAAE;IACjD,IAAI,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAA;IACxD,IAAI,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAA;IAC3C,IAAI,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;IAC3D,IAAI,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;IAClD,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;IAEhD,OAAO,IAAI,CAAA;AACb,CAAC,CAAA;AAED;;;;GAIG;AACH,MAAM,WAAW,GAAG,CAAC,IAAY,EAAE,OAAiC,EAAU,EAAE;IAC9E,MAAM,KAAK,GAAG,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAA;IAChC,MAAM,IAAI,GAAG,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAA;IAEzB,IAAI,IAAI,KAAK,SAAS;QAAE,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAE/D,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;AACxB,CAAC,CAAA;AAED,MAAM,UAAU,GAAG,CAAC,IAAY,EAAU,EAAE,CAAC,WAAW,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAA;AAExE,MAAM,YAAY,GAAG,CAAC,IAAY,EAAU,EAAE,CAAC,WAAW,CAAC,IAAI,EAAE,UAAU,CAAC,CAAA;AAE5E;;;;;;;GAOG;AACH,MAAM,UAAU,GAAG,CAAC,KAAwB,EAAE,IAAY,EAAU,EAAE,CACpE,YAAY,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,IAAI,GAAG,CAAA;AAEjD;;;;;;;GAOG;AACH,MAAM,UAAU,GAAG,CAAC,SAAiB,EAAE,IAAY,EAAU,EAAE,CAAC,MAAM,SAAS,IAAI,IAAI,KAAK,CAAA;AAuB5F,MAAM,eAAe,GAAG,CAAC,KAAc,EAAW,EAAE,CAClD,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAK,KAA4B,CAAC,IAAI,KAAK,QAAQ,CAAA;AAEhG,MAAM,MAAM,GAAG,KAAK,EAAE,IAAY,EAAoB,EAAE;IACtD,IAAI,CAAC;QACH,MAAM,IAAI,CAAC,IAAI,CAAC,CAAA;QAChB,OAAO,IAAI,CAAA;IACb,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAA;IACd,CAAC;AACH,CAAC,CAAA;AAED,MAAM,KAAK,GAAG,KAAK,EACjB,IAAY,EACZ,KAAa,EACb,QAAgB,EAChB,KAAc,EACC,EAAE;IACjB,MAAM,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;IAE/C,IAAI,CAAC;QACH,8EAA8E;QAC9E,yEAAyE;QACzE,MAAM,SAAS,CAAC,IAAI,EAAE,QAAQ,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAA;IACjF,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC;YAAE,MAAM,KAAK,CAAA;QAExC,MAAM,IAAI,aAAa,CAAC,GAAG,KAAK,gDAAgD,CAAC,CAAA;IACnF,CAAC;AACH,CAAC,CAAA;AAED,MAAM,QAAQ,GAAG,KAAK,EAAE,IAAmB,EAAE,IAAgB,EAAE,GAAW,EAAmB,EAAE;IAC7F,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,CAAA;IAEhC,iFAAiF;IACjF,8EAA8E;IAC9E,IAAI,IAAI,KAAK,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACrD,IAAI,CAAC,GAAG,IAAI,CAAC,IAAI,+BAA+B,IAAI,CAAC,OAAO,EAAE,CAAC,CAAA;QAC/D,OAAO,CAAC,CAAA;IACV,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,GAAG,CAAC,CAAA;IACpC,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACjC,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,SAAS,CAAC,IAAI,CAAC,CAAA;IACtD,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,EAAE,IAAI,CAAC,IAAI,IAAI,CAAA;IACzC,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;IAEjC,kFAAkF;IAClF,oDAAoD;IACpD,MAAM,SAAS,GAAG,KAAK,IAAI,CAAC,MAAM,MAAM,CAAC,IAAI,CAAC,CAAC,CAAA;IAE/C,MAAM,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,SAAS,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAA;IAEnD,IAAI,SAAS;QAAE,IAAI,CAAC,GAAG,KAAK,sCAAsC,CAAC,CAAA;IAEnE,kFAAkF;IAClF,wEAAwE;IACxE,EAAE,CAAC,KAAK,CAAC,CAAA;IACT,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAA;IAE1B,OAAO,CAAC,CAAA;AACV,CAAC,CAAA;AAED,MAAM,SAAS,GAAG,CAAC,IAAmB,EAAc,EAAE,CACpD,aAAa,CAAC;IACZ,IAAI,EAAE,IAAI,CAAC,IAAI;IACf,KAAK,EAAE,MAAM;IACb,OAAO,EAAE,IAAI,CAAC,OAAO;IACrB,KAAK,EAAE,IAAI,CAAC,KAAK;IACjB,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,CAAC;CACtD,CAAC,CAAA;AAEJ,MAAM,SAAS,GAAG,CAAC,IAAY,EAAa,EAAE;IAC5C,MAAM,KAAK,GAAG,UAAU,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAA;IAC5C,MAAM,KAAK,GAAG,SAAS,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAA;IAEzC,OAAO;QACL,IAAI,EAAE,IAAI,CAAC,QAAQ,EAAE,GAAG,SAAS,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC;QAC3D,QAAQ,EAAE,wCAAwC,KAAK,uCAAuC;QAC9F,QAAQ,EAAE;SACL,KAAK;;;;;;EAMZ,UAAU,CAAC,CAAC,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,CAAC,EAAE,gBAAgB,CAAC;;eAEzD,KAAK,aAAa,KAAK;;;;;;CAMrC;KACE,CAAA;AACH,CAAC,CAAA;AAED,MAAM,YAAY,GAAG,CAAC,IAAY,EAAa,EAAE;IAC/C,MAAM,KAAK,GAAG,UAAU,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAA;IAC5C,MAAM,UAAU,GAAG,UAAU,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAA;IAC/C,MAAM,aAAa,GAAG,SAAS,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAA;IAEnD,OAAO;QACL,IAAI,EAAE,IAAI,CAAC,WAAW,EAAE,GAAG,SAAS,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC;QAC5D,QAAQ,EAAE,2CAA2C,UAAU,IAAI;QACnE,QAAQ,EAAE;SACL,UAAU;;;iBAGF,KAAK;;;EAGpB,UAAU,CAAC,CAAC,UAAU,EAAE,MAAM,CAAC,EAAE,qBAAqB,CAAC;;EAEvD,UAAU,CAAC,CAAC,KAAK,CAAC,EAAE,UAAU,CAAC,QAAQ,EAAE,aAAa,CAAC,CAAC;;eAE3C,UAAU;IACrB,KAAK;;;;cAIK,YAAY,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;;CAE3C;KACE,CAAA;AACH,CAAC,CAAA;AAED,MAAM,SAAS,GAAG,CAAC,IAAY,EAAa,EAAE;IAC5C,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,CAAC,CAAA;IAC9B,MAAM,IAAI,GAAG,SAAS,CAAC,IAAI,CAAC,CAAA;IAE5B,OAAO;QACL,IAAI,EAAE,IAAI,CAAC,QAAQ,EAAE,GAAG,IAAI,KAAK,CAAC;QAClC,QAAQ,EAAE,qCAAqC,KAAK,OAAO;QAC3D,QAAQ,EAAE;SACL,KAAK;;;;;EAKZ,UAAU,CAAC,CAAC,OAAO,CAAC,EAAE,iBAAiB,CAAC;EACxC,UAAU,CAAC,CAAC,MAAM,CAAC,EAAE,qBAAqB,CAAC;;eAE9B,KAAK;KACf,IAAI;;;;cAIK,YAAY,CAAC,IAAI,CAAC;;CAE/B;KACE,CAAA;AACH,CAAC,CAAA;AAED,MAAM,UAAU,GAAG,CAAC,IAAY,EAAa,EAAE;IAC7C,MAAM,OAAO,GAAG,SAAS,CAAC,IAAI,CAAC,CAAA;IAC/B,MAAM,UAAU,GAAG,SAAS,CAAC,IAAI,CAAC,CAAA;IAElC,OAAO;QACL,IAAI,EAAE,IAAI,CAAC,SAAS,EAAE,GAAG,UAAU,KAAK,CAAC;QACzC,+EAA+E;QAC/E,+EAA+E;QAC/E,iFAAiF;QACjF,wBAAwB;QACxB,QAAQ,EAAE,yCAAyC,OAAO,uBAAuB;QACjF,QAAQ,EAAE;SACL,UAAU;;;;;;;EAOjB,UAAU,CAAC,CAAC,QAAQ,CAAC,EAAE,gBAAgB,CAAC;;eAE3B,OAAO,oBAAoB,UAAU;CACnD;KACE,CAAA;AACH,CAAC,CAAA;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,GAAG,CAAC,IAAY,EAAa,EAAE;IAC9C,MAAM,QAAQ,GAAG,IAAI;SAClB,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,SAAS,CAAC;SACd,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,KAAK,EAAE,CAAC,CAAA;IAEtC,MAAM,IAAI,GAAG,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,SAAS,CAAC,IAAI,CAAC,CAAA;IAC/C,MAAM,OAAO,GAAG,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;IAClE,MAAM,YAAY,GAAG,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IACnF,MAAM,UAAU,GAAG,GAAG,UAAU,CAAC,IAAI,CAAC,GAAG,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,EAAE,CAAA;IAClG,MAAM,QAAQ,GACZ,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,IAAI,CAAC,IAAI,SAAS,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,EAAE,CAAA;IAC7F,MAAM,WAAW,GAAG,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IAEtC,OAAO;QACL,IAAI,EAAE,IAAI,CAAC,UAAU,EAAE,GAAG,QAAQ,KAAK,CAAC;QACxC,QAAQ,EAAE,0CAA0C,UAAU,IAAI;QAClE,QAAQ,EAAE;SACL,WAAW;;;;;;;EAOlB,UAAU,CAAC,CAAC,SAAS,CAAC,EAAE,gBAAgB,CAAC;EACzC,UAAU,CAAC,CAAC,MAAM,CAAC,EAAE,kBAAkB,CAAC;;eAE3B,UAAU,eAAe,WAAW;kBACjC,YAAY,CAAC,IAAI,CAAC,GAAG,YAAY,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,YAAY,EAAE;;;;2BAIzD,WAAW;;;;;CAKrC;KACE,CAAA;AACH,CAAC,CAAA;AAED,MAAM,UAAU,GAAG,CAAC,IAAY,EAAa,EAAE;IAC7C,MAAM,OAAO,GAAG,SAAS,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAA;IAC3C,MAAM,UAAU,GAAG,GAAG,UAAU,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAA;IAE5D,OAAO;QACL,IAAI,EAAE,IAAI,CAAC,UAAU,EAAE,GAAG,SAAS,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC;QAC3D,QAAQ,EAAE,sCAAsC,UAAU,OAAO;QACjE,QAAQ,EAAE;wBACU,OAAO,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC;;;;;;EAMhD,UAAU,CAAC,CAAC,QAAQ,CAAC,EAAE,gBAAgB,CAAC;;eAE3B,UAAU,cAAc,OAAO;4BAClB,OAAO;8BACL,OAAO;8BACP,OAAO;8BACP,OAAO;;CAEpC;KACE,CAAA;AACH,CAAC,CAAA;AAED,wFAAwF;AACxF,MAAM,CAAC,MAAM,YAAY,GAA0B;IACjD,SAAS,CAAC;QACR,IAAI,EAAE,YAAY;QAClB,OAAO,EAAE,kCAAkC;QAC3C,KAAK,EAAE,sCAAsC;QAC7C,OAAO,EAAE,0BAA0B;QACnC,IAAI,EAAE,SAAS;KAChB,CAAC;IACF,SAAS,CAAC;QACR,IAAI,EAAE,eAAe;QACrB,OAAO,EAAE,4CAA4C;QACrD,KAAK,EAAE,yCAAyC;QAChD,OAAO,EAAE,6BAA6B;QACtC,IAAI,EAAE,YAAY;KACnB,CAAC;IACF,SAAS,CAAC;QACR,IAAI,EAAE,YAAY;QAClB,OAAO,EAAE,8BAA8B;QACvC,KAAK,EAAE,sCAAsC;QAC7C,OAAO,EAAE,0BAA0B;QACnC,IAAI,EAAE,SAAS;KAChB,CAAC;IACF,SAAS,CAAC;QACR,IAAI,EAAE,aAAa;QACnB,OAAO,EAAE,sCAAsC;QAC/C,KAAK,EAAE,uCAAuC;QAC9C,OAAO,EAAE,2BAA2B;QACpC,IAAI,EAAE,UAAU;KACjB,CAAC;IACF,SAAS,CAAC;QACR,IAAI,EAAE,cAAc;QACpB,OAAO,EAAE,+BAA+B;QACxC,KAAK,EAAE,gDAAgD;QACvD,OAAO,EAAE,qCAAqC;QAC9C,IAAI,EAAE,WAAW;KAClB,CAAC;IACF,SAAS,CAAC;QACR,IAAI,EAAE,aAAa;QACnB,OAAO,EAAE,0BAA0B;QACnC,KAAK,EAAE,0CAA0C;QACjD,OAAO,EAAE,4BAA4B;QACrC,IAAI,EAAE,UAAU;KACjB,CAAC;CACH,CAAA;AAED,QAAQ,CAAC,GAAG,YAAY,CAAC,CAAA"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type McpEndpoint } from '../project.js';
|
|
2
|
+
/** The environment variable the token arrives in, because a pipe carries no headers. */
|
|
3
|
+
export declare const TOKEN_VARIABLE = "ASSEMORA_AGENT_TOKEN";
|
|
4
|
+
export type McpSession = {
|
|
5
|
+
/** Defaults to `process.stdin`. */
|
|
6
|
+
readonly input?: NodeJS.ReadableStream;
|
|
7
|
+
/** Defaults to `process.stdout`. */
|
|
8
|
+
readonly output?: NodeJS.WritableStream;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Serves the endpoint until the input closes, and resolves when it has.
|
|
12
|
+
*
|
|
13
|
+
* Sequential on purpose. The protocol permits a client to have several requests in
|
|
14
|
+
* flight, and answering them in order costs a stdio client nothing — it is one process
|
|
15
|
+
* talking to one process — while concurrency here would put the ordering of two writes
|
|
16
|
+
* to one pipe in the hands of whichever handler finished first.
|
|
17
|
+
*/
|
|
18
|
+
export declare const serveMcp: (endpoint: McpEndpoint, token: string, session?: McpSession) => Promise<void>;
|
|
19
|
+
//# sourceMappingURL=mcp.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../../src/commands/mcp.ts"],"names":[],"mappings":"AAuBA,OAAO,EAAe,KAAK,WAAW,EAAE,MAAM,eAAe,CAAA;AAG7D,wFAAwF;AACxF,eAAO,MAAM,cAAc,yBAAyB,CAAA;AAEpD,MAAM,MAAM,UAAU,GAAG;IACvB,mCAAmC;IACnC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,cAAc,CAAA;IACtC,oCAAoC;IACpC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,cAAc,CAAA;CACxC,CAAA;AAqBD;;;;;;;GAOG;AACH,eAAO,MAAM,QAAQ,aACT,WAAW,SACd,MAAM,YACJ,UAAU,KAClB,OAAO,CAAC,IAAI,CAsCd,CAAA"}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `assemora mcp` — the project's MCP endpoint, over a pipe (SPEC.md §68, §76).
|
|
3
|
+
*
|
|
4
|
+
* The application already speaks the protocol; what it lacked was the transport every
|
|
5
|
+
* client actually uses. Claude Code, Claude Desktop and Cursor start a process and talk
|
|
6
|
+
* to it over stdin and stdout, and until this existed the only way in was a bespoke
|
|
7
|
+
* `POST /api/mcp` that no such client knows how to reach.
|
|
8
|
+
*
|
|
9
|
+
* It reimplements nothing. The endpoint is the one `assemora()` built — the same
|
|
10
|
+
* generated tools, the same buses, the same seven checks of SPEC.md §76 — handed over by
|
|
11
|
+
* the project's own config, which is how this package drives a feature it may not import
|
|
12
|
+
* (ADR-0021).
|
|
13
|
+
*
|
|
14
|
+
* The framing is newline-delimited JSON, which is what the MCP stdio transport specifies:
|
|
15
|
+
* one message per line, and no message may contain a newline of its own. `JSON.stringify`
|
|
16
|
+
* escapes them, so that holds by construction.
|
|
17
|
+
*/
|
|
18
|
+
import { createInterface } from 'node:readline';
|
|
19
|
+
import { ConfigurationError } from '@assemora/core';
|
|
20
|
+
import { loadConfig } from '../config.js';
|
|
21
|
+
import { detail } from '../output.js';
|
|
22
|
+
import { loadProject } from '../project.js';
|
|
23
|
+
import { defineCommand, register } from '../registry.js';
|
|
24
|
+
/** The environment variable the token arrives in, because a pipe carries no headers. */
|
|
25
|
+
export const TOKEN_VARIABLE = 'ASSEMORA_AGENT_TOKEN';
|
|
26
|
+
/**
|
|
27
|
+
* A JSON-RPC error, as a message rather than as a thrown thing.
|
|
28
|
+
*
|
|
29
|
+
* A transport that dies on a malformed line takes the session with it, and a client
|
|
30
|
+
* that sent one bad message is owed an answer rather than a closed pipe. `-32700` is
|
|
31
|
+
* parse error and `-32603` is internal error, both from the JSON-RPC 2.0 specification.
|
|
32
|
+
*/
|
|
33
|
+
const failure = (id, code, message) => JSON.stringify({ jsonrpc: '2.0', id: id ?? null, error: { code, message } });
|
|
34
|
+
/** The id of a message that may not have parsed, for the error to be addressed to. */
|
|
35
|
+
const idOf = (line) => {
|
|
36
|
+
try {
|
|
37
|
+
return JSON.parse(line).id ?? null;
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Serves the endpoint until the input closes, and resolves when it has.
|
|
45
|
+
*
|
|
46
|
+
* Sequential on purpose. The protocol permits a client to have several requests in
|
|
47
|
+
* flight, and answering them in order costs a stdio client nothing — it is one process
|
|
48
|
+
* talking to one process — while concurrency here would put the ordering of two writes
|
|
49
|
+
* to one pipe in the hands of whichever handler finished first.
|
|
50
|
+
*/
|
|
51
|
+
export const serveMcp = async (endpoint, token, session = {}) => {
|
|
52
|
+
const input = session.input ?? process.stdin;
|
|
53
|
+
const output = session.output ?? process.stdout;
|
|
54
|
+
const lines = createInterface({ input, crlfDelay: Number.POSITIVE_INFINITY });
|
|
55
|
+
for await (const line of lines) {
|
|
56
|
+
const trimmed = line.trim();
|
|
57
|
+
if (trimmed === '')
|
|
58
|
+
continue;
|
|
59
|
+
let message;
|
|
60
|
+
try {
|
|
61
|
+
message = JSON.parse(trimmed);
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
output.write(`${failure(null, -32_700, 'That line is not JSON')}\n`);
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
try {
|
|
68
|
+
const answered = await endpoint.handle(message, { token });
|
|
69
|
+
// A notification is answered by not answering, and writing an empty line for one
|
|
70
|
+
// is how a client comes to read a reply it never asked for.
|
|
71
|
+
if (answered !== undefined)
|
|
72
|
+
output.write(`${JSON.stringify(answered)}\n`);
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
// Reported to the client rather than thrown: a refused message is one message,
|
|
76
|
+
// and the session is still good. What went wrong goes to stderr, which is not
|
|
77
|
+
// the protocol's channel and is where a client shows a server's own output.
|
|
78
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
79
|
+
detail(reason);
|
|
80
|
+
output.write(`${failure(idOf(trimmed), -32_603, reason)}\n`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
input.pause();
|
|
84
|
+
};
|
|
85
|
+
const mcpCommand = async ({ cwd }) => {
|
|
86
|
+
// Loaded before the token is looked for, and the order is the whole point: a project
|
|
87
|
+
// reads its own `.env` as it is imported — that is what makes one file serve `dev`,
|
|
88
|
+
// `db:migrate` and everything else — so a token kept there is not in the environment
|
|
89
|
+
// until this line has run. Asked for first, this command told a developer that
|
|
90
|
+
// `ASSEMORA_AGENT_TOKEN` was unset while they were looking at it in `.env`.
|
|
91
|
+
const loaded = await loadConfig(cwd);
|
|
92
|
+
const project = await loadProject(loaded);
|
|
93
|
+
const token = process.env[TOKEN_VARIABLE];
|
|
94
|
+
if (token === undefined || token.trim() === '') {
|
|
95
|
+
throw new ConfigurationError(`${TOKEN_VARIABLE} is not set, and an MCP session is somebody. Put it in .env, or ` +
|
|
96
|
+
'run `assemora agents:create <name> --permissions … --write-mcp-json`, which ' +
|
|
97
|
+
'creates the identity and writes both — an anonymous caller reaches every tool ' +
|
|
98
|
+
'with no permissions at all, which is a confusing way to be refused.');
|
|
99
|
+
}
|
|
100
|
+
if (project.mcp === undefined) {
|
|
101
|
+
throw new ConfigurationError(`${loaded.file}: this project has no MCP endpoint. Assemble the application with ` +
|
|
102
|
+
'assemora({ mcp: true }) and have the config return it whole — `app: () => ' +
|
|
103
|
+
'createApp()` rather than `createApp().app`, which drops the half that speaks ' +
|
|
104
|
+
'the protocol.');
|
|
105
|
+
}
|
|
106
|
+
await serveMcp(project.mcp, token.trim());
|
|
107
|
+
return 0;
|
|
108
|
+
};
|
|
109
|
+
register(defineCommand({
|
|
110
|
+
name: 'mcp',
|
|
111
|
+
group: 'run',
|
|
112
|
+
summary: 'serve this project to an agent over stdin and stdout',
|
|
113
|
+
usage: 'assemora mcp',
|
|
114
|
+
handler: mcpCommand,
|
|
115
|
+
}));
|
|
116
|
+
//# sourceMappingURL=mcp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp.js","sourceRoot":"","sources":["../../src/commands/mcp.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type ParsedArgs } from '../args.js';
|
|
2
|
+
/** Exactly what `scaffold()` in `create-assemora` accepts. */
|
|
3
|
+
export type ScaffoldOptions = {
|
|
4
|
+
readonly name: string;
|
|
5
|
+
readonly directory: string;
|
|
6
|
+
readonly database?: string;
|
|
7
|
+
readonly studio?: boolean;
|
|
8
|
+
readonly pages?: boolean;
|
|
9
|
+
readonly mcp?: boolean;
|
|
10
|
+
readonly template?: string;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* argv, as the scaffolder's options.
|
|
14
|
+
*
|
|
15
|
+
* The name is not validated beyond being present. What makes a legal project name is
|
|
16
|
+
* `create-assemora`'s question — it is the thing that writes the directory and the
|
|
17
|
+
* `package.json` — and answering it twice is how the two would come to disagree.
|
|
18
|
+
*/
|
|
19
|
+
export declare const scaffoldOptions: (name: string, args: ParsedArgs, cwd: string) => ScaffoldOptions;
|
|
20
|
+
//# sourceMappingURL=new.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"new.d.ts","sourceRoot":"","sources":["../../src/commands/new.ts"],"names":[],"mappings":"AAiBA,OAAO,EAAc,KAAK,UAAU,EAAE,MAAM,YAAY,CAAA;AAIxD,8DAA8D;AAC9D,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAA;IACxB,QAAQ,CAAC,GAAG,CAAC,EAAE,OAAO,CAAA;IACtB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAC3B,CAAA;AAqBD;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,SAAU,MAAM,QAAQ,UAAU,OAAO,MAAM,KAAG,eAc7E,CAAA"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `assemora new <name>` — the convenience, not a second scaffolder (SPEC.md §78).
|
|
3
|
+
*
|
|
4
|
+
* `pnpm create assemora my-project` is the primary way a project starts, and this
|
|
5
|
+
* command runs the same `scaffold()` it does. Nothing about the layout of a
|
|
6
|
+
* generated project is decided here: a second implementation would drift from the
|
|
7
|
+
* starter the moment either changed, and the starter is the one CI typechecks
|
|
8
|
+
* (ADR-0021).
|
|
9
|
+
*
|
|
10
|
+
* What this file owns is the translation from argv to that function's options, and
|
|
11
|
+
* the defaults SPEC.md §78 fixes — Studio, Pages and MCP are all in unless the
|
|
12
|
+
* invocation says otherwise.
|
|
13
|
+
*/
|
|
14
|
+
import { relative, resolve } from 'node:path';
|
|
15
|
+
import { scaffold } from 'create-assemora';
|
|
16
|
+
import { bool, flag } from '../args.js';
|
|
17
|
+
import { fail, line, ok } from '../output.js';
|
|
18
|
+
import { defineCommand, register } from '../registry.js';
|
|
19
|
+
/**
|
|
20
|
+
* Whether an optional part of the starter is included.
|
|
21
|
+
*
|
|
22
|
+
* SPEC.md §78 makes all three default to yes, so the question here is only whether
|
|
23
|
+
* the invocation turned one off. Both spellings are accepted because both get typed:
|
|
24
|
+
* `--no-studio` is what a person writes, and `--studio=false` is what a script does.
|
|
25
|
+
*/
|
|
26
|
+
const included = (args, name) => {
|
|
27
|
+
if (args.flags[`no-${name}`] !== undefined)
|
|
28
|
+
return !bool(args, `no-${name}`);
|
|
29
|
+
if (args.flags[name] !== undefined)
|
|
30
|
+
return bool(args, name);
|
|
31
|
+
return true;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* argv, as the scaffolder's options.
|
|
35
|
+
*
|
|
36
|
+
* The name is not validated beyond being present. What makes a legal project name is
|
|
37
|
+
* `create-assemora`'s question — it is the thing that writes the directory and the
|
|
38
|
+
* `package.json` — and answering it twice is how the two would come to disagree.
|
|
39
|
+
*/
|
|
40
|
+
export const scaffoldOptions = (name, args, cwd) => {
|
|
41
|
+
const directory = flag(args, 'directory');
|
|
42
|
+
const database = flag(args, 'database');
|
|
43
|
+
const template = flag(args, 'template');
|
|
44
|
+
return {
|
|
45
|
+
name,
|
|
46
|
+
directory: resolve(cwd, directory ?? name),
|
|
47
|
+
...(database === undefined ? {} : { database }),
|
|
48
|
+
...(template === undefined ? {} : { template }),
|
|
49
|
+
studio: included(args, 'studio'),
|
|
50
|
+
pages: included(args, 'pages'),
|
|
51
|
+
mcp: included(args, 'mcp'),
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
const runScaffold = async (options) => scaffold(options);
|
|
55
|
+
/** An absolute path is a poor thing to type; a relative one is what `cd` wants. */
|
|
56
|
+
const shortest = (from, directory) => {
|
|
57
|
+
const step = relative(from, directory);
|
|
58
|
+
return step === '' || step.startsWith('..') ? directory : step;
|
|
59
|
+
};
|
|
60
|
+
const create = async ({ args, cwd }) => {
|
|
61
|
+
const name = args.positionals[0];
|
|
62
|
+
if (name === undefined || name.trim() === '') {
|
|
63
|
+
fail('`assemora new` needs a name for the project: `assemora new my-project`.');
|
|
64
|
+
return 2;
|
|
65
|
+
}
|
|
66
|
+
const options = scaffoldOptions(name, args, cwd);
|
|
67
|
+
const created = await runScaffold(options);
|
|
68
|
+
ok(`Created ${options.name}: ${created.files.length} files in ${created.directory}`);
|
|
69
|
+
line();
|
|
70
|
+
line('Next');
|
|
71
|
+
line(` cd ${shortest(cwd, created.directory)}`);
|
|
72
|
+
line(' pnpm install');
|
|
73
|
+
line(' assemora dev');
|
|
74
|
+
return 0;
|
|
75
|
+
};
|
|
76
|
+
register(defineCommand({
|
|
77
|
+
name: 'new',
|
|
78
|
+
group: 'project',
|
|
79
|
+
summary: 'scaffold a new project',
|
|
80
|
+
usage: 'assemora new <name> [--directory <path>] [--database <url>] [--template <name>] [--no-studio] [--no-pages] [--no-mcp]',
|
|
81
|
+
handler: create,
|
|
82
|
+
}));
|
|
83
|
+
//# sourceMappingURL=new.js.map
|