@pikku/cli 0.12.57 → 0.12.59

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 (87) hide show
  1. package/cli.schema.json +1 -1
  2. package/console-app/assets/{index-xN8LW0II.js → index-CcpTZVoP.js} +1 -1
  3. package/console-app/index.html +1 -1
  4. package/dist/.pikku/agent/pikku-agent-types.gen.d.ts +1 -1
  5. package/dist/.pikku/channel/pikku-channel-types.gen.d.ts +1 -1
  6. package/dist/.pikku/channel/pikku-channel-types.gen.js +1 -1
  7. package/dist/.pikku/cli/pikku-cli-channel.js +6 -1
  8. package/dist/.pikku/cli/pikku-cli-client.gen.d.ts +1 -1
  9. package/dist/.pikku/cli/pikku-cli-client.gen.js +1 -1
  10. package/dist/.pikku/cli/pikku-cli-contracts-meta.gen.d.ts +1 -1
  11. package/dist/.pikku/cli/pikku-cli-contracts-meta.gen.js +1 -1
  12. package/dist/.pikku/cli/pikku-cli-types.gen.d.ts +1 -1
  13. package/dist/.pikku/cli/pikku-cli-types.gen.js +1 -1
  14. package/dist/.pikku/cli/pikku-cli-wirings-meta.gen.js +1 -1
  15. package/dist/.pikku/cli/pikku-cli-wirings-meta.gen.json +19 -1
  16. package/dist/.pikku/cli/pikku-cli-wirings.gen.d.ts +1 -1
  17. package/dist/.pikku/cli/pikku-cli-wirings.gen.js +1 -1
  18. package/dist/.pikku/cli/pikku-cli.gen.d.ts +1 -1
  19. package/dist/.pikku/cli/pikku-cli.gen.js +1 -1
  20. package/dist/.pikku/console/pikku-node-types.gen.d.ts +1 -1
  21. package/dist/.pikku/function/pikku-function-types.gen.d.ts +1 -1
  22. package/dist/.pikku/function/pikku-function-types.gen.js +1 -1
  23. package/dist/.pikku/function/pikku-functions-meta.gen.js +1 -1
  24. package/dist/.pikku/function/pikku-functions-meta.gen.json +18 -3
  25. package/dist/.pikku/function/pikku-functions.gen.js +3 -1
  26. package/dist/.pikku/http/pikku-http-types.gen.d.ts +1 -1
  27. package/dist/.pikku/http/pikku-http-types.gen.js +1 -1
  28. package/dist/.pikku/http/pikku-http-wirings-meta.gen.js +1 -1
  29. package/dist/.pikku/http/pikku-http-wirings.gen.d.ts +1 -1
  30. package/dist/.pikku/http/pikku-http-wirings.gen.js +1 -1
  31. package/dist/.pikku/mcp/pikku-mcp-types.gen.d.ts +1 -1
  32. package/dist/.pikku/mcp/pikku-mcp-types.gen.js +1 -1
  33. package/dist/.pikku/pikku-bootstrap.gen.d.ts +1 -1
  34. package/dist/.pikku/pikku-bootstrap.gen.js +1 -1
  35. package/dist/.pikku/pikku-meta-service.gen.d.ts +1 -1
  36. package/dist/.pikku/pikku-meta-service.gen.js +1 -1
  37. package/dist/.pikku/pikku-services.gen.d.ts +1 -1
  38. package/dist/.pikku/pikku-types.gen.d.ts +1 -1
  39. package/dist/.pikku/pikku-types.gen.js +1 -1
  40. package/dist/.pikku/queue/pikku-queue-types.gen.d.ts +1 -1
  41. package/dist/.pikku/queue/pikku-queue-types.gen.js +1 -1
  42. package/dist/.pikku/queue/pikku-queue-workers-wirings-meta.gen.js +1 -1
  43. package/dist/.pikku/queue/pikku-queue-workers-wirings.gen.d.ts +1 -1
  44. package/dist/.pikku/queue/pikku-queue-workers-wirings.gen.js +1 -1
  45. package/dist/.pikku/rpc/pikku-rpc-wirings-meta.internal.gen.js +1 -1
  46. package/dist/.pikku/rpc/pikku-rpc-wirings-meta.internal.gen.json +1 -0
  47. package/dist/.pikku/scheduler/pikku-scheduler-types.gen.d.ts +1 -1
  48. package/dist/.pikku/scheduler/pikku-scheduler-types.gen.js +1 -1
  49. package/dist/.pikku/schemas/register.gen.js +3 -1
  50. package/dist/.pikku/schemas/schemas/DevInput.schema.json +1 -1
  51. package/dist/.pikku/schemas/schemas/PikkuCLIConfig.schema.json +1 -1
  52. package/dist/.pikku/schemas/schemas/ServeInput.schema.json +1 -0
  53. package/dist/.pikku/secrets/pikku-secret-types.gen.d.ts +1 -1
  54. package/dist/.pikku/secrets/pikku-secret-types.gen.js +1 -1
  55. package/dist/.pikku/secrets/pikku-secrets.gen.d.ts +1 -1
  56. package/dist/.pikku/secrets/pikku-secrets.gen.js +1 -1
  57. package/dist/.pikku/trigger/pikku-trigger-types.gen.d.ts +1 -1
  58. package/dist/.pikku/trigger/pikku-trigger-types.gen.js +1 -1
  59. package/dist/.pikku/variables/pikku-variable-types.gen.d.ts +1 -1
  60. package/dist/.pikku/variables/pikku-variable-types.gen.js +1 -1
  61. package/dist/.pikku/variables/pikku-variables.gen.d.ts +1 -1
  62. package/dist/.pikku/variables/pikku-variables.gen.js +1 -1
  63. package/dist/.pikku/workflow/pikku-workflow-types.gen.d.ts +1 -1
  64. package/dist/.pikku/workflow/pikku-workflow-types.gen.js +1 -1
  65. package/dist/.pikku/workflow/pikku-workflow-wirings-meta.gen.js +1 -1
  66. package/dist/.pikku/workflow/pikku-workflow-wirings.gen.js +1 -1
  67. package/dist/bin/pikku-bin.mjs +2 -2
  68. package/dist/src/cli.wiring.js +19 -1
  69. package/dist/src/deploy/bundler/bundler.js +11 -1
  70. package/dist/src/fabric/functions/validate.function.js +23 -3
  71. package/dist/src/functions/commands/dev.d.ts +3 -0
  72. package/dist/src/functions/commands/dev.js +22 -8
  73. package/dist/src/functions/commands/serve-console.d.ts +4 -0
  74. package/dist/src/functions/commands/serve-console.js +62 -0
  75. package/dist/src/functions/commands/serve.d.ts +10 -0
  76. package/dist/src/functions/commands/serve.js +143 -0
  77. package/dist/src/functions/commands/tests-init.js +6 -3
  78. package/dist/src/functions/db/db-codegen.d.ts +1 -1
  79. package/dist/src/functions/db/db-codegen.js +6 -4
  80. package/dist/src/functions/db/local-db.js +1 -1
  81. package/dist/src/functions/db/zod-codegen.d.ts +1 -1
  82. package/dist/src/functions/validate/workspace-validate.js +1 -1
  83. package/dist/src/functions/wirings/console/pikku-command-nodes-meta.js +6 -0
  84. package/dist/src/utils/parse-cli-filters.d.ts +3 -0
  85. package/dist/src/utils/parse-cli-filters.js +8 -1
  86. package/dist/tsconfig.tsbuildinfo +1 -1
  87. package/package.json +3 -3
