@pikku/cli 0.12.56 → 0.12.58

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 (93) 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 +23 -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 +19 -4
  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 +23 -1
  69. package/dist/src/deploy/bundler/bundler.js +11 -1
  70. package/dist/src/deploy/codegen/per-unit-codegen.js +1 -0
  71. package/dist/src/fabric/functions/validate.function.js +36 -3
  72. package/dist/src/functions/commands/dev.d.ts +3 -0
  73. package/dist/src/functions/commands/dev.js +22 -8
  74. package/dist/src/functions/commands/pikku-command-bootstrap.js +2 -2
  75. package/dist/src/functions/commands/serve-console.d.ts +4 -0
  76. package/dist/src/functions/commands/serve-console.js +62 -0
  77. package/dist/src/functions/commands/serve.d.ts +10 -0
  78. package/dist/src/functions/commands/serve.js +143 -0
  79. package/dist/src/functions/commands/tests-init.js +6 -3
  80. package/dist/src/functions/db/db-codegen.d.ts +1 -1
  81. package/dist/src/functions/db/db-codegen.js +6 -4
  82. package/dist/src/functions/db/local-db.js +1 -1
  83. package/dist/src/functions/db/zod-codegen.d.ts +1 -1
  84. package/dist/src/functions/validate/workspace-validate.js +1 -1
  85. package/dist/src/functions/wirings/console/pikku-command-nodes-meta.js +6 -0
  86. package/dist/src/utils/file-import-path.d.ts +5 -1
  87. package/dist/src/utils/file-import-path.js +6 -2
  88. package/dist/src/utils/generate-bootstrap-file.d.ts +5 -1
  89. package/dist/src/utils/generate-bootstrap-file.js +6 -2
  90. package/dist/src/utils/parse-cli-filters.d.ts +3 -0
  91. package/dist/src/utils/parse-cli-filters.js +8 -1
  92. package/dist/tsconfig.tsbuildinfo +1 -1
  93. package/package.json +2 -2
@@ -1,5 +1,5 @@
1
1
  /**
2
- * This file was generated by @pikku/cli@0.12.56
2
+ * This file was generated by @pikku/cli@0.12.58
3
3
  */
4
4
  import { TypedVariablesService as CoreTypedVariablesService } from '@pikku/core/services';
5
5
  import type { VariablesService } from '@pikku/core/services';
@@ -1,5 +1,5 @@
1
1
  /**
2
- * This file was generated by @pikku/cli@0.12.56
2
+ * This file was generated by @pikku/cli@0.12.58
3
3
  */
4
4
  import { TypedVariablesService as CoreTypedVariablesService } from '@pikku/core/services';
5
5
  const VARIABLES_META = {};
