@geekmidas/cli 10.0.0-alpha.40 → 10.0.0-alpha.42

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -5,10 +5,10 @@ import { d as parseModuleConfig, f as appKey, l as loadWorkspaceConfig, n as Not
5
5
  import { D as assertDeployedStage, E as TEST_STAGE, O as deployedProblems, T as InvalidStages, a as getAppBuildOrder, b as getDeployTargetError, k as stageProblems, m as stripPublicPrefix, o as getAppGkmConfig, p as getPublicEnvPrefix, t as allConstructGlobs, x as isDeployTargetSupported } from "./workspace-DOmIhiau.mjs";
6
6
  import { a as withCustomSecret, i as toEmbeddableSecrets, n as maskPassword, t as initStageSecrets } from "./storage-BzaUxmqe.mjs";
7
7
  import { a as getKeyPath, i as FileSecretsStore, n as secretsStoreFor } from "./store-DeGATCS-.mjs";
8
- import { C as findAvailablePort, D as prepareEntryCredentials, E as loadSecretsForApp, S as createEntryWrapper, T as loadEnvFiles, _ as cacheBackendFor, a as reconcileWorkspace, b as storageBackendFor, c as LOCAL_CA_PATH, d as quoteIdentifier, f as pgClient, g as primaryPortKey, h as fakeKey, i as derivedContainers, l as forgetState, m as rootSite, n as backendsOf, p as resourceName, r as constructGlobs, s as COMPOSE_PATH, t as FAKE_ENV, u as applyPostgres, v as eventsBackendFor, w as isPortAvailable, x as createCredentialsPreload, y as providerOf } from "./workspace-DwS1JK5u.mjs";
8
+ import { C as findAvailablePort, D as prepareEntryCredentials, E as loadSecretsForApp, S as createEntryWrapper, T as loadEnvFiles, _ as cacheBackendFor, a as reconcileWorkspace, b as storageBackendFor, c as LOCAL_CA_PATH, d as quoteIdentifier, f as pgClient, g as primaryPortKey, h as fakeKey, i as derivedContainers, l as forgetState, m as rootSite, n as backendsOf, p as resourceName, r as constructGlobs, s as COMPOSE_PATH, t as FAKE_ENV, u as applyPostgres, v as eventsBackendFor, w as isPortAvailable, x as createCredentialsPreload, y as providerOf } from "./workspace-BTM5-aaI.mjs";
9
9
  import { n as EndpointGenerator, r as runtimeFor } from "./OpenApiTsGenerator-CEh3EKN3.mjs";
10
10
  import { generateOpenApi, openapiCommand, resolveOpenApiConfig } from "./openapi.mjs";
11
- import { a as writeTestHarness, i as TEST_MANIFEST_ENV, n as TEST_READY_FILE, o as cacheBackendsIn, s as driversFor, t as TEST_READY_ENV } from "./ready-D8UXsP6q.mjs";
11
+ import { a as writeTestHarness, c as driversFor, i as TEST_MANIFEST_ENV, n as TEST_READY_FILE, o as SubscriberGenerator, s as cacheBackendsIn, t as TEST_READY_ENV } from "./ready-BZ0fLpyL.mjs";
12
12
  import { i as withRoutes, r as withCompute } from "./emit-Bzq7JsMP.mjs";
13
13
  import { createRequire } from "node:module";
14
14
  import { Command } from "commander";
@@ -37,11 +37,10 @@ import crypto from "crypto";
37
37
  import { Cron } from "@geekmidas/constructs/crons";
38
38
  import { Function } from "@geekmidas/constructs/functions";
39
39
  import { Queue } from "@geekmidas/constructs/queue";
40
- import { Subscriber } from "@geekmidas/constructs/subscribers";
41
40
  import { Topic } from "@geekmidas/constructs/topic";
42
41
  import { lookup } from "node:dns/promises";
43
42
  //#region package.json
44
- var version = "10.0.0-alpha.40";
43
+ var version = "10.0.0-alpha.42";
45
44
  //#endregion
46
45
  //#region src/auth/credentials.ts
47
46
  /**
@@ -1566,9 +1565,9 @@ async function workspaceDevCommand(workspace, options) {
1566
1565
  const fakes = options.fake ? await serveFakes(reconciled.fakes, reconciled.ports) : [];
1567
1566
  for (const { id, port } of fakes) logger$10.log(`🎭 ${id} fake: http://localhost:${port}`);
1568
1567
  if (options.migrate || options.seed) {
1569
- const { prepareDevDatabases } = await import("./migrate-BVI3yqcE.mjs");
1568
+ const { prepareDevDatabases } = await import("./migrate-64EEprA_.mjs");
1570
1569
  await prepareDevDatabases(workspace, reconciled.env, { seed: !!options.seed });
1571
- } else await import("./migrate-BVI3yqcE.mjs").then(({ reportPendingMigrations }) => reportPendingMigrations(workspace, reconciled.env)).catch((error) => {
1570
+ } else await import("./migrate-64EEprA_.mjs").then(({ reportPendingMigrations }) => reportPendingMigrations(workspace, reconciled.env)).catch((error) => {
1572
1571
  logger$10.log(`⚠️ Could not check for pending migrations: ${error instanceof Error ? error.message : String(error)}`);
1573
1572
  });
1574
1573
  const appUrls = {};
@@ -2458,6 +2457,12 @@ export async function setupQueues(
2458
2457
  const services = queue.services.length > 0
2459
2458
  ? await serviceDiscovery.register(queue.services)
2460
2459
  : {};
2460
+ // The queue's database — the worker's, or its own — handed over as \`db\`.
2461
+ const db = queue.databaseService
2462
+ ? (await serviceDiscovery.register([queue.databaseService]))[
2463
+ queue.databaseService.serviceName
2464
+ ]
2465
+ : undefined;
2461
2466
 
2462
2467
  // A queue's messages carry one type — its own name.
2463
2468
  await eventSubscriber.subscribe([queue.name], async (message) => {
@@ -2472,7 +2477,8 @@ export async function setupQueues(
2472
2477
  messages: [validation.value],
2473
2478
  services: services,
2474
2479
  logger: queue.logger,
2475
- });
2480
+ db,
2481
+ } as any);
2476
2482
  } catch (error) {
2477
2483
  logger.error({ error, queue: queue.name }, 'Failed to process queue message');
2478
2484
  // Rethrown so the transport keeps the message for a retry.
@@ -2497,266 +2503,6 @@ export async function setupQueues(
2497
2503
  }
2498
2504
  };
2499
2505
  //#endregion
2500
- //#region src/generators/SubscriberGenerator.ts
2501
- var SubscriberGenerator = class extends ConstructGenerator {
2502
- isConstruct(value) {
2503
- return Subscriber.isSubscriber(value);
2504
- }
2505
- async build(context, constructs, outputDir, options) {
2506
- const provider = options?.provider || "aws-lambda";
2507
- const logger = console;
2508
- const subscriberInfos = [];
2509
- if (provider === "server") {
2510
- await this.generateServerSubscribersFile(outputDir, constructs);
2511
- logger.log(`Generated server subscribers file with ${constructs.length} subscribers (polling mode)`);
2512
- return subscriberInfos;
2513
- }
2514
- if (constructs.length === 0) return subscriberInfos;
2515
- if (provider !== "aws-lambda") return subscriberInfos;
2516
- const subscribersDir = join(outputDir, "subscribers");
2517
- await mkdir(subscribersDir, { recursive: true });
2518
- for (const { key, construct, path } of constructs) {
2519
- const handlerFile = await this.generateSubscriberHandler(subscribersDir, path.relative, key, construct, context, construct.owner);
2520
- subscriberInfos.push({
2521
- name: key,
2522
- handler: relative(process.cwd(), handlerFile).replace(/\.ts$/, ".handler"),
2523
- subscribedEvents: construct.subscribedEvents || [],
2524
- ...construct.topicName ? {
2525
- transport: "topic",
2526
- topic: construct.topicName
2527
- } : {},
2528
- timeout: construct.timeout,
2529
- memorySize: construct.memorySize,
2530
- environment: await construct.getEnvironment({ markOptional: context.markOptional }),
2531
- dependencies: construct.constructs
2532
- });
2533
- logger.log(`Generated subscriber handler: ${key}`);
2534
- }
2535
- return subscriberInfos;
2536
- }
2537
- async generateSubscriberHandler(outputDir, sourceFile, exportName, _subscriber, context, owner) {
2538
- const handlerFileName = `${exportName}.ts`;
2539
- const handlerPath = join(outputDir, handlerFileName);
2540
- const importPath = relative(dirname(handlerPath), sourceFile).replace(/\.ts$/, ".js");
2541
- const runtime = runtimeFor(context, dirname(handlerPath), owner);
2542
- const content = `import { AWSLambdaSubscriber } from '@geekmidas/constructs/aws';
2543
- import { ${exportName} } from '${importPath}';
2544
- ${runtime.imports}
2545
- ${runtime.bindings}
2546
-
2547
- const adapter = new AWSLambdaSubscriber(envParser, ${exportName});
2548
-
2549
- export const handler = adapter.handler;
2550
- `;
2551
- await writeFile(handlerPath, content);
2552
- return handlerPath;
2553
- }
2554
- async generateServerSubscribersFile(outputDir, subscribers) {
2555
- await mkdir(outputDir, { recursive: true });
2556
- const subscribersPath = join(outputDir, "subscribers.ts");
2557
- if (subscribers.length === 0) {
2558
- await writeFile(subscribersPath, `/**
2559
- * Generated subscribers setup — this app declares none.
2560
- */
2561
- import type { EnvironmentParser } from '@geekmidas/envkit';
2562
- import type { Logger } from '@geekmidas/logger';
2563
-
2564
- export async function setupSubscribers(
2565
- _app: unknown,
2566
- _envParser: EnvironmentParser<any>,
2567
- _logger: Logger,
2568
- ): Promise<(port: number) => Promise<void>> {
2569
- return async () => {};
2570
- }
2571
- `);
2572
- return subscribersPath;
2573
- }
2574
- const importsByFile = /* @__PURE__ */ new Map();
2575
- for (const { path, key } of subscribers) {
2576
- const importPath = relative(dirname(subscribersPath), path.relative).replace(/\.ts$/, ".js");
2577
- importsByFile.set(importPath, [...importsByFile.get(importPath) ?? [], key]);
2578
- }
2579
- const content = `/**
2580
- * Generated subscribers setup.
2581
- *
2582
- * Each subscriber is run against the topic it is bound to, through that
2583
- * topic's own connection string:
2584
- *
2585
- * - **sns://** — pushed, not polled. The subscriber gets an HTTP route on this
2586
- * server, and the route is subscribed to the topic with a filter policy of
2587
- * the events it named, so SNS fans out to each subscriber. The route runs the
2588
- * subscriber through the same adaptor a Lambda subscription does.
2589
- * - **anything else** (pgboss://, rabbitmq://, sqs://) — polled. On pg-boss
2590
- * each subscriber drains a queue of its own, \`<topic>/<subscriber>\`, so
2591
- * every subscriber sees every event; replicas of one subscriber share it.
2592
- *
2593
- * GKM_SUBSCRIBERS=off (\`gkm dev --no-subscribers\`) runs none of them.
2594
- * GKM_SUBSCRIBER_PUSH_URL is the address SNS pushes to; against the local
2595
- * emulator it defaults to this server through host.docker.internal.
2596
- */
2597
- import { connect } from 'node:net';
2598
- import type { EnvironmentParser } from '@geekmidas/envkit';
2599
- import type { Logger } from '@geekmidas/logger';
2600
- import { EventConnectionFactory, Subscriber } from '@geekmidas/events';
2601
- import type { EventConnection } from '@geekmidas/events';
2602
- import { ServiceDiscovery } from '@geekmidas/services';
2603
- ${Array.from(importsByFile.entries()).map(([importPath, exports]) => `import { ${exports.join(", ")} } from '${importPath}';`).join("\n")}
2604
-
2605
- const subscribers = [
2606
- ${subscribers.map(({ key, construct }) => {
2607
- const connectionKey = construct.topicName ? provideKey(canonicalId(construct.topicName), "publisherConnectionString") : void 0;
2608
- return ` { id: '${key}', subscriber: ${key}, topic: ${construct.topicName ? `'${construct.topicName}'` : "undefined"}, connectionKey: ${connectionKey ? `'${connectionKey}'` : "undefined"} },`;
2609
- }).join("\n")}
2610
- ];
2611
-
2612
- /** The route a subscriber's pushes arrive on. */
2613
- export const SUBSCRIBER_ROUTE = (id: string) => \`/__gkm/subscribers/\${id}\`;
2614
-
2615
- /** Resolves once something is accepting connections on the port. */
2616
- async function listening(port: number): Promise<void> {
2617
- for (let attempt = 0; attempt < 100; attempt++) {
2618
- const open = await new Promise<boolean>((resolve) => {
2619
- const socket = connect(port, '127.0.0.1');
2620
- socket.once('connect', () => { socket.end(); resolve(true); });
2621
- socket.once('error', () => resolve(false));
2622
- });
2623
- if (open) return;
2624
- await new Promise((resolve) => setTimeout(resolve, 50));
2625
- }
2626
- }
2627
-
2628
- /**
2629
- * Mount each pushed subscriber's route and start each polled one. Returns what
2630
- * to run once the server is listening: subscribing a route makes SNS send its
2631
- * confirmation at once, so the route has to be reachable first.
2632
- */
2633
- export async function setupSubscribers(
2634
- app: { post: (path: string, handler: (c: any) => unknown) => unknown },
2635
- envParser: EnvironmentParser<any>,
2636
- logger: Logger,
2637
- ): Promise<(port: number) => Promise<void>> {
2638
- if (process.env.GKM_SUBSCRIBERS === 'off') {
2639
- logger.info('Subscribers are off (--no-subscribers)');
2640
- return async () => {};
2641
- }
2642
-
2643
- const serviceDiscovery = ServiceDiscovery.getInstance(envParser);
2644
- const connections = new Map<string, EventConnection>();
2645
- const afterListening: ((port: number) => Promise<void>)[] = [];
2646
-
2647
- for (const { id, subscriber, topic, connectionKey } of subscribers) {
2648
- const events = subscriber.subscribedEvents || [];
2649
- if (events.length === 0) {
2650
- logger.warn({ subscriber: id }, 'Subscriber has no subscribed events, skipping');
2651
- continue;
2652
- }
2653
- const connectionString = connectionKey ? process.env[connectionKey] : undefined;
2654
- if (!connectionString) {
2655
- logger.error(
2656
- { subscriber: id, connectionKey },
2657
- 'No connection string for the topic this subscriber is bound to',
2658
- );
2659
- continue;
2660
- }
2661
-
2662
- try {
2663
- if (connectionString.startsWith('sns:')) {
2664
- const { SNSConnection, snsUrl, subscribeHttpEndpoint } = await import('@geekmidas/events/sns');
2665
- const { SnsPushSubscriberAdaptor } = await import('@geekmidas/constructs/aws');
2666
- const address = snsUrl.parse(connectionString);
2667
- const route = SUBSCRIBER_ROUTE(id);
2668
- const adaptor = new SnsPushSubscriberAdaptor(envParser, subscriber, {
2669
- topicArn: address.topicArn,
2670
- // An emulator signs nothing. Decided by the address this server was
2671
- // configured with, never by anything a request says about itself.
2672
- verify: !address.endpoint,
2673
- ...(address.endpoint ? { emulatorEndpoint: address.endpoint } : {}),
2674
- });
2675
-
2676
- app.post(route, async (c: any) => {
2677
- const body = JSON.parse(await c.req.text());
2678
- const response = await adaptor.handle(body);
2679
- return c.json(response.body, response.status);
2680
- });
2681
-
2682
- afterListening.push(async (port) => {
2683
- const base =
2684
- process.env.GKM_SUBSCRIBER_PUSH_URL ??
2685
- (address.endpoint ? \`http://host.docker.internal:\${port}\` : undefined);
2686
- if (!base) {
2687
- logger.error(
2688
- { subscriber: id },
2689
- 'Set GKM_SUBSCRIBER_PUSH_URL to the public URL of this worker, which SNS pushes to',
2690
- );
2691
- return;
2692
- }
2693
- const connection = await SNSConnection.fromConnectionString(connectionString);
2694
- await subscribeHttpEndpoint(connection, {
2695
- endpoint: new URL(route, base).toString(),
2696
- events,
2697
- });
2698
- logger.info({ subscriber: id, events }, 'Subscriber subscribed for push');
2699
- });
2700
- continue;
2701
- }
2702
-
2703
- let connection = connections.get(connectionString);
2704
- if (!connection) {
2705
- connection = await EventConnectionFactory.fromConnectionString(connectionString);
2706
- connections.set(connectionString, connection);
2707
- }
2708
- // Named, so a broker with one address for everything (pg-boss) gives
2709
- // this subscriber a queue of its own: fan-out, not competition.
2710
- const eventSubscriber = await Subscriber.fromConnection(connection, {
2711
- topic,
2712
- subscription: id,
2713
- });
2714
- const services = subscriber.services.length > 0
2715
- ? await serviceDiscovery.register(subscriber.services)
2716
- : {};
2717
-
2718
- await eventSubscriber.subscribe(events, async (event) => {
2719
- try {
2720
- await subscriber.handler({
2721
- events: [event],
2722
- services: services as any,
2723
- logger: subscriber.logger,
2724
- });
2725
- } catch (error) {
2726
- logger.error({ error, event, subscriber: id }, 'Failed to process event');
2727
- // Rethrown so the transport keeps the event for a retry — as a queue
2728
- // consumer's failure does, and as a pushed subscriber's 500 does.
2729
- throw error;
2730
- }
2731
- });
2732
- logger.info({ subscriber: id, events }, 'Subscriber started polling');
2733
- } catch (error) {
2734
- logger.error({ error, subscriber: id }, 'Failed to set up subscriber');
2735
- }
2736
- }
2737
-
2738
- const shutdown = () => {
2739
- for (const connection of connections.values()) void connection.close();
2740
- };
2741
- process.once('SIGTERM', shutdown);
2742
- process.once('SIGINT', shutdown);
2743
-
2744
- return async (port) => {
2745
- if (afterListening.length === 0) return;
2746
- await listening(port);
2747
- for (const subscribe of afterListening) {
2748
- await subscribe(port).catch((error) => {
2749
- logger.error({ error }, 'Failed to subscribe for push');
2750
- });
2751
- }
2752
- };
2753
- }
2754
- `;
2755
- await writeFile(subscribersPath, content);
2756
- return subscribersPath;
2757
- }
2758
- };
2759
- //#endregion
2760
2506
  //#region src/generators/TopicGenerator.ts
