@pikku/cli 0.12.24 → 0.12.26
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-Ba9K10XZ.js +232 -0
- 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 +21 -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 +50 -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 +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 +183 -104
- 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 +3 -1
- 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.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 +13 -9
- 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 +17 -5
- package/dist/.pikku/schemas/schemas/DbAuditInput.schema.json +1 -0
- package/dist/.pikku/schemas/schemas/PikkuCLIConfig.schema.json +1 -1
- package/dist/.pikku/schemas/schemas/PikkuEmailsOutput.schema.json +1 -0
- package/dist/.pikku/schemas/schemas/PikkuFunctionTypesSplitInput.schema.json +1 -0
- package/dist/.pikku/schemas/schemas/PikkuTestsCoverageInput.schema.json +1 -1
- package/dist/.pikku/schemas/schemas/PikkuTriggerTypesInput.schema.json +1 -0
- package/dist/.pikku/schemas/schemas/WorkspaceValidateInput.schema.json +1 -0
- package/dist/.pikku/schemas/schemas/WorkspaceValidateOutput.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/meta/allWorkflow.gen.json +5 -5
- 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 +39 -0
- package/dist/src/fabric/functions/validate-core.d.ts +20 -0
- package/dist/src/fabric/functions/validate-core.js +227 -0
- package/dist/src/fabric/functions/validate.function.js +12 -4
- package/dist/src/functions/commands/bootstrap.js +2 -2
- package/dist/src/functions/commands/console.js +7 -4
- package/dist/src/functions/commands/db-audit.d.ts +1 -0
- package/dist/src/functions/commands/db-audit.js +67 -0
- package/dist/src/functions/commands/db-migrate.js +7 -11
- package/dist/src/functions/commands/db-reset.js +12 -12
- package/dist/src/functions/commands/db-seed.js +11 -11
- package/dist/src/functions/commands/db-shared.d.ts +4 -19
- package/dist/src/functions/commands/db-shared.js +53 -17
- package/dist/src/functions/commands/dev.js +25 -14
- package/dist/src/functions/commands/emails-init.d.ts +5 -0
- package/dist/src/functions/commands/emails-init.js +162 -0
- package/dist/src/functions/commands/load-user-project.js +12 -3
- package/dist/src/functions/commands/new-addon.js +2 -2
- package/dist/src/functions/commands/tests-coverage.d.ts +3 -0
- package/dist/src/functions/commands/tests-coverage.js +34 -0
- package/dist/src/functions/commands/watch.js +7 -4
- package/dist/src/functions/commands/workspace-validate.d.ts +33 -0
- package/dist/src/functions/commands/workspace-validate.js +9 -0
- package/dist/src/functions/db/annotation-parser.d.ts +31 -0
- package/dist/src/functions/db/annotation-parser.js +93 -0
- package/dist/src/functions/db/coercion-plugin.d.ts +7 -0
- package/dist/src/functions/db/coercion-plugin.js +99 -0
- package/dist/src/functions/db/db-codegen.d.ts +24 -0
- package/dist/src/functions/db/db-codegen.js +276 -0
- package/dist/src/functions/db/db-introspector.d.ts +15 -0
- package/dist/src/functions/db/db-introspector.js +1 -0
- package/dist/src/functions/db/db-migrator.d.ts +32 -0
- package/dist/src/functions/db/db-migrator.js +65 -0
- package/dist/src/functions/db/local-db.d.ts +27 -33
- package/dist/src/functions/db/local-db.js +108 -57
- package/dist/src/functions/db/postgres/postgres-introspector.d.ts +10 -0
- package/dist/src/functions/db/postgres/postgres-introspector.js +54 -0
- package/dist/src/functions/db/postgres/postgres-migrator.d.ts +9 -0
- package/dist/src/functions/db/postgres/postgres-migrator.js +32 -0
- package/dist/src/functions/db/{seed.d.ts → sqlite/seed.d.ts} +2 -2
- package/dist/src/functions/db/sqlite/sqlite-introspector.d.ts +9 -0
- package/dist/src/functions/db/sqlite/sqlite-introspector.js +35 -0
- package/dist/src/functions/db/sqlite/sqlite-kysely.d.ts +8 -0
- package/dist/src/functions/db/sqlite/sqlite-kysely.js +62 -0
- package/dist/src/functions/db/sqlite/sqlite-migrator.d.ts +10 -0
- package/dist/src/functions/db/sqlite/sqlite-migrator.js +36 -0
- package/dist/src/functions/db/sqlite/sqlite-runtime-bun.d.ts +2 -0
- package/dist/src/functions/db/sqlite/sqlite-runtime-bun.js +52 -0
- package/dist/src/functions/db/sqlite/sqlite-runtime-node.d.ts +2 -0
- package/dist/src/functions/db/sqlite/sqlite-runtime-node.js +51 -0
- package/dist/src/functions/db/sqlite/sqlite-runtime.d.ts +20 -0
- package/dist/src/functions/db/sqlite/sqlite-runtime.js +13 -0
- package/dist/src/functions/validate/workspace-validate.d.ts +34 -0
- package/dist/src/functions/validate/workspace-validate.js +259 -0
- package/dist/src/functions/wirings/ai-agent/serialize-public-agent.js +2 -1
- package/dist/src/functions/wirings/cli/pikku-command-cli-types.js +1 -1
- package/dist/src/functions/wirings/console/serialize-console-functions.js +4 -4
- package/dist/src/functions/wirings/emails/pikku-command-emails.d.ts +6 -0
- package/dist/src/functions/wirings/emails/pikku-command-emails.js +172 -0
- package/dist/src/functions/wirings/emails/serialize-emails.d.ts +20 -0
- package/dist/src/functions/wirings/emails/serialize-emails.js +168 -0
- package/dist/src/functions/wirings/functions/pikku-command-function-types-split.d.ts +7 -1
- package/dist/src/functions/wirings/functions/pikku-command-function-types-split.js +2 -2
- package/dist/src/functions/wirings/functions/serialize-addon-types.js +1 -1
- package/dist/src/functions/wirings/triggers/pikku-command-trigger-types.d.ts +7 -1
- package/dist/src/functions/wirings/triggers/pikku-command-trigger-types.js +2 -2
- package/dist/src/functions/wirings/workflow/pikku-command-workflow.js +1 -1
- package/dist/src/functions/workflows/all.workflow.js +12 -7
- package/dist/src/scaffold/rpc-remote.gen.js +1 -1
- package/dist/src/services.js +2 -0
- package/dist/src/utils/pikku-cli-config.js +6 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +6 -4
- package/skills/pikku-auth-js/SKILL.md +271 -58
- package/skills/pikku-testing/SKILL.md +208 -0
- package/console-app/assets/index-BDOqBctb.js +0 -232
- package/dist/src/functions/db/sql-migrator.d.ts +0 -26
- package/dist/src/functions/db/sql-migrator.js +0 -104
- package/dist/src/functions/db/sqlite-codegen.d.ts +0 -45
- package/dist/src/functions/db/sqlite-codegen.js +0 -294
- /package/dist/src/functions/db/{seed.js → sqlite/seed.js} +0 -0
|
@@ -3,7 +3,7 @@ import { join, resolve } from 'path';
|
|
|
3
3
|
import { pikkuSessionlessFunc } from '#pikku';
|
|
4
4
|
import chokidar from 'chokidar';
|
|
5
5
|
import { pikkuDevReloader } from '@pikku/core/dev';
|
|
6
|
-
import { ConsoleLogger, InMemoryQueueService, InMemoryWorkflowService, InMemoryTriggerService, InMemoryAIRunStateService, } from '@pikku/core/services';
|
|
6
|
+
import { ConsoleLogger, LocalEmailService, InMemoryQueueService, InMemoryWorkflowService, InMemoryTriggerService, InMemoryAIRunStateService, } from '@pikku/core/services';
|
|
7
7
|
import { KyselyAIStorageService, KyselyAIRunStateService, KyselyAgentRunService, } from '@pikku/kysely';
|
|
8
8
|
import { stopSingletonServices } from '@pikku/core';
|
|
9
9
|
import { pikkuState } from '@pikku/core/internal';
|
|
@@ -14,7 +14,7 @@ import { pikkuWebsocketHandler } from '@pikku/ws';
|
|
|
14
14
|
import { PikkuNodeHTTPServer } from '@pikku/node-http-server';
|
|
15
15
|
import { WebSocketServer } from 'ws';
|
|
16
16
|
import { InMemorySchedulerService } from '@pikku/schedule';
|
|
17
|
-
import {
|
|
17
|
+
import { resolveDb, createKysely } from '../db/local-db.js';
|
|
18
18
|
import { loadUserBootstrap, loadUserModule } from './load-user-project.js';
|
|
19
19
|
export const dev = pikkuSessionlessFunc({
|
|
20
20
|
remote: true,
|
|
@@ -23,6 +23,9 @@ export const dev = pikkuSessionlessFunc({
|
|
|
23
23
|
const hostname = 'localhost';
|
|
24
24
|
const enableWatch = watch !== false;
|
|
25
25
|
const enableHmr = hmr !== false;
|
|
26
|
+
const watchDirectories = [
|
|
27
|
+
...new Set([config.emailTemplatesDir, ...config.srcDirectories].filter(Boolean)),
|
|
28
|
+
];
|
|
26
29
|
const commandSingletonServices = pikkuState(null, 'package', 'singletonServices');
|
|
27
30
|
const commandFunctionMeta = {
|
|
28
31
|
...pikkuState(null, 'function', 'meta'),
|
|
@@ -113,19 +116,25 @@ export const dev = pikkuSessionlessFunc({
|
|
|
113
116
|
const userCreateConfig = configModule[pikkuConfigFactory.variable];
|
|
114
117
|
const userCreateSingletonServices = servicesModule[singletonServicesFactory.variable];
|
|
115
118
|
const userConfig = await userCreateConfig();
|
|
116
|
-
const
|
|
119
|
+
const resolvedDb = resolveDb(userConfig, config.rootDir, config.outDir, config.runtimeDir);
|
|
120
|
+
const resolvedLocalDb = resolvedDb?.dialect === 'sqlite'
|
|
121
|
+
? resolvedDb
|
|
122
|
+
: userConfig.sqliteDb
|
|
123
|
+
? resolveDb({ sqliteDb: userConfig.sqliteDb }, config.rootDir, config.outDir, config.runtimeDir)
|
|
124
|
+
: undefined;
|
|
117
125
|
const kysely = resolvedLocalDb
|
|
118
126
|
? await createKysely(resolvedLocalDb)
|
|
119
127
|
: undefined;
|
|
120
128
|
const resolvedRuntimeDir = config.runtimeDir ?? join(config.rootDir, '.pikku-runtime');
|
|
121
|
-
const localContentConfig = userConfig.
|
|
122
|
-
?.content
|
|
129
|
+
const localContentConfig = userConfig.content
|
|
123
130
|
? {
|
|
124
|
-
localFileUploadPath:
|
|
125
|
-
|
|
126
|
-
|
|
131
|
+
localFileUploadPath: userConfig.content.contentPath
|
|
132
|
+
? resolve(config.rootDir, userConfig.content.contentPath)
|
|
133
|
+
: join(resolvedRuntimeDir, 'content'),
|
|
134
|
+
uploadUrlPrefix: userConfig.content.uploadUrlPrefix ?? '/upload',
|
|
135
|
+
assetUrlPrefix: userConfig.content.assetUrlPrefix ?? '/assets',
|
|
127
136
|
server: `http://${hostname}:${resolvedPort}`,
|
|
128
|
-
|
|
137
|
+
sizeLimit: userConfig.content.sizeLimit,
|
|
129
138
|
}
|
|
130
139
|
: undefined;
|
|
131
140
|
const localContent = localContentConfig
|
|
@@ -151,8 +160,10 @@ export const dev = pikkuSessionlessFunc({
|
|
|
151
160
|
// single instance under both names so addons like @pikku/addon-console
|
|
152
161
|
// can read runs in dev without projects having to wire their own backing
|
|
153
162
|
// store.
|
|
163
|
+
const devLogger = new ConsoleLogger();
|
|
154
164
|
const inMemoryServices = {
|
|
155
|
-
logger:
|
|
165
|
+
logger: devLogger,
|
|
166
|
+
emailService: new LocalEmailService(),
|
|
156
167
|
metaService: new LocalMetaService(pikkuDir),
|
|
157
168
|
schedulerService,
|
|
158
169
|
queueService: new InMemoryQueueService(),
|
|
@@ -205,20 +216,20 @@ export const dev = pikkuSessionlessFunc({
|
|
|
205
216
|
});
|
|
206
217
|
if (enableHmr) {
|
|
207
218
|
await pikkuDevReloader({
|
|
208
|
-
srcDirectories:
|
|
219
|
+
srcDirectories: watchDirectories,
|
|
209
220
|
logger,
|
|
210
221
|
});
|
|
211
222
|
}
|
|
212
223
|
if (enableWatch) {
|
|
213
224
|
const genIgnore = /\.gen\.tsx?$/;
|
|
214
|
-
configWatcher = chokidar.watch(
|
|
225
|
+
configWatcher = chokidar.watch(watchDirectories, {
|
|
215
226
|
ignoreInitial: true,
|
|
216
227
|
ignored: genIgnore,
|
|
217
228
|
});
|
|
218
229
|
const generatorWatcher = () => {
|
|
219
230
|
watcher?.close();
|
|
220
|
-
logger.info(`• Watching directories: \n - ${
|
|
221
|
-
watcher = chokidar.watch(
|
|
231
|
+
logger.info(`• Watching directories: \n - ${watchDirectories.join('\n - ')}`);
|
|
232
|
+
watcher = chokidar.watch(watchDirectories, {
|
|
222
233
|
ignoreInitial: true,
|
|
223
234
|
ignored: genIgnore,
|
|
224
235
|
});
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
type EmailsInitInput = {
|
|
2
|
+
force?: boolean;
|
|
3
|
+
};
|
|
4
|
+
export declare const pikkuEmailsInit: import("#pikku").PikkuFunctionConfig<EmailsInitInput, void, "rpc" | "session", import("#pikku").PikkuFunctionSessionless<EmailsInitInput, void, "rpc" | "session", import("#pikku").Services> | import("#pikku").PikkuFunction<EmailsInitInput, void, "rpc" | "session", import("#pikku").Services>, undefined, undefined>;
|
|
5
|
+
export {};
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
import { pikkuSessionlessFunc } from '#pikku';
|
|
5
|
+
import { generateEmailsArtifacts } from '../wirings/emails/pikku-command-emails.js';
|
|
6
|
+
const DEFAULT_EMAIL_DIR = 'emails';
|
|
7
|
+
const DEFAULT_THEME = {
|
|
8
|
+
appName: 'Pikku App',
|
|
9
|
+
previewText: 'Your app can render localized emails out of the box.',
|
|
10
|
+
colors: {
|
|
11
|
+
background: '#f5f7fb',
|
|
12
|
+
surface: '#ffffff',
|
|
13
|
+
text: '#101828',
|
|
14
|
+
muted: '#475467',
|
|
15
|
+
border: '#d0d5dd',
|
|
16
|
+
primary: '#7c3aed',
|
|
17
|
+
primaryText: '#ffffff',
|
|
18
|
+
footer: '#667085',
|
|
19
|
+
},
|
|
20
|
+
fonts: {
|
|
21
|
+
body: "Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif",
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
const DEFAULT_EN_LOCALE = {
|
|
25
|
+
helloWorld: {
|
|
26
|
+
subject: 'Hello from {{appName}}',
|
|
27
|
+
eyebrow: 'Email templates are live',
|
|
28
|
+
title: 'Your first Pikku email is ready',
|
|
29
|
+
intro: 'This starter template proves the pipeline works and gives you a place to shape your own visual language.',
|
|
30
|
+
body: 'Chat to your AI to create new emails, refine the theme, or localize every message for your product.',
|
|
31
|
+
cta: 'Open the email console',
|
|
32
|
+
footer: 'Built with Pikku email templates.',
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
const DEFAULT_DE_LOCALE = {
|
|
36
|
+
helloWorld: {
|
|
37
|
+
subject: 'Hallo von {{appName}}',
|
|
38
|
+
eyebrow: 'E-Mail-Vorlagen sind aktiv',
|
|
39
|
+
title: 'Deine erste Pikku-E-Mail ist bereit',
|
|
40
|
+
intro: 'Diese Startvorlage zeigt, dass die Pipeline funktioniert, und gibt dir einen Ausgangspunkt fuer dein eigenes Design.',
|
|
41
|
+
body: 'Sprich mit deiner KI, um neue E-Mails zu erstellen, das Theme zu verfeinern oder jede Nachricht zu lokalisieren.',
|
|
42
|
+
cta: 'E-Mail-Konsole oeffnen',
|
|
43
|
+
footer: 'Erstellt mit Pikku-E-Mail-Vorlagen.',
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
function layoutTemplate() {
|
|
47
|
+
return `<!doctype html>
|
|
48
|
+
<html lang="{{locale}}">
|
|
49
|
+
<head>
|
|
50
|
+
<meta charset="utf-8" />
|
|
51
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
52
|
+
<title>{{subject}}</title>
|
|
53
|
+
</head>
|
|
54
|
+
<body style="margin:0;padding:32px 16px;background:{{theme.colors.background}};font-family:{{theme.fonts.body}};color:{{theme.colors.text}};">
|
|
55
|
+
<div style="max-width:640px;margin:0 auto;background:{{theme.colors.surface}};border:1px solid {{theme.colors.border}};border-radius:20px;overflow:hidden;">
|
|
56
|
+
{{content}}
|
|
57
|
+
</div>
|
|
58
|
+
</body>
|
|
59
|
+
</html>
|
|
60
|
+
`;
|
|
61
|
+
}
|
|
62
|
+
function footerPartial() {
|
|
63
|
+
return `<div style="padding:24px 32px;border-top:1px solid {{theme.colors.border}};font-size:13px;line-height:1.6;color:{{theme.colors.footer}};">
|
|
64
|
+
<p style="margin:0;">{{t.helloWorld.footer}}</p>
|
|
65
|
+
</div>
|
|
66
|
+
`;
|
|
67
|
+
}
|
|
68
|
+
function helloWorldHtml() {
|
|
69
|
+
return `<div style="padding:32px;">
|
|
70
|
+
<p style="margin:0 0 16px;font-size:12px;letter-spacing:0.12em;text-transform:uppercase;color:{{theme.colors.primary}};">
|
|
71
|
+
{{t.helloWorld.eyebrow}}
|
|
72
|
+
</p>
|
|
73
|
+
<h1 style="margin:0 0 16px;font-size:32px;line-height:1.15;color:{{theme.colors.text}};">
|
|
74
|
+
{{t.helloWorld.title}}
|
|
75
|
+
</h1>
|
|
76
|
+
<p style="margin:0 0 16px;font-size:16px;line-height:1.7;color:{{theme.colors.muted}};">
|
|
77
|
+
Hello {{userName}}. {{t.helloWorld.intro}}
|
|
78
|
+
</p>
|
|
79
|
+
<p style="margin:0 0 24px;font-size:16px;line-height:1.7;color:{{theme.colors.muted}};">
|
|
80
|
+
{{t.helloWorld.body}}
|
|
81
|
+
</p>
|
|
82
|
+
<a
|
|
83
|
+
href="{{previewUrl}}"
|
|
84
|
+
style="display:inline-block;padding:14px 18px;border-radius:999px;background:{{theme.colors.primary}};color:{{theme.colors.primaryText}};font-weight:600;text-decoration:none;"
|
|
85
|
+
>
|
|
86
|
+
{{t.helloWorld.cta}}
|
|
87
|
+
</a>
|
|
88
|
+
</div>
|
|
89
|
+
{{> footer}}
|
|
90
|
+
`;
|
|
91
|
+
}
|
|
92
|
+
function helloWorldSubject() {
|
|
93
|
+
return `{{t.helloWorld.subject}}
|
|
94
|
+
`;
|
|
95
|
+
}
|
|
96
|
+
function helloWorldText() {
|
|
97
|
+
return `{{t.helloWorld.title}}
|
|
98
|
+
|
|
99
|
+
Hello {{userName}}.
|
|
100
|
+
|
|
101
|
+
{{t.helloWorld.intro}}
|
|
102
|
+
|
|
103
|
+
{{t.helloWorld.body}}
|
|
104
|
+
|
|
105
|
+
{{t.helloWorld.cta}}: {{previewUrl}}
|
|
106
|
+
`;
|
|
107
|
+
}
|
|
108
|
+
async function ensureFile(path, content) {
|
|
109
|
+
await mkdir(dirname(path), { recursive: true });
|
|
110
|
+
await writeFile(path, content, 'utf8');
|
|
111
|
+
}
|
|
112
|
+
async function updateJsonConfig(configDir, emailDir) {
|
|
113
|
+
const configPath = join(configDir, 'pikku.config.json');
|
|
114
|
+
if (!existsSync(configPath)) {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
const raw = await readFile(configPath, 'utf8');
|
|
118
|
+
const parsed = JSON.parse(raw);
|
|
119
|
+
if (typeof parsed.emailTemplatesDir === 'string' && parsed.emailTemplatesDir) {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
parsed.emailTemplatesDir = emailDir;
|
|
123
|
+
await writeFile(configPath, `${JSON.stringify(parsed, null, 2)}\n`, 'utf8');
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
export const pikkuEmailsInit = pikkuSessionlessFunc({
|
|
127
|
+
func: async ({ logger, config }, input) => {
|
|
128
|
+
const force = input?.force;
|
|
129
|
+
const configuredEmailDir = config.emailTemplatesDir ?? join(config.rootDir, DEFAULT_EMAIL_DIR);
|
|
130
|
+
const emailDir = config.emailTemplatesDir && config.emailTemplatesDir.length > 0
|
|
131
|
+
? config.emailTemplatesDir
|
|
132
|
+
: join(config.rootDir, DEFAULT_EMAIL_DIR);
|
|
133
|
+
const files = [
|
|
134
|
+
[join(emailDir, 'theme.json'), `${JSON.stringify(DEFAULT_THEME, null, 2)}\n`],
|
|
135
|
+
[join(emailDir, 'locales', 'en.json'), `${JSON.stringify(DEFAULT_EN_LOCALE, null, 2)}\n`],
|
|
136
|
+
[join(emailDir, 'locales', 'de.json'), `${JSON.stringify(DEFAULT_DE_LOCALE, null, 2)}\n`],
|
|
137
|
+
[join(emailDir, 'partials', 'layout.html'), layoutTemplate()],
|
|
138
|
+
[join(emailDir, 'partials', 'footer.html'), footerPartial()],
|
|
139
|
+
[join(emailDir, 'templates', 'hello-world.html'), helloWorldHtml()],
|
|
140
|
+
[join(emailDir, 'templates', 'hello-world.subject.txt'), helloWorldSubject()],
|
|
141
|
+
[join(emailDir, 'templates', 'hello-world.text.txt'), helloWorldText()],
|
|
142
|
+
];
|
|
143
|
+
const existing = !force
|
|
144
|
+
? files.map(([path]) => path).filter((path) => existsSync(path))
|
|
145
|
+
: [];
|
|
146
|
+
if (existing.length > 0) {
|
|
147
|
+
logger.error(`Email scaffold already exists at ${existing[0]}. Use --force to overwrite.`);
|
|
148
|
+
process.exit(1);
|
|
149
|
+
}
|
|
150
|
+
await Promise.all(files.map(([path, content]) => ensureFile(path, content)));
|
|
151
|
+
const configUpdated = !config.emailTemplatesDir &&
|
|
152
|
+
(await updateJsonConfig(config.configDir, DEFAULT_EMAIL_DIR));
|
|
153
|
+
if (!config.emailTemplatesDir && !configUpdated) {
|
|
154
|
+
logger.warn('Unable to auto-update pikku.config.json. Add "emailTemplatesDir": "emails" manually.');
|
|
155
|
+
}
|
|
156
|
+
await generateEmailsArtifacts(logger, {
|
|
157
|
+
emailTemplatesDir: emailDir,
|
|
158
|
+
outDir: config.outDir,
|
|
159
|
+
});
|
|
160
|
+
logger.info(`Email templates initialized at ${configuredEmailDir}`);
|
|
161
|
+
},
|
|
162
|
+
});
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { existsSync } from 'fs';
|
|
2
2
|
import { join } from 'path';
|
|
3
|
+
import { pathToFileURL } from 'url';
|
|
3
4
|
import { register } from 'tsx/esm/api';
|
|
4
5
|
let tsxRegistered = false;
|
|
6
|
+
function isBunRuntime() {
|
|
7
|
+
return typeof globalThis.Bun !== 'undefined';
|
|
8
|
+
}
|
|
5
9
|
/**
|
|
6
10
|
* Globally register tsx so subsequent `import()` calls go through Node's
|
|
7
11
|
* normal resolver — yielding ONE module graph (and one shared pikkuState),
|
|
@@ -13,22 +17,27 @@ let tsxRegistered = false;
|
|
|
13
17
|
* module state (e.g. `pikkuState` registrations from wireHTTPRoutes).
|
|
14
18
|
*/
|
|
15
19
|
function ensureTsxRegistered() {
|
|
20
|
+
if (isBunRuntime())
|
|
21
|
+
return;
|
|
16
22
|
if (tsxRegistered)
|
|
17
23
|
return;
|
|
18
24
|
register();
|
|
19
25
|
tsxRegistered = true;
|
|
20
26
|
}
|
|
27
|
+
async function importUserPath(filePath) {
|
|
28
|
+
return import(pathToFileURL(filePath).href);
|
|
29
|
+
}
|
|
21
30
|
/**
|
|
22
31
|
* Load the generated `pikku-bootstrap.gen.{ts,js}` from the user's project,
|
|
23
32
|
* which in turn pulls in all wiring/meta files so they register into
|
|
24
33
|
* `pikkuState`.
|
|
25
34
|
*/
|
|
26
35
|
export async function loadUserBootstrap(pikkuDir) {
|
|
27
|
-
ensureTsxRegistered();
|
|
28
36
|
const bootstrapTs = join(pikkuDir, 'pikku-bootstrap.gen.ts');
|
|
29
37
|
const bootstrapJs = join(pikkuDir, 'pikku-bootstrap.gen.js');
|
|
30
38
|
const bootstrapPath = existsSync(bootstrapTs) ? bootstrapTs : bootstrapJs;
|
|
31
|
-
|
|
39
|
+
ensureTsxRegistered();
|
|
40
|
+
await importUserPath(bootstrapPath);
|
|
32
41
|
}
|
|
33
42
|
/**
|
|
34
43
|
* Import a user-source TypeScript file (e.g. their config or services
|
|
@@ -36,5 +45,5 @@ export async function loadUserBootstrap(pikkuDir) {
|
|
|
36
45
|
*/
|
|
37
46
|
export async function loadUserModule(filePath) {
|
|
38
47
|
ensureTsxRegistered();
|
|
39
|
-
return
|
|
48
|
+
return importUserPath(filePath);
|
|
40
49
|
}
|
|
@@ -219,7 +219,7 @@ export const createSingletonServices = pikkuAddonServices(async (
|
|
|
219
219
|
config,
|
|
220
220
|
{ secrets, variables }
|
|
221
221
|
) => {
|
|
222
|
-
const creds = await secrets.
|
|
222
|
+
const creds = await secrets.getSecret<${pascalName}Secrets>('${screamingName}_CREDENTIALS')
|
|
223
223
|
const ${camelName} = new ${pascalName}Service(creds, variables)
|
|
224
224
|
|
|
225
225
|
return { ${camelName} }
|
|
@@ -717,7 +717,7 @@ import { createSingletonServices } from './services.js'
|
|
|
717
717
|
test('${name} addon', async () => {
|
|
718
718
|
const secrets = new LocalSecretService()
|
|
719
719
|
// Set up secrets for the service
|
|
720
|
-
// await secrets.
|
|
720
|
+
// await secrets.setSecret('${screamingName}_CREDENTIALS', { ... })
|
|
721
721
|
|
|
722
722
|
const singletonServices = await createSingletonServices({}, { secrets })
|
|
723
723
|
const rpc = rpcService.getContextRPCService(singletonServices as any, {})
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
export declare const pikkuTestsCoverage: import("#pikku").PikkuFunctionConfig<{
|
|
2
2
|
noRun?: boolean;
|
|
3
|
+
aiOut?: string;
|
|
3
4
|
}, void, "rpc" | "session", import("#pikku").PikkuFunctionSessionless<{
|
|
4
5
|
noRun?: boolean;
|
|
6
|
+
aiOut?: string;
|
|
5
7
|
}, void, "rpc" | "session", import("#pikku").Services> | import("#pikku").PikkuFunction<{
|
|
6
8
|
noRun?: boolean;
|
|
9
|
+
aiOut?: string;
|
|
7
10
|
}, void, "rpc" | "session", import("#pikku").Services>, undefined, undefined>;
|
|
@@ -92,6 +92,7 @@ function lineCoverage(fileCov, span) {
|
|
|
92
92
|
export const pikkuTestsCoverage = pikkuSessionlessFunc({
|
|
93
93
|
func: async ({ logger, config }, input) => {
|
|
94
94
|
const noRun = input?.noRun ?? false;
|
|
95
|
+
const aiOut = input?.aiOut ?? null;
|
|
95
96
|
const packageMappings = config.packageMappings ?? {};
|
|
96
97
|
const srcDirs = config.srcDirectories ?? [];
|
|
97
98
|
const functionsRelDir = Object.keys(packageMappings).find((key) => srcDirs.some((src) => src === key || src.startsWith(key + '/'))) ?? Object.keys(packageMappings)[0];
|
|
@@ -227,5 +228,38 @@ export const pikkuTestsCoverage = pikkuSessionlessFunc({
|
|
|
227
228
|
console.log(` ${summary.covered} covered · ${summary.partial} partial · ${summary.uncovered} uncovered · ${summary.unknown} unknown (overall ${(overallRatio * 100).toFixed(1)}%)`);
|
|
228
229
|
if (uncovered.length)
|
|
229
230
|
console.log(` uncovered: ${uncovered.map((f) => f.name).join(', ')}`);
|
|
231
|
+
if (aiOut !== null) {
|
|
232
|
+
const generatedAt = new Date().toISOString();
|
|
233
|
+
const pct = Math.round(overallRatio * 100);
|
|
234
|
+
const needWork = functions.filter((f) => f.status !== 'covered');
|
|
235
|
+
const lines = [
|
|
236
|
+
`Coverage report — generated ${generatedAt}.`,
|
|
237
|
+
`Overall: ${pct}% (${summary.covered}/${summary.total} functions fully covered)`,
|
|
238
|
+
'',
|
|
239
|
+
needWork.length === 0
|
|
240
|
+
? 'All functions are fully covered — nothing to do!'
|
|
241
|
+
: `Functions needing coverage (${needWork.length}):`,
|
|
242
|
+
...needWork.flatMap((fn) => {
|
|
243
|
+
const ratio = Math.round(fn.ratio * 100);
|
|
244
|
+
const missed = fn.missedLines.length > 0 ? fn.missedLines.join(', ') : 'none';
|
|
245
|
+
return [
|
|
246
|
+
`- ${fn.name} [${fn.status}, ${ratio}% covered, ${fn.coveredLines}/${fn.totalLines} lines]`,
|
|
247
|
+
` file: ${fn.sourceFile}`,
|
|
248
|
+
` missed lines: ${missed}`,
|
|
249
|
+
];
|
|
250
|
+
}),
|
|
251
|
+
];
|
|
252
|
+
if (needWork.length > 0) {
|
|
253
|
+
lines.push('', 'Write @pikku/cucumber Gherkin scenarios (no custom steps) in tests/tests/features/ to cover the missed lines above.', 'Use pikku meta to get versioned RPC names and function schemas before writing.', 'Run pikku tests coverage after writing to verify coverage improved.');
|
|
254
|
+
}
|
|
255
|
+
const prompt = lines.join('\n') + '\n';
|
|
256
|
+
if (aiOut === '-') {
|
|
257
|
+
process.stdout.write(prompt);
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
writeFileSync(aiOut, prompt, 'utf-8');
|
|
261
|
+
console.log(`AI prompt → ${relative(config.rootDir, aiOut)}`);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
230
264
|
},
|
|
231
265
|
});
|
|
@@ -4,21 +4,24 @@ import { pikkuDevReloader } from '@pikku/core/dev';
|
|
|
4
4
|
export const watch = pikkuSessionlessFunc({
|
|
5
5
|
remote: true,
|
|
6
6
|
func: async ({ logger, config }, { hmr }, { rpc }) => {
|
|
7
|
+
const watchDirectories = [
|
|
8
|
+
...new Set([config.emailTemplatesDir, ...config.srcDirectories].filter(Boolean)),
|
|
9
|
+
];
|
|
7
10
|
if (hmr) {
|
|
8
11
|
await pikkuDevReloader({
|
|
9
|
-
srcDirectories:
|
|
12
|
+
srcDirectories: watchDirectories,
|
|
10
13
|
logger,
|
|
11
14
|
});
|
|
12
15
|
}
|
|
13
|
-
const configWatcher = chokidar.watch(
|
|
16
|
+
const configWatcher = chokidar.watch(watchDirectories, {
|
|
14
17
|
ignoreInitial: true,
|
|
15
18
|
ignored: /.*\.gen\.tsx?/,
|
|
16
19
|
});
|
|
17
20
|
let watcher = new chokidar.FSWatcher({});
|
|
18
21
|
const generatorWatcher = () => {
|
|
19
22
|
watcher.close();
|
|
20
|
-
logger.info(`• Watching directories: \n - ${
|
|
21
|
-
watcher = chokidar.watch(
|
|
23
|
+
logger.info(`• Watching directories: \n - ${watchDirectories.join('\n - ')}`);
|
|
24
|
+
watcher = chokidar.watch(watchDirectories, {
|
|
22
25
|
ignoreInitial: true,
|
|
23
26
|
ignored: /.*\.gen\.ts/,
|
|
24
27
|
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { renderWorkspaceValidate } from '../validate/workspace-validate.js';
|
|
2
|
+
export declare const workspaceValidate: import("#pikku").PikkuFunctionConfig<Record<string, never>, {
|
|
3
|
+
ok: boolean;
|
|
4
|
+
root: string;
|
|
5
|
+
findings: {
|
|
6
|
+
id: string;
|
|
7
|
+
severity: "info" | "warn" | "error";
|
|
8
|
+
message: string;
|
|
9
|
+
path: string;
|
|
10
|
+
fixHint: string;
|
|
11
|
+
}[];
|
|
12
|
+
}, "rpc" | "session", import("#pikku").PikkuFunctionSessionless<Record<string, never>, {
|
|
13
|
+
ok: boolean;
|
|
14
|
+
root: string;
|
|
15
|
+
findings: {
|
|
16
|
+
id: string;
|
|
17
|
+
severity: "info" | "warn" | "error";
|
|
18
|
+
message: string;
|
|
19
|
+
path: string;
|
|
20
|
+
fixHint: string;
|
|
21
|
+
}[];
|
|
22
|
+
}, "rpc" | "session", import("#pikku").Services> | import("#pikku").PikkuFunction<Record<string, never>, {
|
|
23
|
+
ok: boolean;
|
|
24
|
+
root: string;
|
|
25
|
+
findings: {
|
|
26
|
+
id: string;
|
|
27
|
+
severity: "info" | "warn" | "error";
|
|
28
|
+
message: string;
|
|
29
|
+
path: string;
|
|
30
|
+
fixHint: string;
|
|
31
|
+
}[];
|
|
32
|
+
}, "rpc" | "session", import("#pikku").Services>, undefined, undefined>;
|
|
33
|
+
export { renderWorkspaceValidate };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { pikkuSessionlessFunc } from '#pikku';
|
|
2
|
+
import { WorkspaceValidateInput, WorkspaceValidateOutput, renderWorkspaceValidate, runWorkspaceValidate, } from '../validate/workspace-validate.js';
|
|
3
|
+
export const workspaceValidate = pikkuSessionlessFunc({
|
|
4
|
+
description: 'Check the current Pikku workspace structure for compatibility. Prints all missing or misconfigured items with fix hints.',
|
|
5
|
+
input: WorkspaceValidateInput,
|
|
6
|
+
output: WorkspaceValidateOutput,
|
|
7
|
+
func: async () => runWorkspaceValidate(),
|
|
8
|
+
});
|
|
9
|
+
export { renderWorkspaceValidate };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { ColumnKind } from './coercion-plugin.js';
|
|
2
|
+
type Classification = 'public' | 'private' | 'secret';
|
|
3
|
+
type AnonymizeStrategy = 'fake:email' | 'fake:name' | 'hash' | 'keep' | null;
|
|
4
|
+
export interface ColAnnotation {
|
|
5
|
+
kind?: ColumnKind;
|
|
6
|
+
/** TypeScript type string for @json columns, e.g. `string[]`. */
|
|
7
|
+
tsType?: string;
|
|
8
|
+
classification?: Classification;
|
|
9
|
+
anonymize?: AnonymizeStrategy;
|
|
10
|
+
}
|
|
11
|
+
/** Per-table, per-column annotation map built from migration SQL comments. */
|
|
12
|
+
export type AnnotationMap = Record<string, Record<string, ColAnnotation>>;
|
|
13
|
+
/**
|
|
14
|
+
* Determine column kind from naming conventions:
|
|
15
|
+
* *_at / *_on → date
|
|
16
|
+
* is_* / has_* / can_* → bool
|
|
17
|
+
*/
|
|
18
|
+
export declare function annotationFromName(colName: string): {
|
|
19
|
+
kind: ColumnKind;
|
|
20
|
+
} | null;
|
|
21
|
+
/**
|
|
22
|
+
* Parse `-- @bool | @date | @json [TsType] | @public | @private[:strategy] | @secret[:strategy]`
|
|
23
|
+
* inline annotations from migration SQL files in `migrationsDir`.
|
|
24
|
+
*
|
|
25
|
+
* Multiple annotations on the same comment line are supported, e.g.:
|
|
26
|
+
* `deleted_at TIMESTAMP -- @date @private:keep`
|
|
27
|
+
*
|
|
28
|
+
* Covers both CREATE TABLE body lines and ALTER TABLE ... ADD [COLUMN] statements.
|
|
29
|
+
*/
|
|
30
|
+
export declare function parseAnnotations(migrationsDir: string): AnnotationMap;
|
|
31
|
+
export {};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { readFileSync, readdirSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
/**
|
|
4
|
+
* Determine column kind from naming conventions:
|
|
5
|
+
* *_at / *_on → date
|
|
6
|
+
* is_* / has_* / can_* → bool
|
|
7
|
+
*/
|
|
8
|
+
export function annotationFromName(colName) {
|
|
9
|
+
if (/_at$|_on$/.test(colName))
|
|
10
|
+
return { kind: 'date' };
|
|
11
|
+
if (/^is_|^has_|^can_/.test(colName))
|
|
12
|
+
return { kind: 'bool' };
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
function parseStrategy(s) {
|
|
16
|
+
if (!s)
|
|
17
|
+
return null;
|
|
18
|
+
const valid = ['fake:email', 'fake:name', 'hash', 'keep'];
|
|
19
|
+
return valid.includes(s) ? s : null;
|
|
20
|
+
}
|
|
21
|
+
function parseComment(comment) {
|
|
22
|
+
const ann = {};
|
|
23
|
+
if (/@bool\b/i.test(comment)) {
|
|
24
|
+
ann.kind = 'bool';
|
|
25
|
+
}
|
|
26
|
+
else if (/@date\b/i.test(comment)) {
|
|
27
|
+
ann.kind = 'date';
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
const jsonM = comment.match(/@json\b(?:\s+([^\s@]+))?/i);
|
|
31
|
+
if (jsonM) {
|
|
32
|
+
ann.kind = 'json';
|
|
33
|
+
if (jsonM[1])
|
|
34
|
+
ann.tsType = jsonM[1].trim();
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const classM = comment.match(/@(public|private|secret)(?::([^\s@]+))?/i);
|
|
38
|
+
if (classM) {
|
|
39
|
+
ann.classification = classM[1].toLowerCase();
|
|
40
|
+
ann.anonymize = parseStrategy(classM[2]);
|
|
41
|
+
}
|
|
42
|
+
return ann;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Parse `-- @bool | @date | @json [TsType] | @public | @private[:strategy] | @secret[:strategy]`
|
|
46
|
+
* inline annotations from migration SQL files in `migrationsDir`.
|
|
47
|
+
*
|
|
48
|
+
* Multiple annotations on the same comment line are supported, e.g.:
|
|
49
|
+
* `deleted_at TIMESTAMP -- @date @private:keep`
|
|
50
|
+
*
|
|
51
|
+
* Covers both CREATE TABLE body lines and ALTER TABLE ... ADD [COLUMN] statements.
|
|
52
|
+
*/
|
|
53
|
+
export function parseAnnotations(migrationsDir) {
|
|
54
|
+
let files;
|
|
55
|
+
try {
|
|
56
|
+
files = readdirSync(migrationsDir).filter((f) => f.endsWith('.sql')).sort();
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return {};
|
|
60
|
+
}
|
|
61
|
+
const result = {};
|
|
62
|
+
function merge(tableName, colName, partial) {
|
|
63
|
+
if (!partial.kind && partial.classification === undefined)
|
|
64
|
+
return;
|
|
65
|
+
if (!result[tableName])
|
|
66
|
+
result[tableName] = {};
|
|
67
|
+
result[tableName][colName] = { ...result[tableName][colName], ...partial };
|
|
68
|
+
}
|
|
69
|
+
for (const file of files) {
|
|
70
|
+
const content = readFileSync(join(migrationsDir, file), 'utf8');
|
|
71
|
+
const createTablePattern = /CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?"?(\w+)"?\s*\(([^;]+)\)/gis;
|
|
72
|
+
let tableMatch;
|
|
73
|
+
while ((tableMatch = createTablePattern.exec(content)) !== null) {
|
|
74
|
+
const tableName = tableMatch[1].toLowerCase();
|
|
75
|
+
const body = tableMatch[2];
|
|
76
|
+
for (const line of body.split('\n')) {
|
|
77
|
+
const trimmed = line.trim();
|
|
78
|
+
if (/^(PRIMARY|UNIQUE|CHECK|FOREIGN|CONSTRAINT)/i.test(trimmed))
|
|
79
|
+
continue;
|
|
80
|
+
const lineMatch = trimmed.match(/^(\w+)\s+\w[^-]*--\s*(.+?)\s*,?\s*$/);
|
|
81
|
+
if (!lineMatch)
|
|
82
|
+
continue;
|
|
83
|
+
merge(tableName, lineMatch[1].toLowerCase(), parseComment(lineMatch[2]));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
const alterPattern = /ALTER\s+TABLE\s+"?(\w+)"?\s+ADD\s+(?:COLUMN\s+)?"?(\w+)"?\s+\w[^;\n-]*(?:;\s*)?--\s*(.+?)(?:\r?\n|$)/gim;
|
|
87
|
+
let alterMatch;
|
|
88
|
+
while ((alterMatch = alterPattern.exec(content)) !== null) {
|
|
89
|
+
merge(alterMatch[1].toLowerCase(), alterMatch[2].toLowerCase(), parseComment(alterMatch[3]));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return result;
|
|
93
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { KyselyPlugin } from 'kysely';
|
|
2
|
+
export type ColumnKind = 'date' | 'bool' | 'json';
|
|
3
|
+
export type CoercionMap = Record<string, Record<string, ColumnKind>>;
|
|
4
|
+
export interface CreateCoercionPluginOptions {
|
|
5
|
+
map: CoercionMap;
|
|
6
|
+
}
|
|
7
|
+
export declare function createCoercionPlugin(options: CreateCoercionPluginOptions): KyselyPlugin;
|