@@ -1,5 +1,5 @@
1
1
  /**
2
- * This file was generated by @pikku/cli@0.12.57
2
+ * This file was generated by @pikku/cli@0.12.59
3
3
  */
4
4
  import { WorkflowCancelledException } from '@pikku/core/workflow';
5
5
  import { template } from '@pikku/core/workflow';
@@ -1,5 +1,5 @@
1
1
  /**
2
- * This file was generated by @pikku/cli@0.12.57
2
+ * This file was generated by @pikku/cli@0.12.59
3
3
  */
4
4
  import { WorkflowCancelledException } from '@pikku/core/workflow';
5
5
  import { template } from '@pikku/core/workflow';
@@ -1,5 +1,5 @@
1
1
  /**
2
- * This file was generated by @pikku/cli@0.12.57
2
+ * This file was generated by @pikku/cli@0.12.59
3
3
  */
4
4
  import { pikkuState } from '@pikku/core/internal';
5
5
  import allWorkflowMeta from './meta/allWorkflow.gen.json' with { type: 'json' };
@@ -1,5 +1,5 @@
1
1
  /**
2
- * This file was generated by @pikku/cli@0.12.57
2
+ * This file was generated by @pikku/cli@0.12.59
3
3
  */
4
4
  import { addWorkflow } from '@pikku/core/workflow';
5
5
  import './pikku-workflow-wirings-meta.gen.js';
@@ -11,8 +11,8 @@ async function checkForUpdate() {
11
11
  })
12
12
  if (!res.ok) return
13
13
  const { version: latest } = await res.json()
14
- if (latest !== '0.12.57') {
15
- process.stderr.write(`\n Update available 0.12.57 → ${latest}\n brew upgrade pikku or npm install -g @pikku/cli\n\n`)
14
+ if (latest !== '0.12.59') {
15
+ process.stderr.write(`\n Update available 0.12.59 → ${latest}\n brew upgrade pikku or npm install -g @pikku/cli\n\n`)
16
16
  }
17
17
  } catch {}
18
18
  }
@@ -16,6 +16,7 @@ import { watch } from './functions/commands/watch.js';
16
16
  import { consoleCommand } from './functions/commands/console.js';
17
17
  import { login, logout, whoami } from './functions/commands/login.js';
18
18
  import { dev } from './functions/commands/dev.js';
19
+ import { serve } from './functions/commands/serve.js';
19
20
  import { dbMigrate } from './functions/commands/db-migrate.js';
20
21
  import { dbGenerate } from './functions/commands/db-generate.js';
21
22
  import { dbSeed } from './functions/commands/db-seed.js';
@@ -194,6 +195,23 @@ wireCLI({
194
195
  description: 'Enable hot module reload',
195
196
  default: true,
196
197
  },
198
+ console: {
199
+ description: 'Also serve the Pikku Console UI (optionally specify port, default 51442)',
200
+ },
201
+ },
202
+ }),
203
+ serve: pikkuCLICommand({
204
+ func: serve,
205
+ description: 'Start a server using the bundled bun or node runner (no watch, no codegen)',
206
+ options: {
207
+ port: {
208
+ description: 'Port for the server',
209
+ default: '3000',
210
+ short: 'p',
211
+ },
212
+ console: {
213
+ description: 'Also serve the Pikku Console UI (optionally specify port, default 51442)',
214
+ },
197
215
  },
198
216
  }),