2761
2507
  /**
2762
2508
  * Discovers topics (`new Topic(…)`) into `manifest.topics`. A topic is a
@@ -9576,26 +9322,26 @@ function loadPackageJson() {
9576
9322
  * Run: pnpm --filter @geekmidas/cli sync-versions
9577
9323
  */
9578
9324
  const GEEKMIDAS_VERSIONS = {
9579
- "@geekmidas/audit": "~10.0.0-alpha.40",
9580
- "@geekmidas/auth": "~10.0.0-alpha.40",
9581
- "@geekmidas/cache": "~10.0.0-alpha.40",
9582
- "@geekmidas/client": "~10.0.0-alpha.40",
9583
- "@geekmidas/cloud": "~10.0.0-alpha.40",
9584
- "@geekmidas/constructs": "~10.0.0-alpha.40",
9585
- "@geekmidas/db": "~10.0.0-alpha.40",
9586
- "@geekmidas/emailkit": "~10.0.0-alpha.40",
9587
- "@geekmidas/envkit": "~10.0.0-alpha.40",
9588
- "@geekmidas/errors": "~10.0.0-alpha.40",
9589
- "@geekmidas/events": "~10.0.0-alpha.40",
9590
- "@geekmidas/logger": "~10.0.0-alpha.40",
9591
- "@geekmidas/manifest": "~10.0.0-alpha.40",
9592
- "@geekmidas/rate-limit": "~10.0.0-alpha.40",
9593
- "@geekmidas/schema": "~10.0.0-alpha.40",
9594
- "@geekmidas/services": "~10.0.0-alpha.40",
9595
- "@geekmidas/storage": "~10.0.0-alpha.40",
9596
- "@geekmidas/studio": "~10.0.0-alpha.40",
9597
- "@geekmidas/telescope": "~10.0.0-alpha.40",
9598
- "@geekmidas/testkit": "~10.0.0-alpha.40",
9325
+ "@geekmidas/audit": "~10.0.0-alpha.42",
9326
+ "@geekmidas/auth": "~10.0.0-alpha.42",
9327
+ "@geekmidas/cache": "~10.0.0-alpha.42",
9328
+ "@geekmidas/client": "~10.0.0-alpha.42",
9329
+ "@geekmidas/cloud": "~10.0.0-alpha.42",
9330
+ "@geekmidas/constructs": "~10.0.0-alpha.42",
9331
+ "@geekmidas/db": "~10.0.0-alpha.42",
9332
+ "@geekmidas/emailkit": "~10.0.0-alpha.42",
9333
+ "@geekmidas/envkit": "~10.0.0-alpha.42",
9334
+ "@geekmidas/errors": "~10.0.0-alpha.42",
9335
+ "@geekmidas/events": "~10.0.0-alpha.42",
9336
+ "@geekmidas/logger": "~10.0.0-alpha.42",
9337
+ "@geekmidas/manifest": "~10.0.0-alpha.42",
9338
+ "@geekmidas/rate-limit": "~10.0.0-alpha.42",
9339
+ "@geekmidas/schema": "~10.0.0-alpha.42",
9340
+ "@geekmidas/services": "~10.0.0-alpha.42",
9341
+ "@geekmidas/storage": "~10.0.0-alpha.42",
9342
+ "@geekmidas/studio": "~10.0.0-alpha.42",
9343
+ "@geekmidas/telescope": "~10.0.0-alpha.42",
9344
+ "@geekmidas/testkit": "~10.0.0-alpha.42",
9599
9345
  "@geekmidas/cli": `~${loadPackageJson().version}`
9600
9346
  };
