@beignet/cli 0.0.47 → 0.0.49
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/CHANGELOG.md +46 -0
- package/README.md +126 -42
- package/dist/analysis/contract-factories.d.ts +17 -0
- package/dist/analysis/contract-factories.d.ts.map +1 -0
- package/dist/analysis/contract-factories.js +176 -0
- package/dist/analysis/contract-factories.js.map +1 -0
- package/dist/analysis/layers.d.ts +5 -0
- package/dist/analysis/layers.d.ts.map +1 -0
- package/dist/analysis/layers.js +172 -0
- package/dist/analysis/layers.js.map +1 -0
- package/dist/analysis/port-wiring.d.ts +23 -0
- package/dist/analysis/port-wiring.d.ts.map +1 -0
- package/dist/analysis/port-wiring.js +379 -0
- package/dist/analysis/port-wiring.js.map +1 -0
- package/dist/analysis/source-index.d.ts +5 -0
- package/dist/analysis/source-index.d.ts.map +1 -1
- package/dist/analysis/source-index.js +171 -42
- package/dist/analysis/source-index.js.map +1 -1
- package/dist/analysis/workspace.d.ts +10 -4
- package/dist/analysis/workspace.d.ts.map +1 -1
- package/dist/analysis/workspace.js +66 -6
- package/dist/analysis/workspace.js.map +1 -1
- package/dist/app-map.d.ts.map +1 -1
- package/dist/app-map.js +311 -92
- package/dist/app-map.js.map +1 -1
- package/dist/check.d.ts +8 -1
- package/dist/check.d.ts.map +1 -1
- package/dist/check.js +55 -9
- package/dist/check.js.map +1 -1
- package/dist/db.d.ts +38 -0
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +52 -0
- package/dist/db.js.map +1 -1
- package/dist/explain.js +4 -4
- package/dist/explain.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +73 -6
- package/dist/index.js.map +1 -1
- package/dist/inspect.d.ts.map +1 -1
- package/dist/inspect.js +454 -170
- package/dist/inspect.js.map +1 -1
- package/dist/lib.d.ts +2 -2
- package/dist/lib.d.ts.map +1 -1
- package/dist/lib.js +1 -1
- package/dist/lib.js.map +1 -1
- package/dist/lint.d.ts.map +1 -1
- package/dist/lint.js +309 -487
- package/dist/lint.js.map +1 -1
- package/dist/make/inbox.d.ts.map +1 -1
- package/dist/make/inbox.js +12 -5
- package/dist/make/inbox.js.map +1 -1
- package/dist/make/payments.js +3 -3
- package/dist/make/payments.js.map +1 -1
- package/dist/make/tenancy.js +1 -1
- package/dist/make/tenancy.js.map +1 -1
- package/dist/make.d.ts.map +1 -1
- package/dist/make.js +144 -8
- package/dist/make.js.map +1 -1
- package/dist/mcp.d.ts +1 -1
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +56 -19
- package/dist/mcp.js.map +1 -1
- package/dist/preflight.d.ts +11 -3
- package/dist/preflight.d.ts.map +1 -1
- package/dist/preflight.js +139 -16
- package/dist/preflight.js.map +1 -1
- package/dist/provider-add.d.ts.map +1 -1
- package/dist/provider-add.js +149 -9
- package/dist/provider-add.js.map +1 -1
- package/dist/provider-audit.d.ts +13 -0
- package/dist/provider-audit.d.ts.map +1 -1
- package/dist/provider-audit.js +122 -8
- package/dist/provider-audit.js.map +1 -1
- package/dist/registry-edits.d.ts +7 -0
- package/dist/registry-edits.d.ts.map +1 -1
- package/dist/registry-edits.js +237 -53
- package/dist/registry-edits.js.map +1 -1
- package/dist/templates/agents.d.ts.map +1 -1
- package/dist/templates/agents.js +29 -7
- package/dist/templates/agents.js.map +1 -1
- package/dist/templates/base.d.ts +1 -0
- package/dist/templates/base.d.ts.map +1 -1
- package/dist/templates/base.js +20 -6
- package/dist/templates/base.js.map +1 -1
- package/dist/templates/db/index.d.ts +1 -0
- package/dist/templates/db/index.d.ts.map +1 -1
- package/dist/templates/db/index.js.map +1 -1
- package/dist/templates/db/migration-status.d.ts +9 -0
- package/dist/templates/db/migration-status.d.ts.map +1 -0
- package/dist/templates/db/migration-status.js +117 -0
- package/dist/templates/db/migration-status.js.map +1 -0
- package/dist/templates/db/mysql.d.ts.map +1 -1
- package/dist/templates/db/mysql.js +23 -0
- package/dist/templates/db/mysql.js.map +1 -1
- package/dist/templates/db/postgres.d.ts.map +1 -1
- package/dist/templates/db/postgres.js +21 -0
- package/dist/templates/db/postgres.js.map +1 -1
- package/dist/templates/db/sqlite.d.ts.map +1 -1
- package/dist/templates/db/sqlite.js +53 -0
- package/dist/templates/db/sqlite.js.map +1 -1
- package/dist/templates/index.d.ts.map +1 -1
- package/dist/templates/index.js +5 -0
- package/dist/templates/index.js.map +1 -1
- package/dist/templates/server.d.ts.map +1 -1
- package/dist/templates/server.js +2 -0
- package/dist/templates/server.js.map +1 -1
- package/dist/templates/shared.d.ts +1 -0
- package/dist/templates/shared.d.ts.map +1 -1
- package/dist/templates/shared.js +4 -2
- package/dist/templates/shared.js.map +1 -1
- package/package.json +7 -6
- package/skills/app-structure/SKILL.md +56 -11
- package/src/analysis/contract-factories.ts +256 -0
- package/src/analysis/layers.ts +266 -0
- package/src/analysis/port-wiring.ts +506 -0
- package/src/analysis/source-index.ts +225 -57
- package/src/analysis/workspace.ts +115 -6
- package/src/app-map.ts +418 -109
- package/src/check.ts +86 -10
- package/src/db.ts +97 -0
- package/src/explain.ts +4 -4
- package/src/index.ts +108 -7
- package/src/inspect.ts +659 -216
- package/src/lib.ts +9 -1
- package/src/lint.ts +393 -643
- package/src/make/inbox.ts +12 -5
- package/src/make/payments.ts +3 -3
- package/src/make/tenancy.ts +1 -1
- package/src/make.ts +225 -8
- package/src/mcp.ts +73 -21
- package/src/preflight.ts +195 -16
- package/src/provider-add.ts +193 -9
- package/src/provider-audit.ts +210 -18
- package/src/registry-edits.ts +313 -63
- package/src/templates/agents.ts +29 -7
- package/src/templates/base.ts +21 -4
- package/src/templates/db/index.ts +1 -0
- package/src/templates/db/migration-status.ts +126 -0
- package/src/templates/db/mysql.ts +23 -0
- package/src/templates/db/postgres.ts +21 -0
- package/src/templates/db/sqlite.ts +53 -0
- package/src/templates/index.ts +5 -0
- package/src/templates/server.ts +2 -0
- package/src/templates/shared.ts +4 -2
package/src/registry-edits.ts
CHANGED
|
@@ -308,24 +308,324 @@ export function outboxRegistryValueInfo(
|
|
|
308
308
|
source: string,
|
|
309
309
|
property: "events" | "jobs",
|
|
310
310
|
): InitializerInfo | undefined {
|
|
311
|
-
const
|
|
312
|
-
if (!
|
|
311
|
+
const match = outboxRegistryPropertyAnalysis(source, property);
|
|
312
|
+
if (!match?.value || match.indeterminate) return undefined;
|
|
313
|
+
const start = match.value.getStart(match.sourceFile);
|
|
314
|
+
return {
|
|
315
|
+
text: source.slice(start, match.value.end),
|
|
316
|
+
start,
|
|
317
|
+
end: match.value.end,
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
export type OutboxRegistryEntryState =
|
|
322
|
+
| "absent"
|
|
323
|
+
| "empty"
|
|
324
|
+
| "present"
|
|
325
|
+
| "indeterminate";
|
|
326
|
+
|
|
327
|
+
/** Static state of one outbox registry property. */
|
|
328
|
+
export function outboxRegistryEntryState(
|
|
329
|
+
source: string,
|
|
330
|
+
property: "events" | "jobs",
|
|
331
|
+
): OutboxRegistryEntryState {
|
|
332
|
+
const match = outboxRegistryPropertyAnalysis(source, property);
|
|
333
|
+
if (!match) return "absent";
|
|
334
|
+
if (match.indeterminate) return "indeterminate";
|
|
335
|
+
if (!match.value) return "empty";
|
|
336
|
+
return resolvedRegistryExpressions(match.value, match.sourceFile).length > 0
|
|
337
|
+
? "present"
|
|
338
|
+
: "empty";
|
|
339
|
+
}
|
|
313
340
|
|
|
314
|
-
|
|
315
|
-
|
|
341
|
+
/** Whether a registry property is non-empty or cannot be proven empty. */
|
|
342
|
+
export function outboxRegistryHasEntries(
|
|
343
|
+
source: string,
|
|
344
|
+
property: "events" | "jobs",
|
|
345
|
+
): boolean {
|
|
346
|
+
const state = outboxRegistryEntryState(source, property);
|
|
347
|
+
return state === "present" || state === "indeterminate";
|
|
348
|
+
}
|
|
316
349
|
|
|
317
|
-
|
|
318
|
-
|
|
350
|
+
/** Identifiers referenced by a registry property after resolving local array aliases. */
|
|
351
|
+
export function outboxRegistryIdentifiers(
|
|
352
|
+
source: string,
|
|
353
|
+
property: "events" | "jobs",
|
|
354
|
+
): Set<string> {
|
|
355
|
+
const match = outboxRegistryPropertyAnalysis(source, property);
|
|
356
|
+
if (!match?.value || match.indeterminate) return new Set();
|
|
357
|
+
|
|
358
|
+
const identifiers = new Set<string>();
|
|
359
|
+
for (const expression of resolvedRegistryExpressions(
|
|
360
|
+
match.value,
|
|
361
|
+
match.sourceFile,
|
|
362
|
+
)) {
|
|
363
|
+
const start = expression.getStart(match.sourceFile);
|
|
364
|
+
for (const identifier of identifiersFromArrayExpression(
|
|
365
|
+
source.slice(start, expression.end),
|
|
366
|
+
)) {
|
|
367
|
+
identifiers.add(identifier);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
return identifiers;
|
|
371
|
+
}
|
|
319
372
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
373
|
+
type OutboxRegistryPropertyAnalysis = {
|
|
374
|
+
sourceFile: ts.SourceFile;
|
|
375
|
+
value?: ts.Expression;
|
|
376
|
+
indeterminate: boolean;
|
|
377
|
+
};
|
|
323
378
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
379
|
+
function outboxRegistryPropertyAnalysis(
|
|
380
|
+
source: string,
|
|
381
|
+
property: "events" | "jobs",
|
|
382
|
+
): OutboxRegistryPropertyAnalysis | undefined {
|
|
383
|
+
const sourceFile = ts.createSourceFile(
|
|
384
|
+
"outbox-registry.ts",
|
|
385
|
+
source,
|
|
386
|
+
ts.ScriptTarget.Latest,
|
|
387
|
+
true,
|
|
388
|
+
ts.ScriptKind.TS,
|
|
389
|
+
);
|
|
390
|
+
const initializers = registryInitializers(sourceFile);
|
|
391
|
+
const selected = selectOutboxRegistryCall(sourceFile);
|
|
392
|
+
if (!selected.call) {
|
|
393
|
+
return selected.indeterminate
|
|
394
|
+
? { sourceFile, indeterminate: true }
|
|
395
|
+
: undefined;
|
|
396
|
+
}
|
|
327
397
|
|
|
328
|
-
|
|
398
|
+
const firstArgument = selected.call.arguments[0];
|
|
399
|
+
const object = firstArgument
|
|
400
|
+
? resolveRegistryExpression(firstArgument, initializers)
|
|
401
|
+
: undefined;
|
|
402
|
+
if (!object || !ts.isObjectLiteralExpression(object)) {
|
|
403
|
+
return { sourceFile, indeterminate: true };
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
let value: ts.Expression | undefined;
|
|
407
|
+
let indeterminate = false;
|
|
408
|
+
for (const member of object.properties) {
|
|
409
|
+
if (ts.isSpreadAssignment(member)) {
|
|
410
|
+
value = undefined;
|
|
411
|
+
indeterminate = true;
|
|
412
|
+
continue;
|
|
413
|
+
}
|
|
414
|
+
const name = member.name ? registryPropertyName(member.name) : undefined;
|
|
415
|
+
if (!name) {
|
|
416
|
+
value = undefined;
|
|
417
|
+
indeterminate = true;
|
|
418
|
+
continue;
|
|
419
|
+
}
|
|
420
|
+
if (name !== property) continue;
|
|
421
|
+
if (
|
|
422
|
+
ts.isPropertyAssignment(member) ||
|
|
423
|
+
ts.isShorthandPropertyAssignment(member)
|
|
424
|
+
) {
|
|
425
|
+
value = ts.isPropertyAssignment(member)
|
|
426
|
+
? member.initializer
|
|
427
|
+
: member.name;
|
|
428
|
+
indeterminate = false;
|
|
429
|
+
} else {
|
|
430
|
+
value = undefined;
|
|
431
|
+
indeterminate = true;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
if (value && !isStaticRegistryValue(value, initializers)) {
|
|
435
|
+
return { sourceFile, indeterminate: true };
|
|
436
|
+
}
|
|
437
|
+
return { sourceFile, value, indeterminate };
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function isStaticRegistryValue(
|
|
441
|
+
expression: ts.Expression,
|
|
442
|
+
initializers: Map<string, ts.Expression>,
|
|
443
|
+
visited = new Set<string>(),
|
|
444
|
+
): boolean {
|
|
445
|
+
const current = unwrapRegistryExpression(expression);
|
|
446
|
+
if (ts.isArrayLiteralExpression(current)) return true;
|
|
447
|
+
if (!ts.isIdentifier(current)) return false;
|
|
448
|
+
|
|
449
|
+
const initializer = initializers.get(current.text);
|
|
450
|
+
if (!initializer) return true;
|
|
451
|
+
if (visited.has(current.text)) return false;
|
|
452
|
+
|
|
453
|
+
const nextVisited = new Set(visited);
|
|
454
|
+
nextVisited.add(current.text);
|
|
455
|
+
return isStaticRegistryValue(initializer, initializers, nextVisited);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
function selectOutboxRegistryCall(sourceFile: ts.SourceFile): {
|
|
459
|
+
call?: ts.CallExpression;
|
|
460
|
+
indeterminate: boolean;
|
|
461
|
+
} {
|
|
462
|
+
const exportedNames = locallyExportedRegistryNames(sourceFile);
|
|
463
|
+
const candidates: {
|
|
464
|
+
name?: string;
|
|
465
|
+
exported: boolean;
|
|
466
|
+
calls: ts.CallExpression[];
|
|
467
|
+
}[] = [];
|
|
468
|
+
|
|
469
|
+
for (const statement of sourceFile.statements) {
|
|
470
|
+
if (!ts.isVariableStatement(statement)) continue;
|
|
471
|
+
const statementExported = Boolean(
|
|
472
|
+
statement.modifiers?.some(
|
|
473
|
+
(modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword,
|
|
474
|
+
),
|
|
475
|
+
);
|
|
476
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
477
|
+
if (!declaration.initializer) continue;
|
|
478
|
+
const calls = outboxRegistryCalls(declaration.initializer);
|
|
479
|
+
if (calls.length === 0) continue;
|
|
480
|
+
const name = ts.isIdentifier(declaration.name)
|
|
481
|
+
? declaration.name.text
|
|
482
|
+
: undefined;
|
|
483
|
+
candidates.push({
|
|
484
|
+
...(name ? { name } : {}),
|
|
485
|
+
exported: statementExported || (name ? exportedNames.has(name) : false),
|
|
486
|
+
calls,
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
const canonical = candidates.filter(
|
|
492
|
+
(candidate) => candidate.name === "outboxRegistry" && candidate.exported,
|
|
493
|
+
);
|
|
494
|
+
const exported = candidates.filter((candidate) => candidate.exported);
|
|
495
|
+
const selected =
|
|
496
|
+
canonical.length > 0
|
|
497
|
+
? canonical
|
|
498
|
+
: exported.length > 0
|
|
499
|
+
? exported
|
|
500
|
+
: candidates;
|
|
501
|
+
if (selected.length === 1 && selected[0].calls.length === 1) {
|
|
502
|
+
return { call: selected[0].calls[0], indeterminate: false };
|
|
503
|
+
}
|
|
504
|
+
if (selected.length > 0) return { indeterminate: true };
|
|
505
|
+
|
|
506
|
+
const fallback = outboxRegistryCalls(sourceFile);
|
|
507
|
+
return fallback.length === 1
|
|
508
|
+
? { call: fallback[0], indeterminate: false }
|
|
509
|
+
: { indeterminate: fallback.length > 0 };
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
function locallyExportedRegistryNames(sourceFile: ts.SourceFile): Set<string> {
|
|
513
|
+
const names = new Set<string>();
|
|
514
|
+
for (const statement of sourceFile.statements) {
|
|
515
|
+
if (
|
|
516
|
+
!ts.isExportDeclaration(statement) ||
|
|
517
|
+
!statement.exportClause ||
|
|
518
|
+
!ts.isNamedExports(statement.exportClause) ||
|
|
519
|
+
statement.moduleSpecifier
|
|
520
|
+
) {
|
|
521
|
+
continue;
|
|
522
|
+
}
|
|
523
|
+
for (const element of statement.exportClause.elements) {
|
|
524
|
+
names.add(element.propertyName?.text ?? element.name.text);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
return names;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
function outboxRegistryCalls(node: ts.Node): ts.CallExpression[] {
|
|
531
|
+
const calls: ts.CallExpression[] = [];
|
|
532
|
+
const visit = (child: ts.Node): void => {
|
|
533
|
+
if (
|
|
534
|
+
ts.isCallExpression(child) &&
|
|
535
|
+
((ts.isIdentifier(child.expression) &&
|
|
536
|
+
child.expression.text === "defineOutboxRegistry") ||
|
|
537
|
+
(ts.isPropertyAccessExpression(child.expression) &&
|
|
538
|
+
child.expression.name.text === "defineOutboxRegistry"))
|
|
539
|
+
) {
|
|
540
|
+
calls.push(child);
|
|
541
|
+
}
|
|
542
|
+
ts.forEachChild(child, visit);
|
|
543
|
+
};
|
|
544
|
+
visit(node);
|
|
545
|
+
return calls;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
function resolvedRegistryExpressions(
|
|
549
|
+
expression: ts.Expression,
|
|
550
|
+
sourceFile: ts.SourceFile,
|
|
551
|
+
): ts.Expression[] {
|
|
552
|
+
const initializers = registryInitializers(sourceFile);
|
|
553
|
+
|
|
554
|
+
const resolve = (
|
|
555
|
+
candidate: ts.Expression,
|
|
556
|
+
visited: Set<string>,
|
|
557
|
+
): ts.Expression[] => {
|
|
558
|
+
const current = unwrapRegistryExpression(candidate);
|
|
559
|
+
if (ts.isArrayLiteralExpression(current)) {
|
|
560
|
+
return current.elements.flatMap((element) =>
|
|
561
|
+
ts.isSpreadElement(element)
|
|
562
|
+
? resolve(element.expression, new Set(visited))
|
|
563
|
+
: ts.isExpression(element)
|
|
564
|
+
? [element]
|
|
565
|
+
: [],
|
|
566
|
+
);
|
|
567
|
+
}
|
|
568
|
+
if (ts.isIdentifier(current) && !visited.has(current.text)) {
|
|
569
|
+
const initializer = initializers.get(current.text);
|
|
570
|
+
if (initializer) {
|
|
571
|
+
const nextVisited = new Set(visited);
|
|
572
|
+
nextVisited.add(current.text);
|
|
573
|
+
return resolve(initializer, nextVisited);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
return [current];
|
|
577
|
+
};
|
|
578
|
+
|
|
579
|
+
return resolve(expression, new Set());
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
function registryInitializers(
|
|
583
|
+
sourceFile: ts.SourceFile,
|
|
584
|
+
): Map<string, ts.Expression> {
|
|
585
|
+
const initializers = new Map<string, ts.Expression>();
|
|
586
|
+
for (const statement of sourceFile.statements) {
|
|
587
|
+
if (!ts.isVariableStatement(statement)) continue;
|
|
588
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
589
|
+
if (ts.isIdentifier(declaration.name) && declaration.initializer) {
|
|
590
|
+
initializers.set(declaration.name.text, declaration.initializer);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
return initializers;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
function resolveRegistryExpression(
|
|
598
|
+
expression: ts.Expression,
|
|
599
|
+
initializers: Map<string, ts.Expression>,
|
|
600
|
+
visited = new Set<string>(),
|
|
601
|
+
): ts.Expression {
|
|
602
|
+
const current = unwrapRegistryExpression(expression);
|
|
603
|
+
if (!ts.isIdentifier(current) || visited.has(current.text)) return current;
|
|
604
|
+
const initializer = initializers.get(current.text);
|
|
605
|
+
if (!initializer) return current;
|
|
606
|
+
const nextVisited = new Set(visited);
|
|
607
|
+
nextVisited.add(current.text);
|
|
608
|
+
return resolveRegistryExpression(initializer, initializers, nextVisited);
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
function unwrapRegistryExpression(expression: ts.Expression): ts.Expression {
|
|
612
|
+
let current = expression;
|
|
613
|
+
while (
|
|
614
|
+
ts.isParenthesizedExpression(current) ||
|
|
615
|
+
ts.isAsExpression(current) ||
|
|
616
|
+
ts.isSatisfiesExpression(current) ||
|
|
617
|
+
ts.isTypeAssertionExpression(current) ||
|
|
618
|
+
ts.isNonNullExpression(current)
|
|
619
|
+
) {
|
|
620
|
+
current = current.expression;
|
|
621
|
+
}
|
|
622
|
+
return current;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
function registryPropertyName(name: ts.PropertyName): string | undefined {
|
|
626
|
+
return ts.isIdentifier(name) || ts.isStringLiteralLike(name)
|
|
627
|
+
? name.text
|
|
628
|
+
: undefined;
|
|
329
629
|
}
|
|
330
630
|
|
|
331
631
|
/**
|
|
@@ -390,53 +690,3 @@ function appendEntry(
|
|
|
390
690
|
if (next === source) return { kind: "unchanged" };
|
|
391
691
|
return { kind: "updated", source: next };
|
|
392
692
|
}
|
|
393
|
-
|
|
394
|
-
function topLevelValueSlice(
|
|
395
|
-
source: string,
|
|
396
|
-
valueStart: number,
|
|
397
|
-
hardEnd: number,
|
|
398
|
-
): InitializerInfo | undefined {
|
|
399
|
-
let start = valueStart;
|
|
400
|
-
while (start < hardEnd && /\s/.test(source[start])) start++;
|
|
401
|
-
if (start >= hardEnd) return undefined;
|
|
402
|
-
|
|
403
|
-
let depth = 0;
|
|
404
|
-
|
|
405
|
-
for (const index of codeCharacterIndexes(source, start, hardEnd + 1)) {
|
|
406
|
-
const char = source[index];
|
|
407
|
-
|
|
408
|
-
if (char === "[" || char === "(" || char === "{") {
|
|
409
|
-
depth++;
|
|
410
|
-
continue;
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
if (char === "]" || char === ")" || char === "}") {
|
|
414
|
-
if (depth === 0) {
|
|
415
|
-
return trimmedInfo(source, start, index);
|
|
416
|
-
}
|
|
417
|
-
depth--;
|
|
418
|
-
continue;
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
if (char === "," && depth === 0) {
|
|
422
|
-
return trimmedInfo(source, start, index);
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
return undefined;
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
function trimmedInfo(
|
|
430
|
-
source: string,
|
|
431
|
-
start: number,
|
|
432
|
-
end: number,
|
|
433
|
-
): InitializerInfo {
|
|
434
|
-
let trimmedEnd = end;
|
|
435
|
-
while (trimmedEnd > start && /\s/.test(source[trimmedEnd - 1])) trimmedEnd--;
|
|
436
|
-
|
|
437
|
-
return {
|
|
438
|
-
text: source.slice(start, trimmedEnd),
|
|
439
|
-
start,
|
|
440
|
-
end: trimmedEnd,
|
|
441
|
-
};
|
|
442
|
-
}
|
package/src/templates/agents.ts
CHANGED
|
@@ -41,6 +41,9 @@ https://beignetjs.com (agent-friendly index at https://beignetjs.com/llms.txt).
|
|
|
41
41
|
README.md covers setup and the app map; this file covers what is not
|
|
42
42
|
discoverable from the code.
|
|
43
43
|
|
|
44
|
+
This app requires Node.js 22.12 or newer. Bun 1.3.14 or newer is supported for
|
|
45
|
+
Beignet commands.
|
|
46
|
+
|
|
44
47
|
## Registration is explicit
|
|
45
48
|
|
|
46
49
|
Hand-writing an artifact file does not wire it into the app. Non-registration
|
|
@@ -54,6 +57,11 @@ is a silent failure — the file exists but never runs:
|
|
|
54
57
|
- Tasks must be added to \`defineTasks([...])\` in \`server/tasks.ts\`.
|
|
55
58
|
- Outbox events and jobs must be registered in \`defineOutboxRegistry({...})\`
|
|
56
59
|
in \`server/outbox.ts\`.
|
|
60
|
+
- An outbox registry containing jobs also requires \`jobs: JobDispatcherPort\`
|
|
61
|
+
to be bound directly or deferred to a provider. \`${cli} make job\` installs
|
|
62
|
+
an app-owned inline dispatcher only when no dispatcher is already wired;
|
|
63
|
+
adding an Inngest or BullMQ preset later replaces only that marked generated
|
|
64
|
+
fallback and rejects unmarked custom inline wiring as a conflict.
|
|
57
65
|
- Listeners must be added to the \`listeners\` array in \`server/listeners.ts\`
|
|
58
66
|
and wired through a \`registerListeners(...)\` call in server provider wiring.
|
|
59
67
|
- Apps using Inngest must also spread feature job registries into
|
|
@@ -79,7 +87,8 @@ array anchors are unambiguous. It also syncs missing default Beignet
|
|
|
79
87
|
provider-table exports when \`infra/db/schema/beignet.ts\` is missing or still
|
|
80
88
|
matches \`${cli} db schema sync\` output and the schema index has no custom
|
|
81
89
|
named re-export for that file. Run \`${cli} db generate\` and
|
|
82
|
-
\`${cli} db migrate\` after accepting that source
|
|
90
|
+
\`${cli} db migrate\`, then \`${cli} db status\` after accepting that source
|
|
91
|
+
repair. Custom or ambiguous
|
|
83
92
|
code stays diagnostic-only.
|
|
84
93
|
|
|
85
94
|
## Prefer generators
|
|
@@ -89,10 +98,12 @@ files — prefer it over hand-writing them. See \`${cli} make --help\` for the
|
|
|
89
98
|
artifact list. Use \`${cli} make feature <name> --recipe full-slice\` when you
|
|
90
99
|
need a richer reference slice with policy, client helpers, workflow artifacts,
|
|
91
100
|
events, listener registration, jobs, and outbox wiring. After changing the Drizzle schema in
|
|
92
|
-
\`infra/db/schema/\`, run \`${cli} db generate
|
|
101
|
+
\`infra/db/schema/\`, run \`${cli} db generate\`, \`${cli} db migrate\`, then
|
|
102
|
+
\`${cli} db status\`.
|
|
93
103
|
When MCP is available, use \`db_schema_sync\` for Beignet provider table
|
|
94
104
|
re-exports and \`db\` for \`generate\`, \`migrate\`, \`seed\`, or \`reset\`
|
|
95
|
-
instead of falling back to a shell. Use \`
|
|
105
|
+
instead of falling back to a shell. Use the read-only \`db_status\` tool before
|
|
106
|
+
deploying. Use \`task_run\`, \`schedule_run\`,
|
|
96
107
|
\`outbox_inspect\`, and \`outbox_run\` for registered operational workflows
|
|
97
108
|
and outbox recovery.
|
|
98
109
|
|
|
@@ -103,6 +114,7 @@ apps have reimplemented by accident:
|
|
|
103
114
|
|
|
104
115
|
| Need | Use |
|
|
105
116
|
| --- | --- |${capabilityClientRow}
|
|
117
|
+
| Query parameters on an HTTP contract | Pass both the Standard Schema and an explicit \`defineQueryTransport({ ... })\` to \`.query(schema, transport)\`. Use \`query.string()\`, \`query.number()\`, \`query.integer()\`, \`query.boolean()\`, \`query.dateTime()\`, \`query.date()\`, repeated scalar arrays, or one flat \`deepObject\`; never make schema coercion define the URL format. |
|
|
106
118
|
| App context outside HTTP — agents, queues, scripts, backfills | \`server.createServiceContext(input)\`, or \`server.runServiceContext(input, fn)\` in one-off scripts. Never hand-assemble a context or call \`gate.attach(...)\` — the server owns gate attachment. |
|
|
107
119
|
| Ports outside a request — auth callbacks, module-level helpers | \`const { ports } = await getServer()\` (dynamic \`import("@/server")\` breaks module cycles). Do not construct parallel provider clients or fall back to \`console.*\` when \`ports.logger\` exists. |
|
|
108
120
|
| Routes that cannot be contracts — webhooks, third-party callbacks, streaming | \`createWebhookRoute\`, \`createPaymentWebhookRoute\`, \`createScheduleRoute\`, \`createOutboxDrainRoute\` from \`@beignet/next\`; \`server.rawRoute(...)\` for anything else. All run the hooks pipeline — never hand-enforce rate limits in a route body. |
|
|
@@ -130,6 +142,13 @@ formatting. The individual commands (\`${lint}\`, \`${cli} lint\`,
|
|
|
130
142
|
\`${cli} doctor --strict\`, \`${test}\`, \`${typecheck}\`) still work when you
|
|
131
143
|
need one check alone.
|
|
132
144
|
|
|
145
|
+
Beignet's architecture lint parses TypeScript imports and re-exports,
|
|
146
|
+
\`require()\`, import assignments, and literal dynamic imports, then follows
|
|
147
|
+
local value imports through feature-root helpers. When lint cannot verify a
|
|
148
|
+
computed module reference inside a constrained layer, use a string-literal
|
|
149
|
+
specifier or move runtime loading behind an allowed boundary. Move near-miss
|
|
150
|
+
feature layers to the canonical path named in the diagnostic.
|
|
151
|
+
|
|
133
152
|
Before exploring unfamiliar code, run \`${cli} map\` for the human inventory
|
|
134
153
|
or \`${cli} map --json --feature <name>\` for an evidence-backed graph scoped
|
|
135
154
|
to one feature. The graph honors the app's complete TypeScript \`baseUrl\` and
|
|
@@ -175,7 +194,7 @@ skill-loading block.
|
|
|
175
194
|
\`.mcp.json\` registers the app-local \`@beignet/cli\` bin at
|
|
176
195
|
\`./node_modules/.bin/beignet mcp\`, which exposes app context, validation,
|
|
177
196
|
generation, and operations as structured tools named exactly: \`app_map\`,
|
|
178
|
-
\`explain\`, \`check\`, \`db\`, \`db_schema_sync\`, \`task_run\`,
|
|
197
|
+
\`explain\`, \`check\`, \`db\`, \`db_status\`, \`db_schema_sync\`, \`task_run\`,
|
|
179
198
|
\`schedule_run\`, \`outbox_inspect\`, \`outbox_run\`, \`routes\`, \`doctor\`,
|
|
180
199
|
\`doctor_fix_plan\`, \`doctor_fix\`, \`lint\`, \`make\`, \`provider_add\`.
|
|
181
200
|
It also publishes \`beignet://app/guidance\` and focused
|
|
@@ -190,11 +209,14 @@ relationships, conventions, findings, and suggested files for any mapped
|
|
|
190
209
|
concept or diagnostic; its kinds match the app map. Use \`check\` after edits
|
|
191
210
|
to run the complete validation loop; it returns the same versioned result as
|
|
192
211
|
\`${cli} check --json\`, bounds failure output, and cancels the active package
|
|
193
|
-
script when the MCP request is cancelled.
|
|
194
|
-
|
|
212
|
+
script when the MCP request is cancelled. Pass \`preflightConnect: true\` to
|
|
213
|
+
include migration status and dependency health checks; \`connectTimeoutMs\`
|
|
214
|
+
sets their per-check timeout. It does not apply Beignet fixes, but app-owned
|
|
215
|
+
scripts retain their normal side effects. Use \`db_schema_sync\` to
|
|
195
216
|
preview or apply idempotent provider-table schema re-exports, then call \`db\`
|
|
196
217
|
with \`generate\` and \`migrate\`; database output is bounded, commands time
|
|
197
|
-
out, and cancellation stops the active process tree.
|
|
218
|
+
out, and cancellation stops the active process tree. Use \`db_status\` for a
|
|
219
|
+
read-only current, pending, or failed migration report. Treat \`seed\` and
|
|
198
220
|
especially \`reset\` as app-owned mutations. Lifecycle \`dryRun\` validates
|
|
199
221
|
and reports the script without executing it; it does not simulate SQL or data
|
|
200
222
|
changes. Use \`task_run\` and \`schedule_run\` for registered operational
|
package/src/templates/base.ts
CHANGED
|
@@ -103,6 +103,7 @@ export function packageJson(ctx: TemplateContext): string {
|
|
|
103
103
|
"db:generate": "drizzle-kit generate",
|
|
104
104
|
"db:migrate": "drizzle-kit migrate",
|
|
105
105
|
"db:reset": currentGeneratedPackageScripts["db:reset"],
|
|
106
|
+
"db:status": currentGeneratedPackageScripts["db:status"],
|
|
106
107
|
};
|
|
107
108
|
|
|
108
109
|
return json({
|
|
@@ -110,6 +111,9 @@ export function packageJson(ctx: TemplateContext): string {
|
|
|
110
111
|
version: "0.1.0",
|
|
111
112
|
private: true,
|
|
112
113
|
type: "module",
|
|
114
|
+
engines: {
|
|
115
|
+
node: ">=22.12.0",
|
|
116
|
+
},
|
|
113
117
|
scripts,
|
|
114
118
|
intent: {
|
|
115
119
|
skills: intentSkillPackages(ctx),
|
|
@@ -243,6 +247,9 @@ route groups such as \`app/(app)\` are not part of the URL.
|
|
|
243
247
|
|
|
244
248
|
Beignet app scaffolded with \`@beignet/cli\`.
|
|
245
249
|
|
|
250
|
+
Node.js 22.12 or newer is required. Bun 1.3.14 or newer is supported for
|
|
251
|
+
Beignet commands.
|
|
252
|
+
|
|
246
253
|
## Getting started
|
|
247
254
|
|
|
248
255
|
\`\`\`bash
|
|
@@ -254,9 +261,10 @@ ${startDatabase}## Prepare the database
|
|
|
254
261
|
|
|
255
262
|
\`\`\`bash
|
|
256
263
|
${cli} db migrate
|
|
264
|
+
${cli} db status
|
|
257
265
|
\`\`\`
|
|
258
266
|
|
|
259
|
-
The starter ships its initial Drizzle migration in \`drizzle/\`, so the first run only applies it. After you change \`infra/db/schema/\`, run \`${cli} db generate\` and \`${cli} db migrate\` together
|
|
267
|
+
The starter ships its initial Drizzle migration in \`drizzle/\`, so the first run only applies it. After you change \`infra/db/schema/\`, run \`${cli} db generate\` and \`${cli} db migrate\` together, then verify the target database with \`${cli} db status\`.
|
|
260
268
|
|
|
261
269
|
## Start the app
|
|
262
270
|
|
|
@@ -280,7 +288,9 @@ use cases, workflows, ports, providers, and tests connect. \`check\` runs the
|
|
|
280
288
|
whole validation loop in one pass: \`${cli} lint\` (dependency direction),
|
|
281
289
|
\`${cli} doctor --strict\` (route, OpenAPI, and resource drift), and the
|
|
282
290
|
\`lint\`, \`typecheck\`, and \`test\` package scripts. Use \`${format}\` to
|
|
283
|
-
apply Biome formatting.
|
|
291
|
+
apply Biome formatting. Beignet lint uses the TypeScript import graph, follows
|
|
292
|
+
feature-root helpers, and reports unverifiable computed module references in
|
|
293
|
+
constrained layers.
|
|
284
294
|
${testingNotes}
|
|
285
295
|
## Coding agents
|
|
286
296
|
|
|
@@ -303,10 +313,11 @@ ${start}
|
|
|
303
313
|
${cli} make feature projects
|
|
304
314
|
${cli} db generate
|
|
305
315
|
${cli} db migrate
|
|
316
|
+
${cli} db status
|
|
306
317
|
${cli} check
|
|
307
318
|
\`\`\`
|
|
308
319
|
|
|
309
|
-
\`make feature\` creates a contract-to-test vertical slice with Drizzle schema and repository files, so regenerate and
|
|
320
|
+
\`make feature\` creates a contract-to-test vertical slice with Drizzle schema and repository files, so regenerate, migrate, and verify the database before running the app against the new feature.
|
|
310
321
|
Use \`${cli} make feature projects --recipe full-slice\` when you want a richer reference slice with policy, feature client helpers, workflow artifacts, events, listener registration, jobs, and outbox wiring.
|
|
311
322
|
|
|
312
323
|
## App map
|
|
@@ -352,7 +363,8 @@ ${optionalAuthRedirects}
|
|
|
352
363
|
" db migrate` against it before starting the app."
|
|
353
364
|
}
|
|
354
365
|
- Size the process-wide database client for the deployment. Postgres and MySQL use the generated pool-limit environment variable; count web, worker, preview, and task processes against the database connection limit, and keep writable SQLite files on one host.
|
|
355
|
-
- Run \`${cli} db generate
|
|
366
|
+
- Run \`${cli} db generate\`, \`${cli} db migrate\`, and \`${cli} db status\` after changing the Drizzle schema.
|
|
367
|
+
- Run \`${cli} db migrate\` in one serialized migration job per environment, then \`${cli} db status\` and \`${cli} preflight --connect\` before shifting traffic. Do not migrate independently from every web replica.
|
|
356
368
|
- Run \`${cli} db reset\` to rebuild a local ${
|
|
357
369
|
ctx.database === "sqlite"
|
|
358
370
|
? "SQLite"
|
|
@@ -429,6 +441,7 @@ export function providersDoc(ctx: TemplateContext): string {
|
|
|
429
441
|
"- Peer dependencies: `@upstash/ratelimit` and `@upstash/redis`",
|
|
430
442
|
"- The starter wires `createUpstashRateLimitProvider()` in `server/providers.ts` and adds `rateLimit: RateLimitPort` to `AppPorts`.",
|
|
431
443
|
"- `.env.example` ships dev placeholders so the app boots; set real `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` values before rate limiting requests.",
|
|
444
|
+
"- Rate-limit decisions time out after 5000ms and fail closed by default. Opt into `UPSTASH_TIMEOUT_POLICY=fail-open` only when availability outweighs enforcement across this provider during an outage.",
|
|
432
445
|
"",
|
|
433
446
|
);
|
|
434
447
|
}
|
|
@@ -510,6 +523,8 @@ export function envExample(ctx: TemplateContext): string {
|
|
|
510
523
|
"UPSTASH_REDIS_REST_URL=https://dev-placeholder.upstash.io",
|
|
511
524
|
"UPSTASH_REDIS_REST_TOKEN=dev-placeholder-change-me",
|
|
512
525
|
"UPSTASH_PREFIX=beignet:ratelimit",
|
|
526
|
+
"# UPSTASH_TIMEOUT_MS=5000",
|
|
527
|
+
"# UPSTASH_TIMEOUT_POLICY=fail-closed",
|
|
513
528
|
"",
|
|
514
529
|
);
|
|
515
530
|
}
|
|
@@ -518,6 +533,8 @@ export function envExample(ctx: TemplateContext): string {
|
|
|
518
533
|
}
|
|
519
534
|
|
|
520
535
|
const files = {
|
|
536
|
+
gitattributes: `**/drizzle/*.sql text eol=lf
|
|
537
|
+
`,
|
|
521
538
|
gitignore: `.next
|
|
522
539
|
node_modules
|
|
523
540
|
dist
|