@pikku/cli 0.12.55 → 0.12.56

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 (125) hide show
  1. package/cli.schema.json +1 -1
  2. package/dist/.pikku/agent/pikku-agent-types.gen.d.ts +1 -1
  3. package/dist/.pikku/channel/pikku-channel-types.gen.d.ts +1 -1
  4. package/dist/.pikku/channel/pikku-channel-types.gen.js +1 -1
  5. package/dist/.pikku/cli/pikku-cli-channel.js +6 -1
  6. package/dist/.pikku/cli/pikku-cli-client.gen.d.ts +1 -1
  7. package/dist/.pikku/cli/pikku-cli-client.gen.js +1 -1
  8. package/dist/.pikku/cli/pikku-cli-contracts-meta.gen.d.ts +1 -1
  9. package/dist/.pikku/cli/pikku-cli-contracts-meta.gen.js +1 -1
  10. package/dist/.pikku/cli/pikku-cli-contracts-meta.gen.json +14 -0
  11. package/dist/.pikku/cli/pikku-cli-types.gen.d.ts +1 -1
  12. package/dist/.pikku/cli/pikku-cli-types.gen.js +1 -1
  13. package/dist/.pikku/cli/pikku-cli-wirings-meta.gen.js +1 -1
  14. package/dist/.pikku/cli/pikku-cli-wirings-meta.gen.json +14 -0
  15. package/dist/.pikku/cli/pikku-cli-wirings.gen.d.ts +1 -1
  16. package/dist/.pikku/cli/pikku-cli-wirings.gen.js +1 -1
  17. package/dist/.pikku/cli/pikku-cli.gen.d.ts +1 -1
  18. package/dist/.pikku/cli/pikku-cli.gen.js +1 -1
  19. package/dist/.pikku/console/pikku-node-types.gen.d.ts +1 -1
  20. package/dist/.pikku/function/pikku-function-types.gen.d.ts +7 -30
  21. package/dist/.pikku/function/pikku-function-types.gen.js +1 -1
  22. package/dist/.pikku/function/pikku-functions-meta.gen.js +1 -1
  23. package/dist/.pikku/function/pikku-functions-meta.gen.json +24 -5
  24. package/dist/.pikku/function/pikku-functions.gen.js +3 -1
  25. package/dist/.pikku/http/pikku-http-types.gen.d.ts +1 -1
  26. package/dist/.pikku/http/pikku-http-types.gen.js +1 -1
  27. package/dist/.pikku/http/pikku-http-wirings-meta.gen.js +1 -1
  28. package/dist/.pikku/http/pikku-http-wirings.gen.d.ts +1 -1
  29. package/dist/.pikku/http/pikku-http-wirings.gen.js +1 -1
  30. package/dist/.pikku/mcp/pikku-mcp-types.gen.d.ts +1 -1
  31. package/dist/.pikku/mcp/pikku-mcp-types.gen.js +1 -1
  32. package/dist/.pikku/pikku-bootstrap.gen.d.ts +1 -1
  33. package/dist/.pikku/pikku-bootstrap.gen.js +1 -1
  34. package/dist/.pikku/pikku-meta-service.gen.d.ts +1 -1
  35. package/dist/.pikku/pikku-meta-service.gen.js +1 -1
  36. package/dist/.pikku/pikku-services.gen.d.ts +4 -2
  37. package/dist/.pikku/pikku-services.gen.js +2 -0
  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-meta.gen.json +0 -248
  44. package/dist/.pikku/queue/pikku-queue-workers-wirings.gen.d.ts +1 -1
  45. package/dist/.pikku/queue/pikku-queue-workers-wirings.gen.js +1 -1
  46. package/dist/.pikku/rpc/pikku-rpc-wirings-meta.internal.gen.js +1 -1
  47. package/dist/.pikku/rpc/pikku-rpc-wirings-meta.internal.gen.json +1 -0
  48. package/dist/.pikku/scheduler/pikku-scheduler-types.gen.d.ts +1 -1
  49. package/dist/.pikku/scheduler/pikku-scheduler-types.gen.js +1 -1
  50. package/dist/.pikku/schemas/register.gen.js +5 -1
  51. package/dist/.pikku/schemas/schemas/FabricAddonVerifyInput.schema.json +1 -0
  52. package/dist/.pikku/schemas/schemas/FabricAddonVerifyOutput.schema.json +1 -0
  53. package/dist/.pikku/schemas/schemas/PikkuCLIConfig.schema.json +1 -1
  54. package/dist/.pikku/secrets/pikku-secret-types.gen.d.ts +1 -1
  55. package/dist/.pikku/secrets/pikku-secret-types.gen.js +1 -1
  56. package/dist/.pikku/secrets/pikku-secrets.gen.d.ts +1 -1
  57. package/dist/.pikku/secrets/pikku-secrets.gen.js +1 -1
  58. package/dist/.pikku/trigger/pikku-trigger-types.gen.d.ts +1 -1
  59. package/dist/.pikku/trigger/pikku-trigger-types.gen.js +1 -1
  60. package/dist/.pikku/variables/pikku-variable-types.gen.d.ts +1 -1
  61. package/dist/.pikku/variables/pikku-variable-types.gen.js +1 -1
  62. package/dist/.pikku/variables/pikku-variables.gen.d.ts +1 -1
  63. package/dist/.pikku/variables/pikku-variables.gen.js +1 -1
  64. package/dist/.pikku/workflow/pikku-workflow-types.gen.d.ts +1 -1
  65. package/dist/.pikku/workflow/pikku-workflow-types.gen.js +1 -1
  66. package/dist/.pikku/workflow/pikku-workflow-wirings-meta.gen.js +1 -1
  67. package/dist/.pikku/workflow/pikku-workflow-wirings.gen.js +1 -1
  68. package/dist/bin/pikku-bin.mjs +2 -2
  69. package/dist/src/deploy/analyzer/analyzer.d.ts +6 -0
  70. package/dist/src/deploy/analyzer/analyzer.js +5 -4
  71. package/dist/src/deploy/build-pipeline.d.ts +5 -1
  72. package/dist/src/deploy/build-pipeline.js +5 -5
  73. package/dist/src/deploy/bundler/bun-bundler.d.ts +14 -0
  74. package/dist/src/deploy/bundler/bun-bundler.js +121 -0
  75. package/dist/src/deploy/bundler/bundler.d.ts +25 -30
  76. package/dist/src/deploy/bundler/bundler.interface.d.ts +54 -0
  77. package/dist/src/deploy/bundler/bundler.interface.js +11 -0
  78. package/dist/src/deploy/bundler/bundler.js +120 -190
  79. package/dist/src/deploy/bundler/dep-extractor.d.ts +11 -3
  80. package/dist/src/deploy/bundler/dep-extractor.js +12 -6
  81. package/dist/src/deploy/bundler/index.d.ts +5 -2
  82. package/dist/src/deploy/bundler/index.js +4 -2
  83. package/dist/src/deploy/bundler/node-bundler.d.ts +13 -0
  84. package/dist/src/deploy/bundler/node-bundler.js +80 -0
  85. package/dist/src/fabric/fabric-commands.d.ts +37 -0
  86. package/dist/src/fabric/fabric-commands.js +8 -0
  87. package/dist/src/fabric/functions/addon-verify.function.d.ts +54 -0
  88. package/dist/src/fabric/functions/addon-verify.function.js +153 -0
  89. package/dist/src/fabric/functions/llm-key.function.js +1 -1
  90. package/dist/src/fabric/functions/publish.function.js +8 -3
  91. package/dist/src/functions/commands/deploy-apply.js +3 -1
  92. package/dist/src/functions/commands/deploy-plan.js +3 -1
  93. package/dist/src/functions/commands/dev.js +11 -45
  94. package/dist/src/functions/db/db-codegen.js +14 -0
  95. package/dist/src/functions/wirings/functions/serialize-function-types.js +6 -29
  96. package/dist/src/server/bun-server-runner.d.ts +17 -0
  97. package/dist/src/server/bun-server-runner.js +25 -0
  98. package/dist/src/server/dev-server-runner.interface.d.ts +31 -0
  99. package/dist/src/server/dev-server-runner.interface.js +11 -0
  100. package/dist/src/server/node-server-runner.d.ts +12 -0
  101. package/dist/src/server/node-server-runner.js +30 -0
  102. package/dist/src/services.js +10 -0
  103. package/dist/src/utils/parse-cli-filters.d.ts +1 -0
  104. package/dist/src/utils/parse-cli-filters.js +1 -0
  105. package/dist/tsconfig.tsbuildinfo +1 -1
  106. package/package.json +3 -3
  107. package/skills/pikku-addon/SKILL.md +25 -117
  108. package/skills/pikku-addon/references/addon-package-manifest.md +63 -0
  109. package/skills/pikku-cli/SKILL.md +7 -93
  110. package/skills/pikku-cli/references/complete-example.md +82 -0
  111. package/skills/pikku-concepts/SKILL.md +17 -69
  112. package/skills/pikku-concepts/references/concept-mapping.md +37 -13
  113. package/skills/pikku-concepts/references/packages.md +29 -0
  114. package/skills/pikku-http/SKILL.md +14 -105
  115. package/skills/pikku-http/references/http-options.md +57 -0
  116. package/skills/pikku-middleware/SKILL.md +11 -68
  117. package/skills/pikku-middleware/references/middleware-patterns.md +61 -0
  118. package/skills/pikku-realtime/SKILL.md +56 -105
  119. package/skills/pikku-realtime/references/other-routes.md +23 -0
  120. package/skills/pikku-services/SKILL.md +25 -108
  121. package/skills/pikku-services/references/audit-wire-service.md +34 -0
  122. package/skills/pikku-testing/SKILL.md +51 -359
  123. package/skills/pikku-testing/references/cucumber-bdd-testing.md +176 -0
  124. package/skills/pikku-workflow/SKILL.md +93 -259
  125. package/skills/pikku-workflow/references/workflow-reference.md +63 -0
