@beignet/cli 0.0.45 → 0.0.46
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 +12 -0
- package/README.md +19 -9
- package/dist/choices.d.ts +1 -1
- package/dist/choices.d.ts.map +1 -1
- package/dist/choices.js +3 -0
- package/dist/choices.js.map +1 -1
- package/dist/db.d.ts +4 -0
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +4 -2
- package/dist/db.js.map +1 -1
- package/dist/inspect.d.ts.map +1 -1
- package/dist/inspect.js +520 -41
- package/dist/inspect.js.map +1 -1
- package/dist/mcp.js +2 -2
- package/dist/mcp.js.map +1 -1
- package/dist/templates/agents.d.ts.map +1 -1
- package/dist/templates/agents.js +13 -2
- package/dist/templates/agents.js.map +1 -1
- package/dist/templates/shared.js +1 -1
- package/package.json +2 -2
- package/skills/app-structure/SKILL.md +7 -1
- package/src/choices.ts +3 -0
- package/src/db.ts +4 -2
- package/src/inspect.ts +730 -41
- package/src/mcp.ts +2 -2
- package/src/templates/agents.ts +13 -2
- package/src/templates/shared.ts +1 -1
package/src/inspect.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { readdir, readFile, stat } from "node:fs/promises";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { parseProviderPackageMetadata } from "@beignet/core/providers";
|
|
4
4
|
import { createPainter } from "./ansi.js";
|
|
5
|
+
import type { DatabaseSchemaDialect, DatabaseSchemaTable } from "./choices.js";
|
|
5
6
|
import {
|
|
6
7
|
type BeignetConfig,
|
|
7
8
|
type BeignetDatabaseTables,
|
|
@@ -15,6 +16,7 @@ import {
|
|
|
15
16
|
type ResolvedBeignetConfig,
|
|
16
17
|
resolveConfig,
|
|
17
18
|
} from "./config.js";
|
|
19
|
+
import { renderDatabaseSchema, resolveDatabaseSchemaDialect } from "./db.js";
|
|
18
20
|
import {
|
|
19
21
|
applyPlannedDoctorFixes,
|
|
20
22
|
createDoctorFixPlan,
|
|
@@ -390,6 +392,36 @@ async function buildDoctorFixPlan(
|
|
|
390
392
|
);
|
|
391
393
|
if (listenerFix) operations.push(listenerFix);
|
|
392
394
|
|
|
395
|
+
const databaseFix = await planMissingProviderTables(targetDir, files, config);
|
|
396
|
+
if (databaseFix) operations.push(databaseFix);
|
|
397
|
+
|
|
398
|
+
const inngestFix = await planUnregisteredInngestJobs(
|
|
399
|
+
targetDir,
|
|
400
|
+
files,
|
|
401
|
+
config,
|
|
402
|
+
);
|
|
403
|
+
if (inngestFix) operations.push(inngestFix);
|
|
404
|
+
|
|
405
|
+
const nonOverlappingOperations =
|
|
406
|
+
nonOverlappingDoctorFixOperations(operations);
|
|
407
|
+
operations.splice(0, operations.length, ...nonOverlappingOperations);
|
|
408
|
+
const sourceOverrides = new Map<string, string>();
|
|
409
|
+
for (const operation of operations) {
|
|
410
|
+
for (const change of operation.changes) {
|
|
411
|
+
sourceOverrides.set(change.file, change.after);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
const runtimeManifestFix = await planRuntimeManifestRegistries(
|
|
415
|
+
targetDir,
|
|
416
|
+
files,
|
|
417
|
+
config,
|
|
418
|
+
convention,
|
|
419
|
+
sourceOverrides,
|
|
420
|
+
);
|
|
421
|
+
if (runtimeManifestFix) {
|
|
422
|
+
addRuntimeManifestFix(operations, runtimeManifestFix);
|
|
423
|
+
}
|
|
424
|
+
|
|
393
425
|
const openApiFix = await planDirectOpenApiArrayDrift(
|
|
394
426
|
targetDir,
|
|
395
427
|
files,
|
|
@@ -403,10 +435,77 @@ async function buildDoctorFixPlan(
|
|
|
403
435
|
return createDoctorFixPlan({
|
|
404
436
|
targetDir,
|
|
405
437
|
strict: Boolean(options.strict),
|
|
406
|
-
operations,
|
|
438
|
+
operations: nonOverlappingDoctorFixOperations(operations),
|
|
407
439
|
});
|
|
408
440
|
}
|
|
409
441
|
|
|
442
|
+
function nonOverlappingDoctorFixOperations(
|
|
443
|
+
operations: PlannedDoctorFixOperation[],
|
|
444
|
+
): PlannedDoctorFixOperation[] {
|
|
445
|
+
const claimedFiles = new Set<string>();
|
|
446
|
+
const safeOperations: PlannedDoctorFixOperation[] = [];
|
|
447
|
+
|
|
448
|
+
for (const operation of operations) {
|
|
449
|
+
if (operation.changes.some((change) => claimedFiles.has(change.file))) {
|
|
450
|
+
continue;
|
|
451
|
+
}
|
|
452
|
+
safeOperations.push(operation);
|
|
453
|
+
for (const change of operation.changes) claimedFiles.add(change.file);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
return safeOperations;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
const workflowDoctorFixOperationIds = new Set<DoctorFixOperationId>([
|
|
460
|
+
"schedules.register-missing",
|
|
461
|
+
"tasks.register-missing",
|
|
462
|
+
"workflows.register-missing",
|
|
463
|
+
"outbox.register-missing",
|
|
464
|
+
"listeners.register-missing",
|
|
465
|
+
"inngest.register-missing",
|
|
466
|
+
"runtime-manifest.register-missing",
|
|
467
|
+
]);
|
|
468
|
+
|
|
469
|
+
function addRuntimeManifestFix(
|
|
470
|
+
operations: PlannedDoctorFixOperation[],
|
|
471
|
+
runtimeManifestFix: PlannedDoctorFixOperation,
|
|
472
|
+
): void {
|
|
473
|
+
const runtimeChange = runtimeManifestFix.changes[0];
|
|
474
|
+
if (!runtimeChange) return;
|
|
475
|
+
|
|
476
|
+
const overlappingOperation = operations.find((operation) =>
|
|
477
|
+
operation.changes.some((change) => change.file === runtimeChange.file),
|
|
478
|
+
);
|
|
479
|
+
if (!overlappingOperation) {
|
|
480
|
+
operations.push(runtimeManifestFix);
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
if (!workflowDoctorFixOperationIds.has(overlappingOperation.id)) return;
|
|
484
|
+
|
|
485
|
+
overlappingOperation.id = "workflows.register-missing";
|
|
486
|
+
overlappingOperation.fixes.push(...runtimeManifestFix.fixes);
|
|
487
|
+
overlappingOperation.changes = overlappingOperation.changes.map((change) =>
|
|
488
|
+
change.file === runtimeChange.file
|
|
489
|
+
? planDoctorFixFileChange({
|
|
490
|
+
file: change.file,
|
|
491
|
+
before: change.before,
|
|
492
|
+
after: runtimeChange.after,
|
|
493
|
+
})
|
|
494
|
+
: change,
|
|
495
|
+
);
|
|
496
|
+
|
|
497
|
+
const existingWorkflowOperation = operations.find(
|
|
498
|
+
(operation) =>
|
|
499
|
+
operation !== overlappingOperation &&
|
|
500
|
+
operation.id === "workflows.register-missing",
|
|
501
|
+
);
|
|
502
|
+
if (!existingWorkflowOperation) return;
|
|
503
|
+
|
|
504
|
+
existingWorkflowOperation.fixes.push(...overlappingOperation.fixes);
|
|
505
|
+
existingWorkflowOperation.changes.push(...overlappingOperation.changes);
|
|
506
|
+
operations.splice(operations.indexOf(overlappingOperation), 1);
|
|
507
|
+
}
|
|
508
|
+
|
|
410
509
|
/**
|
|
411
510
|
* Format inspected routes as a CLI table.
|
|
412
511
|
*/
|
|
@@ -3269,27 +3368,28 @@ async function inspectInngestProductionPath(
|
|
|
3269
3368
|
"utf8",
|
|
3270
3369
|
);
|
|
3271
3370
|
const registry = arrayInitializerInfo(registrySource, "inngestJobs");
|
|
3272
|
-
const
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
const
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3371
|
+
const featureJobs = (
|
|
3372
|
+
await readFeatureWorkflowRegistries(targetDir, files, config)
|
|
3373
|
+
).filter((entry) => entry.kind === "jobs");
|
|
3374
|
+
const registeredJobs = canonicalRegisteredWorkflowIdentifiers({
|
|
3375
|
+
source: registrySource,
|
|
3376
|
+
registered: registry
|
|
3377
|
+
? identifiersFromArrayExpression(registry.text)
|
|
3378
|
+
: new Set<string>(),
|
|
3379
|
+
centralFile: registryFile,
|
|
3380
|
+
files,
|
|
3381
|
+
registries: featureJobs,
|
|
3382
|
+
});
|
|
3383
|
+
const unregisteredJobs = unregisteredWorkflowRegistries(
|
|
3384
|
+
featureJobs,
|
|
3385
|
+
registeredJobs,
|
|
3280
3386
|
);
|
|
3281
|
-
for (const
|
|
3282
|
-
const source = await readFile(path.join(targetDir, jobIndex), "utf8");
|
|
3283
|
-
const match = source.match(/export const\s+([A-Za-z_$][\w$]*Jobs)\s*=/);
|
|
3284
|
-
const registryName = match?.[1];
|
|
3285
|
-
if (!registryName || registeredJobs.has(registryName)) {
|
|
3286
|
-
continue;
|
|
3287
|
-
}
|
|
3387
|
+
for (const { registry: featureRegistry } of unregisteredJobs) {
|
|
3288
3388
|
diagnostics.push({
|
|
3289
3389
|
severity: "warning",
|
|
3290
3390
|
code: "BEIGNET_INNGEST_JOB_REGISTRATION_MISSING",
|
|
3291
3391
|
file: registryFile,
|
|
3292
|
-
message: `${
|
|
3392
|
+
message: `${featureRegistry.indexFile} exports ${featureRegistry.registryName}, but it is not registered in inngestJobs in ${registryFile}. Add ...${featureRegistry.registryName} so Inngest can execute the job.`,
|
|
3293
3393
|
});
|
|
3294
3394
|
}
|
|
3295
3395
|
}
|
|
@@ -4064,6 +4164,10 @@ async function inspectRuntimeManifestDrift(
|
|
|
4064
4164
|
files,
|
|
4065
4165
|
config,
|
|
4066
4166
|
);
|
|
4167
|
+
const manifestSource = await readFile(
|
|
4168
|
+
path.join(targetDir, manifest.file),
|
|
4169
|
+
"utf8",
|
|
4170
|
+
);
|
|
4067
4171
|
const diagnostics: InspectDiagnostic[] = [];
|
|
4068
4172
|
|
|
4069
4173
|
const checks: readonly {
|
|
@@ -4105,9 +4209,18 @@ async function inspectRuntimeManifestDrift(
|
|
|
4105
4209
|
];
|
|
4106
4210
|
|
|
4107
4211
|
for (const check of checks) {
|
|
4212
|
+
const kindRegistries = registries.filter(
|
|
4213
|
+
(registry) => registry.kind === check.kind,
|
|
4214
|
+
);
|
|
4108
4215
|
const missing = unregisteredWorkflowRegistries(
|
|
4109
|
-
|
|
4110
|
-
|
|
4216
|
+
kindRegistries,
|
|
4217
|
+
canonicalRegisteredWorkflowIdentifiers({
|
|
4218
|
+
source: manifestSource,
|
|
4219
|
+
registered: check.registered,
|
|
4220
|
+
centralFile: manifest.file,
|
|
4221
|
+
files,
|
|
4222
|
+
registries: kindRegistries,
|
|
4223
|
+
}),
|
|
4111
4224
|
);
|
|
4112
4225
|
|
|
4113
4226
|
for (const { registry, missingMembers } of missing) {
|
|
@@ -4282,6 +4395,48 @@ function unregisteredWorkflowRegistries(
|
|
|
4282
4395
|
return unregistered;
|
|
4283
4396
|
}
|
|
4284
4397
|
|
|
4398
|
+
function canonicalRegisteredWorkflowIdentifiers(options: {
|
|
4399
|
+
source: string;
|
|
4400
|
+
registered: Set<string>;
|
|
4401
|
+
centralFile: string;
|
|
4402
|
+
files: string[];
|
|
4403
|
+
registries: FeatureWorkflowRegistry[];
|
|
4404
|
+
}): Set<string> {
|
|
4405
|
+
const canonical = new Set(options.registered);
|
|
4406
|
+
const imports = parseNamedImportSources(options.source);
|
|
4407
|
+
|
|
4408
|
+
for (const identifier of options.registered) {
|
|
4409
|
+
const imported = imports.get(identifier);
|
|
4410
|
+
if (!imported) continue;
|
|
4411
|
+
|
|
4412
|
+
const importedFile = sourceFileFromImport(
|
|
4413
|
+
imported.sourcePath,
|
|
4414
|
+
options.centralFile,
|
|
4415
|
+
options.files,
|
|
4416
|
+
);
|
|
4417
|
+
if (!importedFile) continue;
|
|
4418
|
+
|
|
4419
|
+
for (const registry of options.registries) {
|
|
4420
|
+
if (
|
|
4421
|
+
imported.importedName === registry.registryName &&
|
|
4422
|
+
importedFile === registry.indexFile
|
|
4423
|
+
) {
|
|
4424
|
+
canonical.add(registry.registryName);
|
|
4425
|
+
}
|
|
4426
|
+
|
|
4427
|
+
if (
|
|
4428
|
+
registry.members.includes(imported.importedName) &&
|
|
4429
|
+
(importedFile === registry.indexFile ||
|
|
4430
|
+
registry.memberFiles.get(imported.importedName) === importedFile)
|
|
4431
|
+
) {
|
|
4432
|
+
canonical.add(imported.importedName);
|
|
4433
|
+
}
|
|
4434
|
+
}
|
|
4435
|
+
}
|
|
4436
|
+
|
|
4437
|
+
return canonical;
|
|
4438
|
+
}
|
|
4439
|
+
|
|
4285
4440
|
async function readFeatureWorkflowRegistries(
|
|
4286
4441
|
targetDir: string,
|
|
4287
4442
|
files: string[],
|
|
@@ -4361,6 +4516,8 @@ async function readRuntimeManifestIdentifiers(
|
|
|
4361
4516
|
targetDir: string,
|
|
4362
4517
|
files: string[],
|
|
4363
4518
|
): Promise<RuntimeManifestIdentifierSets | undefined> {
|
|
4519
|
+
const manifests: RuntimeManifestIdentifierSets[] = [];
|
|
4520
|
+
|
|
4364
4521
|
for (const file of files) {
|
|
4365
4522
|
if (!file.endsWith(".ts") && !file.endsWith(".tsx")) continue;
|
|
4366
4523
|
if (file.endsWith(".d.ts") || isWorkflowTestFile(file)) continue;
|
|
@@ -4368,25 +4525,29 @@ async function readRuntimeManifestIdentifiers(
|
|
|
4368
4525
|
const source = await readFile(path.join(targetDir, file), "utf8");
|
|
4369
4526
|
if (!source.includes("defineRuntimeManifest")) continue;
|
|
4370
4527
|
|
|
4528
|
+
const calls = runtimeManifestCallMatches(source);
|
|
4529
|
+
if (calls.length === 0) continue;
|
|
4530
|
+
if (calls.length !== 1) return undefined;
|
|
4371
4531
|
const identifiers = runtimeManifestIdentifiers(source);
|
|
4372
4532
|
if (identifiers) {
|
|
4373
|
-
|
|
4533
|
+
manifests.push({
|
|
4374
4534
|
file,
|
|
4375
4535
|
...identifiers,
|
|
4376
|
-
};
|
|
4536
|
+
});
|
|
4377
4537
|
}
|
|
4378
4538
|
}
|
|
4379
4539
|
|
|
4380
|
-
return undefined;
|
|
4540
|
+
return manifests.length === 1 ? manifests[0] : undefined;
|
|
4381
4541
|
}
|
|
4382
4542
|
|
|
4383
4543
|
function runtimeManifestIdentifiers(
|
|
4384
4544
|
source: string,
|
|
4385
4545
|
): Omit<RuntimeManifestIdentifierSets, "file"> | undefined {
|
|
4386
|
-
const
|
|
4387
|
-
if (
|
|
4546
|
+
const calls = runtimeManifestCallMatches(source);
|
|
4547
|
+
if (calls.length !== 1) return undefined;
|
|
4548
|
+
const call = calls[0];
|
|
4388
4549
|
|
|
4389
|
-
const arg = firstCallArgInfo(source, call.index + call[0].length);
|
|
4550
|
+
const arg = firstCallArgInfo(source, (call.index ?? 0) + call[0].length);
|
|
4390
4551
|
if (!arg) return undefined;
|
|
4391
4552
|
|
|
4392
4553
|
const outbox = objectPropertyValueInfo(arg.text, "outbox");
|
|
@@ -4404,6 +4565,15 @@ function runtimeManifestIdentifiers(
|
|
|
4404
4565
|
};
|
|
4405
4566
|
}
|
|
4406
4567
|
|
|
4568
|
+
function runtimeManifestCallMatches(source: string): RegExpMatchArray[] {
|
|
4569
|
+
const codeIndexes = new Set(codeCharacterIndexes(source));
|
|
4570
|
+
return [
|
|
4571
|
+
...source.matchAll(/\bdefineRuntimeManifest\s*(?:<[^>]*>\s*)?\(/g),
|
|
4572
|
+
].filter(
|
|
4573
|
+
(match) => match.index !== undefined && codeIndexes.has(match.index),
|
|
4574
|
+
);
|
|
4575
|
+
}
|
|
4576
|
+
|
|
4407
4577
|
function identifiersFromPropertyArray(
|
|
4408
4578
|
source: string,
|
|
4409
4579
|
property: string,
|
|
@@ -4417,24 +4587,48 @@ function objectPropertyValueInfo(
|
|
|
4417
4587
|
source: string,
|
|
4418
4588
|
property: string,
|
|
4419
4589
|
): { text: string; start: number; end: number } | undefined {
|
|
4420
|
-
|
|
4421
|
-
|
|
4590
|
+
let depth = 0;
|
|
4591
|
+
|
|
4592
|
+
for (const index of codeCharacterIndexes(source)) {
|
|
4593
|
+
const char = source[index];
|
|
4594
|
+
if (char === "{" || char === "[" || char === "(") {
|
|
4595
|
+
depth++;
|
|
4596
|
+
continue;
|
|
4597
|
+
}
|
|
4598
|
+
if (char === "}" || char === "]" || char === ")") {
|
|
4599
|
+
depth--;
|
|
4600
|
+
continue;
|
|
4601
|
+
}
|
|
4602
|
+
if (
|
|
4603
|
+
depth !== 1 ||
|
|
4604
|
+
source.slice(index, index + property.length) !== property ||
|
|
4605
|
+
/[A-Za-z0-9_$]/.test(source[index - 1] ?? "")
|
|
4606
|
+
) {
|
|
4607
|
+
continue;
|
|
4608
|
+
}
|
|
4422
4609
|
|
|
4423
|
-
|
|
4424
|
-
|
|
4610
|
+
let colon = index + property.length;
|
|
4611
|
+
if (/[A-Za-z0-9_$]/.test(source[colon] ?? "")) continue;
|
|
4612
|
+
while (colon < source.length && /\s/.test(source[colon])) colon++;
|
|
4613
|
+
if (source[colon] !== ":") continue;
|
|
4425
4614
|
|
|
4426
|
-
|
|
4427
|
-
|
|
4428
|
-
|
|
4615
|
+
let start = colon + 1;
|
|
4616
|
+
while (start < source.length && /\s/.test(source[start])) start++;
|
|
4617
|
+
const open = source[start];
|
|
4618
|
+
const close = open === "[" ? "]" : open === "{" ? "}" : undefined;
|
|
4619
|
+
if (!close) return undefined;
|
|
4429
4620
|
|
|
4430
|
-
|
|
4431
|
-
|
|
4621
|
+
const end = matchingDelimiterIndex(source, start, open, close);
|
|
4622
|
+
if (end === -1) return undefined;
|
|
4432
4623
|
|
|
4433
|
-
|
|
4434
|
-
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
|
|
4624
|
+
return {
|
|
4625
|
+
text: source.slice(start, end + 1),
|
|
4626
|
+
start,
|
|
4627
|
+
end: end + 1,
|
|
4628
|
+
};
|
|
4629
|
+
}
|
|
4630
|
+
|
|
4631
|
+
return undefined;
|
|
4438
4632
|
}
|
|
4439
4633
|
|
|
4440
4634
|
function registeredWorkflowIdentifiers(
|
|
@@ -5036,7 +5230,9 @@ async function inspectDatabaseLifecycleDrift(
|
|
|
5036
5230
|
);
|
|
5037
5231
|
for (const schemaFile of schemaFiles) {
|
|
5038
5232
|
const moduleName = path.basename(schemaFile, ".ts");
|
|
5039
|
-
if (schemaIndexSource
|
|
5233
|
+
if (schemaIndexExportKind(schemaIndexSource, `./${moduleName}`)) {
|
|
5234
|
+
continue;
|
|
5235
|
+
}
|
|
5040
5236
|
diagnostics.push({
|
|
5041
5237
|
severity: "warning",
|
|
5042
5238
|
code: "BEIGNET_DB_SCHEMA_EXPORT_MISSING",
|
|
@@ -6903,6 +7099,467 @@ function findRouteGroupContract(
|
|
|
6903
7099
|
);
|
|
6904
7100
|
}
|
|
6905
7101
|
|
|
7102
|
+
const managedDatabaseSchemaMarker = "// Managed by beignet db schema sync.";
|
|
7103
|
+
const databaseSchemaExports: Record<DatabaseSchemaTable, string> = {
|
|
7104
|
+
audit: "auditLog",
|
|
7105
|
+
idempotency: "idempotencyRecords",
|
|
7106
|
+
outbox: "outboxMessages",
|
|
7107
|
+
};
|
|
7108
|
+
|
|
7109
|
+
async function planMissingProviderTables(
|
|
7110
|
+
targetDir: string,
|
|
7111
|
+
files: string[],
|
|
7112
|
+
config: ResolvedBeignetConfig,
|
|
7113
|
+
): Promise<PlannedDoctorFixOperation | undefined> {
|
|
7114
|
+
const infrastructurePath = directoryPath(
|
|
7115
|
+
path.dirname(config.paths.portWiring),
|
|
7116
|
+
);
|
|
7117
|
+
const schemaDir = `${infrastructurePath}/db/schema`;
|
|
7118
|
+
const output = `${schemaDir}/beignet.ts`;
|
|
7119
|
+
const indexFile = `${schemaDir}/index.ts`;
|
|
7120
|
+
if (!files.includes(indexFile)) return undefined;
|
|
7121
|
+
|
|
7122
|
+
const sourceCache = new Map<string, string>();
|
|
7123
|
+
const usageFiles = durableDrizzleUsageFiles(files, config);
|
|
7124
|
+
const requiredDefaultTables: DatabaseSchemaTable[] = [];
|
|
7125
|
+
const missingTables: DatabaseSchemaTable[] = [];
|
|
7126
|
+
const fixes: InspectFix[] = [];
|
|
7127
|
+
|
|
7128
|
+
for (const requirement of durableDrizzleTableRequirements) {
|
|
7129
|
+
const defaultTableName =
|
|
7130
|
+
defaultBeignetConfig.database.tables[requirement.table];
|
|
7131
|
+
if (config.database.tables[requirement.table] !== defaultTableName) {
|
|
7132
|
+
continue;
|
|
7133
|
+
}
|
|
7134
|
+
|
|
7135
|
+
const configuredTables = await configuredDurableDrizzleTableNames(
|
|
7136
|
+
targetDir,
|
|
7137
|
+
usageFiles,
|
|
7138
|
+
config,
|
|
7139
|
+
requirement,
|
|
7140
|
+
sourceCache,
|
|
7141
|
+
);
|
|
7142
|
+
if (!configuredTables.has(defaultTableName)) continue;
|
|
7143
|
+
requiredDefaultTables.push(requirement.table);
|
|
7144
|
+
|
|
7145
|
+
if (
|
|
7146
|
+
await durableDrizzleTableExists(
|
|
7147
|
+
targetDir,
|
|
7148
|
+
files,
|
|
7149
|
+
config,
|
|
7150
|
+
infrastructurePath,
|
|
7151
|
+
schemaDir,
|
|
7152
|
+
requirement,
|
|
7153
|
+
defaultTableName,
|
|
7154
|
+
sourceCache,
|
|
7155
|
+
)
|
|
7156
|
+
) {
|
|
7157
|
+
continue;
|
|
7158
|
+
}
|
|
7159
|
+
|
|
7160
|
+
missingTables.push(requirement.table);
|
|
7161
|
+
fixes.push({
|
|
7162
|
+
code: requirement.code,
|
|
7163
|
+
file: output,
|
|
7164
|
+
message: `Added the default ${defaultTableName} provider table export to ${output}.`,
|
|
7165
|
+
});
|
|
7166
|
+
}
|
|
7167
|
+
|
|
7168
|
+
const outputPath = path.join(targetDir, output);
|
|
7169
|
+
let originalOutput: string | undefined;
|
|
7170
|
+
try {
|
|
7171
|
+
originalOutput = await readFile(outputPath, "utf8");
|
|
7172
|
+
} catch (error) {
|
|
7173
|
+
if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
|
|
7174
|
+
}
|
|
7175
|
+
const existingTables =
|
|
7176
|
+
originalOutput === undefined
|
|
7177
|
+
? []
|
|
7178
|
+
: managedDatabaseSchemaTables(originalOutput);
|
|
7179
|
+
if (existingTables === undefined) return undefined;
|
|
7180
|
+
const originalIndex = await readFile(path.join(targetDir, indexFile), "utf8");
|
|
7181
|
+
const managedExportKind = schemaIndexExportKind(originalIndex, "./beignet");
|
|
7182
|
+
if (managedExportKind === "named") return undefined;
|
|
7183
|
+
const needsManagedExport =
|
|
7184
|
+
originalOutput !== undefined &&
|
|
7185
|
+
existingTables.some((table) => requiredDefaultTables.includes(table)) &&
|
|
7186
|
+
managedExportKind === undefined;
|
|
7187
|
+
if (missingTables.length === 0 && !needsManagedExport) return undefined;
|
|
7188
|
+
if (needsManagedExport) {
|
|
7189
|
+
fixes.push({
|
|
7190
|
+
code: "BEIGNET_DB_SCHEMA_EXPORT_MISSING",
|
|
7191
|
+
file: indexFile,
|
|
7192
|
+
message: `Exported ${output} from ${indexFile}.`,
|
|
7193
|
+
});
|
|
7194
|
+
}
|
|
7195
|
+
|
|
7196
|
+
const selectedTables = (
|
|
7197
|
+
Object.keys(databaseSchemaExports) as DatabaseSchemaTable[]
|
|
7198
|
+
).filter(
|
|
7199
|
+
(table) => existingTables.includes(table) || missingTables.includes(table),
|
|
7200
|
+
);
|
|
7201
|
+
|
|
7202
|
+
let nextOutput = originalOutput;
|
|
7203
|
+
if (missingTables.length > 0) {
|
|
7204
|
+
let dialect: Awaited<ReturnType<typeof resolveDatabaseSchemaDialect>>;
|
|
7205
|
+
try {
|
|
7206
|
+
dialect = await resolveDatabaseSchemaDialect(targetDir, undefined);
|
|
7207
|
+
} catch {
|
|
7208
|
+
return undefined;
|
|
7209
|
+
}
|
|
7210
|
+
nextOutput = renderDatabaseSchema(dialect, selectedTables);
|
|
7211
|
+
}
|
|
7212
|
+
if (nextOutput === undefined) return undefined;
|
|
7213
|
+
|
|
7214
|
+
const addedExportNames = missingTables.map(
|
|
7215
|
+
(table) => databaseSchemaExports[table],
|
|
7216
|
+
);
|
|
7217
|
+
const otherSchemaFiles = files.filter(
|
|
7218
|
+
(file) =>
|
|
7219
|
+
file.startsWith(`${schemaDir}/`) &&
|
|
7220
|
+
file.endsWith(".ts") &&
|
|
7221
|
+
file !== indexFile &&
|
|
7222
|
+
file !== output,
|
|
7223
|
+
);
|
|
7224
|
+
for (const schemaFile of [indexFile, ...otherSchemaFiles]) {
|
|
7225
|
+
const source =
|
|
7226
|
+
schemaFile === indexFile
|
|
7227
|
+
? originalIndex
|
|
7228
|
+
: await readFile(path.join(targetDir, schemaFile), "utf8");
|
|
7229
|
+
if (addedExportNames.some((name) => exportsIdentifier(source, name))) {
|
|
7230
|
+
return undefined;
|
|
7231
|
+
}
|
|
7232
|
+
}
|
|
7233
|
+
const nextIndex =
|
|
7234
|
+
managedExportKind === "all"
|
|
7235
|
+
? originalIndex
|
|
7236
|
+
: `${originalIndex.trimEnd()}\nexport * from "./beignet";\n`;
|
|
7237
|
+
|
|
7238
|
+
const changes = [
|
|
7239
|
+
...(originalOutput === nextOutput
|
|
7240
|
+
? []
|
|
7241
|
+
: [
|
|
7242
|
+
planDoctorFixFileChange({
|
|
7243
|
+
file: output,
|
|
7244
|
+
before: originalOutput,
|
|
7245
|
+
after: nextOutput,
|
|
7246
|
+
}),
|
|
7247
|
+
]),
|
|
7248
|
+
...(originalIndex === nextIndex
|
|
7249
|
+
? []
|
|
7250
|
+
: [
|
|
7251
|
+
planDoctorFixFileChange({
|
|
7252
|
+
file: indexFile,
|
|
7253
|
+
before: originalIndex,
|
|
7254
|
+
after: nextIndex,
|
|
7255
|
+
}),
|
|
7256
|
+
]),
|
|
7257
|
+
];
|
|
7258
|
+
if (changes.length === 0) return undefined;
|
|
7259
|
+
|
|
7260
|
+
return {
|
|
7261
|
+
id: "database.sync-provider-tables",
|
|
7262
|
+
fixes,
|
|
7263
|
+
changes,
|
|
7264
|
+
};
|
|
7265
|
+
}
|
|
7266
|
+
|
|
7267
|
+
function schemaIndexExportKind(
|
|
7268
|
+
source: string,
|
|
7269
|
+
moduleSpecifier: string,
|
|
7270
|
+
): "all" | "named" | undefined {
|
|
7271
|
+
const codeIndexes = new Set(codeCharacterIndexes(source));
|
|
7272
|
+
const escaped = escapeRegExp(moduleSpecifier);
|
|
7273
|
+
const allPattern = new RegExp(
|
|
7274
|
+
`\\bexport\\s*\\*\\s*from\\s*["']${escaped}["']`,
|
|
7275
|
+
"g",
|
|
7276
|
+
);
|
|
7277
|
+
if (
|
|
7278
|
+
[...source.matchAll(allPattern)].some(
|
|
7279
|
+
(match) => match.index !== undefined && codeIndexes.has(match.index),
|
|
7280
|
+
)
|
|
7281
|
+
) {
|
|
7282
|
+
return "all";
|
|
7283
|
+
}
|
|
7284
|
+
|
|
7285
|
+
const customPatterns = [
|
|
7286
|
+
new RegExp(
|
|
7287
|
+
`\\bexport\\s*\\{[\\s\\S]*?\\}\\s*from\\s*["']${escaped}["']`,
|
|
7288
|
+
"g",
|
|
7289
|
+
),
|
|
7290
|
+
new RegExp(
|
|
7291
|
+
`\\bexport\\s*\\*\\s+as\\s+[A-Za-z_$][\\w$]*\\s+from\\s*["']${escaped}["']`,
|
|
7292
|
+
"g",
|
|
7293
|
+
),
|
|
7294
|
+
];
|
|
7295
|
+
return customPatterns.some((pattern) =>
|
|
7296
|
+
[...source.matchAll(pattern)].some(
|
|
7297
|
+
(match) => match.index !== undefined && codeIndexes.has(match.index),
|
|
7298
|
+
),
|
|
7299
|
+
)
|
|
7300
|
+
? "named"
|
|
7301
|
+
: undefined;
|
|
7302
|
+
}
|
|
7303
|
+
|
|
7304
|
+
function exportsIdentifier(source: string, identifier: string): boolean {
|
|
7305
|
+
const executableSource = Array.from({ length: source.length }, () => " ");
|
|
7306
|
+
for (const index of codeCharacterIndexes(source)) {
|
|
7307
|
+
executableSource[index] = source.charAt(index);
|
|
7308
|
+
}
|
|
7309
|
+
|
|
7310
|
+
const escaped = escapeRegExp(identifier);
|
|
7311
|
+
const patterns = [
|
|
7312
|
+
new RegExp(
|
|
7313
|
+
`\\bexport\\s+(?:const|let|var|class|function)\\s+${escaped}\\b`,
|
|
7314
|
+
),
|
|
7315
|
+
new RegExp(`\\bexport\\s*\\{[^}]*\\b${escaped}\\b[^}]*\\}`),
|
|
7316
|
+
];
|
|
7317
|
+
|
|
7318
|
+
const code = executableSource.join("");
|
|
7319
|
+
return patterns.some((pattern) => pattern.test(code));
|
|
7320
|
+
}
|
|
7321
|
+
|
|
7322
|
+
function managedDatabaseSchemaTables(
|
|
7323
|
+
source: string,
|
|
7324
|
+
): DatabaseSchemaTable[] | undefined {
|
|
7325
|
+
if (!source.startsWith(managedDatabaseSchemaMarker)) return undefined;
|
|
7326
|
+
|
|
7327
|
+
const dialect =
|
|
7328
|
+
/@beignet\/provider-db-drizzle\/(sqlite|postgres|mysql)\/schema/.exec(
|
|
7329
|
+
source,
|
|
7330
|
+
)?.[1] as DatabaseSchemaDialect | undefined;
|
|
7331
|
+
if (!dialect) return undefined;
|
|
7332
|
+
|
|
7333
|
+
const tables = (
|
|
7334
|
+
Object.entries(databaseSchemaExports) as Array<
|
|
7335
|
+
[DatabaseSchemaTable, string]
|
|
7336
|
+
>
|
|
7337
|
+
)
|
|
7338
|
+
.filter(([, exportName]) =>
|
|
7339
|
+
new RegExp(`\\b${escapeRegExp(exportName)}\\b`).test(source),
|
|
7340
|
+
)
|
|
7341
|
+
.map(([table]) => table);
|
|
7342
|
+
if (tables.length === 0) return undefined;
|
|
7343
|
+
|
|
7344
|
+
return renderDatabaseSchema(dialect, tables) === source ? tables : undefined;
|
|
7345
|
+
}
|
|
7346
|
+
|
|
7347
|
+
async function planUnregisteredInngestJobs(
|
|
7348
|
+
targetDir: string,
|
|
7349
|
+
files: string[],
|
|
7350
|
+
config: ResolvedBeignetConfig,
|
|
7351
|
+
): Promise<PlannedDoctorFixOperation | undefined> {
|
|
7352
|
+
const packageJson = await readPackageJson(targetDir, files);
|
|
7353
|
+
if (
|
|
7354
|
+
!installedPackageNames(packageJson).has("@beignet/provider-jobs-inngest")
|
|
7355
|
+
) {
|
|
7356
|
+
return undefined;
|
|
7357
|
+
}
|
|
7358
|
+
|
|
7359
|
+
const serverDir = directoryPath(path.dirname(config.paths.server));
|
|
7360
|
+
const registryFile = `${serverDir}/inngest.ts`;
|
|
7361
|
+
if (!files.includes(registryFile)) return undefined;
|
|
7362
|
+
|
|
7363
|
+
const source = await readFile(path.join(targetDir, registryFile), "utf8");
|
|
7364
|
+
const registry = arrayInitializerInfo(source, "inngestJobs");
|
|
7365
|
+
if (!registry) return undefined;
|
|
7366
|
+
|
|
7367
|
+
const featureJobs = (
|
|
7368
|
+
await readFeatureWorkflowRegistries(targetDir, files, config)
|
|
7369
|
+
).filter((entry) => entry.kind === "jobs");
|
|
7370
|
+
const unregistered = unregisteredWorkflowRegistries(
|
|
7371
|
+
featureJobs,
|
|
7372
|
+
canonicalRegisteredWorkflowIdentifiers({
|
|
7373
|
+
source,
|
|
7374
|
+
registered: identifiersFromArrayExpression(registry.text),
|
|
7375
|
+
centralFile: registryFile,
|
|
7376
|
+
files,
|
|
7377
|
+
registries: featureJobs,
|
|
7378
|
+
}),
|
|
7379
|
+
);
|
|
7380
|
+
|
|
7381
|
+
return planWorkflowRegistryOperation("inngest.register-missing", {
|
|
7382
|
+
targetDir,
|
|
7383
|
+
files,
|
|
7384
|
+
centralFile: registryFile,
|
|
7385
|
+
unregistered,
|
|
7386
|
+
code: "BEIGNET_INNGEST_JOB_REGISTRATION_MISSING",
|
|
7387
|
+
listName: "the Inngest jobs array",
|
|
7388
|
+
importSpecifier: (indexFile) => relativeModule(registryFile, indexFile),
|
|
7389
|
+
append: (nextSource, entry, importLine) =>
|
|
7390
|
+
appendToNamedArray(nextSource, "inngestJobs", entry, importLine),
|
|
7391
|
+
});
|
|
7392
|
+
}
|
|
7393
|
+
|
|
7394
|
+
const runtimeManifestRepairChecks: readonly {
|
|
7395
|
+
kind: WorkflowRegistryKind;
|
|
7396
|
+
code: string;
|
|
7397
|
+
listName: string;
|
|
7398
|
+
}[] = [
|
|
7399
|
+
{
|
|
7400
|
+
kind: "listeners",
|
|
7401
|
+
code: "BEIGNET_RUNTIME_MANIFEST_LISTENER_MISSING",
|
|
7402
|
+
listName: "the runtime manifest listeners array",
|
|
7403
|
+
},
|
|
7404
|
+
{
|
|
7405
|
+
kind: "schedules",
|
|
7406
|
+
code: "BEIGNET_RUNTIME_MANIFEST_SCHEDULE_MISSING",
|
|
7407
|
+
listName: "the runtime manifest schedules array",
|
|
7408
|
+
},
|
|
7409
|
+
{
|
|
7410
|
+
kind: "tasks",
|
|
7411
|
+
code: "BEIGNET_RUNTIME_MANIFEST_TASK_MISSING",
|
|
7412
|
+
listName: "the runtime manifest tasks array",
|
|
7413
|
+
},
|
|
7414
|
+
{
|
|
7415
|
+
kind: "events",
|
|
7416
|
+
code: "BEIGNET_RUNTIME_MANIFEST_OUTBOX_EVENT_MISSING",
|
|
7417
|
+
listName: "the runtime manifest outbox events array",
|
|
7418
|
+
},
|
|
7419
|
+
{
|
|
7420
|
+
kind: "jobs",
|
|
7421
|
+
code: "BEIGNET_RUNTIME_MANIFEST_OUTBOX_JOB_MISSING",
|
|
7422
|
+
listName: "the runtime manifest outbox jobs array",
|
|
7423
|
+
},
|
|
7424
|
+
];
|
|
7425
|
+
|
|
7426
|
+
async function planRuntimeManifestRegistries(
|
|
7427
|
+
targetDir: string,
|
|
7428
|
+
files: string[],
|
|
7429
|
+
config: ResolvedBeignetConfig,
|
|
7430
|
+
convention: InspectConvention,
|
|
7431
|
+
sourceOverrides: ReadonlyMap<string, string> = new Map(),
|
|
7432
|
+
): Promise<PlannedDoctorFixOperation | undefined> {
|
|
7433
|
+
if (!convention.resourceGenerator) return undefined;
|
|
7434
|
+
|
|
7435
|
+
const manifest = await readRuntimeManifestIdentifiers(targetDir, files);
|
|
7436
|
+
if (!manifest) return undefined;
|
|
7437
|
+
|
|
7438
|
+
const original =
|
|
7439
|
+
sourceOverrides.get(manifest.file) ??
|
|
7440
|
+
(await readFile(path.join(targetDir, manifest.file), "utf8"));
|
|
7441
|
+
const registries = await readFeatureWorkflowRegistries(
|
|
7442
|
+
targetDir,
|
|
7443
|
+
files,
|
|
7444
|
+
config,
|
|
7445
|
+
);
|
|
7446
|
+
let next = original;
|
|
7447
|
+
const fixes: InspectFix[] = [];
|
|
7448
|
+
|
|
7449
|
+
for (const check of runtimeManifestRepairChecks) {
|
|
7450
|
+
const parsedRegistered =
|
|
7451
|
+
runtimeManifestIdentifiers(next)?.[
|
|
7452
|
+
runtimeManifestIdentifierKey(check.kind)
|
|
7453
|
+
];
|
|
7454
|
+
if (!parsedRegistered) continue;
|
|
7455
|
+
const kindRegistries = registries.filter(
|
|
7456
|
+
(registry) => registry.kind === check.kind,
|
|
7457
|
+
);
|
|
7458
|
+
const registered = canonicalRegisteredWorkflowIdentifiers({
|
|
7459
|
+
source: next,
|
|
7460
|
+
registered: parsedRegistered,
|
|
7461
|
+
centralFile: manifest.file,
|
|
7462
|
+
files,
|
|
7463
|
+
registries: kindRegistries,
|
|
7464
|
+
});
|
|
7465
|
+
|
|
7466
|
+
const edit = planWorkflowRegistryEdit({
|
|
7467
|
+
files,
|
|
7468
|
+
centralFile: manifest.file,
|
|
7469
|
+
source: next,
|
|
7470
|
+
unregistered: unregisteredWorkflowRegistries(kindRegistries, registered),
|
|
7471
|
+
code: check.code,
|
|
7472
|
+
listName: check.listName,
|
|
7473
|
+
importSpecifier: (indexFile) => relativeModule(manifest.file, indexFile),
|
|
7474
|
+
append: (nextSource, entry, importLine) =>
|
|
7475
|
+
appendToRuntimeManifestArray(nextSource, check.kind, entry, importLine),
|
|
7476
|
+
});
|
|
7477
|
+
if (!edit) continue;
|
|
7478
|
+
next = edit.after;
|
|
7479
|
+
fixes.push(edit.fix);
|
|
7480
|
+
}
|
|
7481
|
+
|
|
7482
|
+
if (fixes.length === 0 || next === original) return undefined;
|
|
7483
|
+
|
|
7484
|
+
return {
|
|
7485
|
+
id: "runtime-manifest.register-missing",
|
|
7486
|
+
fixes,
|
|
7487
|
+
changes: [
|
|
7488
|
+
planDoctorFixFileChange({
|
|
7489
|
+
file: manifest.file,
|
|
7490
|
+
before: original,
|
|
7491
|
+
after: next,
|
|
7492
|
+
}),
|
|
7493
|
+
],
|
|
7494
|
+
};
|
|
7495
|
+
}
|
|
7496
|
+
|
|
7497
|
+
function runtimeManifestIdentifierKey(
|
|
7498
|
+
kind: WorkflowRegistryKind,
|
|
7499
|
+
): keyof Omit<RuntimeManifestIdentifierSets, "file"> {
|
|
7500
|
+
return kind;
|
|
7501
|
+
}
|
|
7502
|
+
|
|
7503
|
+
function runtimeManifestArrayInfo(
|
|
7504
|
+
source: string,
|
|
7505
|
+
kind: WorkflowRegistryKind,
|
|
7506
|
+
): { text: string; start: number; end: number } | undefined {
|
|
7507
|
+
const calls = runtimeManifestCallMatches(source);
|
|
7508
|
+
if (calls.length !== 1) return undefined;
|
|
7509
|
+
const call = calls[0];
|
|
7510
|
+
|
|
7511
|
+
const arg = firstCallArgInfo(source, (call.index ?? 0) + call[0].length);
|
|
7512
|
+
if (!arg) return undefined;
|
|
7513
|
+
|
|
7514
|
+
if (kind === "listeners" || kind === "schedules" || kind === "tasks") {
|
|
7515
|
+
const value = objectPropertyValueInfo(arg.text, kind);
|
|
7516
|
+
return value
|
|
7517
|
+
? {
|
|
7518
|
+
text: value.text,
|
|
7519
|
+
start: arg.start + value.start,
|
|
7520
|
+
end: arg.start + value.end,
|
|
7521
|
+
}
|
|
7522
|
+
: undefined;
|
|
7523
|
+
}
|
|
7524
|
+
|
|
7525
|
+
const outbox = objectPropertyValueInfo(arg.text, "outbox");
|
|
7526
|
+
if (!outbox) return undefined;
|
|
7527
|
+
const value = objectPropertyValueInfo(outbox.text, kind);
|
|
7528
|
+
return value
|
|
7529
|
+
? {
|
|
7530
|
+
text: value.text,
|
|
7531
|
+
start: arg.start + outbox.start + value.start,
|
|
7532
|
+
end: arg.start + outbox.start + value.end,
|
|
7533
|
+
}
|
|
7534
|
+
: undefined;
|
|
7535
|
+
}
|
|
7536
|
+
|
|
7537
|
+
function appendToRuntimeManifestArray(
|
|
7538
|
+
source: string,
|
|
7539
|
+
kind: WorkflowRegistryKind,
|
|
7540
|
+
entry: string,
|
|
7541
|
+
importLine?: string,
|
|
7542
|
+
): AppendResult {
|
|
7543
|
+
const value = runtimeManifestArrayInfo(source, kind);
|
|
7544
|
+
if (!value?.text.startsWith("[")) return { kind: "missing" };
|
|
7545
|
+
|
|
7546
|
+
const entryName = entry.replace(/^\.{3}/, "").trim();
|
|
7547
|
+
if (identifiersFromArrayExpression(value.text).has(entryName)) {
|
|
7548
|
+
return { kind: "unchanged" };
|
|
7549
|
+
}
|
|
7550
|
+
|
|
7551
|
+
const nextArray = appendToArrayExpression(value.text, [entry]);
|
|
7552
|
+
let next = `${source.slice(0, value.start)}${nextArray}${source.slice(
|
|
7553
|
+
value.end,
|
|
7554
|
+
)}`;
|
|
7555
|
+
if (importLine && !next.includes(importLine)) {
|
|
7556
|
+
next = insertAfterImports(next, importLine);
|
|
7557
|
+
}
|
|
7558
|
+
return next === source
|
|
7559
|
+
? { kind: "unchanged" }
|
|
7560
|
+
: { kind: "updated", source: next };
|
|
7561
|
+
}
|
|
7562
|
+
|
|
6906
7563
|
async function planDirectOpenApiArrayDrift(
|
|
6907
7564
|
targetDir: string,
|
|
6908
7565
|
files: string[],
|
|
@@ -7460,7 +8117,8 @@ function planWorkflowRegistryEdit(
|
|
|
7460
8117
|
const registeredNames: string[] = [];
|
|
7461
8118
|
|
|
7462
8119
|
for (const { registry } of candidates) {
|
|
7463
|
-
const
|
|
8120
|
+
const imports = parseNamedImportSources(next);
|
|
8121
|
+
const imported = imports.get(registry.registryName);
|
|
7464
8122
|
let importLine: string | undefined;
|
|
7465
8123
|
|
|
7466
8124
|
if (imported) {
|
|
@@ -7471,6 +8129,21 @@ function planWorkflowRegistryEdit(
|
|
|
7471
8129
|
);
|
|
7472
8130
|
if (importedFile !== registry.indexFile) return undefined;
|
|
7473
8131
|
} else {
|
|
8132
|
+
const aliasesRegistry = [...imports.entries()].some(
|
|
8133
|
+
([localName, candidate]) =>
|
|
8134
|
+
localName !== registry.registryName &&
|
|
8135
|
+
candidate.importedName === registry.registryName &&
|
|
8136
|
+
sourceFileFromImport(
|
|
8137
|
+
candidate.sourcePath,
|
|
8138
|
+
options.centralFile,
|
|
8139
|
+
options.files,
|
|
8140
|
+
) === registry.indexFile,
|
|
8141
|
+
);
|
|
8142
|
+
if (aliasesRegistry) return undefined;
|
|
8143
|
+
if (sourceContainsCodeIdentifier(next, registry.registryName)) {
|
|
8144
|
+
return undefined;
|
|
8145
|
+
}
|
|
8146
|
+
|
|
7474
8147
|
importLine = `import { ${registry.registryName} } from "${options.importSpecifier(
|
|
7475
8148
|
registry.indexFile,
|
|
7476
8149
|
)}";`;
|
|
@@ -7500,6 +8173,22 @@ function planWorkflowRegistryEdit(
|
|
|
7500
8173
|
};
|
|
7501
8174
|
}
|
|
7502
8175
|
|
|
8176
|
+
function sourceContainsCodeIdentifier(
|
|
8177
|
+
source: string,
|
|
8178
|
+
identifier: string,
|
|
8179
|
+
): boolean {
|
|
8180
|
+
const codeIndexes = new Set(codeCharacterIndexes(source));
|
|
8181
|
+
const pattern = new RegExp(
|
|
8182
|
+
`(^|[^A-Za-z0-9_$])${escapeRegExp(identifier)}(?![A-Za-z0-9_$])`,
|
|
8183
|
+
"g",
|
|
8184
|
+
);
|
|
8185
|
+
return [...source.matchAll(pattern)].some(
|
|
8186
|
+
(match) =>
|
|
8187
|
+
match.index !== undefined &&
|
|
8188
|
+
codeIndexes.has(match.index + (match[1]?.length ?? 0)),
|
|
8189
|
+
);
|
|
8190
|
+
}
|
|
8191
|
+
|
|
7503
8192
|
function routeRegistryFileFromServerSource(
|
|
7504
8193
|
source: string,
|
|
7505
8194
|
serverFile: string,
|