@promptbook/cli 0.112.0-88 → 0.112.0-90

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.
Files changed (27) hide show
  1. package/apps/agents-server/next.config.ts +58 -3
  2. package/apps/agents-server/src/database/ensureAutomaticDatabaseMigrations.ts +8 -1
  3. package/apps/agents-server/src/database/runDatabaseMigrations.ts +31 -1
  4. package/apps/agents-server/src/database/sqlite/$provideLocalSqliteSupabase.ts +2 -2
  5. package/apps/agents-server/src/middleware/createMiddlewareRequestContext.ts +1 -1
  6. package/apps/agents-server/src/tools/$provideServer.ts +27 -0
  7. package/apps/agents-server/src/utils/serverRegistry.ts +117 -3
  8. package/esm/apps/agents-server/src/utils/serverRegistry.d.ts +6 -0
  9. package/esm/index.es.js +348 -17
  10. package/esm/index.es.js.map +1 -1
  11. package/esm/src/cli/cli-commands/agents-server/buildAgentsServer.d.ts +31 -0
  12. package/esm/src/cli/cli-commands/agents-server/startAgentsServer.d.ts +6 -0
  13. package/esm/src/version.d.ts +1 -1
  14. package/package.json +5 -3
  15. package/src/cli/cli-commands/agents-server/buildAgentsServer.ts +330 -8
  16. package/src/cli/cli-commands/agents-server/run.ts +2 -1
  17. package/src/cli/cli-commands/agents-server/startAgentsServer.ts +40 -16
  18. package/src/other/templates/getTemplatesPipelineCollection.ts +691 -794
  19. package/src/version.ts +2 -2
  20. package/src/versions.txt +2 -0
  21. package/umd/apps/agents-server/src/utils/serverRegistry.d.ts +6 -0
  22. package/umd/index.umd.js +346 -15
  23. package/umd/index.umd.js.map +1 -1
  24. package/umd/src/cli/cli-commands/agents-server/buildAgentsServer.d.ts +31 -0
  25. package/umd/src/cli/cli-commands/agents-server/startAgentsServer.d.ts +6 -0
  26. package/umd/src/version.d.ts +1 -1
  27. package/src/wizard/test/sub/subsub/subsubsub/.promptbook/executions-cache/8/c/report-whoami-6e340b9cffb37a98.json +0 -104
@@ -1,4 +1,5 @@
1
1
  import type { NextConfig } from 'next';
2
+ import { readdirSync } from 'fs';
2
3
  import path from 'path';
3
4
 
4
5
  /**
@@ -9,6 +10,21 @@ import path from 'path';
9
10
  */
10
11
  const nextDistDir = process.env.NEXT_DIST_DIR || '.next';
11
12
 
13
+ /**
14
+ * Dependency root passed by `ptbk agents-server` when the bundled app is copied into a project cache.
15
+ */
16
+ const agentsServerNodeModulesPath = process.env.PTBK_AGENTS_SERVER_NODE_MODULES_PATH;
17
+
18
+ /**
19
+ * Whether the CLI-owned build should skip duplicate validation already covered by repository tests.
20
+ */
21
+ const isNextValidationIgnored = process.env.PTBK_AGENTS_SERVER_IGNORE_NEXT_VALIDATION === 'true';
22
+
23
+ /**
24
+ * Exact aliases for local generated Promptbook package entrypoints.
25
+ */
26
+ const promptbookLocalPackageAliases = createPromptbookLocalPackageAliases();
27
+
12
28
  /**
13
29
  * Map of next config.
14
30
  */
