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