9601
9347
  //#endregion
@@ -16344,7 +16090,7 @@ function testDatabases(reconciled) {
16344
16090
  * starts without the reference data the app needs.
16345
16091
  */
16346
16092
  async function migrateTestStage(workspace, env) {
16347
- const { migrateAndSeed } = await import("./migrate-BVI3yqcE.mjs");
16093
+ const { migrateAndSeed } = await import("./migrate-64EEprA_.mjs");
16348
16094
  const { discover } = await import("./discover-BLhf7V_J.mjs").then((n) => n.n);
16349
16095
  const sources = {};
16350
16096
  const manifest = await discover({
@@ -16791,7 +16537,7 @@ program.command("migrate").description("Apply each database construct's migratio
16791
16537
  try {
16792
16538
  const globalOptions = program.opts();
16793
16539
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
16794
- const { migrateCommand } = await import("./migrate-BVI3yqcE.mjs");
16540
+ const { migrateCommand } = await import("./migrate-64EEprA_.mjs");
16795
16541
  await migrateCommand({
16796
16542
  ...options,
16797
16543
  ...construct ? { construct } : {}
@@ -16805,7 +16551,7 @@ program.command("seed").description("Migrate, then run each database construct's
16805
16551
  try {
16806
16552
  const globalOptions = program.opts();
16807
16553
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
16808
- const { seedCommand } = await import("./migrate-BVI3yqcE.mjs");
16554
+ const { seedCommand } = await import("./migrate-64EEprA_.mjs");
16809
16555
  await seedCommand({
16810
16556
  ...options,
16811
16557
  ...construct ? { construct } : {}
@@ -16819,7 +16565,7 @@ program.command("migration").description("Write a construct's next migration: an
16819
16565
  try {
16820
16566
  const globalOptions = program.opts();
16821
16567
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
16822
- const { migrationCommand } = await import("./migrate-BVI3yqcE.mjs");
16568
+ const { migrationCommand } = await import("./migrate-64EEprA_.mjs");
16823
16569
  await migrationCommand({
16824
16570
  construct,
16825
16571
  ...name ? { name } : {}
@@ -16958,7 +16704,7 @@ program.command("secrets:reconcile").description("Backfill missing custom secret
16958
16704
  console.error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
16959
16705
  process.exit(1);
16960
16706
  }
16961
- const { derivedContainers } = await import("./workspace-DwS1JK5u.mjs").then((n) => n.o);
16707
+ const { derivedContainers } = await import("./workspace-BTM5-aaI.mjs").then((n) => n.o);
16962
16708
  const result = reconcileMissingSecrets(secrets, workspace, await derivedContainers(workspace, stage));
16963
16709
  if (!result) {
16964
16710
  console.log(`\n✓ Secrets for stage "${stage}" are up-to-date`);