@@ -17,7 +33,13 @@ const nextConfig: NextConfig = {
17
33
  // <- TODO: [🐱‍🚀][🧠] How to properly build Next.js app, for both Vercel and Doceker?
18
34
 
19
35
  distDir: nextDistDir,
20
- serverExternalPackages: ['pg', '@napi-rs/canvas'],
36
+ eslint: {
37
+ ignoreDuringBuilds: isNextValidationIgnored,
38
+ },
39
+ serverExternalPackages: ['pg', '@napi-rs/canvas', 'playwright', 'playwright-core'],
40
+ typescript: {
41
+ ignoreBuildErrors: isNextValidationIgnored,
42
+ },
21
43
 
22
44
  experimental: {
23
45
  externalDir: true,
@@ -29,12 +51,25 @@ const nextConfig: NextConfig = {
29
51
 
30
52
  resolveAlias: {
31
53
  '@': path.resolve(__dirname),
32
- '@common': path.resolve(__dirname, '../common'),
33
- '@promptbook-local': path.resolve(__dirname, '../../src/_packages'),
54
+ '@common': path.resolve(__dirname, '../_common'),
55
+ ...promptbookLocalPackageAliases,
34
56
  },
35
57
  },
36
58
 
37
59
  webpack(config, { isServer }) {
60
+ config.resolve.alias = {
61
+ ...config.resolve.alias,
62
+ '@': path.resolve(__dirname),
63
+ '@common': path.resolve(__dirname, '../_common'),
64
+ ...promptbookLocalPackageAliases,
65
+ };
66
+
67
+ if (agentsServerNodeModulesPath) {
68
+ config.resolve.modules = Array.from(
69
+ new Set([...(config.resolve.modules || ['node_modules']), agentsServerNodeModulesPath]),
70
+ );
71
+ }
72
+
38
73
  // Exclude Node.js-only modules from client bundle
39
74
  if (!isServer) {
40
75
  config.resolve.fallback = {
@@ -81,3 +116,23 @@ const nextConfig: NextConfig = {
81
116
  };
82
117
 
83
118
  export default nextConfig;
119
+
120
+ /**
121
+ * Creates webpack/Turbopack aliases from imports like `@promptbook-local/core` to generated local sources.
122
+ */
123
+ function createPromptbookLocalPackageAliases(): Record<string, string> {
124
+ const packagesIndexPath = path.resolve(__dirname, '../../src/_packages');
125
+
126
+ try {
127
+ return Object.fromEntries(
128
+ readdirSync(packagesIndexPath)
129
+ .filter((filename) => filename.endsWith('.index.ts'))
130
+ .map((filename) => [
131
+ `@promptbook-local/${filename.replace(/\.index\.ts$/u, '')}`,
132
+ path.resolve(packagesIndexPath, filename),
133
+ ]),
134
+ );
135
+ } catch {
136
+ return {};
137
+ }
138
+ }
@@ -3,6 +3,7 @@ import {
3
3
  resolveDatabaseMigrationConnectionStringFromEnvironment,
4
4
  runDatabaseMigrations,
5
5
  } from './runDatabaseMigrations';
6
+ import { listEnvironmentRegisteredServers } from '../utils/serverRegistry';
6
7
 
7
8
  /**
8
9
  * Opt-out environment flag for automatic runtime migrations.
@@ -24,7 +25,13 @@ const automaticDatabaseMigrationPromiseByPrefix = new Map<string, Promise<void>>
24
25
  * @private internal startup helper for Agents Server runtime
25
26
  */
26
27
  export async function ensureAutomaticDatabaseMigrations(): Promise<void> {
27
- return ensureAutomaticDatabaseMigrationsForPrefix(process.env.SUPABASE_TABLE_PREFIX || '');
28
+ const environmentServerPrefixes = listEnvironmentRegisteredServers().map((server) => server.tablePrefix);
29
+
30
+ if (environmentServerPrefixes.length === 0) {
31
+ return ensureAutomaticDatabaseMigrationsForPrefix(process.env.SUPABASE_TABLE_PREFIX || '');
32
+ }
33
+
34
+ await Promise.all(environmentServerPrefixes.map((prefix) => ensureAutomaticDatabaseMigrationsForPrefix(prefix)));
28
35
  }
29
36
 
30
37
  /**
@@ -16,6 +16,7 @@ import { listRegisteredServersFromDatabase } from './listRegisteredServersFromDa
16
16
  import { migratePrefix } from './migratePrefix';
17
17
  import { readMigrationFiles, resolveMigrationsDirectory } from './resolveMigrationsDirectory';
18
18
  import { selectPrefixesForMigration } from './selectPrefixesForMigration';
19
+ import { listEnvironmentRegisteredServers } from '../utils/serverRegistry';
19
20
 
20
21
  /**
21
22
  * Allowed values describing who applied a migration record.
@@ -175,7 +176,10 @@ export async function resolveDatabaseMigrationRuntimeConfiguration(
175
176
  try {
176
177
  await client.connect();
177
178
 
178
- const registeredServers = await listRegisteredServersFromDatabase(client);
179
+ const registeredServers = mergeRegisteredServers(
180
+ await listRegisteredServersFromDatabase(client),
181
+ listEnvironmentRegisteredServers(),
182
+ );
179
183
  const hasExplicitDefaultPrefix = process.env[SUPABASE_TABLE_PREFIX_ENV_NAME] !== undefined;
180
184
  const configuredPrefixes = uniquePrefixes([
181
185
  ...(hasExplicitDefaultPrefix
@@ -355,6 +359,32 @@ function uniquePrefixes(prefixes: ReadonlyArray<string>): Array<string> {
355
359
  return result;
356
360
  }
357
361
 
362
+ /**
363
+ * Combines database and environment registry rows, keeping database rows authoritative.
364
+ *
365
+ * @param databaseServers - Persistent `_Server` rows.
366
+ * @param environmentServers - Virtual rows derived from `SERVERS`.
367
+ * @returns Merged rows without duplicate domains.
368
+ */
369
+ function mergeRegisteredServers(
370
+ databaseServers: ReadonlyArray<ServerRecord>,
371
+ environmentServers: ReadonlyArray<ServerRecord>,
372
+ ): Array<ServerRecord> {
373
+ const seenDomains = new Set(databaseServers.map((server) => server.domain));
374
+ const mergedServers = [...databaseServers];
375
+
376
+ for (const environmentServer of environmentServers) {
377
+ if (seenDomains.has(environmentServer.domain)) {
378
+ continue;
379
+ }
380
+
381
+ mergedServers.push(environmentServer);
382
+ seenDomains.add(environmentServer.domain);
383
+ }
384
+
385
+ return mergedServers;
386
+ }
387
+
358
388
  /**
359
389
  * Creates one PostgreSQL client using the lazily loaded `pg` package.
360
390
  *
@@ -750,11 +750,11 @@ class LocalSqliteQueryBuilder implements PromiseLike<LocalSqliteQueryResult> {
750
750
  */
751
751
  private selectMatchingRowids(): Array<number | bigint> {
752
752
  const where = this.createWhereClause();
753
- const sql = `SELECT rowid FROM ${quoteIdentifier(this.tableName)} ${where.sql}`;
753
+ const sql = `SELECT rowid AS "__rowid" FROM ${quoteIdentifier(this.tableName)} ${where.sql}`;
754
754
  return this.database
755
755
  .prepare(sql)
756
756
  .all(...where.values)
757
- .map((row) => row.rowid as number | bigint);
757
+ .map((row) => row.__rowid as number | bigint);
758
758
  }
759
759
 
760
760
  /**
@@ -37,7 +37,7 @@ export async function createMiddlewareRequestContext(request: NextRequest): Prom
37
37
  const requestIp = getRequestIp(request);
38
38
  const host = request.headers.get('host');
39
39
  const supabase = getMiddlewareSupabase();
40
- const registeredServers = supabase ? await loadRegisteredServers() : [];
40
+ const registeredServers = supabase || process.env.SERVERS ? await loadRegisteredServers() : [];
41
41
  const { canQueryServerTables, customDomainResolution, tablePrefixForRequest } = await resolveMiddlewareServerRouting(
42
42
  {
43
43
  host,
@@ -38,6 +38,33 @@ const getCachedProvidedServer = cache(async (): Promise<ProvidedServer> => {
38
38
  const xPromptbookServer = headersList.get('x-promptbook-server');
39
39
 
40
40
  if (isAgentsServerSqliteMode()) {
41
+ if (isLocalDevelopmentHost(requestHost)) {
42
+ return {
43
+ id: null,
44
+ publicUrl: resolveFallbackPublicUrl(requestHost),
45
+ tablePrefix: SUPABASE_TABLE_PREFIX,
46
+ };
47
+ }
48
+
49
+ const registeredServers = await listRegisteredServersUsingServiceRole();
50
+ if (registeredServers.length > 0) {
51
+ const { currentServer: resolvedSqliteServer } = resolveServerSelection({
52
+ host: requestHost,
53
+ forwardedServerHost: xPromptbookServer,
54
+ registeredServers,
55
+ });
56
+
57
+ if (!resolvedSqliteServer) {
58
+ throw new Error(`Server with host "${requestHost}" is not registered in SERVERS`);
59
+ }
60
+
61
+ return {
62
+ id: resolvedSqliteServer.id,
63
+ publicUrl: createServerPublicUrl(resolvedSqliteServer.domain),
64
+ tablePrefix: resolvedSqliteServer.tablePrefix,
65
+ };
66
+ }
67
+
41
68
  return {
42
69
  id: null,
43
70
  publicUrl: resolveFallbackPublicUrl(requestHost),
@@ -62,6 +62,16 @@ const SERVER_REGISTRY_SELECT = 'id,name,environment,domain,tablePrefix,createdAt
62
62
  */
63
63
  const SERVER_REGISTRY_TABLE_NAME = '_Server';
64
64
 
65
+ /**
66
+ * Environment variable with comma-separated standalone server domains.
67
+ */
68
+ const SERVERS_ENV_NAME = 'SERVERS';
69
+
70
+ /**
71
+ * Stable timestamp used by virtual server records derived from environment variables.
72
+ */
73
+ const ENVIRONMENT_SERVER_TIMESTAMP = '1970-01-01T00:00:00.000Z';
74
+
65
75
  /**
66
76
  * In-memory cache TTL for repeated registry lookups inside one runtime process.
67
77
  */
@@ -127,8 +137,10 @@ export async function listRegisteredServers(supabase: Pick<SupabaseClient, 'from
127
137
  export async function listRegisteredServersUsingServiceRole(options?: {
128
138
  readonly forceRefresh?: boolean;
129
139
  }): Promise<Array<ServerRecord>> {
140
+ const environmentServers = listEnvironmentRegisteredServers();
141
+
130
142
  if (isAgentsServerSqliteMode()) {
131
- return [];
143
+ return environmentServers;
132
144
  }
133
145
 
134
146
  const shouldReuseCache =
@@ -137,7 +149,7 @@ export async function listRegisteredServersUsingServiceRole(options?: {
137
149
  Date.now() - cachedServerRegistry.loadedAt < SERVER_REGISTRY_CACHE_TTL_MS;
138
150
 
139
151
  if (shouldReuseCache) {
140
- return cachedServerRegistry!.serversPromise;
152
+ return mergeRegisteredServers(await cachedServerRegistry!.serversPromise, environmentServers);
141
153
  }
142
154
 
143
155
  const serversPromise = listRegisteredServers(getServerRegistryClient());
@@ -147,7 +159,7 @@ export async function listRegisteredServersUsingServiceRole(options?: {
147
159
  };
148
160
 
149
161
  try {
150
- return await serversPromise;
162
+ return mergeRegisteredServers(await serversPromise, environmentServers);
151
163
  } catch (error) {
152
164
  if (cachedServerRegistry?.serversPromise === serversPromise) {
153
165
  cachedServerRegistry = null;
@@ -156,6 +168,35 @@ export async function listRegisteredServersUsingServiceRole(options?: {
156
168
  }
157
169
  }
158
170
 
171
+ /**
172
+ * Loads virtual server records from the comma-separated `SERVERS` environment variable.
173
+ *
174
+ * @returns Server records with deterministic table prefixes derived from normalized domains.
175
+ */
176
+ export function listEnvironmentRegisteredServers(): Array<ServerRecord> {
177
+ const rawServers = process.env[SERVERS_ENV_NAME];
178
+ if (!rawServers) {
179
+ return [];
180
+ }
181
+
182
+ const normalizedDomains = uniqueStrings(
183
+ rawServers
184
+ .split(',')
185
+ .map((server) => normalizeServerDomain(server))
186
+ .filter((server): server is string => Boolean(server)),
187
+ );
188
+
189
+ return normalizedDomains.map((domain, index) => ({
190
+ id: -(index + 1),
191
+ name: domain,
192
+ environment: SERVER_ENVIRONMENT.PRODUCTION,
193
+ domain,
194
+ tablePrefix: buildEnvironmentServerTablePrefix(domain),
195
+ createdAt: ENVIRONMENT_SERVER_TIMESTAMP,
196
+ updatedAt: ENVIRONMENT_SERVER_TIMESTAMP,
197
+ }));
198
+ }
199
+
159
200
  /**
160
201
  * Finds one registered server by incoming host header.
161
202
  *
@@ -404,3 +445,76 @@ function hasHttpProtocol(value: string): boolean {
404
445
  function isDefaultPortForProtocol(protocol: string, port: string): boolean {
405
446
  return (protocol === 'http:' && port === '80') || (protocol === 'https:' && port === '443');
406
447
  }
448
+
449
+ /**
450
+ * Combines database and environment registry rows, keeping database rows authoritative.
451
+ *
452
+ * @param databaseServers - Persistent `_Server` rows.
453
+ * @param environmentServers - Virtual rows derived from `SERVERS`.
454
+ * @returns Merged rows without duplicate domains.
455
+ */
456
+ function mergeRegisteredServers(
457
+ databaseServers: ReadonlyArray<ServerRecord>,
458
+ environmentServers: ReadonlyArray<ServerRecord>,
459
+ ): Array<ServerRecord> {
460
+ const seenDomains = new Set<string>();
461
+ const mergedServers = [...databaseServers];
462
+
463
+ for (const databaseServer of databaseServers) {
464
+ const normalizedDomain = normalizeServerDomain(databaseServer.domain);
465
+ if (normalizedDomain) {
466
+ seenDomains.add(normalizedDomain);
467
+ }
468
+ }
469
+
470
+ for (const environmentServer of environmentServers) {
471
+ const normalizedDomain = normalizeServerDomain(environmentServer.domain);
472
+ if (!normalizedDomain || seenDomains.has(normalizedDomain)) {
473
+ continue;
474
+ }
475
+
476
+ mergedServers.push(environmentServer);
477
+ seenDomains.add(normalizedDomain);
478
+ }
479
+
480
+ return mergedServers;
481
+ }
482
+
483
+ /**
484
+ * Builds a deterministic table prefix from a normalized domain.
485
+ *
486
+ * @param domain - Normalized server domain.
487
+ * @returns Prefix such as `server_www_example_com_`.
488
+ */
489
+ function buildEnvironmentServerTablePrefix(domain: string): string {
490
+ const prefixSuffix = domain
491
+ .toLowerCase()
492
+ .replace(/-/gu, '_dash_')
493
+ .replace(/\./gu, '_')
494
+ .replace(/:/gu, '_port_')
495
+ .replace(/[^a-z0-9_]/gu, '_')
496
+ .replace(/_+/gu, '_')
497
+ .replace(/^_+|_+$/gu, '');
498
+
499
+ return `server_${prefixSuffix}_`;
500
+ }
501
+
502
+ /**
503
+ * Deduplicates non-empty strings while preserving input order.
504
+ *
505
+ * @param values - Raw string values.
506
+ * @returns Unique non-empty strings.
507
+ */
508
+ function uniqueStrings(values: ReadonlyArray<string>): Array<string> {
509
+ const uniqueValues: Array<string> = [];
510
+
511
+ for (const value of values) {
512
+ if (!value || uniqueValues.includes(value)) {
513
+ continue;
514
+ }
515
+
516
+ uniqueValues.push(value);
517
+ }
518
+
519
+ return uniqueValues;
520
+ }
@@ -65,6 +65,12 @@ export declare function listRegisteredServers(supabase: Pick<SupabaseClient, 'fr
65
65
  export declare function listRegisteredServersUsingServiceRole(options?: {
66
66
  readonly forceRefresh?: boolean;
67
67
  }): Promise<Array<ServerRecord>>;
68
+ /**
69
+ * Loads virtual server records from the comma-separated `SERVERS` environment variable.
70
+ *
71
+ * @returns Server records with deterministic table prefixes derived from normalized domains.
72
+ */
73
+ export declare function listEnvironmentRegisteredServers(): Array<ServerRecord>;
68
74
  /**
69
75
  * Finds one registered server by incoming host header.
70
76
  *