@pikku/cli 0.12.89 → 0.12.91

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) hide show
  1. package/README.md +25 -2
  2. package/console-app/assets/{index-BaOGIr2O.js → index-CpkvmAGb.js} +148 -148
  3. package/console-app/assets/{index-CSzCJzBb.css → index-D0HG8q0B.css} +1 -1
  4. package/console-app/index.html +2 -2
  5. package/dist/.pikku/agent/pikku-agent-types.gen.d.ts +1 -1
  6. package/dist/.pikku/channel/pikku-channel-types.gen.d.ts +1 -1
  7. package/dist/.pikku/channel/pikku-channel-types.gen.js +1 -1
  8. package/dist/.pikku/cli/pikku-cli-channel.js +21 -1
  9. package/dist/.pikku/cli/pikku-cli-client.gen.d.ts +1 -1
  10. package/dist/.pikku/cli/pikku-cli-client.gen.js +1 -1
  11. package/dist/.pikku/cli/pikku-cli-contracts-meta.gen.d.ts +1 -1
  12. package/dist/.pikku/cli/pikku-cli-contracts-meta.gen.js +1 -1
  13. package/dist/.pikku/cli/pikku-cli-contracts-meta.gen.json +6 -1
  14. package/dist/.pikku/cli/pikku-cli-types.gen.d.ts +1 -1
  15. package/dist/.pikku/cli/pikku-cli-types.gen.js +1 -1
  16. package/dist/.pikku/cli/pikku-cli-wirings-meta.gen.js +1 -1
  17. package/dist/.pikku/cli/pikku-cli-wirings-meta.gen.json +52 -1
  18. package/dist/.pikku/cli/pikku-cli-wirings.gen.d.ts +1 -1
  19. package/dist/.pikku/cli/pikku-cli-wirings.gen.js +1 -1
  20. package/dist/.pikku/cli/pikku-cli.gen.d.ts +1 -1
  21. package/dist/.pikku/cli/pikku-cli.gen.js +1 -1
  22. package/dist/.pikku/console/pikku-node-types.gen.d.ts +1 -1
  23. package/dist/.pikku/function/pikku-function-types.gen.d.ts +4 -4
  24. package/dist/.pikku/function/pikku-function-types.gen.js +1 -1
  25. package/dist/.pikku/function/pikku-functions-meta.gen.js +1 -1
  26. package/dist/.pikku/function/pikku-functions-meta.gen.json +73 -13
  27. package/dist/.pikku/function/pikku-functions.gen.js +9 -1
  28. package/dist/.pikku/http/pikku-http-types.gen.d.ts +1 -1
  29. package/dist/.pikku/http/pikku-http-types.gen.js +1 -1
  30. package/dist/.pikku/mcp/pikku-mcp-types.gen.d.ts +1 -1
  31. package/dist/.pikku/mcp/pikku-mcp-types.gen.js +1 -1
  32. package/dist/.pikku/pikku-bootstrap-scenarios.gen.d.ts +8 -0
  33. package/dist/.pikku/pikku-bootstrap-scenarios.gen.js +8 -0
  34. package/dist/.pikku/pikku-bootstrap.gen.d.ts +1 -1
  35. package/dist/.pikku/pikku-bootstrap.gen.js +1 -1
  36. package/dist/.pikku/pikku-meta-service.gen.d.ts +1 -1
  37. package/dist/.pikku/pikku-meta-service.gen.js +1 -1
  38. package/dist/.pikku/pikku-services.gen.d.ts +1 -1
  39. package/dist/.pikku/pikku-types.gen.d.ts +1 -1
  40. package/dist/.pikku/pikku-types.gen.js +1 -1
  41. package/dist/.pikku/queue/pikku-queue-types.gen.d.ts +1 -1
  42. package/dist/.pikku/queue/pikku-queue-types.gen.js +1 -1
  43. package/dist/.pikku/queue/pikku-queue-workers-wirings-meta.gen.js +1 -1
  44. package/dist/.pikku/queue/pikku-queue-workers-wirings.gen.d.ts +1 -1
  45. package/dist/.pikku/queue/pikku-queue-workers-wirings.gen.js +1 -1
  46. package/dist/.pikku/rpc/pikku-rpc-wirings-meta.internal.gen.js +1 -1
  47. package/dist/.pikku/rpc/pikku-rpc-wirings-meta.internal.gen.json +4 -0
  48. package/dist/.pikku/scenarios/pikku-scenario-functions-meta.gen.d.ts +1 -0
  49. package/dist/.pikku/scenarios/pikku-scenario-functions-meta.gen.js +10 -0
  50. package/dist/.pikku/scenarios/pikku-scenario-functions-meta.gen.json +1 -0
  51. package/dist/.pikku/scenarios/pikku-scenario-functions.gen.d.ts +4 -0
  52. package/dist/.pikku/scenarios/pikku-scenario-functions.gen.js +1 -0
  53. package/dist/.pikku/scenarios/pikku-scenario-wirings-meta.gen.d.ts +1 -0
  54. package/dist/.pikku/scenarios/pikku-scenario-wirings-meta.gen.js +10 -0
  55. package/dist/.pikku/scenarios/pikku-scenario-wirings.gen.d.ts +4 -0
  56. package/dist/.pikku/scenarios/pikku-scenario-wirings.gen.js +1 -0
  57. package/dist/.pikku/scheduler/pikku-scheduler-types.gen.d.ts +1 -1
  58. package/dist/.pikku/scheduler/pikku-scheduler-types.gen.js +1 -1
  59. package/dist/.pikku/schemas/register.gen.js +9 -1
  60. package/dist/.pikku/schemas/schemas/DbBaselineInput.schema.json +1 -0
  61. package/dist/.pikku/schemas/schemas/DbCheckInput.schema.json +1 -0
  62. package/dist/.pikku/schemas/schemas/DbCodegenInput.schema.json +1 -0
  63. package/dist/.pikku/schemas/schemas/DbExportInput.schema.json +1 -0
  64. package/dist/.pikku/schemas/schemas/FabricValidateInput.schema.json +1 -1
  65. package/dist/.pikku/schemas/schemas/PikkuCLIConfig.schema.json +1 -1
  66. package/dist/.pikku/schemas/schemas/ScenarioRunInput.schema.json +1 -1
  67. package/dist/.pikku/scopes/pikku-scope-types.gen.d.ts +1 -1
  68. package/dist/.pikku/scopes/pikku-scope-types.gen.js +1 -1
  69. package/dist/.pikku/scopes/pikku-scopes.gen.d.ts +1 -1
  70. package/dist/.pikku/secrets/pikku-secret-types.gen.d.ts +1 -1
  71. package/dist/.pikku/secrets/pikku-secret-types.gen.js +1 -1
  72. package/dist/.pikku/secrets/pikku-secrets.gen.d.ts +1 -1
  73. package/dist/.pikku/secrets/pikku-secrets.gen.js +1 -1
  74. package/dist/.pikku/trigger/pikku-trigger-types.gen.d.ts +1 -1
  75. package/dist/.pikku/trigger/pikku-trigger-types.gen.js +1 -1
  76. package/dist/.pikku/variables/pikku-variable-types.gen.d.ts +1 -1
  77. package/dist/.pikku/variables/pikku-variable-types.gen.js +1 -1
  78. package/dist/.pikku/variables/pikku-variables.gen.d.ts +1 -1
  79. package/dist/.pikku/variables/pikku-variables.gen.js +1 -1
  80. package/dist/.pikku/workflow/pikku-scenario-actors.gen.d.ts +19 -0
  81. package/dist/.pikku/workflow/pikku-scenario-actors.gen.js +17 -0
  82. package/dist/.pikku/workflow/pikku-workflow-types.gen.d.ts +152 -3
  83. package/dist/.pikku/workflow/pikku-workflow-types.gen.js +39 -1
  84. package/dist/.pikku/workflow/pikku-workflow-wirings-meta.gen.js +1 -1
  85. package/dist/.pikku/workflow/pikku-workflow-wirings.gen.js +1 -1
  86. package/dist/bin/pikku-bin.mjs +2 -2
  87. package/dist/src/cli.wiring.js +42 -0
  88. package/dist/src/fabric/fabric-commands.d.ts +9 -3
  89. package/dist/src/fabric/fabric-commands.js +6 -0
  90. package/dist/src/fabric/functions/validate.function.d.ts +15 -5
  91. package/dist/src/fabric/functions/validate.function.js +69 -16
  92. package/dist/src/fabric/lib/config.d.ts +11 -1
  93. package/dist/src/fabric/lib/config.js +39 -1
  94. package/dist/src/fabric/lib/frontend-typecheck.d.ts +46 -0
  95. package/dist/src/fabric/lib/frontend-typecheck.js +139 -0
  96. package/dist/src/functions/commands/db-baseline.d.ts +13 -0
  97. package/dist/src/functions/commands/db-baseline.js +48 -0
  98. package/dist/src/functions/commands/db-check.d.ts +12 -0
  99. package/dist/src/functions/commands/db-check.js +64 -0
  100. package/dist/src/functions/commands/db-codegen.d.ts +11 -0
  101. package/dist/src/functions/commands/db-codegen.js +39 -0
  102. package/dist/src/functions/commands/db-export.d.ts +12 -0
  103. package/dist/src/functions/commands/db-export.js +31 -0
  104. package/dist/src/functions/commands/db-generate.js +19 -26
  105. package/dist/src/functions/commands/dev.js +2 -0
  106. package/dist/src/functions/commands/load-user-project.d.ts +7 -0
  107. package/dist/src/functions/commands/load-user-project.js +21 -0
  108. package/dist/src/functions/commands/pikku-command-bootstrap.js +12 -0
  109. package/dist/src/functions/commands/scenario-browser.d.ts +69 -0
  110. package/dist/src/functions/commands/scenario-browser.js +60 -0
  111. package/dist/src/functions/commands/scenario-formatter.d.ts +81 -0
  112. package/dist/src/functions/commands/scenario-formatter.js +120 -0
  113. package/dist/src/functions/commands/scenario-ladder.d.ts +66 -0
  114. package/dist/src/functions/commands/scenario-ladder.js +132 -0
  115. package/dist/src/functions/commands/scenario-plan.d.ts +61 -0
  116. package/dist/src/functions/commands/scenario-plan.js +106 -0
  117. package/dist/src/functions/commands/scenario.d.ts +18 -0
  118. package/dist/src/functions/commands/scenario.js +305 -38
  119. package/dist/src/functions/commands/serve.js +2 -0
  120. package/dist/src/functions/db/db-migrator.d.ts +35 -4
  121. package/dist/src/functions/db/db-migrator.js +67 -10
  122. package/dist/src/functions/db/local-db.d.ts +235 -13
  123. package/dist/src/functions/db/local-db.js +430 -45
  124. package/dist/src/functions/db/postgres/postgres-introspector.js +19 -1
  125. package/dist/src/functions/db/postgres/postgres-migrator.d.ts +1 -0
  126. package/dist/src/functions/db/postgres/postgres-migrator.js +4 -1
  127. package/dist/src/functions/db/sqlite/sqlite-introspector.js +2 -1
  128. package/dist/src/functions/db/sqlite/sqlite-migrator.d.ts +1 -0
  129. package/dist/src/functions/db/sqlite/sqlite-migrator.js +6 -1
  130. package/dist/src/functions/wirings/functions/pikku-command-functions.js +26 -8
  131. package/dist/src/functions/wirings/functions/serialize-function-types.js +3 -3
  132. package/dist/src/functions/wirings/scenarios/scenario-partition.d.ts +29 -0
  133. package/dist/src/functions/wirings/scenarios/scenario-partition.js +58 -0
  134. package/dist/src/functions/wirings/scenarios/serialize-feature-meta.d.ts +14 -0
  135. package/dist/src/functions/wirings/scenarios/serialize-feature-meta.js +29 -0
  136. package/dist/src/functions/wirings/scenarios/serialize-scenario-meta.d.ts +12 -0
  137. package/dist/src/functions/wirings/scenarios/serialize-scenario-meta.js +56 -0
  138. package/dist/src/functions/wirings/scenarios/serialize-scenario-registration.d.ts +7 -0
  139. package/dist/src/functions/wirings/scenarios/serialize-scenario-registration.js +40 -0
  140. package/dist/src/functions/wirings/workflow/pikku-command-workflow.js +43 -8
  141. package/dist/src/functions/wirings/workflow/serialize-scenario-actors.d.ts +1 -1
  142. package/dist/src/functions/wirings/workflow/serialize-scenario-actors.js +13 -2
  143. package/dist/src/functions/wirings/workflow/serialize-scenario-step-map.d.ts +4 -0
  144. package/dist/src/functions/wirings/workflow/serialize-scenario-step-map.js +62 -0
  145. package/dist/src/functions/wirings/workflow/serialize-workflow-bootstrap-map.js +10 -3
  146. package/dist/src/functions/wirings/workflow/serialize-workflow-map.js +10 -3
  147. package/dist/src/functions/wirings/workflow/serialize-workflow-meta.js +4 -4
  148. package/dist/src/functions/wirings/workflow/serialize-workflow-types.d.ts +1 -1
  149. package/dist/src/functions/wirings/workflow/serialize-workflow-types.js +232 -3
  150. package/dist/src/server/server-ready.d.ts +12 -0
  151. package/dist/src/server/server-ready.js +12 -0
  152. package/dist/src/server/spawn-dev-server.d.ts +50 -0
  153. package/dist/src/server/spawn-dev-server.js +112 -0
  154. package/dist/src/services.js +12 -1
  155. package/dist/src/utils/meta-diff.js +5 -2
  156. package/dist/src/utils/pikku-cli-config.js +26 -0
  157. package/dist/src/utils/resolve-scenario-actors.d.ts +23 -0
  158. package/dist/src/utils/resolve-scenario-actors.js +74 -0
  159. package/dist/tsconfig.tsbuildinfo +1 -1
  160. package/package.json +16 -6
  161. package/skills/pikku-i18n/SKILL.md +53 -6
  162. package/skills/pikku-scenario/SKILL.md +196 -29
