@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.
- 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 +23 -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 +19 -4
- 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 +23 -1
- package/dist/src/deploy/bundler/bundler.js +11 -1
- package/dist/src/deploy/codegen/per-unit-codegen.js +1 -0
- package/dist/src/fabric/functions/validate.function.js +36 -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/pikku-command-bootstrap.js +2 -2
- 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/file-import-path.d.ts +5 -1
- package/dist/src/utils/file-import-path.js +6 -2
- package/dist/src/utils/generate-bootstrap-file.d.ts +5 -1
- package/dist/src/utils/generate-bootstrap-file.js +6 -2
- 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 +2 -2
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.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
|
}
|
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';
|
|
@@ -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
|
-
|
|
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
|
}
|
|
@@ -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 &&
|
|
445
|
-
|
|
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
|
-
|
|
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',
|
|
@@ -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
|
-
[
|
|
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 ??
|
|
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`
|
|
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 ??
|
|
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.
|