@beignet/cli 0.0.42 → 0.0.44
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 +24 -0
- package/README.md +21 -5
- package/dist/analysis/source-index.d.ts +1 -1
- package/dist/analysis/source-index.d.ts.map +1 -1
- package/dist/analysis/source-index.js +4 -2
- package/dist/analysis/source-index.js.map +1 -1
- package/dist/analysis/workspace.d.ts +3 -2
- package/dist/analysis/workspace.d.ts.map +1 -1
- package/dist/analysis/workspace.js +51 -39
- package/dist/analysis/workspace.js.map +1 -1
- package/dist/app-map.d.ts.map +1 -1
- package/dist/app-map.js +65 -4
- package/dist/app-map.js.map +1 -1
- package/dist/inspect.d.ts +5 -0
- package/dist/inspect.d.ts.map +1 -1
- package/dist/inspect.js +32 -135
- package/dist/inspect.js.map +1 -1
- package/dist/make/inbox.js +4 -4
- package/dist/make/payments.js +6 -6
- package/dist/make/tenancy.d.ts.map +1 -1
- package/dist/make/tenancy.js +12 -14
- package/dist/make/tenancy.js.map +1 -1
- package/dist/make.d.ts.map +1 -1
- package/dist/make.js +33 -23
- package/dist/make.js.map +1 -1
- package/dist/registry-edits.d.ts +7 -1
- package/dist/registry-edits.d.ts.map +1 -1
- package/dist/registry-edits.js +61 -43
- package/dist/registry-edits.js.map +1 -1
- package/dist/templates/agents.d.ts.map +1 -1
- package/dist/templates/agents.js +10 -3
- package/dist/templates/agents.js.map +1 -1
- package/dist/templates/base.d.ts.map +1 -1
- package/dist/templates/base.js +4 -2
- package/dist/templates/base.js.map +1 -1
- package/dist/templates/db/mysql.js +3 -3
- package/dist/templates/db/postgres.js +3 -3
- package/dist/templates/db/sqlite.js +3 -3
- package/dist/templates/server.js +4 -4
- package/dist/templates/server.js.map +1 -1
- package/dist/templates/todos.js +2 -2
- package/package.json +2 -2
- package/skills/app-structure/SKILL.md +9 -0
- package/src/analysis/source-index.ts +12 -3
- package/src/analysis/workspace.ts +94 -52
- package/src/app-map.ts +82 -4
- package/src/inspect.ts +40 -150
- package/src/make/inbox.ts +4 -4
- package/src/make/payments.ts +6 -6
- package/src/make/tenancy.ts +12 -14
- package/src/make.ts +36 -24
- package/src/registry-edits.ts +81 -39
- package/src/templates/agents.ts +10 -3
- package/src/templates/base.ts +4 -2
- package/src/templates/db/mysql.ts +3 -3
- package/src/templates/db/postgres.ts +3 -3
- package/src/templates/db/sqlite.ts +3 -3
- package/src/templates/server.ts +4 -4
- package/src/templates/todos.ts +2 -2
package/src/make/tenancy.ts
CHANGED
|
@@ -952,7 +952,7 @@ export const workspaceMemberWithUserSchema = workspaceMemberSchema.extend({
|
|
|
952
952
|
export const workspaceInviteSchema = z.object({
|
|
953
953
|
\tid: z.string().min(1),
|
|
954
954
|
\tworkspaceId: z.string().min(1),
|
|
955
|
-
\temail: z.
|
|
955
|
+
\temail: z.email(),
|
|
956
956
|
\trole: workspaceRoleSchema,
|
|
957
957
|
\ttoken: z.string().min(1),
|
|
958
958
|
\tinvitedByUserId: z.string().min(1),
|
|
@@ -1019,7 +1019,7 @@ export const listInvitesOutputSchema = z.object({
|
|
|
1019
1019
|
});
|
|
1020
1020
|
|
|
1021
1021
|
export const createInviteInputSchema = z.object({
|
|
1022
|
-
\temail: z.
|
|
1022
|
+
\temail: z.email(),
|
|
1023
1023
|
\trole: workspaceRoleSchema.default("member"),
|
|
1024
1024
|
});
|
|
1025
1025
|
|
|
@@ -2005,7 +2005,7 @@ const invitePayloadSchema = z.object({
|
|
|
2005
2005
|
\tinviteId: z.string().min(1),
|
|
2006
2006
|
\tworkspaceId: z.string().min(1),
|
|
2007
2007
|
\tworkspaceName: z.string().min(1),
|
|
2008
|
-
\temail: z.
|
|
2008
|
+
\temail: z.email(),
|
|
2009
2009
|
\trole: z.enum(["admin", "member"]),
|
|
2010
2010
|
\ttoken: z.string().min(1),
|
|
2011
2011
|
\tinvitedByName: z.string().optional(),
|
|
@@ -3242,9 +3242,7 @@ export const workspaces = ${dialect.tableFunction}(
|
|
|
3242
3242
|
\t\tcreatedAt: ${column("created_at")}.notNull(),
|
|
3243
3243
|
\t\tupdatedAt: ${column("updated_at")}.notNull(),
|
|
3244
3244
|
\t},
|
|
3245
|
-
\t(table) => (
|
|
3246
|
-
\t\tslugUnique: uniqueIndex("workspaces_slug_unique").on(table.slug),
|
|
3247
|
-
\t}),
|
|
3245
|
+
\t(table) => [uniqueIndex("workspaces_slug_unique").on(table.slug)],
|
|
3248
3246
|
);
|
|
3249
3247
|
|
|
3250
3248
|
export const workspaceMembers = ${dialect.tableFunction}(
|
|
@@ -3256,12 +3254,12 @@ export const workspaceMembers = ${dialect.tableFunction}(
|
|
|
3256
3254
|
\t\trole: ${column("role")}.$type<WorkspaceRole>().notNull(),
|
|
3257
3255
|
\t\tjoinedAt: ${column("joined_at")}.notNull(),
|
|
3258
3256
|
\t},
|
|
3259
|
-
\t(table) =>
|
|
3260
|
-
\t\
|
|
3257
|
+
\t(table) => [
|
|
3258
|
+
\t\tuniqueIndex(
|
|
3261
3259
|
\t\t\t"workspace_members_workspace_user_unique",
|
|
3262
3260
|
\t\t).on(table.workspaceId, table.userId),
|
|
3263
|
-
\t\
|
|
3264
|
-
\t
|
|
3261
|
+
\t\tindex("workspace_members_user_idx").on(table.userId),
|
|
3262
|
+
\t],
|
|
3265
3263
|
);
|
|
3266
3264
|
|
|
3267
3265
|
export const workspaceInvites = ${dialect.tableFunction}(
|
|
@@ -3278,15 +3276,15 @@ export const workspaceInvites = ${dialect.tableFunction}(
|
|
|
3278
3276
|
\t\tacceptedAt: ${column("accepted_at")},
|
|
3279
3277
|
\t\tcreatedAt: ${column("created_at")}.notNull(),
|
|
3280
3278
|
\t},
|
|
3281
|
-
\t(table) =>
|
|
3282
|
-
\t\
|
|
3279
|
+
\t(table) => [
|
|
3280
|
+
\t\tuniqueIndex("workspace_invites_token_unique").on(
|
|
3283
3281
|
\t\t\ttable.token,
|
|
3284
3282
|
\t\t),
|
|
3285
|
-
\t\
|
|
3283
|
+
\t\tindex("workspace_invites_workspace_status_idx").on(
|
|
3286
3284
|
\t\t\ttable.workspaceId,
|
|
3287
3285
|
\t\t\ttable.status,
|
|
3288
3286
|
\t\t),
|
|
3289
|
-
\t
|
|
3287
|
+
\t],
|
|
3290
3288
|
);
|
|
3291
3289
|
`;
|
|
3292
3290
|
}
|
package/src/make.ts
CHANGED
|
@@ -1575,7 +1575,8 @@ const cronSecretEnvEntry = `\t\tCRON_SECRET: z.string().min(1).optional(),`;
|
|
|
1575
1575
|
|
|
1576
1576
|
// The literal APP_URL line every generated lib/env.ts ships with. CRON_SECRET
|
|
1577
1577
|
// slots in directly below it, matching the documented env shape.
|
|
1578
|
-
const cronSecretEnvAnchor = `\t\tAPP_URL: z.
|
|
1578
|
+
const cronSecretEnvAnchor = `\t\tAPP_URL: z.url().default("http://localhost:3000"),`;
|
|
1579
|
+
const legacyCronSecretEnvAnchor = `\t\tAPP_URL: z.string().url().default("http://localhost:3000"),`;
|
|
1579
1580
|
|
|
1580
1581
|
type CronSecretWiringPlan = {
|
|
1581
1582
|
files: Array<GeneratedFile & { result: "created" | "updated" }>;
|
|
@@ -1596,10 +1597,15 @@ async function planCronSecretWiring(
|
|
|
1596
1597
|
`Could not find the generated env file ${cronSecretEnvFile}. ${manualInstruction}`,
|
|
1597
1598
|
);
|
|
1598
1599
|
}
|
|
1600
|
+
const envAnchor = envSource.includes(cronSecretEnvAnchor)
|
|
1601
|
+
? cronSecretEnvAnchor
|
|
1602
|
+
: envSource.includes(legacyCronSecretEnvAnchor)
|
|
1603
|
+
? legacyCronSecretEnvAnchor
|
|
1604
|
+
: undefined;
|
|
1599
1605
|
|
|
1600
1606
|
if (/\bCRON_SECRET\b/.test(envSource)) {
|
|
1601
1607
|
plan.skippedFiles.push(cronSecretEnvFile);
|
|
1602
|
-
} else if (
|
|
1608
|
+
} else if (envAnchor === undefined) {
|
|
1603
1609
|
throw new Error(
|
|
1604
1610
|
`Could not find the generated APP_URL server entry in ${cronSecretEnvFile}. ${manualInstruction}`,
|
|
1605
1611
|
);
|
|
@@ -1607,8 +1613,8 @@ async function planCronSecretWiring(
|
|
|
1607
1613
|
plan.files.push({
|
|
1608
1614
|
path: cronSecretEnvFile,
|
|
1609
1615
|
content: envSource.replace(
|
|
1610
|
-
|
|
1611
|
-
`${
|
|
1616
|
+
envAnchor,
|
|
1617
|
+
`${envAnchor}\n${cronSecretEnvEntry}`,
|
|
1612
1618
|
),
|
|
1613
1619
|
result: "updated",
|
|
1614
1620
|
});
|
|
@@ -2218,6 +2224,10 @@ export async function makeUpload(
|
|
|
2218
2224
|
dryRun: true,
|
|
2219
2225
|
},
|
|
2220
2226
|
);
|
|
2227
|
+
// Validate central registration before generating feature files or applying
|
|
2228
|
+
// provider wiring. Customized routes require an explicit manual edit rather
|
|
2229
|
+
// than a successful-looking partial generation.
|
|
2230
|
+
await updateUploadRoute(targetDir, names, config, { dryRun: true });
|
|
2221
2231
|
|
|
2222
2232
|
const result = await makeFeatureArtifact({
|
|
2223
2233
|
targetDir,
|
|
@@ -3716,7 +3726,9 @@ async function updateUploadRoute(
|
|
|
3716
3726
|
|
|
3717
3727
|
const registry = defineUploadsInitializerInfo(next, "uploadRegistry");
|
|
3718
3728
|
if (!registry) {
|
|
3719
|
-
|
|
3729
|
+
throw new Error(
|
|
3730
|
+
`Cannot register ${registryName} in ${file}: expected an uploadRegistry initialized with defineUploads({ ... }). Restore that registry shape or add ${registryKey}: ${registryName} manually before rerunning beignet make upload.`,
|
|
3731
|
+
);
|
|
3720
3732
|
}
|
|
3721
3733
|
if (!new RegExp(`\\b${registryKey}\\s*:`).test(registry.text)) {
|
|
3722
3734
|
next = `${next.slice(0, registry.end)}\t${registryKey}: ${registryName},\n${next.slice(
|
|
@@ -3912,7 +3924,7 @@ function schemasFile(
|
|
|
3912
3924
|
return `import { z } from "zod";
|
|
3913
3925
|
|
|
3914
3926
|
export const ${names.singularPascal}Schema = z.object({
|
|
3915
|
-
id: z.
|
|
3927
|
+
id: z.uuid(),
|
|
3916
3928
|
${options.tenantScoped ? `\ttenantId: z.string().min(1),\n` : ""} name: z.string().min(1),
|
|
3917
3929
|
version: z.number().int().min(1),
|
|
3918
3930
|
createdAt: z.string().datetime(),
|
|
@@ -3926,7 +3938,7 @@ const ${names.singularPascal}CursorPayloadSchema = z.object({
|
|
|
3926
3938
|
sortBy: ${names.singularPascal}SortBySchema,
|
|
3927
3939
|
sortDirection: ${names.singularPascal}SortDirectionSchema,
|
|
3928
3940
|
sortValue: z.string(),
|
|
3929
|
-
id: z.
|
|
3941
|
+
id: z.uuid(),
|
|
3930
3942
|
});
|
|
3931
3943
|
|
|
3932
3944
|
export type ${names.singularPascal}SortBy = z.infer<
|
|
@@ -4019,7 +4031,7 @@ ${
|
|
|
4019
4031
|
mode === "resource"
|
|
4020
4032
|
? `
|
|
4021
4033
|
export const ${names.singularPascal}IdInputSchema = z.object({
|
|
4022
|
-
id: z.
|
|
4034
|
+
id: z.uuid(),
|
|
4023
4035
|
});
|
|
4024
4036
|
|
|
4025
4037
|
export const Update${names.singularPascal}BodySchema = z.object({
|
|
@@ -4479,16 +4491,16 @@ export const outboxMessages = mysqlTable(
|
|
|
4479
4491
|
\t\tcreatedAt: varchar("created_at", { length: 32 }).notNull(),
|
|
4480
4492
|
\t\tupdatedAt: varchar("updated_at", { length: 32 }).notNull(),
|
|
4481
4493
|
\t},
|
|
4482
|
-
\t(table) =>
|
|
4483
|
-
\t\
|
|
4494
|
+
\t(table) => [
|
|
4495
|
+
\t\tindex("outbox_messages_available_idx").on(
|
|
4484
4496
|
\t\t\ttable.status,
|
|
4485
4497
|
\t\t\ttable.availableAt,
|
|
4486
4498
|
\t\t),
|
|
4487
|
-
\t\
|
|
4499
|
+
\t\tindex("outbox_messages_locked_idx").on(
|
|
4488
4500
|
\t\t\ttable.status,
|
|
4489
4501
|
\t\t\ttable.lockedUntil,
|
|
4490
4502
|
\t\t),
|
|
4491
|
-
\t
|
|
4503
|
+
\t],
|
|
4492
4504
|
);
|
|
4493
4505
|
`;
|
|
4494
4506
|
}
|
|
@@ -4515,16 +4527,16 @@ export const outboxMessages = ${dialect.tableFunction}(
|
|
|
4515
4527
|
\t\tcreatedAt: text("created_at").notNull(),
|
|
4516
4528
|
\t\tupdatedAt: text("updated_at").notNull(),
|
|
4517
4529
|
\t},
|
|
4518
|
-
\t(table) =>
|
|
4519
|
-
\t\
|
|
4530
|
+
\t(table) => [
|
|
4531
|
+
\t\tindex("outbox_messages_available_idx").on(
|
|
4520
4532
|
\t\t\ttable.status,
|
|
4521
4533
|
\t\t\ttable.availableAt,
|
|
4522
4534
|
\t\t),
|
|
4523
|
-
\t\
|
|
4535
|
+
\t\tindex("outbox_messages_locked_idx").on(
|
|
4524
4536
|
\t\t\ttable.status,
|
|
4525
4537
|
\t\t\ttable.lockedUntil,
|
|
4526
4538
|
\t\t),
|
|
4527
|
-
\t
|
|
4539
|
+
\t],
|
|
4528
4540
|
);
|
|
4529
4541
|
`;
|
|
4530
4542
|
}
|
|
@@ -4912,7 +4924,7 @@ const ${names.pluralCamel} = defineContractGroup()
|
|
|
4912
4924
|
});
|
|
4913
4925
|
|
|
4914
4926
|
export const ${names.singularCamel}Schema = z.object({
|
|
4915
|
-
id: z.
|
|
4927
|
+
id: z.uuid(),
|
|
4916
4928
|
name: z.string().min(1),
|
|
4917
4929
|
});
|
|
4918
4930
|
|
|
@@ -5121,19 +5133,19 @@ import { z } from "zod";
|
|
|
5121
5133
|
|
|
5122
5134
|
export const ${names.singularPascal}Created = defineEvent("${names.pluralCamel}.created", {
|
|
5123
5135
|
payload: z.object({
|
|
5124
|
-
id: z.
|
|
5136
|
+
id: z.uuid(),
|
|
5125
5137
|
}),
|
|
5126
5138
|
});
|
|
5127
5139
|
|
|
5128
5140
|
export const ${names.singularPascal}Updated = defineEvent("${names.pluralCamel}.updated", {
|
|
5129
5141
|
payload: z.object({
|
|
5130
|
-
id: z.
|
|
5142
|
+
id: z.uuid(),
|
|
5131
5143
|
}),
|
|
5132
5144
|
});
|
|
5133
5145
|
|
|
5134
5146
|
export const ${names.singularPascal}Deleted = defineEvent("${names.pluralCamel}.deleted", {
|
|
5135
5147
|
payload: z.object({
|
|
5136
|
-
id: z.
|
|
5148
|
+
id: z.uuid(),
|
|
5137
5149
|
}),
|
|
5138
5150
|
});
|
|
5139
5151
|
|
|
@@ -5209,7 +5221,7 @@ function eventFile(names: EventNames): string {
|
|
|
5209
5221
|
import { z } from "zod";
|
|
5210
5222
|
|
|
5211
5223
|
export const ${names.payloadSchemaName} = z.object({
|
|
5212
|
-
\tid: z.
|
|
5224
|
+
\tid: z.uuid(),
|
|
5213
5225
|
});
|
|
5214
5226
|
|
|
5215
5227
|
export type ${names.payloadTypeName} = z.infer<typeof ${names.payloadSchemaName}>;
|
|
@@ -5226,7 +5238,7 @@ import { z } from "zod";
|
|
|
5226
5238
|
import { defineJob } from "${aliasModule(config.paths.jobsBuilder)}";
|
|
5227
5239
|
|
|
5228
5240
|
export const ${names.payloadSchemaName} = z.object({
|
|
5229
|
-
\tid: z.
|
|
5241
|
+
\tid: z.uuid(),
|
|
5230
5242
|
});
|
|
5231
5243
|
|
|
5232
5244
|
export type ${names.payloadTypeName} = z.infer<typeof ${names.payloadSchemaName}>;
|
|
@@ -5317,9 +5329,9 @@ import { z } from "zod";
|
|
|
5317
5329
|
import { defineNotification } from "${aliasModule(config.paths.notificationsBuilder)}";
|
|
5318
5330
|
|
|
5319
5331
|
export const ${names.payloadSchemaName} = z.object({
|
|
5320
|
-
\tid: z.
|
|
5332
|
+
\tid: z.uuid(),
|
|
5321
5333
|
\tsubject: z.string(),
|
|
5322
|
-
\trecipientEmail: z.
|
|
5334
|
+
\trecipientEmail: z.email(),
|
|
5323
5335
|
});
|
|
5324
5336
|
|
|
5325
5337
|
export type ${names.payloadTypeName} = z.infer<typeof ${names.payloadSchemaName}>;
|
package/src/registry-edits.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Shared string-edit helpers for registry files.
|
|
3
5
|
*
|
|
@@ -28,21 +30,35 @@ export type AppendResult =
|
|
|
28
30
|
| { kind: "missing" };
|
|
29
31
|
|
|
30
32
|
/**
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
+
* Yield indexes that belong to executable source, excluding string literals
|
|
34
|
+
* and line/block comments. Registry scanners share this lexer so delimiters
|
|
35
|
+
* in user comments cannot corrupt their depth tracking.
|
|
33
36
|
*/
|
|
34
|
-
export function
|
|
37
|
+
export function* codeCharacterIndexes(
|
|
35
38
|
source: string,
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
): number {
|
|
40
|
-
let depth = 0;
|
|
39
|
+
start = 0,
|
|
40
|
+
end = source.length,
|
|
41
|
+
): Generator<number> {
|
|
41
42
|
let inString: string | undefined;
|
|
42
43
|
let escaped = false;
|
|
44
|
+
let inLineComment = false;
|
|
45
|
+
let inBlockComment = false;
|
|
43
46
|
|
|
44
|
-
for (let index =
|
|
47
|
+
for (let index = start; index < end; index++) {
|
|
45
48
|
const char = source[index];
|
|
49
|
+
const next = source[index + 1];
|
|
50
|
+
|
|
51
|
+
if (inLineComment) {
|
|
52
|
+
if (char === "\n" || char === "\r") inLineComment = false;
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (inBlockComment) {
|
|
56
|
+
if (char === "*" && next === "/") {
|
|
57
|
+
inBlockComment = false;
|
|
58
|
+
index++;
|
|
59
|
+
}
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
46
62
|
if (inString) {
|
|
47
63
|
if (escaped) {
|
|
48
64
|
escaped = false;
|
|
@@ -54,11 +70,40 @@ export function matchingDelimiterIndex(
|
|
|
54
70
|
continue;
|
|
55
71
|
}
|
|
56
72
|
|
|
73
|
+
if (char === "/" && next === "/") {
|
|
74
|
+
inLineComment = true;
|
|
75
|
+
index++;
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (char === "/" && next === "*") {
|
|
79
|
+
inBlockComment = true;
|
|
80
|
+
index++;
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
57
83
|
if (char === '"' || char === "'" || char === "`") {
|
|
58
84
|
inString = char;
|
|
59
85
|
continue;
|
|
60
86
|
}
|
|
61
87
|
|
|
88
|
+
yield index;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Find the index of the delimiter closing the one at `openIndex`, skipping
|
|
94
|
+
* string literals and comments.
|
|
95
|
+
*/
|
|
96
|
+
export function matchingDelimiterIndex(
|
|
97
|
+
source: string,
|
|
98
|
+
openIndex: number,
|
|
99
|
+
open: string,
|
|
100
|
+
close: string,
|
|
101
|
+
): number {
|
|
102
|
+
let depth = 0;
|
|
103
|
+
|
|
104
|
+
for (const index of codeCharacterIndexes(source, openIndex)) {
|
|
105
|
+
const char = source[index];
|
|
106
|
+
|
|
62
107
|
if (char === open) {
|
|
63
108
|
depth++;
|
|
64
109
|
continue;
|
|
@@ -77,21 +122,32 @@ export function matchingDelimiterIndex(
|
|
|
77
122
|
* Insert a line directly after the last top-level import statement.
|
|
78
123
|
*/
|
|
79
124
|
export function insertAfterImports(source: string, line: string): string {
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
125
|
+
const sourceFile = ts.createSourceFile(
|
|
126
|
+
"registry.ts",
|
|
127
|
+
source,
|
|
128
|
+
ts.ScriptTarget.Latest,
|
|
129
|
+
true,
|
|
130
|
+
ts.ScriptKind.TS,
|
|
131
|
+
);
|
|
132
|
+
let lastImport: ts.ImportDeclaration | undefined;
|
|
133
|
+
for (const statement of sourceFile.statements) {
|
|
134
|
+
if (ts.isImportDeclaration(statement)) lastImport = statement;
|
|
87
135
|
}
|
|
88
136
|
|
|
89
|
-
if (
|
|
137
|
+
if (!lastImport) {
|
|
90
138
|
return `${line}\n${source}`;
|
|
91
139
|
}
|
|
92
140
|
|
|
93
|
-
|
|
94
|
-
|
|
141
|
+
const lineBreak = source.includes("\r\n") ? "\r\n" : "\n";
|
|
142
|
+
const lineEnd = source.indexOf(lineBreak, lastImport.end);
|
|
143
|
+
if (lineEnd === -1) {
|
|
144
|
+
return `${source}${lineBreak}${line}${lineBreak}`;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const insertAt = lineEnd + lineBreak.length;
|
|
148
|
+
return `${source.slice(0, insertAt)}${line}${lineBreak}${source.slice(
|
|
149
|
+
insertAt,
|
|
150
|
+
)}`;
|
|
95
151
|
}
|
|
96
152
|
|
|
97
153
|
/**
|
|
@@ -147,11 +203,14 @@ export function addNamedValueImport(
|
|
|
147
203
|
export function identifiersFromArrayExpression(
|
|
148
204
|
expression: string,
|
|
149
205
|
): Set<string> {
|
|
150
|
-
const
|
|
206
|
+
const code = Array.from({ length: expression.length }, () => " ");
|
|
207
|
+
for (const index of codeCharacterIndexes(expression)) {
|
|
208
|
+
code[index] = expression[index];
|
|
209
|
+
}
|
|
151
210
|
|
|
152
211
|
return new Set(
|
|
153
212
|
Array.from(
|
|
154
|
-
|
|
213
|
+
code.join("").matchAll(/\b[A-Za-z_$][\w$]*\b/g),
|
|
155
214
|
([name]) => name,
|
|
156
215
|
),
|
|
157
216
|
);
|
|
@@ -342,26 +401,9 @@ function topLevelValueSlice(
|
|
|
342
401
|
if (start >= hardEnd) return undefined;
|
|
343
402
|
|
|
344
403
|
let depth = 0;
|
|
345
|
-
let inString: string | undefined;
|
|
346
|
-
let escaped = false;
|
|
347
404
|
|
|
348
|
-
for (
|
|
405
|
+
for (const index of codeCharacterIndexes(source, start, hardEnd + 1)) {
|
|
349
406
|
const char = source[index];
|
|
350
|
-
if (inString) {
|
|
351
|
-
if (escaped) {
|
|
352
|
-
escaped = false;
|
|
353
|
-
} else if (char === "\\") {
|
|
354
|
-
escaped = true;
|
|
355
|
-
} else if (char === inString) {
|
|
356
|
-
inString = undefined;
|
|
357
|
-
}
|
|
358
|
-
continue;
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
if (char === '"' || char === "'" || char === "`") {
|
|
362
|
-
inString = char;
|
|
363
|
-
continue;
|
|
364
|
-
}
|
|
365
407
|
|
|
366
408
|
if (char === "[" || char === "(" || char === "{") {
|
|
367
409
|
depth++;
|
package/src/templates/agents.ts
CHANGED
|
@@ -31,8 +31,8 @@ export function agentsMd(ctx: TemplateContext): string {
|
|
|
31
31
|
"\n| Server Component request context | Use an app-owned server-only helper such as `lib/server-context.ts` that wraps `server.createContextFromNext()` in React `cache(...)`. Layouts may read `ctx.auth`, `ctx.tenant`, and request metadata for redirects and shell state; feature data still belongs behind use cases. |" +
|
|
32
32
|
"\n| Server Component React Query prefetch | Use an app-owned helper such as `lib/server-react-query.ts` on top of the request context: keep the `rq(contract).queryOptions(...)` key, replace only the server `queryFn` with the direct use case call, and do not create a parallel `rqServer` abstraction. |";
|
|
33
33
|
const specificSkills = ctx.api
|
|
34
|
-
? "`@beignet/next#routes-server`, `@beignet/provider-db-drizzle#database-provider`, `@beignet/provider-auth-better-auth#auth-provider`, or `@beignet/cli#app-structure`"
|
|
35
|
-
: "`@beignet/next#routes-server`, `@beignet/react-query#client`, `@beignet/react-hook-form#forms`, `@beignet/provider-db-drizzle#database-provider`, `@beignet/provider-auth-better-auth#auth-provider`, or `@beignet/cli#app-structure`";
|
|
34
|
+
? "`@beignet/next#routes-server`, `@beignet/web#fetch-server`, `@beignet/devtools#runtime-safety`, `@beignet/provider-db-drizzle#database-provider`, `@beignet/provider-auth-better-auth#auth-provider`, or `@beignet/cli#app-structure`"
|
|
35
|
+
: "`@beignet/next#routes-server`, `@beignet/web#fetch-server`, `@beignet/devtools#runtime-safety`, `@beignet/react-query#client`, `@beignet/react-hook-form#forms`, `@beignet/provider-db-drizzle#database-provider`, `@beignet/provider-auth-better-auth#auth-provider`, or `@beignet/cli#app-structure`";
|
|
36
36
|
|
|
37
37
|
return `# ${ctx.name} — agent guide
|
|
38
38
|
|
|
@@ -62,6 +62,9 @@ use, so their absence is fine. \`${cli} make event\`, \`${cli} make job\`,
|
|
|
62
62
|
\`${cli} make listener\`, \`${cli} make schedule\`, \`${cli} make task\`,
|
|
63
63
|
\`${cli} make seed\`, and \`${cli} make upload\` create or update their
|
|
64
64
|
required app entrypoints.
|
|
65
|
+
Upload generation validates the central \`uploadRegistry = defineUploads({ ... })\`
|
|
66
|
+
shape before writing; when a custom route cannot be edited safely, follow the
|
|
67
|
+
manual registry instruction in the error.
|
|
65
68
|
\`${cli} doctor\` detects registration drift. Preview exact repair patches
|
|
66
69
|
with \`${cli} doctor --fix --dry-run\`, then apply the returned plan with
|
|
67
70
|
\`${cli} doctor --fix --plan <plan-id>\` and optional
|
|
@@ -115,7 +118,11 @@ need one check alone.
|
|
|
115
118
|
|
|
116
119
|
Before exploring unfamiliar code, run \`${cli} map\` for the human inventory
|
|
117
120
|
or \`${cli} map --json --feature <name>\` for an evidence-backed graph scoped
|
|
118
|
-
to one feature.
|
|
121
|
+
to one feature. The graph honors the app's complete TypeScript \`baseUrl\` and
|
|
122
|
+
\`paths\` configuration. Fix \`local_import_unresolved\` and
|
|
123
|
+
\`contract_declaration_unresolved\` entries rather than treating their missing
|
|
124
|
+
edges as proof that no relationship exists. Registration diagnostics expose
|
|
125
|
+
the declaration as \`subject.file\` and \`subject.exportName\`.
|
|
119
126
|
|
|
120
127
|
## Package skills
|
|
121
128
|
|
package/src/templates/base.ts
CHANGED
|
@@ -122,7 +122,9 @@ export function packageJson(ctx: TemplateContext): string {
|
|
|
122
122
|
function intentSkillPackages(ctx: TemplateContext): string[] {
|
|
123
123
|
const packages = [
|
|
124
124
|
"@beignet/core",
|
|
125
|
+
"@beignet/devtools",
|
|
125
126
|
"@beignet/next",
|
|
127
|
+
"@beignet/web",
|
|
126
128
|
"@beignet/cli",
|
|
127
129
|
"@beignet/provider-auth-better-auth",
|
|
128
130
|
"@beignet/provider-db-drizzle",
|
|
@@ -362,7 +364,7 @@ ${optionalAuthRedirects}
|
|
|
362
364
|
? ""
|
|
363
365
|
: "; it refuses non-local hosts unless `BEIGNET_ALLOW_DATABASE_RESET=true`"
|
|
364
366
|
}.
|
|
365
|
-
- Remove \`DEVTOOLS_ENABLED=true\`
|
|
367
|
+
- Remove \`DEVTOOLS_ENABLED=true\` outside development unless you add authentication and stricter redaction.
|
|
366
368
|
- Set \`APP_URL\`, \`BETTER_AUTH_SECRET\`, \`LOG_LEVEL\`, and provider-specific variables in your hosting environment.
|
|
367
369
|
- Set \`BETTER_AUTH_TRUSTED_ORIGINS\` before serving auth across multiple origins.
|
|
368
370
|
- Review the starter authorization policy before exposing user-owned data.
|
|
@@ -463,7 +465,7 @@ export function envExample(ctx: TemplateContext): string {
|
|
|
463
465
|
"# Copy to .env.local and fill in values for your app.",
|
|
464
466
|
"APP_URL=http://localhost:3000",
|
|
465
467
|
"",
|
|
466
|
-
"#
|
|
468
|
+
"# Local-development default; remove or protect outside development.",
|
|
467
469
|
"DEVTOOLS_ENABLED=true",
|
|
468
470
|
"",
|
|
469
471
|
"LOG_LEVEL=info",
|
|
@@ -126,9 +126,9 @@ export const todos = mysqlTable(
|
|
|
126
126
|
// unmapped, and list ordering compares the strings lexicographically.
|
|
127
127
|
createdAt: varchar("created_at", { length: 32 }).notNull(),
|
|
128
128
|
},
|
|
129
|
-
(table) =>
|
|
130
|
-
|
|
131
|
-
|
|
129
|
+
(table) => [
|
|
130
|
+
index("todos_user_idx").on(table.userId, table.createdAt),
|
|
131
|
+
],
|
|
132
132
|
);
|
|
133
133
|
`,
|
|
134
134
|
drizzleTodoRepository: `import "@beignet/core/server-only";
|
|
@@ -116,9 +116,9 @@ export const todos = pgTable(
|
|
|
116
116
|
// unmapped, and list ordering compares the strings lexicographically.
|
|
117
117
|
createdAt: text("created_at").notNull(),
|
|
118
118
|
},
|
|
119
|
-
(table) =>
|
|
120
|
-
|
|
121
|
-
|
|
119
|
+
(table) => [
|
|
120
|
+
index("todos_user_idx").on(table.userId, table.createdAt),
|
|
121
|
+
],
|
|
122
122
|
);
|
|
123
123
|
`,
|
|
124
124
|
drizzleTodoRepository: `import "@beignet/core/server-only";
|
|
@@ -112,9 +112,9 @@ export const todos = sqliteTable(
|
|
|
112
112
|
.default(false),
|
|
113
113
|
createdAt: text("created_at").notNull(),
|
|
114
114
|
},
|
|
115
|
-
(table) =>
|
|
116
|
-
|
|
117
|
-
|
|
115
|
+
(table) => [
|
|
116
|
+
index("todos_user_idx").on(table.userId, table.createdAt),
|
|
117
|
+
],
|
|
118
118
|
);
|
|
119
119
|
`,
|
|
120
120
|
drizzleTodoRepository: `import "@beignet/core/server-only";
|
package/src/templates/server.ts
CHANGED
|
@@ -34,7 +34,7 @@ export function serverProviders(ctx: TemplateContext): string {
|
|
|
34
34
|
|
|
35
35
|
const entries = [
|
|
36
36
|
"\tcreateDevtoolsProvider(),",
|
|
37
|
-
"\tcreateBetterAuthProvider<AuthUser, AuthSessionMetadata>(auth),",
|
|
37
|
+
"\tcreateBetterAuthProvider<AuthUser, AuthSessionMetadata>({ auth }),",
|
|
38
38
|
"\tcreatePinoLoggerProvider(),",
|
|
39
39
|
`\t${db.providerInstance},`,
|
|
40
40
|
"\tstarterDatabaseProvider,",
|
|
@@ -196,12 +196,12 @@ export const env = createEnv({
|
|
|
196
196
|
NODE_ENV: z
|
|
197
197
|
.enum(["development", "test", "production"])
|
|
198
198
|
.default("development"),
|
|
199
|
-
APP_URL: z.
|
|
199
|
+
APP_URL: z.url().default("http://localhost:3000"),
|
|
200
200
|
DEVTOOLS_ENABLED: BooleanEnv.optional(),
|
|
201
201
|
BETTER_AUTH_SECRET: isProductionRuntime
|
|
202
202
|
? BetterAuthSecret
|
|
203
203
|
: BetterAuthSecret.default(LOCAL_BETTER_AUTH_SECRET),
|
|
204
|
-
BETTER_AUTH_URL: z.
|
|
204
|
+
BETTER_AUTH_URL: z.url().default("http://localhost:3000"),
|
|
205
205
|
BETTER_AUTH_TRUSTED_ORIGINS: z.string().optional(),
|
|
206
206
|
${databaseEnvLines}
|
|
207
207
|
LOG_LEVEL: z
|
|
@@ -217,7 +217,7 @@ ${inngestEnvLines}
|
|
|
217
217
|
export const isProduction = env.NODE_ENV === "production";
|
|
218
218
|
|
|
219
219
|
export const isDevtoolsEnabled =
|
|
220
|
-
env.DEVTOOLS_ENABLED ??
|
|
220
|
+
env.DEVTOOLS_ENABLED ?? env.NODE_ENV === "development";
|
|
221
221
|
`;
|
|
222
222
|
}
|
|
223
223
|
|
package/src/templates/todos.ts
CHANGED
|
@@ -2,7 +2,7 @@ const files = {
|
|
|
2
2
|
schemas: `import { z } from "zod";
|
|
3
3
|
|
|
4
4
|
export const TodoSchema = z.object({
|
|
5
|
-
id: z.
|
|
5
|
+
id: z.uuid(),
|
|
6
6
|
userId: z.string(),
|
|
7
7
|
title: z.string().min(1),
|
|
8
8
|
completed: z.boolean(),
|
|
@@ -26,7 +26,7 @@ export const ListTodosOutputSchema = z.object({
|
|
|
26
26
|
});
|
|
27
27
|
|
|
28
28
|
export const TodoIdInputSchema = z.object({
|
|
29
|
-
id: z.
|
|
29
|
+
id: z.uuid(),
|
|
30
30
|
});
|
|
31
31
|
|
|
32
32
|
export const CreateTodoInputSchema = z.object({
|