@@ -0,0 +1,46 @@
1
+ export interface FrontendTypeCheckResult {
2
+ /** fabric.config.json frontend key, e.g. "app" */
3
+ name: string;
4
+ /** absolute directory that was type-checked */
5
+ dir: string;
6
+ /**
7
+ * The invocation that actually ran, e.g. `yarn run tsc` or `npx tsc --noEmit`.
8
+ * A finding tells the user to reproduce it, so it has to be the command this
9
+ * check chose — not a guess at their package manager.
10
+ */
11
+ command: string;
12
+ /** raw `file(line,col): error TSxxxx: message` lines, in tsc's order */
13
+ errors: string[];
14
+ /** set when the frontend could not be checked at all (no tsconfig, tsc missing) */
15
+ skipped?: string;
16
+ }
17
+ /**
18
+ * Run each deployable frontend's type-check exactly as the Fabric build
19
+ * container does, so a type error fails `fabric validate` on the developer's
20
+ * machine instead of eight minutes into a deploy.
21
+ *
22
+ * The container runs the frontend's own `tsc` script (falling back to
23
+ * `tsc --noEmit`) and aborts the deploy on a non-zero exit; the deploy budget
24
+ * is 10/day, so spending one on a TS2353 is expensive. Everything else in
25
+ * validate is a structural heuristic *approximating* this compile — this is the
26
+ * compile.
27
+ */
28
+ export declare function typeCheckFrontends(root: string, frontendDirs: Array<{
29
+ name: string;
30
+ dir: string;
31
+ }>): Promise<FrontendTypeCheckResult[]>;
32
+ /**
33
+ * The frontend's own `tsc` script when it has one — that is what the build
34
+ * container runs — otherwise `tsc --noEmit` through the project's package
35
+ * manager, so the compiler resolved is the workspace-local one.
36
+ */
37
+ export declare function resolveTypeCheckCommand(root: string, dir: string): Promise<{
38
+ command: string;
39
+ args: string[];
40
+ }>;
41
+ /**
42
+ * Keep only tsc's diagnostic lines. The runner wraps the command with its own
43
+ * chatter ("$ tsc --noEmit", "error Command failed with exit code 2"), which is
44
+ * noise in a finding.
45
+ */
46
+ export declare function parseTscErrors(output: string): string[];
@@ -0,0 +1,139 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { existsSync } from 'node:fs';
3
+ import { readFile } from 'node:fs/promises';
4
+ import { join } from 'node:path';
5
+ /**
6
+ * Run each deployable frontend's type-check exactly as the Fabric build
7
+ * container does, so a type error fails `fabric validate` on the developer's
8
+ * machine instead of eight minutes into a deploy.
9
+ *
10
+ * The container runs the frontend's own `tsc` script (falling back to
11
+ * `tsc --noEmit`) and aborts the deploy on a non-zero exit; the deploy budget
12
+ * is 10/day, so spending one on a TS2353 is expensive. Everything else in
13
+ * validate is a structural heuristic *approximating* this compile — this is the
14
+ * compile.
15
+ */
16
+ export async function typeCheckFrontends(root, frontendDirs) {
17
+ const results = [];
18
+ for (const { name, dir } of frontendDirs) {
19
+ const { command, args } = await resolveTypeCheckCommand(root, dir);
20
+ const invocation = [command, ...args].join(' ');
21
+ if (!existsSync(join(dir, 'tsconfig.json'))) {
22
+ results.push({
23
+ name,
24
+ dir,
25
+ command: invocation,
26
+ errors: [],
27
+ skipped: 'no tsconfig.json',
28
+ });
29
+ continue;
30
+ }
31
+ const { code, output, spawnError } = await run(command, args, dir);
32
+ if (spawnError) {
33
+ results.push({
34
+ name,
35
+ dir,
36
+ command: invocation,
37
+ errors: [],
38
+ skipped: spawnError,
39
+ });
40
+ continue;
41
+ }
42
+ results.push({
43
+ name,
44
+ dir,
45
+ command: invocation,
46
+ errors: code === 0 ? [] : parseTscErrors(output),
47
+ });
48
+ }
49
+ return results;
50
+ }
51
+ /**
52
+ * The frontend's own `tsc` script when it has one — that is what the build
53
+ * container runs — otherwise `tsc --noEmit` through the project's package
54
+ * manager, so the compiler resolved is the workspace-local one.
55
+ */
56
+ export async function resolveTypeCheckCommand(root, dir) {
57
+ const pkg = await readPackageJson(dir);
58
+ const runner = await resolveRunner(root, dir);
59
+ return pkg?.scripts?.tsc
60
+ ? { command: runner, args: ['run', 'tsc'] }
61
+ : { command: runner === 'npm' ? 'npx' : runner, args: ['tsc', '--noEmit'] };
62
+ }
63
+ async function readPackageJson(dir) {
64
+ try {
65
+ return JSON.parse(await readFile(join(dir, 'package.json'), 'utf-8'));
66
+ }
67
+ catch {
68
+ // A frontend without a readable package.json still gets checked via the
69
+ // `tsc --noEmit` fallback — the missing manifest is reported by other checks.
70
+ return null;
71
+ }
72
+ }
73
+ /**
74
+ * The package manager the project declares, so `run tsc` resolves the same
75
+ * workspace-local typescript the deploy would. Falls back to npm.
76
+ */
77
+ async function resolveRunner(root, dir) {
78
+ for (const candidate of [dir, root]) {
79
+ const pkg = await readPackageJson(candidate);
80
+ const declared = pkg?.packageManager;
81
+ if (declared)
82
+ return declared.split('@')[0];
83
+ }
84
+ if (existsSync(join(root, 'bun.lock')) || existsSync(join(root, 'bun.lockb')))
85
+ return 'bun';
86
+ if (existsSync(join(root, 'yarn.lock')))
87
+ return 'yarn';
88
+ if (existsSync(join(root, 'pnpm-lock.yaml')))
89
+ return 'pnpm';
90
+ return 'npm';
91
+ }
92
+ function run(command, args, cwd) {
93
+ return new Promise((resolve) => {
94
+ let output = '';
95
+ const child = spawn(command, args, {
96
+ cwd,
97
+ shell: process.platform === 'win32',
98
+ env: { ...process.env, FORCE_COLOR: '0', NO_COLOR: '1' },
99
+ });
100
+ child.stdout?.on('data', (chunk) => {
101
+ output += chunk.toString();
102
+ });
103
+ child.stderr?.on('data', (chunk) => {
104
+ output += chunk.toString();
105
+ });
106
+ child.on('error', (error) => {
107
+ resolve({
108
+ code: 1,
109
+ output,
110
+ spawnError: error.code === 'ENOENT'
111
+ ? `${command} not found on PATH`
112
+ : error.message,
113
+ });
114
+ });
115
+ child.on('close', (code) => resolve({ code: code ?? 1, output }));
116
+ });
117
+ }
118
+ /**
119
+ * Keep only tsc's diagnostic lines. The runner wraps the command with its own
120
+ * chatter ("$ tsc --noEmit", "error Command failed with exit code 2"), which is
121
+ * noise in a finding.
122
+ */
123
+ export function parseTscErrors(output) {
124
+ const diagnostic = /^\S.*?\(\d+,\d+\): (?:error|warning) TS\d+: /;
125
+ const errors = output
126
+ .split('\n')
127
+ .map((line) => line.trimEnd())
128
+ .filter((line) => diagnostic.test(line));
129
+ if (errors.length > 0)
130
+ return errors;
131
+ // A non-zero exit with no parseable diagnostic still failed the build — keep
132
+ // the tail rather than reporting a clean pass. A silent failure (no output at
133
+ // all) must still return something: an empty list here reads as "compiled
134
+ // fine" and lets the deploy the check exists to prevent go ahead.
135
+ const tail = output.trim().split('\n').slice(-5).filter(Boolean);
136
+ return tail.length > 0
137
+ ? tail
138
+ : ['type-check exited non-zero without producing any output'];
139
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Record the pending migrations as applied, without running them.
3
+ *
4
+ * For a database that already contains what they describe. The case it exists
5
+ * for is a schema that was created at boot and written down afterwards: every
6
+ * deployment already has those tables, so applying the migration fails, and
7
+ * leaving it pending forever means the history never catches up with reality.
8
+ *
9
+ * Refuses unless the database really is up to date, because that is the entire
10
+ * premise. A database that is behind gets the same report `db check` gives —
11
+ * baselining it would bury a real gap under a history claiming otherwise.
12
+ */
13
+ export declare const dbBaseline: import("#pikku").PikkuFunctionConfig<{}, void, "rpc" | "session", import("#pikku").PikkuFunctionSessionless<{}, void, "rpc" | "session", import("#pikku").WiredServices> | import("#pikku").PikkuFunction<{}, void, "rpc" | "session", import("#pikku").WiredServices>, undefined, undefined>;
@@ -0,0 +1,48 @@
1
+ import { pikkuSessionlessFunc } from '#pikku';
2
+ import { resolveDb, baseline } from '../db/local-db.js';
3
+ import { loadUserConfigForDb } from './db-shared.js';
4
+ /**
5
+ * Record the pending migrations as applied, without running them.
6
+ *
7
+ * For a database that already contains what they describe. The case it exists
8
+ * for is a schema that was created at boot and written down afterwards: every
9
+ * deployment already has those tables, so applying the migration fails, and
10
+ * leaving it pending forever means the history never catches up with reality.
11
+ *
12
+ * Refuses unless the database really is up to date, because that is the entire
13
+ * premise. A database that is behind gets the same report `db check` gives —
14
+ * baselining it would bury a real gap under a history claiming otherwise.
15
+ */
16
+ export const dbBaseline = pikkuSessionlessFunc({
17
+ remote: true,
18
+ func: async ({ logger, config }) => {
19
+ const userConfig = await loadUserConfigForDb({ config, logger });
20
+ if (!userConfig)
21
+ return;
22
+ const resolved = resolveDb(userConfig, config.rootDir, config.outDir, config.runtimeDir);
23
+ if (!resolved) {
24
+ logger.error('pikku db baseline: no database configured — set sqliteDb or postgresUrl in your createConfig.');
25
+ throw new Error('no database configured');
26
+ }
27
+ const result = await baseline(resolved, config.rootDir, config.srcDirectories, logger);
28
+ if (result.status === 'behind') {
29
+ logger.error('db baseline: refused — the database does not yet contain everything the migrations describe:');
30
+ if (result.drift.missingTables.length) {
31
+ logger.error(` missing tables: ${result.drift.missingTables.join(', ')}`);
32
+ }
33
+ for (const { table, columns } of result.drift.missingColumns) {
34
+ logger.error(` ${table} missing columns: ${columns.join(', ')}`);
35
+ }
36
+ logger.error(' Baselining now would record migrations that never ran. Run `pikku db migrate` instead.');
37
+ throw new Error('database is behind its migrations');
38
+ }
39
+ if (result.recorded.length === 0) {
40
+ logger.info('db baseline: nothing pending — every migration is recorded');
41
+ return;
42
+ }
43
+ logger.info(`db baseline: recorded ${result.recorded.length} migration(s) as applied without running them:`);
44
+ for (const name of result.recorded) {
45
+ logger.info(` ${name}`);
46
+ }
47
+ },
48
+ });
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Report how the configured database differs from the schema its migrations
3
+ * define.
4
+ *
5
+ * Answers the question nobody can otherwise answer without going and looking by
6
+ * hand: does this database still match what we wrote down? Missing tables and
7
+ * columns fail the command — the database is behind and a migration has not
8
+ * been applied. Tables the migrations never mention are reported but never
9
+ * fail: something created them outside the migration history, which is worth
10
+ * seeing but is not this command's to fix.
11
+ */
12
+ export declare const dbCheck: import("#pikku").PikkuFunctionConfig<{}, void, "rpc" | "session", import("#pikku").PikkuFunctionSessionless<{}, void, "rpc" | "session", import("#pikku").WiredServices> | import("#pikku").PikkuFunction<{}, void, "rpc" | "session", import("#pikku").WiredServices>, undefined, undefined>;
@@ -0,0 +1,64 @@
1
+ import { pikkuSessionlessFunc } from '#pikku';
2
+ import { resolveDb, computeSchemaDrift } from '../db/local-db.js';
3
+ import { loadUserConfigForDb } from './db-shared.js';
4
+ /**
5
+ * Report how the configured database differs from the schema its migrations
6
+ * define.
7
+ *
8
+ * Answers the question nobody can otherwise answer without going and looking by
9
+ * hand: does this database still match what we wrote down? Missing tables and
10
+ * columns fail the command — the database is behind and a migration has not
11
+ * been applied. Tables the migrations never mention are reported but never
12
+ * fail: something created them outside the migration history, which is worth
13
+ * seeing but is not this command's to fix.
14
+ */
15
+ export const dbCheck = pikkuSessionlessFunc({
16
+ remote: true,
17
+ func: async ({ logger, config }) => {
18
+ const userConfig = await loadUserConfigForDb({ config, logger });
19
+ if (!userConfig)
20
+ return;
21
+ const resolved = resolveDb(userConfig, config.rootDir, config.outDir, config.runtimeDir);
22
+ if (!resolved) {
23
+ logger.error('pikku db check: no database configured — set sqliteDb or postgresUrl in your createConfig.');
24
+ throw new Error('no database configured');
25
+ }
26
+ const drift = await computeSchemaDrift(resolved, config.rootDir, config.srcDirectories, logger);
27
+ // Table names elide the `public` schema, which reads as ambiguous in a
28
+ // report whose whole point can be a second copy of `app.orders` sitting in
29
+ // `public`. Put the schema back for display only.
30
+ const qualify = (table) => resolved.dialect === 'postgres' && !table.includes('.')
31
+ ? `public.${table}`
32
+ : table;
33
+ if (drift.runtimeTables.length) {
34
+ logger.info(`db check: ${drift.runtimeTables.length} table(s) the pikku runtime created at boot, which no migration records:`);
35
+ for (const table of drift.runtimeTables) {
36
+ logger.info(` ${qualify(table)}`);
37
+ }
38
+ logger.info(' Run `pikku db generate` to write them down, so the schema stops depending on which services happened to start.');
39
+ }
40
+ if (drift.extraTables.length) {
41
+ logger.info(`db check: ${drift.extraTables.length} table(s) in the database that no migration creates:`);
42
+ for (const table of drift.extraTables) {
43
+ logger.info(` ${qualify(table)}`);
44
+ }
45
+ logger.info(' Left alone — a migration cannot know whether these hold data worth keeping.');
46
+ for (const { schema, requires, owner } of drift.skippedRuntimeSchemas) {
47
+ logger.info(` Note: the runtime's '${schema}' tables could not be recognised — it needs '${requires}', which this project does not create. ${owner} owns it.`);
48
+ }
49
+ }
50
+ if (drift.inSync) {
51
+ logger.info('db check: the database matches its migrations');
52
+ return;
53
+ }
54
+ logger.error('db check: the database is behind its migrations:');
55
+ if (drift.missingTables.length) {
56
+ logger.error(` missing tables: ${drift.missingTables.map(qualify).join(', ')}`);
57
+ }
58
+ for (const { table, columns } of drift.missingColumns) {
59
+ logger.error(` ${qualify(table)} missing columns: ${columns.join(', ')}`);
60
+ }
61
+ logger.error(' Run `pikku db migrate` to apply them.');
62
+ throw new Error('database schema is behind its migrations');
63
+ },
64
+ });
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Regenerate the database types from the migration files alone, without
3
+ * connecting to the configured database.
4
+ *
5
+ * `db migrate` can only emit types after it has migrated the real database,
6
+ * which forces codegen to run late — after a deploy has already moved the
7
+ * schema. This command applies the same migrations to a throwaway database and
8
+ * introspects that, so `pikku all` can be given an accurate table zod on a
9
+ * machine with no database reachable at all.
10
+ */
11
+ export declare const dbCodegen: import("#pikku").PikkuFunctionConfig<{}, void, "rpc" | "session", import("#pikku").PikkuFunctionSessionless<{}, void, "rpc" | "session", import("#pikku").WiredServices> | import("#pikku").PikkuFunction<{}, void, "rpc" | "session", import("#pikku").WiredServices>, undefined, undefined>;
@@ -0,0 +1,39 @@
1
+ import { pikkuSessionlessFunc } from '#pikku';
2
+ import { resolveDb, migrateAndCodegen } from '../db/local-db.js';
3
+ import { loadUserConfigForDb } from './db-shared.js';
4
+ /**
5
+ * Regenerate the database types from the migration files alone, without
6
+ * connecting to the configured database.
7
+ *
8
+ * `db migrate` can only emit types after it has migrated the real database,
9
+ * which forces codegen to run late — after a deploy has already moved the
10
+ * schema. This command applies the same migrations to a throwaway database and
11
+ * introspects that, so `pikku all` can be given an accurate table zod on a
12
+ * machine with no database reachable at all.
13
+ */
14
+ export const dbCodegen = pikkuSessionlessFunc({
15
+ remote: true,
16
+ func: async ({ logger, config }) => {
17
+ const userConfig = await loadUserConfigForDb({ config, logger });
18
+ if (!userConfig)
19
+ return;
20
+ const resolved = resolveDb(userConfig, config.rootDir, config.outDir, config.runtimeDir);
21
+ if (!resolved) {
22
+ logger.error('pikku db codegen: no database configured — set sqliteDb or postgresUrl in your createConfig.');
23
+ throw new Error('no database configured');
24
+ }
25
+ const { migrate, codegen, zod } = await migrateAndCodegen(resolved, {
26
+ scratch: true,
27
+ });
28
+ for (const warning of codegen.warnings) {
29
+ logger.diagnostic(warning);
30
+ }
31
+ logger.info(`db codegen: applied ${migrate.applied.length} migration(s) to a scratch ${resolved.dialect} database`);
32
+ logger.info(codegen.written
33
+ ? `db codegen: regenerated ${codegen.outFile} (${codegen.tables.length} tables)`
34
+ : `db codegen: ${codegen.outFile} unchanged`);
35
+ logger.info(zod.written
36
+ ? `db codegen: regenerated ${zod.outFile} (${zod.tables.length} tables)`
37
+ : `db codegen: ${zod.outFile} unchanged`);
38
+ },
39
+ });
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Publish this package's schema so a project consuming it as an addon can
3
+ * create its tables.
4
+ *
5
+ * An addon has no database of its own — it runs inside the consumer, against
6
+ * the consumer's — so it must never create tables at boot. It says what it
7
+ * needs, and the consumer's `db generate` folds that into its own migration
8
+ * history, where the project can review it like any other migration.
9
+ *
10
+ * Run this from the addon's build, beside the rest of its codegen.
11
+ */
12
+ export declare const dbExport: import("#pikku").PikkuFunctionConfig<{}, void, "rpc" | "session", import("#pikku").PikkuFunctionSessionless<{}, void, "rpc" | "session", import("#pikku").WiredServices> | import("#pikku").PikkuFunction<{}, void, "rpc" | "session", import("#pikku").WiredServices>, undefined, undefined>;
@@ -0,0 +1,31 @@
1
+ import { mkdirSync, writeFileSync } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ import { pikkuSessionlessFunc } from '#pikku';
4
+ import { exportSchema } from '../db/local-db.js';
5
+ /**
6
+ * Publish this package's schema so a project consuming it as an addon can
7
+ * create its tables.
8
+ *
9
+ * An addon has no database of its own — it runs inside the consumer, against
10
+ * the consumer's — so it must never create tables at boot. It says what it
11
+ * needs, and the consumer's `db generate` folds that into its own migration
12
+ * history, where the project can review it like any other migration.
13
+ *
14
+ * Run this from the addon's build, beside the rest of its codegen.
15
+ */
16
+ export const dbExport = pikkuSessionlessFunc({
17
+ remote: true,
18
+ func: async ({ logger, config }) => {
19
+ const artifact = await exportSchema(config.rootDir);
20
+ const dialects = Object.keys(artifact);
21
+ if (dialects.length === 0) {
22
+ logger.info('db export: no db/sqlite or db/postgres migrations — nothing to publish');
23
+ return;
24
+ }
25
+ const file = join(config.outDir, 'db', 'pikku-db-meta.gen.json');
26
+ mkdirSync(dirname(file), { recursive: true });
27
+ writeFileSync(file, `${JSON.stringify(artifact, null, 2)}\n`, 'utf8');
28
+ logger.info(`db export: wrote ${file} for ${dialects.join(', ')}`);
29
+ logger.info(' Ship it with the package — a consumer resolves it through the package name.');
30
+ },
31
+ });
@@ -1,9 +1,9 @@
1
1
  import { pikkuSessionlessFunc } from '#pikku';
2
- import { resolveDb, generateAuthMigration } from '../db/local-db.js';
2
+ import { resolveDb, generateMigrations } from '../db/local-db.js';
3
3
  import { loadUserConfigForDb } from './db-shared.js';
4
4
  export const dbGenerate = pikkuSessionlessFunc({
5
5
  remote: true,
6
- func: async ({ logger, config }) => {
6
+ func: async ({ logger, config, getInspectorState }) => {
7
7
  const userConfig = await loadUserConfigForDb({ config, logger });
8
8
  if (!userConfig)
9
9
  return;
@@ -12,31 +12,24 @@ export const dbGenerate = pikkuSessionlessFunc({
12
12
  logger.error('pikku db generate: no database configured — set sqliteDb or postgresUrl in your createConfig.');
13
13
  throw new Error('no database configured');
14
14
  }
15
- const result = await generateAuthMigration(resolved, config.rootDir, config.srcDirectories, logger);
16
- switch (result.status) {
17
- case 'no-auth':
18
- logger.info('db generate: no pikkuBetterAuth found — nothing to generate');
19
- return;
20
- case 'up-to-date':
21
- logger.info('db generate: Better Auth schema already covered by existing migrations — nothing to generate');
22
- return;
23
- case 'incremental-unsupported': {
24
- const cols = (result.missingColumns ?? [])
25
- .map((m) => `${m.table}(${m.columns.join(', ')})`)
26
- .join('; ');
27
- logger.error('db generate: the Better Auth config requires schema changes on top of an existing auth schema:');
28
- if (result.missingTables?.length) {
29
- logger.error(` missing tables: ${result.missingTables.join(', ')}`);
30
- }
31
- if (cols)
32
- logger.error(` missing columns: ${cols}`);
33
- logger.error(' Write a forward migration adding these by hand (incremental auto-generation is not yet supported).');
34
- throw new Error('incremental auth schema change requires a manual migration');
15
+ const state = await getInspectorState();
16
+ const addons = [...state.rpc.wireAddonDeclarations.values()].map(({ package: pkg, remote }) => ({ package: pkg, remote }));
17
+ const { upToDate, written } = await generateMigrations(resolved, config.rootDir, config.srcDirectories, logger, addons);
18
+ for (const source of upToDate) {
19
+ logger.info(`db generate: ${source} is already covered by existing migrations`);
20
+ }
21
+ if (written.length === 0) {
22
+ if (upToDate.length === 0) {
23
+ logger.info('db generate: nothing declares tables — nothing to generate');
24
+ }
25
+ return;
26
+ }
27
+ for (const { source, file, needsBackfill } of written) {
28
+ logger.info(`db generate: wrote ${file} for ${source}`);
29
+ for (const column of needsBackfill) {
30
+ logger.warn(` ${column} is NOT NULL with no default — decide what existing rows get before applying.`);
35
31
  }
36
- case 'written':
37
- logger.info(`db generate: wrote ${result.file}`);
38
- logger.info(' Review it, then run `pikku db migrate` to apply.');
39
- return;
40
32
  }
33
+ logger.info(' Review them, then run `pikku db migrate` to apply.');
41
34
  },
42
35
  });
@@ -14,6 +14,7 @@ import { loadUserBootstrap, loadUserModule } from './load-user-project.js';
14
14
  import { startCoverageService } from './start-coverage.js';
15
15
  import { createDevAIAgentRunner } from './dev-ai-runner.js';
16
16
  import { resolveConsoleMount } from './serve-console.js';
17
+ import { serverReadyLine } from '../../server/server-ready.js';
17
18
  export const dev = pikkuSessionlessFunc({
18
19
  remote: true,
19
20
  func: async ({ logger, config, getInspectorState, invalidateInspectorState, variables, devServerRunner, }, { port, watch, hmr, coverage, test }, { rpc }) => {
@@ -238,6 +239,7 @@ export const dev = pikkuSessionlessFunc({
238
239
  if (consoleMount) {
239
240
  logger.info(`Pikku Console available at http://${hostname}:${resolvedPort}${consoleMount.urlPrefix}`);
240
241
  }
242
+ logger.info(serverReadyLine(hostname, resolvedPort));
241
243
  let configWatcher;
242
244
  let watcher;
243
245
  process.once('SIGINT', async () => {
@@ -4,6 +4,13 @@
4
4
  * `pikkuState`.
5
5
  */
6
6
  export declare function loadUserBootstrap(pikkuDir: string): Promise<void>;
7
+ /**
8
+ * Load the generated `pikku-bootstrap-scenarios.gen.{ts,js}`, which imports the
9
+ * app bootstrap and then registers the scenarios, features and steps that are
10
+ * deliberately kept out of it. Falls back to the app bootstrap for a project
11
+ * generated before the split existed.
12
+ */
13
+ export declare function loadScenarioBootstrap(pikkuDir: string): Promise<void>;
7
14
  /**
8
15
  * Import a user-source TypeScript file (e.g. their config or services
9
16
  * factory) so it can be loaded from inside @pikku/cli's compiled JS.
@@ -39,6 +39,27 @@ export async function loadUserBootstrap(pikkuDir) {
39
39
  ensureTsxRegistered();
40
40
  await importUserPath(bootstrapPath);
41
41
  }
42
+ /**
43
+ * Load the generated `pikku-bootstrap-scenarios.gen.{ts,js}`, which imports the
44
+ * app bootstrap and then registers the scenarios, features and steps that are
45
+ * deliberately kept out of it. Falls back to the app bootstrap for a project
46
+ * generated before the split existed.
47
+ */
48
+ export async function loadScenarioBootstrap(pikkuDir) {
49
+ const scenarioTs = join(pikkuDir, 'pikku-bootstrap-scenarios.gen.ts');
50
+ const scenarioJs = join(pikkuDir, 'pikku-bootstrap-scenarios.gen.js');
51
+ const scenarioPath = existsSync(scenarioTs)
52
+ ? scenarioTs
53
+ : existsSync(scenarioJs)
54
+ ? scenarioJs
55
+ : null;
56
+ if (!scenarioPath) {
57
+ await loadUserBootstrap(pikkuDir);
58
+ return;
59
+ }
60
+ ensureTsxRegistered();
61
+ await importUserPath(scenarioPath);
62
+ }
42
63
  /**
43
64
  * Import a user-source TypeScript file (e.g. their config or services
44
65
  * factory) so it can be loaded from inside @pikku/cli's compiled JS.
@@ -64,5 +64,17 @@ export const pikkuBootstrap = pikkuSessionlessFunc({
64
64
  })
65
65
  .join('\n');
66
66
  await writeFileInDir(logger, config.bootstrapFile, allBootstrapImports);
67
+ // The scenario bootstrap is the only entry point that registers scenarios,
68
+ // features and their steps. It pulls in the app bootstrap first so a runner
69
+ // needs to load one file, and so the meta files it merges onto are already
70
+ // in place however this module is reached.
71
+ const scenarioBootstrapImports = [
72
+ config.bootstrapFile,
73
+ config.scenarioStepsMetaFile,
74
+ config.scenarioStepsFile,
75
+ config.scenarioWiringsMetaFile,
76
+ config.scenarioWiringsFile,
77
+ ].map((to) => `import '${getFileImportRelativePath(config.scenarioBootstrapFile, to, config.packageMappings, config.forceRelativeImports)}'`);
78
+ await writeFileInDir(logger, config.scenarioBootstrapFile, scenarioBootstrapImports.join('\n'));
67
79
  },
68
80
  });
@@ -0,0 +1,69 @@
1
+ /**
2
+ * The scenario runner's browser lifecycle.
3
+ *
4
+ * Two jobs, both kept out of the run loop so they are testable without
5
+ * launching a browser: deciding whether a run needs a driver at all (and
6
+ * failing early, with instructions, when it does and cannot have one), and
7
+ * making the driver's optional isolation and diagnostics safe to call
8
+ * unconditionally.
9
+ */
10
+ import type { ScenarioBrowserFailure, ScenarioBrowserProvider } from '@pikku/core/workflow';
11
+ import type { ScenarioActorConfig } from '@pikku/core/services';
12
+ /** The default driver, used when a project names no other. */
13
+ export declare const DEFAULT_BROWSER_DRIVER = "@pikku/playwright";
14
+ export interface ScenarioBrowserDriverOptions {
15
+ secret: string;
16
+ actors: Record<string, ScenarioActorConfig>;
17
+ signInPath?: string;
18
+ failureDir?: string;
19
+ config: unknown;
20
+ }
21
+ /**
22
+ * What a browser driver package exports.
23
+ *
24
+ * Nothing here is playwright-specific: a driver is anything that can hand a
25
+ * scenario step a `ScenarioBrowserProvider`. `@pikku/playwright` is the one
26
+ * pikku ships, named by `scenarios.browserDriver` only as a default.
27
+ */
28
+ export interface ScenarioBrowserDriver {
29
+ createScenarioBrowserProvider?: (options: ScenarioBrowserDriverOptions) => ScenarioBrowserProvider;
30
+ /** The class form the bundled driver exports. */
31
+ PlaywrightScenarioBrowserProvider?: new (options: ScenarioBrowserDriverOptions) => ScenarioBrowserProvider;
32
+ /** Driver-specific config (headed, slowMo, timeouts) resolved from the env. */
33
+ browserConfigFromEnv?: (overrides: {
34
+ appUrl: string;
35
+ apiUrl: string;
36
+ }) => unknown;
37
+ }
38
+ export interface ResolveScenarioBrowserProviderOptions {
39
+ /** The environment name, for error messages that tell the user what to edit. */
40
+ environment: string;
41
+ apiUrl: string;
42
+ appUrl?: string;
43
+ secret: string;
44
+ actors: Record<string, ScenarioActorConfig>;
45
+ signInPath?: string;
46
+ /** Where failure screenshots are written. */
47
+ failureDir: string;
48
+ /** The scenarios that declared browser steps, named in the missing-driver error. */
49
+ browserScenarios: string[];
50
+ /** Package to drive the browser. Defaults to `@pikku/playwright`. */
51
+ driver?: string;
52
+ importDriver?: (specifier: string) => Promise<ScenarioBrowserDriver>;
53
+ }
54
+ /**
55
+ * Both failure modes here are discovered before the first scenario starts. A
56
+ * missing `appUrl` or an uninstalled driver found mid-run costs a whole run to
57
+ * learn about.
58
+ */
59
+ export declare const resolveScenarioBrowserProvider: ({ environment, apiUrl, appUrl, secret, actors, signInPath, failureDir, browserScenarios, driver, importDriver, }: ResolveScenarioBrowserProviderOptions) => Promise<ScenarioBrowserProvider>;
60
+ /**
61
+ * The three calls the run loop makes, safe against a run with no browser and
62
+ * against a driver that implements neither optional member.
63
+ */
64
+ export interface ScenarioBrowserLifecycle {
65
+ reset(): Promise<void>;
66
+ captureFailure(label: string): Promise<ScenarioBrowserFailure[]>;
67
+ close(): Promise<void>;
68
+ }
69
+ export declare const scenarioBrowserLifecycle: (provider: ScenarioBrowserProvider | undefined) => ScenarioBrowserLifecycle;