@@ -0,0 +1,153 @@
1
+ import { z } from 'zod';
2
+ import { readFile } from 'node:fs/promises';
3
+ import { existsSync } from 'node:fs';
4
+ import { join, resolve } from 'node:path';
5
+ import { pikkuSessionlessFunc } from '../../../.pikku/pikku-types.gen.js';
6
+ import { added, removed, dim } from '../lib/output.js';
7
+ const CheckSchema = z.object({
8
+ name: z.string(),
9
+ ok: z.boolean(),
10
+ detail: z.string().optional(),
11
+ });
12
+ export const FabricAddonVerifyInput = z.object({
13
+ dir: z.string().optional(),
14
+ });
15
+ export const FabricAddonVerifyOutputSchema = z.object({
16
+ ok: z.boolean(),
17
+ addonDir: z.string(),
18
+ packageName: z.string().optional(),
19
+ version: z.string().optional(),
20
+ checks: z.array(CheckSchema),
21
+ });
22
+ async function readJsonSafe(path) {
23
+ if (!existsSync(path))
24
+ return { ok: false, reason: 'missing' };
25
+ try {
26
+ return { ok: true, value: JSON.parse(await readFile(path, 'utf8')) };
27
+ }
28
+ catch {
29
+ return { ok: false, reason: 'invalid' };
30
+ }
31
+ }
32
+ export const FabricAddonVerify = pikkuSessionlessFunc({
33
+ description: 'Verify an addon directory is correctly built and ready to publish',
34
+ input: FabricAddonVerifyInput,
35
+ output: FabricAddonVerifyOutputSchema,
36
+ func: async (_services, { dir }) => {
37
+ const addonDir = resolve(dir ?? process.cwd());
38
+ const checks = [];
39
+ const pass = (name, detail) => ({
40
+ name,
41
+ ok: true,
42
+ detail,
43
+ });
44
+ const fail = (name, detail) => ({
45
+ name,
46
+ ok: false,
47
+ detail,
48
+ });
49
+ const pkgResult = await readJsonSafe(join(addonDir, 'package.json'));
50
+ if (!pkgResult.ok) {
51
+ checks.push(fail('package.json', pkgResult.reason === 'missing' ? 'not found' : 'invalid JSON'));
52
+ return { ok: false, addonDir, checks };
53
+ }
54
+ checks.push(pass('package.json'));
55
+ const pkg = pkgResult.value;
56
+ if (!pkg.name)
57
+ checks.push(fail('package name', 'missing name field'));
58
+ else
59
+ checks.push(pass('package name', pkg.name));
60
+ if (!pkg.version)
61
+ checks.push(fail('package version', 'missing version field'));
62
+ else
63
+ checks.push(pass('package version', pkg.version));
64
+ const hasDistInFiles = pkg.files?.some((f) => f === 'dist' || f === 'dist/' || f.startsWith('dist/'));
65
+ if (!hasDistInFiles) {
66
+ checks.push(fail('files field', 'must include "dist" (or "dist/", "dist/**/*")'));
67
+ }
68
+ else {
69
+ checks.push(pass('files field', '"dist" included'));
70
+ }
71
+ const pikkuConfigResult = await readJsonSafe(join(addonDir, 'pikku.config.json'));
72
+ if (!pikkuConfigResult.ok) {
73
+ checks.push(fail('pikku.config.json', pikkuConfigResult.reason === 'missing' ? 'not found' : 'invalid JSON'));
74
+ }
75
+ else if (!pikkuConfigResult.value.addon) {
76
+ checks.push(fail('pikku.config.json', 'addon: true not set'));
77
+ }
78
+ else {
79
+ checks.push(pass('pikku.config.json', 'addon: true'));
80
+ }
81
+ if (!existsSync(join(addonDir, 'dist'))) {
82
+ checks.push(fail('dist/', 'not found — run build first'));
83
+ return {
84
+ ok: false,
85
+ addonDir,
86
+ packageName: pkg.name,
87
+ version: pkg.version,
88
+ checks,
89
+ };
90
+ }
91
+ checks.push(pass('dist/'));
92
+ const distPikku = join(addonDir, 'dist', '.pikku');
93
+ if (!existsSync(distPikku)) {
94
+ checks.push(fail('dist/.pikku/', 'missing — build script must run `cp -r .pikku dist/`'));
95
+ return {
96
+ ok: false,
97
+ addonDir,
98
+ packageName: pkg.name,
99
+ version: pkg.version,
100
+ checks,
101
+ };
102
+ }
103
+ checks.push(pass('dist/.pikku/'));
104
+ const funcsMetaResult = await readJsonSafe(join(distPikku, 'function', 'pikku-functions-meta.gen.json'));
105
+ const funcCount = funcsMetaResult.ok
106
+ ? Object.keys(funcsMetaResult.value).length
107
+ : 0;
108
+ if (funcCount === 0) {
109
+ checks.push(fail('functions', 'no functions found in dist/.pikku/function/pikku-functions-meta.gen.json'));
110
+ }
111
+ else {
112
+ checks.push(pass('functions', `${funcCount} function${funcCount === 1 ? '' : 's'} exported`));
113
+ }
114
+ const schemasDir = join(distPikku, 'schemas', 'schemas');
115
+ checks.push(pass('schemas dir', existsSync(schemasDir) ? 'present' : 'empty (ok)'));
116
+ if (!existsSync(join(addonDir, 'README.md'))) {
117
+ checks.push(fail('README.md', 'missing'));
118
+ }
119
+ else {
120
+ checks.push(pass('README.md'));
121
+ }
122
+ const consoleMetaResult = await readJsonSafe(join(distPikku, 'console', 'pikku-addon-meta.gen.json'));
123
+ const icon = consoleMetaResult.ok
124
+ ? consoleMetaResult.value.package?.icon
125
+ : undefined;
126
+ if (!icon) {
127
+ checks.push({ name: 'icon', ok: true, detail: 'none (optional)' });
128
+ }
129
+ else {
130
+ checks.push(pass('icon', icon.slice(0, 40)));
131
+ }
132
+ const ok = checks.every((c) => c.ok);
133
+ return { ok, addonDir, packageName: pkg.name, version: pkg.version, checks };
134
+ },
135
+ });
136
+ export function renderAddonVerify(_s, result) {
137
+ console.log(`\n${dim('Addon:')} ${result.packageName ?? '(unknown)'}@${result.version ?? '?'}`);
138
+ console.log(`${dim('Dir: ')} ${result.addonDir}\n`);
139
+ for (const c of result.checks) {
140
+ const icon = c.ok ? added('✓') : removed('✗');
141
+ const label = c.ok ? c.name : removed(c.name);
142
+ const detail = c.detail ? ` ${dim(c.detail)}` : '';
143
+ console.log(` ${icon} ${label}${detail}`);
144
+ }
145
+ console.log('');
146
+ if (result.ok) {
147
+ console.log(added(' Ready to publish.'));
148
+ }
149
+ else {
150
+ console.log(removed(' Fix the errors above before publishing.'));
151
+ process.exitCode = 1;
152
+ }
153
+ }
@@ -30,7 +30,7 @@ export const FabricLLMKey = pikkuSessionlessFunc({
30
30
  throw new UnauthorizedError('Not logged in. Run `pikku fabric login` first.');
31
31
  }
32
32
  const rpc = getFabricRPC({ apiUrl: ctx.apiUrl, token: ctx.token });
33
- const result = (await rpc.invoke('getDeveloperLiteLLMKey', {}));
33
+ const result = await rpc.invoke('getDeveloperLiteLLMKey', {});
34
34
  const format = shell
35
35
  ? 'shell'
36
36
  : env
@@ -29,7 +29,12 @@ export const FabricPublish = pikkuSessionlessFunc({
29
29
  description: 'Publish a package directory to the Fabric community registry.',
30
30
  input: FabricPublishInput,
31
31
  output: FabricPublishOutput,
32
- func: async (_services, { dir, apiUrl: apiUrlOverride }) => {
32
+ func: async (_services, { dir, apiUrl: apiUrlOverride }, { rpc, cli }) => {
33
+ const verification = await rpc.invoke('FabricAddonVerify', { dir });
34
+ await cli?.channel.send(verification);
35
+ if (!verification.ok) {
36
+ throw new Error('Addon verification failed — fix the errors above before publishing.');
37
+ }
33
38
  const ctx = await resolveApiContext({ apiUrlOverride });
34
39
  if (!ctx.token)
35
40
  throw new Error('Not logged in. Run `pikku fabric login` first.');
@@ -64,14 +69,14 @@ export const FabricPublish = pikkuSessionlessFunc({
64
69
  return r.json();
65
70
  };
66
71
  // 1. presigned upload URL (short-lived)
67
- const { uploadUrl, artifactKey } = await post('/registry/packages/publish-url', { packageName: pkg.name, version: pkg.version });
72
+ const { uploadUrl, artifactKey } = await post('/registry/addons/publish-url', { packageName: pkg.name, version: pkg.version });
68
73
  // 2. PUT the artifact to the exact signed URL (no extra headers — the URL
69
74
  // is signed over host only; mismatched headers break the signature).
70
75
  const put = await fetch(uploadUrl, { method: 'PUT', body: artifact });
71
76
  if (!put.ok)
72
77
  throw new Error(`upload failed → ${put.status}: ${await put.text()}`);
73
78
  // 3. finalize — server reads the artifact back, extracts meta, indexes it
74
- const entry = await post('/registry/packages/publish', { artifactKey });
79
+ const entry = await post('/registry/addons/publish', { artifactKey });
75
80
  const publisher = entry.publisher?.name ?? null;
76
81
  console.log(`[fabric] published ${entry.name}@${entry.version} (id=${entry.id})` +
77
82
  (publisher ? ` as ${publisher}` : ''));
@@ -176,7 +176,7 @@ async function runDeploy(provider, providerDir, logger, resultFile) {
176
176
  }
177
177
  }
178
178
  export const deployApply = pikkuSessionlessFunc({
179
- func: async ({ logger, config, getInspectorState }, data) => {
179
+ func: async ({ logger, config, getInspectorState, bundler }, data) => {
180
180
  const projectDir = config.rootDir;
181
181
  const provider = await resolveProvider(config, data?.provider, {
182
182
  runtime: data?.runtime,
@@ -209,10 +209,12 @@ export const deployApply = pikkuSessionlessFunc({
209
209
  provider,
210
210
  inspectorState,
211
211
  serverlessIncompatible: config.deploy?.serverlessIncompatible,
212
+ defaultTarget: config.deploy?.defaultTarget,
212
213
  getEntryContext,
213
214
  outDir: config.outDir,
214
215
  debugArtifacts: data?.debugArtifacts ?? false,
215
216
  logger,
217
+ bundler,
216
218
  });
217
219
  if (buildResult.manifest.units.length === 0) {
218
220
  logger.info('No deployment units found. Nothing to deploy.');
@@ -43,7 +43,7 @@ function createEmptyProvider() {
43
43
  };
44
44
  }
45
45
  export const deployPlan = pikkuSessionlessFunc({
46
- func: async ({ logger, config, getInspectorState }, data) => {
46
+ func: async ({ logger, config, getInspectorState, bundler }, data) => {
47
47
  const projectDir = config.rootDir;
48
48
  const inspectorState = await getInspectorState(true);
49
49
  const projectId = await resolveProjectId(projectDir);
@@ -56,10 +56,12 @@ export const deployPlan = pikkuSessionlessFunc({
56
56
  provider,
57
57
  inspectorState,
58
58
  serverlessIncompatible: config.deploy?.serverlessIncompatible,
59
+ defaultTarget: config.deploy?.defaultTarget,
59
60
  getEntryContext,
60
61
  outDir: config.outDir,
61
62
  debugArtifacts: data?.debugArtifacts ?? false,
62
63
  logger,
64
+ bundler,
63
65
  });
64
66
  if (result.manifest.units.length === 0) {
65
67
  logger.info('No deployment units found.');
@@ -7,18 +7,14 @@ import { KyselyAIStorageService, KyselyAIRunStateService, KyselyAgentRunService,
7
7
  import { stopSingletonServices } from '@pikku/core';
8
8
  import { pikkuState } from '@pikku/core/internal';
9
9
  import { LocalMetaService } from '@pikku/core/services/local-meta';
10
- import { LocalEventHubService } from '@pikku/core/channel/local';
11
10
  import { LocalContent, } from '@pikku/core/services/local-content';
12
- import { pikkuWebsocketHandler } from '@pikku/ws';
13
- import { PikkuNodeHTTPServer } from '@pikku/node-http-server';
14
- import { WebSocketServer } from 'ws';
15
11
  import { InMemorySchedulerService } from '@pikku/schedule';
16
12
  import { resolveDb, createKysely, parseDatabaseUrl, } from '../db/local-db.js';
17
13
  import { loadUserBootstrap, loadUserModule } from './load-user-project.js';
18
14
  import { createDevAIAgentRunner } from './dev-ai-runner.js';
19
15
  export const dev = pikkuSessionlessFunc({
20
16
  remote: true,
21
- func: async ({ logger, config, getInspectorState, variables }, { port, watch, hmr }, { rpc }) => {
17
+ func: async ({ logger, config, getInspectorState, variables, devServerRunner }, { port, watch, hmr }, { rpc }) => {
22
18
  const resolvedPort = parseInt(port || '3000', 10);
23
19
  const hostname = 'localhost';
24
20
  // Bind on IPv4 loopback explicitly. Under Bun, hostname 'localhost' resolves
@@ -174,20 +170,10 @@ export const dev = pikkuSessionlessFunc({
174
170
  variables,
175
171
  })
176
172
  : undefined;
177
- // When the CLI itself runs under bun (e.g. the compiled brew binary), serve
178
- // over @pikku/bun-server (native Bun.serve WebSockets) instead of the node
179
- // http server + ws package. The bun runtime is dynamically imported so a
180
- // node-run CLI never loads it. The same BunEventHubService instance is
181
- // shared with the singleton services so function-side broadcasts reach the
182
- // sockets the transport holds.
183
- const isBun = typeof globalThis.Bun !== 'undefined';
184
- const bun = isBun
185
- ? await (async () => {
186
- const mod = await import('@pikku/bun-server');
187
- return { mod, eventHub: new mod.BunEventHubService() };
188
- })()
189
- : null;
190
- const eventHub = bun ? bun.eventHub : new LocalEventHubService();
173
+ // The dev server runner (node http+ws, or bun-server) is resolved by DI in
174
+ // services.ts. Its EventHub is shared into the singleton services so
175
+ // function-side broadcasts reach the sockets the transport holds.
176
+ const eventHub = await devServerRunner.createEventHub();
191
177
  const inMemoryServices = {
192
178
  logger: devLogger,
193
179
  ...(aiAgentRunner ? { aiAgentRunner } : {}),
@@ -213,29 +199,12 @@ export const dev = pikkuSessionlessFunc({
213
199
  ...singletonServices,
214
200
  getInspectorState,
215
201
  });
216
- let wss;
217
- let pikkuServer;
218
- if (bun) {
219
- pikkuServer = new bun.mod.PikkuBunServer({
220
- ...userConfig,
221
- hostname: bindHostname,
222
- port: resolvedPort,
223
- content: localContentConfig,
224
- }, logger, { eventHub: bun.eventHub });
225
- }
226
- else {
227
- wss = new WebSocketServer({ noServer: true });
228
- pikkuServer = new PikkuNodeHTTPServer({
229
- ...userConfig,
230
- hostname: bindHostname,
231
- port: resolvedPort,
232
- content: localContentConfig,
233
- }, logger, {
234
- configureServer: (httpServer) => {
235
- pikkuWebsocketHandler({ server: httpServer, wss: wss, logger });
236
- },
237
- });
238
- }
202
+ const pikkuServer = devServerRunner.createServer({
203
+ ...userConfig,
204
+ hostname: bindHostname,
205
+ port: resolvedPort,
206
+ content: localContentConfig,
207
+ }, logger);
239
208
  await pikkuServer.init();
240
209
  await schedulerService.start();
241
210
  await pikkuServer.start();
@@ -247,9 +216,6 @@ export const dev = pikkuSessionlessFunc({
247
216
  await stopSingletonServices();
248
217
  await configWatcher?.close();
249
218
  await watcher?.close();
250
- if (wss) {
251
- await new Promise((resolve, reject) => wss.close((err) => (err ? reject(err) : resolve())));
252
- }
253
219
  await pikkuServer.stop();
254
220
  }
255
221
  finally {
@@ -191,6 +191,20 @@ function emitInterface(table, camelCase, explicitAnnotations, dialect, enumByNam
191
191
  const typeAnn = typingKind || effectiveTsType
192
192
  ? { kind: typingKind, tsType: effectiveTsType }
193
193
  : null;
194
+ // JSON/JSONB columns carry no inherent TypeScript shape — without a
195
+ // concrete `tsType` they degrade to `unknown`, erasing type-safety at
196
+ // every call site. Warn (non-blocking) so an AI or developer can give the
197
+ // column a real type. An explicit `tsType: 'unknown'`/`'any'` is allowed
198
+ // (the developer has acknowledged it) but is still flagged as discouraged.
199
+ const isJsonColumn = col.type.toUpperCase().includes('JSON') || ann?.kind === 'json';
200
+ if (isJsonColumn) {
201
+ const resolved = selectBase(typeAnn, col);
202
+ if (resolved === 'unknown' || resolved === 'any') {
203
+ warnings.push(`Column "${bare}.${col.name}" is ${col.type} and will be typed as ` +
204
+ `\`${resolved}\` — JSON/JSONB columns need a concrete TypeScript type. ` +
205
+ `In db/annotations.ts add: "${col.name}": { kind: 'json', tsType: 'YourType' }.`);
206
+ }
207
+ }
194
208
  // A `format` validator refines the zod schema only and keeps the TS type
195
209
  // as `string`. It therefore applies only when the resolved select base is
196
210
  // plain `string`; on anything else (Date/Uuid/boolean/enum/unknown via
@@ -372,26 +372,14 @@ export type PikkuFunctionConfigWithSchema<
372
372
  * Creates a Pikku function that can be either session-aware or sessionless.
373
373
  * This is the main function wrapper for creating API endpoints.
374
374
  *
375
- * Supports two patterns:
376
- * 1. Generic types: \`pikkuFunc<Input, Output>({ func: ... })\`
377
- * 2. Zod schemas: \`pikkuFunc({ input: z.object(...), output: z.object(...), func: ... })\`
375
+ * Define the input and output with Zod schemas — the function's types are
376
+ * inferred from them, and the schemas double as runtime validation.
378
377
  *
379
- * @template In - Input type for the function (inferred from schema if provided)
380
- * @template Out - Output type for the function (inferred from schema if provided)
381
- * @param func - Function definition, either direct function or configuration object
378
+ * @param config - Function definition with \`input\`/\`output\` Zod schemas and \`func\`.
382
379
  * @returns The normalized configuration object
383
380
  *
384
381
  * @example
385
382
  * \`\`\`typescript
386
- * // Pattern 1: Using generic types
387
- * const createUser = pikkuFunc<{name: string, email: string}, {id: number}>({
388
- * func: async ({db}, input) => {
389
- * const user = await db.users.create(input)
390
- * return { id: user.id }
391
- * }
392
- * })
393
- *
394
- * // Pattern 2: Using Zod schemas (types inferred automatically)
395
383
  * const createUserInput = z.object({ name: z.string(), email: z.string() })
396
384
  * const createUserOutput = z.object({ id: z.number() })
397
385
  *
@@ -483,25 +471,14 @@ export type PikkuFunctionSessionlessConfigWithSchema<
483
471
  * Creates a sessionless Pikku function that doesn't require user authentication.
484
472
  * Use this for public endpoints, webhooks, or background tasks.
485
473
  *
486
- * Supports two patterns:
487
- * 1. Generic types: \`pikkuSessionlessFunc<Input, Output>({ func: ... })\`
488
- * 2. Zod schemas: \`pikkuSessionlessFunc({ input: z.object(...), func: ... })\`
474
+ * Define the input and output with Zod schemas — the function's types are
475
+ * inferred from them, and the schemas double as runtime validation.
489
476
  *
490
- * @template In - Input type for the function (inferred from schema if provided)
491
- * @template Out - Output type for the function (inferred from schema if provided)
492
- * @param func - Function definition, either direct function or configuration object
477
+ * @param config - Function definition with \`input\`/\`output\` Zod schemas and \`func\`.
493
478
  * @returns The normalized configuration object
494
479
  *
495
480
  * @example
496
481
  * \`\`\`typescript
497
- * // Pattern 1: Using generic types
498
- * const healthCheck = pikkuSessionlessFunc<void, {status: string}>({
499
- * func: async ({logger}) => {
500
- * return { status: 'healthy' }
501
- * }
502
- * })
503
- *
504
- * // Pattern 2: Using Zod schemas
505
482
  * const greetInput = z.object({ name: z.string() })
506
483
  * const greetOutput = z.object({ message: z.string() })
507
484
  *
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Bun dev server runner — `@pikku/bun-server` (native `Bun.serve` WebSockets).
3
+ * Used when the CLI runs under Bun. The bun-server module is dynamically
4
+ * imported so a node-run CLI never loads its bun-only code; types are pulled via
5
+ * `import type` (erased at runtime). The same BunEventHubService instance is
6
+ * shared with singleton services so function-side broadcasts reach the sockets
7
+ * the transport holds.
8
+ */
9
+ import type { EventHubService } from '@pikku/core';
10
+ import type { Logger } from '@pikku/core/services';
11
+ import type { DevServerRunner, DevServerInstance, DevServerConfig } from './dev-server-runner.interface.js';
12
+ export declare class BunServerRunner implements DevServerRunner {
13
+ private mod?;
14
+ private eventHub?;
15
+ createEventHub(): Promise<EventHubService<any>>;
16
+ createServer(config: DevServerConfig, logger: Logger): DevServerInstance;
17
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Bun dev server runner — `@pikku/bun-server` (native `Bun.serve` WebSockets).
3
+ * Used when the CLI runs under Bun. The bun-server module is dynamically
4
+ * imported so a node-run CLI never loads its bun-only code; types are pulled via
5
+ * `import type` (erased at runtime). The same BunEventHubService instance is
6
+ * shared with singleton services so function-side broadcasts reach the sockets
7
+ * the transport holds.
8
+ */
9
+ export class BunServerRunner {
10
+ mod;
11
+ eventHub;
12
+ async createEventHub() {
13
+ this.mod = await import('@pikku/bun-server');
14
+ this.eventHub = new this.mod.BunEventHubService();
15
+ return this.eventHub;
16
+ }
17
+ createServer(config, logger) {
18
+ if (!this.mod || !this.eventHub) {
19
+ throw new Error('createEventHub() must be called before createServer()');
20
+ }
21
+ return new this.mod.PikkuBunServer(config, logger, {
22
+ eventHub: this.eventHub,
23
+ });
24
+ }
25
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Dev server runner abstraction.
3
+ *
4
+ * The `pikku dev` server differs by runtime: under Node it's
5
+ * `@pikku/node-http-server` + the `ws` WebSocketServer; under Bun it's
6
+ * `@pikku/bun-server` (native `Bun.serve` WebSockets). Both also supply the
7
+ * EventHub that is shared into singleton services so function-side broadcasts
8
+ * reach the transport's sockets. The right runner is resolved once in
9
+ * `services.ts` and injected — no `typeof Bun` checks in the dev command.
10
+ */
11
+ import type { EventHubService } from '@pikku/core';
12
+ import type { Logger } from '@pikku/core/services';
13
+ import type { NodeHTTPServerConfig } from '@pikku/node-http-server';
14
+ export interface DevServerInstance {
15
+ init(): Promise<void>;
16
+ start(): Promise<void>;
17
+ /** Full teardown — closes the server and any owned WebSocket server. */
18
+ stop(): Promise<void>;
19
+ }
20
+ /** Config shape both runtimes accept (Core config + host/port/content). */
21
+ export type DevServerConfig = NodeHTTPServerConfig;
22
+ export interface DevServerRunner {
23
+ /**
24
+ * Create the EventHub for this runtime. Shared into singleton services so
25
+ * function-side broadcasts reach the transport's sockets. Must be called
26
+ * once before `createServer`.
27
+ */
28
+ createEventHub(): Promise<EventHubService<any>>;
29
+ /** Create the dev HTTP/WS server. Must be called after `createEventHub`. */
30
+ createServer(config: DevServerConfig, logger: Logger): DevServerInstance;
31
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Dev server runner abstraction.
3
+ *
4
+ * The `pikku dev` server differs by runtime: under Node it's
5
+ * `@pikku/node-http-server` + the `ws` WebSocketServer; under Bun it's
6
+ * `@pikku/bun-server` (native `Bun.serve` WebSockets). Both also supply the
7
+ * EventHub that is shared into singleton services so function-side broadcasts
8
+ * reach the transport's sockets. The right runner is resolved once in
9
+ * `services.ts` and injected — no `typeof Bun` checks in the dev command.
10
+ */
11
+ export {};
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Node dev server runner — `@pikku/node-http-server` + the `ws` WebSocketServer.
3
+ * Used when the CLI runs under Node. Owns the WebSocketServer so teardown
4
+ * (close ws then the http server) is encapsulated in the returned instance.
5
+ */
6
+ import type { EventHubService } from '@pikku/core';
7
+ import type { Logger } from '@pikku/core/services';
8
+ import type { DevServerRunner, DevServerInstance, DevServerConfig } from './dev-server-runner.interface.js';
9
+ export declare class NodeServerRunner implements DevServerRunner {
10
+ createEventHub(): Promise<EventHubService<any>>;
11
+ createServer(config: DevServerConfig, logger: Logger): DevServerInstance;
12
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Node dev server runner — `@pikku/node-http-server` + the `ws` WebSocketServer.
3
+ * Used when the CLI runs under Node. Owns the WebSocketServer so teardown
4
+ * (close ws then the http server) is encapsulated in the returned instance.
5
+ */
6
+ import { LocalEventHubService } from '@pikku/core/channel/local';
7
+ import { PikkuNodeHTTPServer } from '@pikku/node-http-server';
8
+ import { pikkuWebsocketHandler } from '@pikku/ws';
9
+ import { WebSocketServer } from 'ws';
10
+ export class NodeServerRunner {
11
+ async createEventHub() {
12
+ return new LocalEventHubService();
13
+ }
14
+ createServer(config, logger) {
15
+ const wss = new WebSocketServer({ noServer: true });
16
+ const server = new PikkuNodeHTTPServer(config, logger, {
17
+ configureServer: (httpServer) => {
18
+ pikkuWebsocketHandler({ server: httpServer, wss, logger });
19
+ },
20
+ });
21
+ return {
22
+ init: () => server.init(),
23
+ start: () => server.start(),
24
+ stop: async () => {
25
+ await new Promise((resolve, reject) => wss.close((err) => (err ? reject(err) : resolve())));
26
+ await server.stop();
27
+ },
28
+ };
29
+ }
30
+ }
@@ -11,6 +11,10 @@ import { existsSync } from 'fs';
11
11
  import { readFile, writeFile } from 'fs/promises';
12
12
  import { loadManifest } from './utils/contract-versions.js';
13
13
  import { join } from 'path';
14
+ import { NodeBundler } from './deploy/bundler/node-bundler.js';
15
+ import { BunBundler } from './deploy/bundler/bun-bundler.js';
16
+ import { NodeServerRunner } from './server/node-server-runner.js';
17
+ import { BunServerRunner } from './server/bun-server-runner.js';
14
18
  import { parseCLIFilters } from './utils/parse-cli-filters.js';
15
19
  const DIAGNOSTIC_CODE_TO_LINT_KEY = {
16
20
  [ErrorCode.SERVICES_NOT_DESTRUCTURED]: 'servicesNotDestructured',
@@ -274,6 +278,10 @@ export const createSingletonServices = async (config) => {
274
278
  return filteredState;
275
279
  };
276
280
  const workflowService = new InMemoryWorkflowService();
281
+ // Resolve the runtime ONCE here, then inject runtime-specific implementations.
282
+ // Keeping the check in this single place avoids `typeof Bun` branches leaking
283
+ // into the deploy pipeline / dev command.
284
+ const isBun = typeof globalThis.Bun !== 'undefined';
277
285
  return {
278
286
  config,
279
287
  logger,
@@ -282,6 +290,8 @@ export const createSingletonServices = async (config) => {
282
290
  audit: new NoopAuditService(),
283
291
  getInspectorState,
284
292
  workflowService,
293
+ bundler: isBun ? new BunBundler() : new NodeBundler(),
294
+ devServerRunner: isBun ? new BunServerRunner() : new NodeServerRunner(),
285
295
  };
286
296
  };
287
297
  export const createWireServices = async ({ logger }, { cli, channel }) => {
@@ -13,6 +13,7 @@ export type CLIFilters = InspectorFilters & {
13
13
  export declare function parseCLIFilters(data: any, cliConfig?: {
14
14
  deploy?: {
15
15
  serverlessIncompatible?: string[];
16
+ defaultTarget?: 'serverless' | 'server';
16
17
  };
17
18
  namedFilters?: Record<string, InspectorFilters>;
18
19
  }): CLIFilters;
@@ -94,6 +94,7 @@ export function parseCLIFilters(data, cliConfig) {
94
94
  filters.excludeTarget = validateTargetList(filters.excludeTarget, '--exclude-target');
95
95
  if (filters.target || filters.excludeTarget) {
96
96
  filters.serverlessIncompatible = cliConfig?.deploy?.serverlessIncompatible;
97
+ filters.defaultTarget = cliConfig?.deploy?.defaultTarget;
97
98
  }
98
99
  return filters;
99
100
  }