@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.
- package/cli.schema.json +1 -1
- package/console-app/assets/{index-xN8LW0II.js → index-CcpTZVoP.js} +1 -1
- package/console-app/index.html +1 -1
- package/dist/.pikku/agent/pikku-agent-types.gen.d.ts +1 -1
- package/dist/.pikku/channel/pikku-channel-types.gen.d.ts +1 -1
- package/dist/.pikku/channel/pikku-channel-types.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli-channel.js +6 -1
- package/dist/.pikku/cli/pikku-cli-client.gen.d.ts +1 -1
- package/dist/.pikku/cli/pikku-cli-client.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli-contracts-meta.gen.d.ts +1 -1
- package/dist/.pikku/cli/pikku-cli-contracts-meta.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli-types.gen.d.ts +1 -1
- package/dist/.pikku/cli/pikku-cli-types.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli-wirings-meta.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli-wirings-meta.gen.json +19 -1
- package/dist/.pikku/cli/pikku-cli-wirings.gen.d.ts +1 -1
- package/dist/.pikku/cli/pikku-cli-wirings.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli.gen.d.ts +1 -1
- package/dist/.pikku/cli/pikku-cli.gen.js +1 -1
- package/dist/.pikku/console/pikku-node-types.gen.d.ts +1 -1
- package/dist/.pikku/function/pikku-function-types.gen.d.ts +1 -1
- package/dist/.pikku/function/pikku-function-types.gen.js +1 -1
- package/dist/.pikku/function/pikku-functions-meta.gen.js +1 -1
- package/dist/.pikku/function/pikku-functions-meta.gen.json +18 -3
- package/dist/.pikku/function/pikku-functions.gen.js +3 -1
- package/dist/.pikku/http/pikku-http-types.gen.d.ts +1 -1
- package/dist/.pikku/http/pikku-http-types.gen.js +1 -1
- package/dist/.pikku/http/pikku-http-wirings-meta.gen.js +1 -1
- package/dist/.pikku/http/pikku-http-wirings.gen.d.ts +1 -1
- package/dist/.pikku/http/pikku-http-wirings.gen.js +1 -1
- package/dist/.pikku/mcp/pikku-mcp-types.gen.d.ts +1 -1
- package/dist/.pikku/mcp/pikku-mcp-types.gen.js +1 -1
- package/dist/.pikku/pikku-bootstrap.gen.d.ts +1 -1
- package/dist/.pikku/pikku-bootstrap.gen.js +1 -1
- package/dist/.pikku/pikku-meta-service.gen.d.ts +1 -1
- package/dist/.pikku/pikku-meta-service.gen.js +1 -1
- package/dist/.pikku/pikku-services.gen.d.ts +1 -1
- package/dist/.pikku/pikku-types.gen.d.ts +1 -1
- package/dist/.pikku/pikku-types.gen.js +1 -1
- package/dist/.pikku/queue/pikku-queue-types.gen.d.ts +1 -1
- package/dist/.pikku/queue/pikku-queue-types.gen.js +1 -1
- package/dist/.pikku/queue/pikku-queue-workers-wirings-meta.gen.js +1 -1
- package/dist/.pikku/queue/pikku-queue-workers-wirings.gen.d.ts +1 -1
- package/dist/.pikku/queue/pikku-queue-workers-wirings.gen.js +1 -1
- package/dist/.pikku/rpc/pikku-rpc-wirings-meta.internal.gen.js +1 -1
- package/dist/.pikku/rpc/pikku-rpc-wirings-meta.internal.gen.json +1 -0
- package/dist/.pikku/scheduler/pikku-scheduler-types.gen.d.ts +1 -1
- package/dist/.pikku/scheduler/pikku-scheduler-types.gen.js +1 -1
- package/dist/.pikku/schemas/register.gen.js +3 -1
- package/dist/.pikku/schemas/schemas/DevInput.schema.json +1 -1
- package/dist/.pikku/schemas/schemas/PikkuCLIConfig.schema.json +1 -1
- package/dist/.pikku/schemas/schemas/ServeInput.schema.json +1 -0
- package/dist/.pikku/secrets/pikku-secret-types.gen.d.ts +1 -1
- package/dist/.pikku/secrets/pikku-secret-types.gen.js +1 -1
- package/dist/.pikku/secrets/pikku-secrets.gen.d.ts +1 -1
- package/dist/.pikku/secrets/pikku-secrets.gen.js +1 -1
- package/dist/.pikku/trigger/pikku-trigger-types.gen.d.ts +1 -1
- package/dist/.pikku/trigger/pikku-trigger-types.gen.js +1 -1
- package/dist/.pikku/variables/pikku-variable-types.gen.d.ts +1 -1
- package/dist/.pikku/variables/pikku-variable-types.gen.js +1 -1
- package/dist/.pikku/variables/pikku-variables.gen.d.ts +1 -1
- package/dist/.pikku/variables/pikku-variables.gen.js +1 -1
- package/dist/.pikku/workflow/pikku-workflow-types.gen.d.ts +1 -1
- package/dist/.pikku/workflow/pikku-workflow-types.gen.js +1 -1
- package/dist/.pikku/workflow/pikku-workflow-wirings-meta.gen.js +1 -1
- package/dist/.pikku/workflow/pikku-workflow-wirings.gen.js +1 -1
- package/dist/bin/pikku-bin.mjs +2 -2
- package/dist/src/cli.wiring.js +19 -1
- package/dist/src/deploy/bundler/bundler.js +11 -1
- package/dist/src/fabric/functions/validate.function.js +23 -3
- package/dist/src/functions/commands/dev.d.ts +3 -0
- package/dist/src/functions/commands/dev.js +22 -8
- package/dist/src/functions/commands/serve-console.d.ts +4 -0
- package/dist/src/functions/commands/serve-console.js +62 -0
- package/dist/src/functions/commands/serve.d.ts +10 -0
- package/dist/src/functions/commands/serve.js +143 -0
- package/dist/src/functions/commands/tests-init.js +6 -3
- package/dist/src/functions/db/db-codegen.d.ts +1 -1
- package/dist/src/functions/db/db-codegen.js +6 -4
- package/dist/src/functions/db/local-db.js +1 -1
- package/dist/src/functions/db/zod-codegen.d.ts +1 -1
- package/dist/src/functions/validate/workspace-validate.js +1 -1
- package/dist/src/functions/wirings/console/pikku-command-nodes-meta.js +6 -0
- package/dist/src/utils/parse-cli-filters.d.ts +3 -0
- package/dist/src/utils/parse-cli-filters.js +8 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
package/dist/bin/pikku-bin.mjs
CHANGED
|
@@ -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.
|
|
15
|
-
process.stderr.write(`\n Update available 0.12.
|
|
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
|
}
|
package/dist/src/cli.wiring.js
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
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 &&
|
|
458
|
-
|
|
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
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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
|
|
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.
|
|
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.
|
|
216
|
-
const schemaInRootDir = join(config.rootDir, 'db', 'schema.
|
|
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
|
-
[
|
|
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.
|
|
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 ??
|
|
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.
|
|
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 ??
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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;
|