199
217
  console: pikkuCLICommand({
@@ -287,7 +305,7 @@ wireCLI({
287
305
  subcommands: {
288
306
  migrate: pikkuCLICommand({
289
307
  func: dbMigrate,
290
- description: 'Apply pending SQL migrations and regenerate db/schema.d.ts',
308
+ description: 'Apply pending SQL migrations and regenerate db/schema.gen.ts',
291
309
  }),
292
310
  generate: pikkuCLICommand({
293
311
  func: dbGenerate,
@@ -101,7 +101,17 @@ export class BaseBundler {
101
101
  results.push(result);
102
102
  }
103
103
  catch (err) {
104
- const message = err instanceof Error ? err.message : String(err);
104
+ // AggregateError (thrown by Bun.build) carries per-file errors in `errors`.
105
+ const aggErrors = err != null &&
106
+ typeof err === 'object' &&
107
+ 'errors' in err &&
108
+ Array.isArray(err.errors)
109
+ ? err.errors
110
+ .map((e) => e?.message ?? String(e))
111
+ .join('\n ')
112
+ : '';
113
+ const message = (err instanceof Error ? err.message : String(err)) +
114
+ (aggErrors ? `\n ${aggErrors}` : '');
105
115
  errors.push({ unitName: unit.name, error: message });
106
116
  }
107
117
  }
@@ -372,6 +372,24 @@ export async function runValidate(startDir = process.cwd()) {
372
372
  if (!fnPkg.imports?.['#pikku']) {
373
373
  e('functions-pkg-missing-pikku-import', 'packages/functions/package.json is missing a bare "#pikku" entry in "imports" — the Fabric-injected telemetry middleware imports from "#pikku" and will fail to bundle without it', fnPkgPath, 'Add to "imports": { "#pikku": "./.pikku/pikku-types.gen.ts", "#pikku/*": "./.pikku/*" }');
374
374
  }
375
+ if (!fnPkg.imports?.['#pikku/*']) {
376
+ e('functions-pkg-missing-pikku-wildcard-import', 'packages/functions/package.json is missing "#pikku/*" in "imports" — wiring files that import e.g. "#pikku/pikku-types.gen.js" fail at runtime with "Cannot find module"', fnPkgPath, 'Add to "imports": { "#pikku": "./.pikku/pikku-types.gen.ts", "#pikku/*": "./.pikku/*" }');
377
+ }
378
+ // .pikku/pikku-types.gen.js wrapper — the sandbox runs `pikku dev` via
379
+ // bare Node.js (no tsx), which cannot load .ts files. Every .gen.ts in
380
+ // .pikku/ needs a matching .gen.js wrapper. The sandbox entrypoint creates
381
+ // these during provisioning but NOT on subsequent restarts after agent
382
+ // edits — so after any restart pikku dev crashes with
383
+ // "Cannot find module '#pikku/pikku-types.gen.js'". Run `pikku fabric smoke`
384
+ // to test locally; the smoke command recreates the wrappers automatically.
385
+ {
386
+ const pikkuGenDir = join(fnDir, '.pikku');
387
+ const typesGenTs = join(pikkuGenDir, 'pikku-types.gen.ts');
388
+ const typesGenJs = join(pikkuGenDir, 'pikku-types.gen.js');
389
+ if (existsSync(typesGenTs) && !existsSync(typesGenJs)) {
390
+ e('pikku-types-js-wrapper-missing', 'packages/functions/.pikku/pikku-types.gen.js is missing — the sandbox runs pikku dev via bare Node.js, which cannot load .ts files; without the .js wrapper it crashes with "Cannot find module \'#pikku/pikku-types.gen.js\'" on every restart', typesGenTs, lines('Create packages/functions/.pikku/pikku-types.gen.js:', " import './pikku-types.gen.ts';", " export * from './pikku-types.gen.ts';", 'Or run `pikku fabric smoke` to test the full boot locally (it creates the wrappers).', 'This file is gitignored — it must be recreated after every `pikku all` run.'));
391
+ }
392
+ }
375
393
  const fnAllDeps = {
376
394
  ...fnPkg.dependencies,
377
395
  ...fnPkg.devDependencies,
@@ -454,8 +472,10 @@ export async function runValidate(startDir = process.cwd()) {
454
472
  const baseURL = text.match(/createAuthClient\s*\([^)]*baseURL\s*:\s*([^,)\n]+)/)?.[1];
455
473
  // Heuristic: flag a bare /api baseURL with no /auth segment anywhere
456
474
  // near the client config.
457
- if (baseURL && /['"`]\/api['"`]/.test(baseURL) && !/auth/i.test(baseURL)) {
458
- w('better-auth-client-baseurl-missing-auth', `createAuthClient baseURL is ${baseURL.trim()} — it omits the /auth segment, so the client calls /api/sign-in/email instead of /api/auth/sign-in/email and auth 404s`, join(appsDir, rel), "Append the auth basePath: baseURL: `${apiUrl()}/auth` (resolving to /api/auth)");
475
+ if (baseURL &&
476
+ /['"`]\/api['"`]/.test(baseURL) &&
477
+ !/auth/i.test(baseURL)) {
478
+ w('better-auth-client-baseurl-missing-auth', `createAuthClient baseURL is ${baseURL.trim()} — it omits the /auth segment, so the client calls /api/sign-in/email instead of /api/auth/sign-in/email and auth 404s`, join(appsDir, rel), 'Append the auth basePath: baseURL: `${apiUrl()}/auth` (resolving to /api/auth)');
459
479
  }
460
480
  }
461
481
  }
@@ -575,7 +595,7 @@ export async function runValidate(startDir = process.cwd()) {
575
595
  dbTypesText.includes('.pikku\\db\\schema');
576
596
  const hasInlineTypes = /(?:^|\n)\s*(?:export\s+)?(?:interface\s+\w|type\s+\w+\s*=)/.test(dbTypesText);
577
597
  if (hasInlineTypes && !isReexport) {
578
- w('db-types-hand-edited', 'src/types/db.types.ts contains inline type definitions — it should only re-export from .pikku', dbTypesPath, "Replace the file with a single line: export type { DB } from '../../.pikku/db/schema.js' then run `yarn db:types` to regenerate");
598
+ w('db-types-hand-edited', 'src/types/db.types.ts contains inline type definitions — it should only re-export from .pikku', dbTypesPath, "Replace the file with a single line: export type { DB } from '../../.pikku/db/schema.gen.js' then run `yarn db:types` to regenerate");
579
599
  }
580
600
  }
581
601
  // Structure: src/functions/, src/wirings/, src/config.ts
@@ -2,12 +2,15 @@ export declare const dev: import("#pikku").PikkuFunctionConfig<{
2
2
  port?: string;
3
3
  watch?: boolean;
4
4
  hmr?: boolean;
5
+ console?: string | boolean;
5
6
  }, void, "rpc" | "session", import("#pikku").PikkuFunctionSessionless<{
6
7
  port?: string;
7
8
  watch?: boolean;
8
9
  hmr?: boolean;
10
+ console?: string | boolean;
9
11
  }, void, "rpc" | "session", import("#pikku").Services> | import("#pikku").PikkuFunction<{
10
12
  port?: string;
11
13
  watch?: boolean;
12
14
  hmr?: boolean;
15
+ console?: string | boolean;
13
16
  }, void, "rpc" | "session", import("#pikku").Services>, undefined, undefined>;
@@ -12,9 +12,10 @@ import { InMemorySchedulerService } from '@pikku/schedule';
12
12
  import { resolveDb, createKysely, parseDatabaseUrl, } from '../db/local-db.js';
13
13
  import { loadUserBootstrap, loadUserModule } from './load-user-project.js';
14
14
  import { createDevAIAgentRunner } from './dev-ai-runner.js';
15
+ import { startConsoleServer } from './serve-console.js';
15
16
  export const dev = pikkuSessionlessFunc({
16
17
  remote: true,
17
- func: async ({ logger, config, getInspectorState, variables, devServerRunner }, { port, watch, hmr }, { rpc }) => {
18
+ func: async ({ logger, config, getInspectorState, variables, devServerRunner }, { port, watch, hmr, console: consoleFlag }, { rpc }) => {
18
19
  const resolvedPort = parseInt(port || '3000', 10);
19
20
  const hostname = 'localhost';
20
21
  // Bind on IPv4 loopback explicitly. Under Bun, hostname 'localhost' resolves
@@ -105,7 +106,6 @@ export const dev = pikkuSessionlessFunc({
105
106
  return;
106
107
  }
107
108
  await loadUserBootstrap(pikkuDir);
108
- workflowService.rewireQueueWorkers();
109
109
  const configModule = await loadUserModule(pikkuConfigFactory.file);
110
110
  const servicesModule = await loadUserModule(singletonServicesFactory.file);
111
111
  const userCreateConfig = configModule[pikkuConfigFactory.variable];
@@ -195,28 +195,42 @@ export const dev = pikkuSessionlessFunc({
195
195
  ...inMemoryServices,
196
196
  getInspectorState,
197
197
  });
198
- pikkuState(null, 'package', 'singletonServices', {
199
- ...singletonServices,
200
- getInspectorState,
201
- });
198
+ const resolvedServices = { ...singletonServices, getInspectorState };
199
+ pikkuState(null, 'package', 'singletonServices', resolvedServices);
200
+ resolvedServices.workflowService?.wireQueueWorkers?.();
201
+ const { serverLifecycleFactory } = inspectorState.filesAndMethods;
202
+ const loadLifecycle = async () => {
203
+ if (!serverLifecycleFactory)
204
+ return undefined;
205
+ const m = await loadUserModule(serverLifecycleFactory.file);
206
+ return m[serverLifecycleFactory.variable];
207
+ };
202
208
  const pikkuServer = devServerRunner.createServer({
203
209
  ...userConfig,
204
210
  hostname: bindHostname,
205
211
  port: resolvedPort,
206
212
  content: localContentConfig,
207
213
  }, logger);
214
+ const lifecycle = await loadLifecycle();
208
215
  await pikkuServer.init();
209
- await schedulerService.start();
216
+ await lifecycle?.beforeStart?.(resolvedServices);
210
217
  await pikkuServer.start();
218
+ await lifecycle?.afterStart?.(resolvedServices);
219
+ const consoleServer = consoleFlag
220
+ ? await startConsoleServer(consoleFlag, hostname, logger)
221
+ : undefined;
211
222
  let configWatcher;
212
223
  let watcher;
213
224
  process.once('SIGINT', async () => {
214
225
  logger.info('Stopping dev server...');
215
226
  try {
227
+ await lifecycle?.beforeStop?.(resolvedServices);
216
228
  await stopSingletonServices();
217
229
  await configWatcher?.close();
218
230
  await watcher?.close();
219
231
  await pikkuServer.stop();
232
+ consoleServer?.close();
233
+ await lifecycle?.afterStop?.(resolvedServices);
220
234
  }
221
235
  finally {
222
236
  process.exit(0);
@@ -246,7 +260,7 @@ export const dev = pikkuSessionlessFunc({
246
260
  try {
247
261
  const start = Date.now();
248
262
  await runAllWithCommandState();
249
- workflowService.rewireQueueWorkers();
263
+ workflowService.wireQueueWorkers();
250
264
  logger.info({
251
265
  message: `✓ Generated in ${Date.now() - start}ms`,
252
266
  type: 'timing',
@@ -0,0 +1,4 @@
1
+ import { createServer } from 'http';
2
+ import type { Logger } from '@pikku/core/services';
3
+ export declare function resolveConsoleDir(): string;
4
+ export declare function startConsoleServer(consoleFlag: string | boolean, hostname: string, logger: Logger): Promise<ReturnType<typeof createServer> | undefined>;
@@ -0,0 +1,62 @@
1
+ import { createServer } from 'http';
2
+ import { readFile, stat } from 'fs/promises';
3
+ import { join, extname } from 'path';
4
+ import { fileURLToPath } from 'url';
5
+ const MIME_TYPES = {
6
+ '.html': 'text/html',
7
+ '.js': 'application/javascript',
8
+ '.css': 'text/css',
9
+ '.json': 'application/json',
10
+ '.png': 'image/png',
11
+ '.svg': 'image/svg+xml',
12
+ '.ico': 'image/x-icon',
13
+ '.webmanifest': 'application/manifest+json',
14
+ '.woff': 'font/woff',
15
+ '.woff2': 'font/woff2',
16
+ '.ttf': 'font/ttf',
17
+ '.map': 'application/json',
18
+ };
19
+ export function resolveConsoleDir() {
20
+ return join(fileURLToPath(import.meta.url), '..', '..', '..', '..', '..', 'console-app');
21
+ }
22
+ export async function startConsoleServer(consoleFlag, hostname, logger) {
23
+ const consoleDir = resolveConsoleDir();
24
+ const consolePort = typeof consoleFlag === 'string' ? parseInt(consoleFlag, 10) : 51442;
25
+ try {
26
+ await stat(consoleDir);
27
+ }
28
+ catch {
29
+ logger.error('Console app not found. Please rebuild @pikku/cli with the console app bundled.');
30
+ return undefined;
31
+ }
32
+ const server = createServer(async (req, res) => {
33
+ const url = new URL(req.url || '/', `http://localhost:${consolePort}`);
34
+ let filePath = join(consoleDir, url.pathname);
35
+ try {
36
+ const s = await stat(filePath);
37
+ if (s.isDirectory())
38
+ filePath = join(filePath, 'index.html');
39
+ }
40
+ catch {
41
+ filePath = join(consoleDir, 'index.html');
42
+ }
43
+ try {
44
+ const content = await readFile(filePath);
45
+ const contentType = MIME_TYPES[extname(filePath)] || 'application/octet-stream';
46
+ res.writeHead(200, { 'Content-Type': contentType });
47
+ res.end(content);
48
+ }
49
+ catch {
50
+ res.writeHead(404, { 'Content-Type': 'text/plain' });
51
+ res.end('Not Found');
52
+ }
53
+ });
54
+ await new Promise((resolve, reject) => {
55
+ server.listen(consolePort, () => {
56
+ logger.info(`Pikku Console running at http://${hostname}:${consolePort}`);
57
+ resolve();
58
+ });
59
+ server.on('error', reject);
60
+ });
61
+ return server;
62
+ }
@@ -0,0 +1,10 @@
1
+ export declare const serve: import("#pikku").PikkuFunctionConfig<{
2
+ port?: string;
3
+ console?: string | boolean;
4
+ }, void, "rpc" | "session", import("#pikku").PikkuFunctionSessionless<{
5
+ port?: string;
6
+ console?: string | boolean;
7
+ }, void, "rpc" | "session", import("#pikku").Services> | import("#pikku").PikkuFunction<{
8
+ port?: string;
9
+ console?: string | boolean;
10
+ }, void, "rpc" | "session", import("#pikku").Services>, undefined, undefined>;
@@ -0,0 +1,143 @@
1
+ import { join, resolve } from 'path';
2
+ import { pikkuSessionlessFunc } from '#pikku';
3
+ import { ConsoleLogger, LocalEmailService, InMemoryQueueService, InMemoryWorkflowService, InMemoryTriggerService, InMemoryAIRunStateService, } from '@pikku/core/services';
4
+ import { KyselyAIStorageService, KyselyAIRunStateService, KyselyAgentRunService, } from '@pikku/kysely';
5
+ import { stopSingletonServices } from '@pikku/core';
6
+ import { pikkuState } from '@pikku/core/internal';
7
+ import { LocalMetaService } from '@pikku/core/services/local-meta';
8
+ import { LocalContent, } from '@pikku/core/services/local-content';
9
+ import { InMemorySchedulerService } from '@pikku/schedule';
10
+ import { resolveDb, createKysely, parseDatabaseUrl, } from '../db/local-db.js';
11
+ import { loadUserBootstrap, loadUserModule } from './load-user-project.js';
12
+ import { createDevAIAgentRunner } from './dev-ai-runner.js';
13
+ import { startConsoleServer } from './serve-console.js';
14
+ export const serve = pikkuSessionlessFunc({
15
+ remote: true,
16
+ func: async ({ logger, config, getInspectorState, variables, devServerRunner }, { port, console: consoleFlag }) => {
17
+ const resolvedPort = parseInt(port || '3000', 10);
18
+ const hostname = 'localhost';
19
+ const bindHostname = '127.0.0.1';
20
+ const pikkuDir = resolve(config.rootDir, config.outDir);
21
+ const inspectorState = await getInspectorState(true);
22
+ const { pikkuConfigFactory, singletonServicesFactory } = inspectorState.filesAndMethods;
23
+ if (!pikkuConfigFactory || !singletonServicesFactory) {
24
+ logger.error('createConfig and createSingletonServices must be defined in your project');
25
+ return;
26
+ }
27
+ await loadUserBootstrap(pikkuDir);
28
+ const workflowService = new InMemoryWorkflowService();
29
+ const configModule = await loadUserModule(pikkuConfigFactory.file);
30
+ const servicesModule = await loadUserModule(singletonServicesFactory.file);
31
+ const userCreateConfig = configModule[pikkuConfigFactory.variable];
32
+ const userCreateSingletonServices = servicesModule[singletonServicesFactory.variable];
33
+ const userConfig = await userCreateConfig();
34
+ const envDatabaseUrl = await variables.get('DATABASE_URL');
35
+ const effectiveDbConfig = envDatabaseUrl
36
+ ? parseDatabaseUrl(envDatabaseUrl)
37
+ : userConfig;
38
+ const resolvedDb = resolveDb(effectiveDbConfig, config.rootDir, config.outDir, config.runtimeDir);
39
+ const resolvedLocalDb = resolvedDb ?? undefined;
40
+ const kysely = resolvedLocalDb
41
+ ? await createKysely(resolvedLocalDb)
42
+ : undefined;
43
+ const resolvedRuntimeDir = config.runtimeDir ?? join(config.rootDir, '.pikku-runtime');
44
+ const localContentConfig = userConfig.content
45
+ ? {
46
+ localFileUploadPath: userConfig.content.contentPath
47
+ ? resolve(config.rootDir, userConfig.content.contentPath)
48
+ : join(resolvedRuntimeDir, 'content'),
49
+ uploadUrlPrefix: userConfig.content.uploadUrlPrefix ?? '/upload',
50
+ assetUrlPrefix: userConfig.content.assetUrlPrefix ?? '/assets',
51
+ server: `http://${hostname}:${resolvedPort}`,
52
+ sizeLimit: userConfig.content.sizeLimit,
53
+ }
54
+ : undefined;
55
+ const localContent = localContentConfig
56
+ ? new LocalContent(localContentConfig, logger)
57
+ : undefined;
58
+ const schedulerService = new InMemorySchedulerService();
59
+ const aiStorage = kysely
60
+ ? new KyselyAIStorageService(kysely)
61
+ : undefined;
62
+ const aiRunState = kysely
63
+ ? new KyselyAIRunStateService(kysely)
64
+ : new InMemoryAIRunStateService();
65
+ const agentRunService = kysely
66
+ ? new KyselyAgentRunService(kysely)
67
+ : undefined;
68
+ if (aiStorage)
69
+ await aiStorage.init();
70
+ if ('init' in aiRunState && typeof aiRunState.init === 'function') {
71
+ await aiRunState.init();
72
+ }
73
+ const devLogger = new ConsoleLogger();
74
+ const hasAgents = Object.keys(inspectorState.agents.agentsMeta).length > 0;
75
+ const aiAgentRunner = hasAgents
76
+ ? await createDevAIAgentRunner({
77
+ logger,
78
+ projectRoot: config.rootDir,
79
+ variables,
80
+ })
81
+ : undefined;
82
+ const eventHub = await devServerRunner.createEventHub();
83
+ const inMemoryServices = {
84
+ logger: devLogger,
85
+ ...(aiAgentRunner ? { aiAgentRunner } : {}),
86
+ emailService: new LocalEmailService(),
87
+ metaService: new LocalMetaService(pikkuDir),
88
+ schedulerService,
89
+ queueService: new InMemoryQueueService(),
90
+ workflowService,
91
+ workflowRunService: workflowService,
92
+ triggerService: new InMemoryTriggerService(),
93
+ aiStorage,
94
+ aiRunState,
95
+ agentRunService,
96
+ eventHub,
97
+ ...(kysely ? { kysely } : {}),
98
+ ...(localContent ? { content: localContent } : {}),
99
+ };
100
+ const singletonServices = await userCreateSingletonServices(userConfig, {
101
+ ...inMemoryServices,
102
+ getInspectorState,
103
+ });
104
+ const resolvedServices = { ...singletonServices, getInspectorState };
105
+ pikkuState(null, 'package', 'singletonServices', resolvedServices);
106
+ resolvedServices.workflowService?.wireQueueWorkers?.();
107
+ const { serverLifecycleFactory } = inspectorState.filesAndMethods;
108
+ const loadLifecycle = async () => {
109
+ if (!serverLifecycleFactory)
110
+ return undefined;
111
+ const m = await loadUserModule(serverLifecycleFactory.file);
112
+ return m[serverLifecycleFactory.variable];
113
+ };
114
+ const pikkuServer = devServerRunner.createServer({
115
+ ...userConfig,
116
+ hostname: bindHostname,
117
+ port: resolvedPort,
118
+ content: localContentConfig,
119
+ }, logger);
120
+ const lifecycle = await loadLifecycle();
121
+ await pikkuServer.init();
122
+ await lifecycle?.beforeStart?.(resolvedServices);
123
+ await pikkuServer.start();
124
+ await lifecycle?.afterStart?.(resolvedServices);
125
+ const consoleServer = consoleFlag
126
+ ? await startConsoleServer(consoleFlag, hostname, logger)
127
+ : undefined;
128
+ process.once('SIGINT', async () => {
129
+ logger.info('Stopping server...');
130
+ try {
131
+ await lifecycle?.beforeStop?.(resolvedServices);
132
+ await stopSingletonServices();
133
+ await pikkuServer.stop();
134
+ consoleServer?.close();
135
+ await lifecycle?.afterStop?.(resolvedServices);
136
+ }
137
+ finally {
138
+ process.exit(0);
139
+ }
140
+ });
141
+ await new Promise(() => { });
142
+ },
143
+ });
@@ -212,8 +212,8 @@ export const pikkuTestsInit = pikkuSessionlessFunc({
212
212
  const configImport = toJs(rel(pikkuConfigFactory.file));
213
213
  const servicesImport = toJs(rel(singletonServicesFactory.file));
214
214
  // coercion/schema may live under outDir/db (new default) or rootDir/db (legacy)
215
- const schemaInOutDir = join(config.outDir, 'db', 'schema.d.ts');
216
- const schemaInRootDir = join(config.rootDir, 'db', 'schema.d.ts');
215
+ const schemaInOutDir = join(config.outDir, 'db', 'schema.gen.ts');
216
+ const schemaInRootDir = join(config.rootDir, 'db', 'schema.gen.ts');
217
217
  const schemaFile = existsSync(schemaInOutDir)
218
218
  ? schemaInOutDir
219
219
  : schemaInRootDir;
@@ -256,7 +256,10 @@ export const pikkuTestsInit = pikkuSessionlessFunc({
256
256
  join(supportDir, 'services.ts'),
257
257
  servicesTs(configImport, servicesImport, schemaImport, coercionImport, pikkuConfigFactory.variable, singletonServicesFactory.variable, repoRootRel, hasDb, migrationsRel, seedRel),
258
258
  ],
259
- [join(ftestDir, 'tests', 'features', 'example.feature'), starterFeature()],
259
+ [
260
+ join(ftestDir, 'tests', 'features', 'example.feature'),
261
+ starterFeature(),
262
+ ],
260
263
  ];
261
264
  for (const [filePath, content] of files) {
262
265
  await mkdir(dirname(filePath), { recursive: true });
@@ -36,7 +36,7 @@ export interface CodegenResult {
36
36
  /**
37
37
  * Per-interface, per-field zod `format` overrides for the zod codegen. Keyed
38
38
  * by interface name (PascalCase) and field name (camelCase), matching the
39
- * shapes the zod emitter parses out of `schema.d.ts`.
39
+ * shapes the zod emitter parses out of `schema.gen.ts`.
40
40
  */
41
41
  zodFormats: Record<string, Record<string, ZodFormat>>;
42
42
  }
@@ -183,7 +183,8 @@ function emitInterface(table, camelCase, explicitAnnotations, dialect, enumByNam
183
183
  // from a Postgres enum (`type` is 'USER-DEFINED', real values via `udtName`)
184
184
  // or, on SQLite, from a `CHECK (col IN (…))` constraint the introspector
185
185
  // parsed onto `col.enumValues`.
186
- const enumValues = col.enumValues ?? (col.udtName ? enumByName.get(col.udtName) : undefined);
186
+ const enumValues = col.enumValues ??
187
+ (col.udtName ? enumByName.get(col.udtName) : undefined);
187
188
  const enumUnion = enumValues && enumValues.length > 0
188
189
  ? enumValues.map((v) => `'${escapeTsString(v)}'`).join(' | ')
189
190
  : null;
@@ -334,7 +335,7 @@ function emitClassificationMap(tables) {
334
335
  }
335
336
  /**
336
337
  * Introspect `introspector` and emit:
337
- * - `schema.d.ts` Kysely DB type with classification brands
338
+ * - `schema.gen.ts` Kysely DB type with classification brands
338
339
  * - `coercion.gen.ts` Runtime CoercionMap for date/bool/json coercion
339
340
  * - `classification.gen.ts` Data-classification manifest (when manifestFile set)
340
341
  */
@@ -352,7 +353,8 @@ function emitEnumsModule(tables, enumByName) {
352
353
  for (const t of tables) {
353
354
  const tablePart = pascal(snakeToCamel(bareTableName(t.name)));
354
355
  for (const col of t.columns) {
355
- const values = col.enumValues ?? (col.udtName ? enumByName.get(col.udtName) : undefined);
356
+ const values = col.enumValues ??
357
+ (col.udtName ? enumByName.get(col.udtName) : undefined);
356
358
  if (!values || values.length === 0)
357
359
  continue;
358
360
  const name = `${tablePart}${pascal(snakeToCamel(col.name))}`;
@@ -393,7 +395,7 @@ export async function generateSchemaTypes(introspector, options) {
393
395
  enumByName.set(e.name, e.values);
394
396
  enumByName.set(`${e.schema}.${e.name}`, e.values);
395
397
  }
396
- // ── schema.d.ts ─────────────────────────────────────────────────────────────
398
+ // ── schema.gen.ts ────────────────────────────────────────────────────────────
397
399
  const warnings = [];
398
400
  const zodFormats = {};
399
401
  const interfaces = tables
@@ -42,7 +42,7 @@ export function resolveDb(userConfig, rootDir, outDir, runtimeDir) {
42
42
  const base = (sub) => ({
43
43
  rootDir,
44
44
  migrationsDir: resolveAgainst(rootDir, sub),
45
- schemaFile: join(outDir, 'db', 'schema.d.ts'),
45
+ schemaFile: join(outDir, 'db', 'schema.gen.ts'),
46
46
  coercionFile: join(outDir, 'db', 'coercion.gen.ts'),
47
47
  manifestFile: join(outDir, 'db', 'classification.gen.ts'),
48
48
  classificationMapFile: join(outDir, 'db', 'classification-map.gen.d.ts'),
@@ -33,7 +33,7 @@ export interface ZodCodegenOptions {
33
33
  outFile: string;
34
34
  /**
35
35
  * Per-interface, per-field `format` overrides. Keyed by the *interface* name
36
- * (PascalCase, as it appears in `schema.d.ts`) and the *field* name (camelCase),
36
+ * (PascalCase, as it appears in `schema.gen.ts`) and the *field* name (camelCase),
37
37
  * matching how `parseTables` reads the schema. Computed by `db-codegen` (which
38
38
  * owns the snake→Pascal/camel name mapping) so this emitter stays a dumb
39
39
  * string→zod translator.
@@ -201,7 +201,7 @@ export async function runWorkspaceValidate(startDir = process.cwd()) {
201
201
  dbTypesText.includes('.pikku\\db\\schema');
202
202
  const hasInlineTypes = /(?:^|\n)\s*(?:export\s+)?(?:interface\s+\w|type\s+\w+\s*=)/.test(dbTypesText);
203
203
  if (hasInlineTypes && !isReexport) {
204
- w('db-types-hand-edited', 'src/types/db.types.ts contains inline type definitions — it should only re-export from .pikku', dbTypesPath, "Replace the file with a single line: export type { DB } from '../../.pikku/db/schema.js' then regenerate");
204
+ w('db-types-hand-edited', 'src/types/db.types.ts contains inline type definitions — it should only re-export from .pikku', dbTypesPath, "Replace the file with a single line: export type { DB } from '../../.pikku/db/schema.gen.js' then regenerate");
205
205
  }
206
206
  }
207
207
  if (!existsSync(join(fnDir, 'src', 'functions'))) {
@@ -52,6 +52,9 @@ export const pikkuNodesMeta = pikkuSessionlessFunc({
52
52
  outputMeta[name] = nodeMetaWithoutIcon;
53
53
  }
54
54
  const packageIcon = await loadIcon(addonMeta?.icon, rootDir, logger);
55
+ const serverlessIncompatible = addonMeta && 'serverlessIncompatible' in addonMeta
56
+ ? addonMeta.serverlessIncompatible
57
+ : undefined;
55
58
  const metaData = {
56
59
  nodes: outputMeta,
57
60
  secrets: secretsMeta,
@@ -62,6 +65,9 @@ export const pikkuNodesMeta = pikkuSessionlessFunc({
62
65
  categories: addonMeta?.categories,
63
66
  },
64
67
  };
68
+ if (serverlessIncompatible && serverlessIncompatible.length > 0) {
69
+ metaData.serverlessIncompatible = serverlessIncompatible;
70
+ }
65
71
  if (addonMetaJsonFile && (config.scaffold?.console || config.addon)) {
66
72
  const minimalMeta = stripVerboseFields(metaData);
67
73
  await writeFileInDir(logger, addonMetaJsonFile, JSON.stringify(minimalMeta, null, 2), { ignoreModifyComment: true });
@@ -15,5 +15,8 @@ export declare function parseCLIFilters(data: any, cliConfig?: {
15
15
  serverlessIncompatible?: string[];
16
16
  defaultTarget?: 'serverless' | 'server';
17
17
  };
18
+ addon?: boolean | {
19
+ serverlessIncompatible?: string[];
20
+ };
18
21
  namedFilters?: Record<string, InspectorFilters>;
19
22
  }): CLIFilters;
@@ -93,7 +93,14 @@ export function parseCLIFilters(data, cliConfig) {
93
93
  filters.target = validateTargetList(filters.target, '--target');
94
94
  filters.excludeTarget = validateTargetList(filters.excludeTarget, '--exclude-target');
95
95
  if (filters.target || filters.excludeTarget) {
96
- filters.serverlessIncompatible = cliConfig?.deploy?.serverlessIncompatible;
96
+ const addonIncompatible = cliConfig?.addon && typeof cliConfig.addon === 'object'
97
+ ? (cliConfig.addon.serverlessIncompatible ?? [])
98
+ : [];
99
+ const merged = [
100
+ ...(cliConfig?.deploy?.serverlessIncompatible ?? []),
101
+ ...addonIncompatible,
102
+ ];
103
+ filters.serverlessIncompatible = merged.length > 0 ? merged : undefined;
97
104
  filters.defaultTarget = cliConfig?.deploy?.defaultTarget;
98
105
  }
99
106
  return filters;