@@ -1,5 +1,5 @@
1
1
  /**
2
- * This file was generated by @pikku/cli@0.12.56
2
+ * This file was generated by @pikku/cli@0.12.58
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.56
2
+ * This file was generated by @pikku/cli@0.12.58
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.56
2
+ * This file was generated by @pikku/cli@0.12.58
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.56
2
+ * This file was generated by @pikku/cli@0.12.58
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.56') {
15
- process.stderr.write(`\n Update available 0.12.56 → ${latest}\n brew upgrade pikku or npm install -g @pikku/cli\n\n`)
14
+ if (latest !== '0.12.58') {
15
+ process.stderr.write(`\n Update available 0.12.58 → ${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';
@@ -157,6 +158,10 @@ wireCLI({
157
158
  excludeTarget: {
158
159
  description: 'Exclude functions by deploy target (comma-separated: serverless, server)',
159
160
  },
161
+ forceRelativeImports: {
162
+ description: 'Emit relative imports even when packageMappings would apply (used by per-unit deploy codegen)',
163
+ default: false,
164
+ },
160
165
  },
161
166
  }),
162
167
  bootstrap: pikkuCLICommand({
@@ -190,6 +195,23 @@ wireCLI({
190
195
  description: 'Enable hot module reload',
191
196
  default: true,
192
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
+ },
193
215
  },
194
216
  }),
195
217
  console: pikkuCLICommand({
@@ -283,7 +305,7 @@ wireCLI({
283
305
  subcommands: {
284
306
  migrate: pikkuCLICommand({
285
307
  func: dbMigrate,
286
- description: 'Apply pending SQL migrations and regenerate db/schema.d.ts',
308
+ description: 'Apply pending SQL migrations and regenerate db/schema.gen.d.ts',
287
309
  }),
288
310
  generate: pikkuCLICommand({
289
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
  }
@@ -226,6 +226,7 @@ export async function generatePerUnitCodegen(options) {
226
226
  `--stateInput=${stateFilePath}`,
227
227
  `--names=${namesArg}`,
228
228
  `--outDir=${unitPikkuDir}`,
229
+ '--force-relative-imports',
229
230
  '--silent',
230
231
  ], {
231
232
  cwd: projectDir,
@@ -258,6 +258,19 @@ export async function runValidate(startDir = process.cwd()) {
258
258
  w('gitignore-missing-generated', `.gitignore does not ignore Fabric generated/runtime artifacts: ${missing.join(', ')} — committing them lets a stale copy shadow the freshly generated one (e.g. a committed __fabric_scaffold.vite.config.mjs or .pikku-runtime breaks the sandbox dev server)`, gitignorePath, lines('Add these entries to .gitignore:', ...missing.map((entry) => ` ${entry}`), 'They are regenerated on every dev boot / scaffold / codegen and must never be committed.'));
259
259
  }
260
260
  }
261
+ // ── required project files ────────────────────────────────────────────
262
+ // These files must exist and be committed — they are seeded from the sandbox
263
+ // but belong to the project so the AI agent can read and update them.
264
+ for (const relPath of [
265
+ 'db/annotations.ts',
266
+ 'knowledge/design-language.md',
267
+ 'knowledge/security.md',
268
+ 'knowledge/technology.md',
269
+ ]) {
270
+ if (!existsSync(join(root, relPath))) {
271
+ w(`missing-required-file-${relPath.replace(/[^a-z0-9]/gi, '-')}`, `${relPath} is missing — this file must be committed to the project`, join(root, relPath), lines(`Create ${relPath} and commit it.`, 'The starter-template ships a stub you can copy as a starting point.'));
272
+ }
273
+ }
261
274
  const rootPkgPath = join(root, 'package.json');
262
275
  const rootPkg = await readJsonSafe(rootPkgPath);
263
276
  if (!rootPkg) {
@@ -359,6 +372,24 @@ export async function runValidate(startDir = process.cwd()) {
359
372
  if (!fnPkg.imports?.['#pikku']) {
360
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/*" }');
361
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
+ }
362
393
  const fnAllDeps = {
363
394
  ...fnPkg.dependencies,
364
395
  ...fnPkg.devDependencies,
@@ -441,8 +472,10 @@ export async function runValidate(startDir = process.cwd()) {
441
472
  const baseURL = text.match(/createAuthClient\s*\([^)]*baseURL\s*:\s*([^,)\n]+)/)?.[1];
442
473
  // Heuristic: flag a bare /api baseURL with no /auth segment anywhere
443
474
  // near the client config.
444
- if (baseURL && /['"`]\/api['"`]/.test(baseURL) && !/auth/i.test(baseURL)) {
445
- 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)');
446
479
  }
447
480
  }
448
481
  }
@@ -562,7 +595,7 @@ export async function runValidate(startDir = process.cwd()) {
562
595
  dbTypesText.includes('.pikku\\db\\schema');
563
596
  const hasInlineTypes = /(?:^|\n)\s*(?:export\s+)?(?:interface\s+\w|type\s+\w+\s*=)/.test(dbTypesText);
564
597
  if (hasInlineTypes && !isReexport) {
565
- 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");
566
599
  }
567
600
  }
568
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',
@@ -12,7 +12,7 @@ export const pikkuBootstrap = pikkuSessionlessFunc({
12
12
  addonBootstraps.push(`${decl.package}/.pikku/pikku-bootstrap.gen.js`);
13
13
  logger.debug(`• Addon bootstrap: ${decl.package}`);
14
14
  }
15
- const wireAddonFileImports = Array.from(stateBeforeBootstrap.rpc?.wireAddonFiles ?? []).map((to) => `import '${getFileImportRelativePath(config.bootstrapFile, to, config.packageMappings)}'`);
15
+ const wireAddonFileImports = Array.from(stateBeforeBootstrap.rpc?.wireAddonFiles ?? []).map((to) => `import '${getFileImportRelativePath(config.bootstrapFile, to, config.packageMappings, config.forceRelativeImports)}'`);
16
16
  const localImportTargets = Array.from(new Set([
17
17
  ...(config.rpcInternalWiringMetaFile &&
18
18
  existsSync(config.rpcInternalWiringMetaFile)
@@ -26,7 +26,7 @@ export const pikkuBootstrap = pikkuSessionlessFunc({
26
26
  logger.warn(`Skipping invalid bootstrap import path: ${String(to)}`);
27
27
  return false;
28
28
  });
29
- const localImports = safeLocalImportTargets.map((to) => `import '${getFileImportRelativePath(config.bootstrapFile, to, config.packageMappings)}'`);
29
+ const localImports = safeLocalImportTargets.map((to) => `import '${getFileImportRelativePath(config.bootstrapFile, to, config.packageMappings, config.forceRelativeImports)}'`);
30
30
  const addonImports = addonBootstraps.map((packagePath) => `import '${packagePath}'`);
31
31
  const outDir = dirname(config.bootstrapFile);
32
32
  const metaServiceFile = join(outDir, 'pikku-meta-service.gen.ts');
@@ -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.d.ts');
216
+ const schemaInRootDir = join(config.rootDir, 'db', 'schema.gen.d.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.d.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.d.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.d.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.d.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.d.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.