@pikku/cli 0.12.66 → 0.12.68

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 (86) hide show
  1. package/cli.schema.json +1 -1
  2. package/console-app/assets/{index-94z1KYC_.js → index-CttM2Aiz.js} +141 -141
  3. package/console-app/index.html +1 -1
  4. package/dist/.pikku/agent/pikku-agent-types.gen.d.ts +1 -1
  5. package/dist/.pikku/channel/pikku-channel-types.gen.d.ts +1 -1
  6. package/dist/.pikku/channel/pikku-channel-types.gen.js +1 -1
  7. package/dist/.pikku/cli/pikku-cli-channel.js +1 -1
  8. package/dist/.pikku/cli/pikku-cli-client.gen.d.ts +1 -1
  9. package/dist/.pikku/cli/pikku-cli-client.gen.js +1 -1
  10. package/dist/.pikku/cli/pikku-cli-contracts-meta.gen.d.ts +1 -1
  11. package/dist/.pikku/cli/pikku-cli-contracts-meta.gen.js +1 -1
  12. package/dist/.pikku/cli/pikku-cli-types.gen.d.ts +1 -1
  13. package/dist/.pikku/cli/pikku-cli-types.gen.js +1 -1
  14. package/dist/.pikku/cli/pikku-cli-wirings-meta.gen.js +1 -1
  15. package/dist/.pikku/cli/pikku-cli-wirings-meta.gen.json +8 -0
  16. package/dist/.pikku/cli/pikku-cli-wirings.gen.d.ts +1 -1
  17. package/dist/.pikku/cli/pikku-cli-wirings.gen.js +1 -1
  18. package/dist/.pikku/cli/pikku-cli.gen.d.ts +1 -1
  19. package/dist/.pikku/cli/pikku-cli.gen.js +1 -1
  20. package/dist/.pikku/console/pikku-node-types.gen.d.ts +1 -1
  21. package/dist/.pikku/function/pikku-function-types.gen.d.ts +1 -1
  22. package/dist/.pikku/function/pikku-function-types.gen.js +1 -1
  23. package/dist/.pikku/function/pikku-functions-meta.gen.js +1 -1
  24. package/dist/.pikku/function/pikku-functions-meta.gen.json +6 -6
  25. package/dist/.pikku/function/pikku-functions.gen.js +1 -1
  26. package/dist/.pikku/http/pikku-http-types.gen.d.ts +1 -1
  27. package/dist/.pikku/http/pikku-http-types.gen.js +1 -1
  28. package/dist/.pikku/http/pikku-http-wirings-meta.gen.js +1 -1
  29. package/dist/.pikku/http/pikku-http-wirings.gen.d.ts +1 -1
  30. package/dist/.pikku/http/pikku-http-wirings.gen.js +1 -1
  31. package/dist/.pikku/mcp/pikku-mcp-types.gen.d.ts +1 -1
  32. package/dist/.pikku/mcp/pikku-mcp-types.gen.js +1 -1
  33. package/dist/.pikku/pikku-bootstrap.gen.d.ts +1 -1
  34. package/dist/.pikku/pikku-bootstrap.gen.js +1 -1
  35. package/dist/.pikku/pikku-meta-service.gen.d.ts +1 -1
  36. package/dist/.pikku/pikku-meta-service.gen.js +1 -1
  37. package/dist/.pikku/pikku-services.gen.d.ts +1 -1
  38. package/dist/.pikku/pikku-types.gen.d.ts +1 -1
  39. package/dist/.pikku/pikku-types.gen.js +1 -1
  40. package/dist/.pikku/queue/pikku-queue-types.gen.d.ts +1 -1
  41. package/dist/.pikku/queue/pikku-queue-types.gen.js +1 -1
  42. package/dist/.pikku/queue/pikku-queue-workers-wirings-meta.gen.js +1 -1
  43. package/dist/.pikku/queue/pikku-queue-workers-wirings.gen.d.ts +1 -1
  44. package/dist/.pikku/queue/pikku-queue-workers-wirings.gen.js +1 -1
  45. package/dist/.pikku/rpc/pikku-rpc-wirings-meta.internal.gen.js +1 -1
  46. package/dist/.pikku/scheduler/pikku-scheduler-types.gen.d.ts +1 -1
  47. package/dist/.pikku/scheduler/pikku-scheduler-types.gen.js +1 -1
  48. package/dist/.pikku/schemas/register.gen.js +1 -1
  49. package/dist/.pikku/schemas/schemas/PikkuCLIConfig.schema.json +1 -1
  50. package/dist/.pikku/secrets/pikku-secret-types.gen.d.ts +1 -1
  51. package/dist/.pikku/secrets/pikku-secret-types.gen.js +1 -1
  52. package/dist/.pikku/secrets/pikku-secrets.gen.d.ts +1 -1
  53. package/dist/.pikku/secrets/pikku-secrets.gen.js +1 -1
  54. package/dist/.pikku/trigger/pikku-trigger-types.gen.d.ts +1 -1
  55. package/dist/.pikku/trigger/pikku-trigger-types.gen.js +1 -1
  56. package/dist/.pikku/variables/pikku-variable-types.gen.d.ts +1 -1
  57. package/dist/.pikku/variables/pikku-variable-types.gen.js +1 -1
  58. package/dist/.pikku/variables/pikku-variables.gen.d.ts +1 -1
  59. package/dist/.pikku/variables/pikku-variables.gen.js +1 -1
  60. package/dist/.pikku/workflow/meta/allWorkflow.gen.json +6 -1
  61. package/dist/.pikku/workflow/pikku-workflow-types.gen.d.ts +1 -1
  62. package/dist/.pikku/workflow/pikku-workflow-types.gen.js +1 -1
  63. package/dist/.pikku/workflow/pikku-workflow-wirings-meta.gen.js +1 -1
  64. package/dist/.pikku/workflow/pikku-workflow-wirings.gen.js +1 -1
  65. package/dist/bin/pikku-bin.mjs +2 -2
  66. package/dist/src/cli.wiring.js +8 -0
  67. package/dist/src/fabric/functions/validate.function.js +1 -1
  68. package/dist/src/functions/commands/audit.js +25 -8
  69. package/dist/src/functions/commands/console.js +1 -1
  70. package/dist/src/functions/commands/enable.js +4 -4
  71. package/dist/src/functions/validate/workspace-validate.js +1 -1
  72. package/dist/src/functions/wirings/console/pikku-command-console-functions.js +13 -0
  73. package/dist/src/functions/wirings/console/pikku-command-nodes-meta.js +5 -1
  74. package/dist/src/functions/wirings/console/serialize-console-functions.js +6 -11
  75. package/dist/src/functions/wirings/functions/pikku-command-functions.js +5 -3
  76. package/dist/src/functions/workflows/all.workflow.js +17 -0
  77. package/dist/src/utils/strip-verbose-meta.d.ts +3 -0
  78. package/dist/src/utils/strip-verbose-meta.js +8 -0
  79. package/dist/src/utils/tsc-check.d.ts +46 -0
  80. package/dist/src/utils/tsc-check.js +141 -0
  81. package/dist/tsconfig.tsbuildinfo +1 -1
  82. package/package.json +3 -3
  83. package/skills/pikku-better-auth/SKILL.md +4 -0
  84. package/skills/pikku-deps/SKILL.md +91 -0
  85. package/skills/pikku-i18n/SKILL.md +58 -185
  86. package/skills/pikku-kysely/SKILL.md +68 -2
@@ -1,5 +1,5 @@
1
1
  /**
2
- * This file was generated by @pikku/cli@0.12.66
2
+ * This file was generated by @pikku/cli@0.12.68
3
3
  */
4
4
  /**
5
5
  * Trigger-specific type definitions for tree-shaking optimization
@@ -1,5 +1,5 @@
1
1
  /**
2
- * This file was generated by @pikku/cli@0.12.66
2
+ * This file was generated by @pikku/cli@0.12.68
3
3
  */
4
4
  /**
5
5
  * Trigger-specific type definitions for tree-shaking optimization
@@ -1,5 +1,5 @@
1
1
  /**
2
- * This file was generated by @pikku/cli@0.12.66
2
+ * This file was generated by @pikku/cli@0.12.68
3
3
  */
4
4
  export { wireVariable } from '@pikku/core/variable';
5
5
  export type { CoreVariable, VariableDefinitionMeta, VariableDefinitionsMeta } from '@pikku/core/variable';
@@ -1,4 +1,4 @@
1
1
  /**
2
- * This file was generated by @pikku/cli@0.12.66
2
+ * This file was generated by @pikku/cli@0.12.68
3
3
  */
4
4
  export { wireVariable } from '@pikku/core/variable';
@@ -1,5 +1,5 @@
1
1
  /**
2
- * This file was generated by @pikku/cli@0.12.66
2
+ * This file was generated by @pikku/cli@0.12.68
3
3
  */
4
4
  import { TypedVariablesService as CoreTypedVariablesService } from '@pikku/core/services';
5
5
  import type { VariablesService } from '@pikku/core/services';
@@ -1,5 +1,5 @@
1
1
  /**
2
- * This file was generated by @pikku/cli@0.12.66
2
+ * This file was generated by @pikku/cli@0.12.68
3
3
  */
4
4
  import { TypedVariablesService as CoreTypedVariablesService } from '@pikku/core/services';
5
5
  const VARIABLES_META = {};
@@ -545,11 +545,16 @@
545
545
  "Summary": {
546
546
  "nodeId": "Summary",
547
547
  "rpcName": "pikkuSummary",
548
+ "next": "Type check",
548
549
  "stepHash": "a6c8f33339e2"
550
+ },
551
+ "Type check": {
552
+ "nodeId": "Type check",
553
+ "flow": "inline"
549
554
  }
550
555
  },
551
556
  "entryNodeIds": [
552
557
  "Bootstrap inspect"
553
558
  ],
554
- "graphHash": "7b64c5235ba0"
559
+ "graphHash": "0c9134fe80d5"
555
560
  }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * This file was generated by @pikku/cli@0.12.66
2
+ * This file was generated by @pikku/cli@0.12.68
3
3
  */
4
4
  import { WorkflowCancelledException } from '@pikku/core/workflow';
5
5
  import { template } from '@pikku/core/workflow';
@@ -1,5 +1,5 @@
1
1
  /**
2
- * This file was generated by @pikku/cli@0.12.66
2
+ * This file was generated by @pikku/cli@0.12.68
3
3
  */
4
4
  import { WorkflowCancelledException } from '@pikku/core/workflow';
5
5
  import { template } from '@pikku/core/workflow';
@@ -1,5 +1,5 @@
1
1
  /**
2
- * This file was generated by @pikku/cli@0.12.66
2
+ * This file was generated by @pikku/cli@0.12.68
3
3
  */
4
4
  import { pikkuState } from '@pikku/core/internal';
5
5
  import allWorkflowMeta from './meta/allWorkflow.gen.json' with { type: 'json' };
@@ -1,5 +1,5 @@
1
1
  /**
2
- * This file was generated by @pikku/cli@0.12.66
2
+ * This file was generated by @pikku/cli@0.12.68
3
3
  */
4
4
  import { addWorkflow } from '@pikku/core/workflow';
5
5
  import './pikku-workflow-wirings-meta.gen.js';
@@ -11,8 +11,8 @@ async function checkForUpdate() {
11
11
  })
12
12
  if (!res.ok) return
13
13
  const { version: latest } = await res.json()
14
- if (latest !== '0.12.66') {
15
- process.stderr.write(`\n Update available 0.12.66 → ${latest}\n brew upgrade pikku or npm install -g @pikku/cli\n\n`)
14
+ if (latest !== '0.12.68') {
15
+ process.stderr.write(`\n Update available 0.12.68 → ${latest}\n brew upgrade pikku or npm install -g @pikku/cli\n\n`)
16
16
  }
17
17
  } catch {}
18
18
  }
@@ -86,6 +86,14 @@ wireCLI({
86
86
  description: 'Fail the build on critical diagnostics. Always on; accepted for symmetry with --fail-on-error/--fail-on-warn.',
87
87
  default: true,
88
88
  },
89
+ tsc: {
90
+ description: "After codegen, run a real tsc --noEmit over the project's tsconfig and fail on type errors. Prints full diagnostics with code frames. Off by default.",
91
+ default: false,
92
+ },
93
+ tscSummary: {
94
+ description: 'Like --tsc but prints a compact one-line-per-error summary (no code frames, node_modules filtered, capped) — cheap for AI agents / CI. Fails on type errors.',
95
+ default: false,
96
+ },
89
97
  userSessionType: {
90
98
  description: 'Specify which UserSession type to use (when multiple exist)',
91
99
  },
@@ -228,7 +228,7 @@ export async function runValidate(startDir = process.cwd()) {
228
228
  key: 'console',
229
229
  severity: 'error',
230
230
  surface: 'app introspection (console:getFunctionsMeta and friends) — the sandbox builder shows no functions',
231
- value: '"no-auth"',
231
+ value: '"auth"',
232
232
  },
233
233
  {
234
234
  key: 'rpc',
@@ -4,6 +4,9 @@ import { spawnSync } from 'node:child_process';
4
4
  import { pikkuSessionlessFunc } from '#pikku';
5
5
  const SCHEMA_VERSION = 1;
6
6
  const SEVERITY_ORDER = ['critical', 'high', 'moderate', 'low', 'info'];
7
+ // Artifact shape (SecurityAuditReport / …Issue / …Update) is the canonical type
8
+ // from @pikku/core — imported above. `Severity`/`PackageManager` below are local
9
+ // runtime-helper aliases, not a second copy of the artifact.
7
10
  const stripAnsi = (s) => s.replace(/\x1b\[[0-9;]*m/g, '');
8
11
  // Walk up from startDir to the nearest workspace root — the dir whose lockfile
9
12
  // the package manager audits. Falls back to startDir.
@@ -30,10 +33,17 @@ function runBun(args, cwd) {
30
33
  cwd,
31
34
  encoding: 'utf8',
32
35
  maxBuffer: 32 * 1024 * 1024,
36
+ timeout: 120_000,
33
37
  env: { ...process.env, NO_COLOR: '1' },
34
38
  });
35
- // bun audit exits non-zero when advisories are found the payload is still on
36
- // stdout, so read it regardless of exit code.
39
+ // A launch failure (bun missing, timeout, maxBuffer exceeded) or a non-zero
40
+ // exit with *no* output means the audit never produced data — surface it so a
41
+ // failed run can't masquerade as "0 advisories". (bun audit exits non-zero
42
+ // WHEN it finds advisories, but still writes the payload to stdout, so a
43
+ // non-zero exit *with* stdout is fine.)
44
+ if (res.error || (res.status !== 0 && !res.stdout)) {
45
+ throw new Error(`bun ${args.join(' ')} failed: ${res.error?.message ?? res.stderr ?? `exit ${res.status}`}`);
46
+ }
37
47
  return res.stdout || '';
38
48
  }
39
49
  function normaliseSeverity(sev) {
@@ -138,12 +148,19 @@ export const pikkuAudit = pikkuSessionlessFunc({
138
148
  const { root, pm } = findProjectRoot(config.rootDir);
139
149
  let report;
140
150
  if (pm === 'bun') {
141
- const issues = parseBunAudit(runBun(['audit', '--json'], root));
142
- const updates = includeOutdated ? parseBunOutdated(runBun(['outdated'], root)) : [];
143
- const latestByPkg = new Map(updates.map((u) => [u.package, u.latest]));
144
- for (const i of issues)
145
- i.recommendedVersion = latestByPkg.get(i.package) ?? null;
146
- report = summarise('bun', issues, updates);
151
+ try {
152
+ const issues = parseBunAudit(runBun(['audit', '--json'], root));
153
+ const updates = includeOutdated ? parseBunOutdated(runBun(['outdated'], root)) : [];
154
+ const latestByPkg = new Map(updates.map((u) => [u.package, u.latest]));
155
+ for (const i of issues)
156
+ i.recommendedVersion = latestByPkg.get(i.package) ?? null;
157
+ report = summarise('bun', issues, updates);
158
+ }
159
+ catch (e) {
160
+ // A failed run must NOT read as clean — emit a note so the UI shows
161
+ // "audit not run" instead of a reassuring "no vulnerabilities".
162
+ report = summarise('bun', [], [], `Security audit could not run: ${e instanceof Error ? e.message : String(e)}`);
163
+ }
147
164
  }
148
165
  else {
149
166
  report = summarise(pm, [], [], `Security audit is not yet supported for the '${pm}' package manager — only bun is implemented.`);
@@ -27,7 +27,7 @@ export const consoleCommand = pikkuSessionlessFunc({
27
27
  ...new Set([config.emailTemplatesDir, ...config.srcDirectories].filter(Boolean)),
28
28
  ];
29
29
  if (!config.scaffold?.console) {
30
- logger.error('Console is not enabled. Add { "scaffold": { "console": "no-auth" } } to your pikku.config.json');
30
+ logger.error('Console is not enabled. Add { "scaffold": { "console": "auth" } } to your pikku.config.json (the console requires an authenticated session — set up Better Auth)');
31
31
  return;
32
32
  }
33
33
  const consoleDir = join(fileURLToPath(import.meta.url), '..', '..', '..', '..', '..', 'console-app');
@@ -4,7 +4,7 @@ import { pikkuVoidFunc } from '#pikku';
4
4
  const FEATURE_DEFAULTS = {
5
5
  rpc: 'auth',
6
6
  agent: 'auth',
7
- console: 'no-auth',
7
+ console: 'auth',
8
8
  workflow: 'auth',
9
9
  events: 'auth',
10
10
  remoteRpc: 'no-auth',
@@ -20,9 +20,9 @@ async function enableFeature(feature, logger, config, data) {
20
20
  if (!json.scaffold.pikkuDir) {
21
21
  json.scaffold.pikkuDir = 'pikku';
22
22
  }
23
- const value = noAuth
24
- ? 'no-auth'
25
- : FEATURE_DEFAULTS[feature];
23
+ // The console is an admin surface — every RPC requires a session, so it can
24
+ // never be scaffolded no-auth (the --no-auth flag is ignored for it).
25
+ const value = feature === 'console' ? 'auth' : noAuth ? 'no-auth' : FEATURE_DEFAULTS[feature];
26
26
  json.scaffold[feature] = value;
27
27
  await writeFile(configPath, JSON.stringify(json, null, 2) + '\n', 'utf-8');
28
28
  logger.info(`Enabled scaffold.${feature} = '${value}' in ${configPath}`);
@@ -97,7 +97,7 @@ export async function runWorkspaceValidate(startDir = process.cwd()) {
97
97
  }
98
98
  const scaffold = pikkuConfig.scaffold;
99
99
  if (!scaffold?.console) {
100
- e('pikku-config-no-console-scaffold', 'pikku.config.json missing "scaffold.console" — Fabric cannot introspect the running app (console:getFunctionsMeta and friends 404), so the sandbox builder shows no functions', pikkuConfigPath, 'Add "console": "no-auth" under "scaffold" in pikku.config.json (use "auth" to require a session)');
100
+ e('pikku-config-no-console-scaffold', 'pikku.config.json missing "scaffold.console" — Fabric cannot introspect the running app (console:getFunctionsMeta and friends 404), so the sandbox builder shows no functions', pikkuConfigPath, 'Add "console": "auth" under "scaffold" in pikku.config.json the console requires an authenticated session, so Better Auth must be set up (see the pikku-better-auth skill)');
101
101
  }
102
102
  }
103
103
  const rootPkgPath = join(root, 'package.json');
@@ -2,6 +2,7 @@ import { pikkuSessionlessFunc } from '#pikku';
2
2
  import { getFileImportRelativePath } from '../../../utils/file-import-path.js';
3
3
  import { writeFileInDir } from '../../../utils/file-writer.js';
4
4
  import { logCommandInfoAndTime } from '../../../middleware/log-command-info-and-time.js';
5
+ import { projectDeclaresBetterAuth } from '../../../utils/detect-better-auth.js';
5
6
  import { serializeConsoleFunctions } from './serialize-console-functions.js';
6
7
  export const pikkuConsoleFunctions = pikkuSessionlessFunc({
7
8
  func: async ({ logger, config, variables }) => {
@@ -10,6 +11,18 @@ export const pikkuConsoleFunctions = pikkuSessionlessFunc({
10
11
  return false;
11
12
  }
12
13
  if (config.scaffold?.console) {
14
+ // Every console RPC now requires an authenticated session (the console is
15
+ // an admin surface), so scaffolding it without an auth strategy produces a
16
+ // console that 403s on every call. Better Auth is the supported strategy —
17
+ // fail fast rather than ship a dead console.
18
+ const hasBetterAuth = await projectDeclaresBetterAuth(config.rootDir, config.srcDirectories, config.ignoreFiles);
19
+ if (!hasBetterAuth) {
20
+ throw new Error(`"scaffold.console" is enabled but no pikkuBetterAuth(...) was found in the project.\n` +
21
+ `The console addon requires an authenticated session on every RPC — a console without auth ` +
22
+ `returns 403 on every call.\n` +
23
+ `Fix: set up Better Auth (add a pikkuBetterAuth(...) config) before scaffolding the console, ` +
24
+ `or remove "scaffold.console" from pikku.config.json.`);
25
+ }
13
26
  const pathToPikkuTypes = getFileImportRelativePath(config.consoleFunctionsFile, config.typesDeclarationFile, config.packageMappings);
14
27
  const pathToAgentTypes = getFileImportRelativePath(config.consoleFunctionsFile, config.agentTypesFile, config.packageMappings);
15
28
  await writeFileInDir(logger, config.consoleFunctionsFile, serializeConsoleFunctions(pathToPikkuTypes, pathToAgentTypes, config.globalHTTPPrefix || ''));
@@ -34,7 +34,8 @@ export const pikkuNodesMeta = pikkuSessionlessFunc({
34
34
  const hasNodes = Object.keys(nodes.meta).length > 0;
35
35
  const hasSecrets = secrets.definitions.length > 0;
36
36
  const hasPackageMeta = !!addonMeta?.icon || !!addonMeta?.displayName;
37
- if (!hasNodes && !hasSecrets && !hasPackageMeta) {
37
+ const hasParentServices = (state.addonRequiredParentServices?.length ?? 0) > 0;
38
+ if (!hasNodes && !hasSecrets && !hasPackageMeta && !hasParentServices) {
38
39
  return undefined;
39
40
  }
40
41
  const allowedCategories = addonMeta?.categories;
@@ -68,6 +69,9 @@ export const pikkuNodesMeta = pikkuSessionlessFunc({
68
69
  if (serverlessIncompatible && serverlessIncompatible.length > 0) {
69
70
  metaData.serverlessIncompatible = serverlessIncompatible;
70
71
  }
72
+ if (hasParentServices) {
73
+ metaData.requiredParentServices = state.addonRequiredParentServices;
74
+ }
71
75
  if (addonMetaJsonFile && (config.scaffold?.console || config.addon)) {
72
76
  const minimalMeta = stripVerboseFields(metaData);
73
77
  await writeFileInDir(logger, addonMetaJsonFile, JSON.stringify(minimalMeta, null, 2), { ignoreModifyComment: true });
@@ -1,7 +1,7 @@
1
1
  export const serializeConsoleFunctions = (pathToPikkuTypes, _pathToAgentTypes, globalHTTPPrefix = '') => {
2
- return `import { pikkuSessionlessFunc, defineHTTPRoutes, wireHTTPRoutes, ref, wireAddon } from '${pathToPikkuTypes}'
2
+ return `import { pikkuFunc, defineHTTPRoutes, wireHTTPRoutes, ref, wireAddon } from '${pathToPikkuTypes}'
3
3
 
4
- export const pikkuConsoleSetSecret = pikkuSessionlessFunc<{
4
+ export const pikkuConsoleSetSecret = pikkuFunc<{
5
5
  secretId: string
6
6
  value: unknown
7
7
  }, {
@@ -10,21 +10,19 @@ export const pikkuConsoleSetSecret = pikkuSessionlessFunc<{
10
10
  tags: ['pikku'],
11
11
  description: 'Set the value of a secret',
12
12
  expose: true,
13
- auth: false,
14
13
  func: async ({ secrets }, { secretId, value }) => {
15
14
  await secrets.setSecret(secretId, value)
16
15
  return { success: true }
17
16
  },
18
17
  })
19
18
 
20
- export const pikkuConsoleGetVariable = pikkuSessionlessFunc<
19
+ export const pikkuConsoleGetVariable = pikkuFunc<
21
20
  { variableId: string },
22
21
  { exists: boolean; value: unknown | null }
23
22
  >({
24
23
  tags: ['pikku'],
25
24
  description: 'Get the current value of a variable',
26
25
  expose: true,
27
- auth: false,
28
26
  func: async ({ variables }, { variableId }) => {
29
27
  const exists = await variables.has(variableId)
30
28
  if (!exists) {
@@ -40,14 +38,13 @@ export const pikkuConsoleGetVariable = pikkuSessionlessFunc<
40
38
  },
41
39
  })
42
40
 
43
- export const pikkuConsoleSetVariable = pikkuSessionlessFunc<
41
+ export const pikkuConsoleSetVariable = pikkuFunc<
44
42
  { variableId: string; value: unknown },
45
43
  { success: boolean }
46
44
  >({
47
45
  tags: ['pikku'],
48
46
  description: 'Set the value of a variable',
49
47
  expose: true,
50
- auth: false,
51
48
  func: async ({ variables }, { variableId, value }) => {
52
49
  if (typeof value === 'string') {
53
50
  await variables.set(variableId, value)
@@ -58,28 +55,26 @@ export const pikkuConsoleSetVariable = pikkuSessionlessFunc<
58
55
  },
59
56
  })
60
57
 
61
- export const pikkuConsoleHasSecret = pikkuSessionlessFunc<
58
+ export const pikkuConsoleHasSecret = pikkuFunc<
62
59
  { secretId: string },
63
60
  { exists: boolean }
64
61
  >({
65
62
  tags: ['pikku'],
66
63
  description: 'Check if a secret exists without reading its value',
67
64
  expose: true,
68
- auth: false,
69
65
  func: async ({ secrets }, { secretId }) => {
70
66
  const exists = await secrets.hasSecret(secretId)
71
67
  return { exists }
72
68
  },
73
69
  })
74
70
 
75
- export const pikkuConsoleGetSecret = pikkuSessionlessFunc<
71
+ export const pikkuConsoleGetSecret = pikkuFunc<
76
72
  { secretId: string },
77
73
  { exists: boolean; value: unknown | null }
78
74
  >({
79
75
  tags: ['pikku'],
80
76
  description: 'Get the current value of a secret',
81
77
  expose: true,
82
- auth: false,
83
78
  func: async ({ secrets }, { secretId }) => {
84
79
  const exists = await secrets.hasSecret(secretId)
85
80
  if (!exists) {
@@ -3,13 +3,15 @@ import { writeFileInDir } from '../../../utils/file-writer.js';
3
3
  import { logCommandInfoAndTime } from '../../../middleware/log-command-info-and-time.js';
4
4
  import { serializeFunctionImports } from './serialize-function-imports.js';
5
5
  import { getFileImportRelativePath } from '../../../utils/file-import-path.js';
6
- import { stripVerboseFields, hasVerboseFields, } from '../../../utils/strip-verbose-meta.js';
6
+ import { stripVerboseFields, hasVerboseFields, reattachFunctionServices, } from '../../../utils/strip-verbose-meta.js';
7
7
  export const pikkuFunctions = pikkuSessionlessFunc({
8
8
  func: async ({ logger, config, getInspectorState }) => {
9
9
  const { functions, rpc } = await getInspectorState();
10
10
  const { functionsMetaFile, functionsMetaJsonFile, functionsFile, packageMappings, schema, } = config;
11
- // Write minimal JSON (runtime-only fields)
12
- const minimalMeta = stripVerboseFields(functions.meta);
11
+ let minimalMeta = stripVerboseFields(functions.meta);
12
+ if (config.addonName) {
13
+ minimalMeta = reattachFunctionServices(minimalMeta, functions.meta);
14
+ }
13
15
  await writeFileInDir(logger, functionsMetaJsonFile, JSON.stringify(minimalMeta, null, 2));
14
16
  // Write verbose JSON only if it has additional fields
15
17
  if (hasVerboseFields(functions.meta)) {
@@ -1,6 +1,7 @@
1
1
  import { existsSync } from 'fs';
2
2
  import { pikkuWorkflowComplexFunc } from '#pikku/workflow/pikku-workflow-types.gen.js';
3
3
  import { assertSingleCoreVersion } from '../../utils/assert-single-core-version.js';
4
+ import { PikkuTypecheckFailedError, renderTscFull, renderTscSummary, runProjectTypecheck, } from '../../utils/tsc-check.js';
4
5
  const scaffoldFiles = (config) => {
5
6
  const files = [];
6
7
  if (config.scaffold?.rpc && config.publicRpcFile)
@@ -272,5 +273,21 @@ export const allWorkflow = pikkuWorkflowComplexFunc({
272
273
  await workflow.do('Versions update', 'pikkuVersionsUpdate', null);
273
274
  await workflow.do('Bootstrap', 'pikkuBootstrap', { allImports });
274
275
  await workflow.do('Summary', 'pikkuSummary', null);
276
+ // Opt-in type-check gate (--tsc / --tsc-summary). Runs last, over the
277
+ // post-codegen project, so generated .pikku files are included — matching a
278
+ // real build. Printed inside the step; the throw lives in the workflow body
279
+ // (like assertSingleCoreVersion) so it reliably fails the run.
280
+ if (config.tsc || config.tscSummary) {
281
+ const errorCount = await workflow.do('Type check', async () => {
282
+ const { result, diagnostics, formatHost } = runProjectTypecheck(config.tsconfig, config.rootDir);
283
+ logger.info(config.tsc
284
+ ? renderTscFull(diagnostics, config.rootDir, formatHost)
285
+ : renderTscSummary(result));
286
+ return result.errorCount;
287
+ });
288
+ if (errorCount > 0) {
289
+ throw new PikkuTypecheckFailedError(`Type check failed: ${errorCount} error${errorCount === 1 ? '' : 's'}`);
290
+ }
291
+ }
275
292
  },
276
293
  });
@@ -29,6 +29,9 @@
29
29
  * Also strips addon namespace prefixes from pikkuFuncId values where packageName is present.
30
30
  */
31
31
  export declare function stripVerboseFields<T>(obj: T): T;
32
+ export declare function reattachFunctionServices<T extends Record<string, {
33
+ services?: unknown;
34
+ }>>(minimalMeta: T, fullMeta: T): T;
32
35
  /**
33
36
  * Check if an object has any verbose fields that would be stripped
34
37
  */
@@ -179,6 +179,14 @@ function _stripVerboseFieldsInner(obj) {
179
179
  }
180
180
  return result;
181
181
  }
182
+ export function reattachFunctionServices(minimalMeta, fullMeta) {
183
+ for (const [id, meta] of Object.entries(fullMeta)) {
184
+ if (meta?.services && minimalMeta[id]) {
185
+ minimalMeta[id].services = meta.services;
186
+ }
187
+ }
188
+ return minimalMeta;
189
+ }
182
190
  /**
183
191
  * Check if an object has any verbose fields that would be stripped
184
192
  */
@@ -0,0 +1,46 @@
1
+ import ts from 'typescript';
2
+ import { PikkuError } from '@pikku/core';
3
+ export declare class PikkuTypecheckFailedError extends PikkuError {
4
+ }
5
+ export interface TscDiagnostic {
6
+ file: string;
7
+ line: number;
8
+ column: number;
9
+ code: number;
10
+ category: 'error' | 'warning' | 'suggestion' | 'message';
11
+ message: string;
12
+ }
13
+ export interface TscCheckResult {
14
+ errorCount: number;
15
+ warningCount: number;
16
+ fileCount: number;
17
+ diagnostics: TscDiagnostic[];
18
+ }
19
+ /**
20
+ * Turn raw tsc diagnostics into a filtered, structured result. Pure — it takes
21
+ * already-computed diagnostics so it can be unit-tested without a program.
22
+ * Anything under node_modules or outside the project root is dropped so the
23
+ * output stays focused on the user's own code (and not lib .d.ts noise).
24
+ */
25
+ export declare const collectTscDiagnostics: (diagnostics: readonly ts.Diagnostic[], rootDir: string) => TscCheckResult;
26
+ /**
27
+ * Compact, token-frugal render of a type-check result: a one-line header plus
28
+ * one line per diagnostic (no code frames), capped. Used by `--tsc-summary` so
29
+ * AI agents and CI logs get the signal without the flood.
30
+ */
31
+ export declare const renderTscSummary: (result: TscCheckResult, maxLines?: number) => string;
32
+ /**
33
+ * Full render: tsc's own formatter with code frames, filtered to project files.
34
+ */
35
+ export declare const renderTscFull: (diagnostics: readonly ts.Diagnostic[], rootDir: string, formatHost: ts.FormatDiagnosticsHost) => string;
36
+ /**
37
+ * Run a real `tsc --noEmit` over the project's own tsconfig — the correct
38
+ * source of truth (lib, paths, strict), unlike the inspector's stripped-down
39
+ * traversal program. Returns both a structured result and the raw diagnostics
40
+ * so the caller can pick the compact (`--tsc-summary`) or full (`--tsc`) render.
41
+ */
42
+ export declare const runProjectTypecheck: (tsconfigPath: string, rootDir: string) => {
43
+ result: TscCheckResult;
44
+ diagnostics: ts.Diagnostic[];
45
+ formatHost: ts.FormatDiagnosticsHost;
46
+ };
@@ -0,0 +1,141 @@
1
+ import ts from 'typescript';
2
+ import { dirname, isAbsolute, relative } from 'node:path';
3
+ import { PikkuError } from '@pikku/core';
4
+ // A type-check failure is expected (the --tsc gate did its job) — throw a
5
+ // PikkuError so the runner logs the message alone, not a stack trace.
6
+ export class PikkuTypecheckFailedError extends PikkuError {
7
+ }
8
+ const CATEGORY = {
9
+ [ts.DiagnosticCategory.Error]: 'error',
10
+ [ts.DiagnosticCategory.Warning]: 'warning',
11
+ [ts.DiagnosticCategory.Suggestion]: 'suggestion',
12
+ [ts.DiagnosticCategory.Message]: 'message',
13
+ };
14
+ const MAX_MESSAGE_LENGTH = 200;
15
+ const DEFAULT_MAX_LINES = 50;
16
+ const NO_FILE = '(project)';
17
+ const isProjectFile = (fileName, rootDir) => {
18
+ if (fileName.includes('/node_modules/'))
19
+ return false;
20
+ const rel = relative(rootDir, fileName);
21
+ return !rel.startsWith('..') && !isAbsolute(rel);
22
+ };
23
+ /**
24
+ * Turn raw tsc diagnostics into a filtered, structured result. Pure — it takes
25
+ * already-computed diagnostics so it can be unit-tested without a program.
26
+ * Anything under node_modules or outside the project root is dropped so the
27
+ * output stays focused on the user's own code (and not lib .d.ts noise).
28
+ */
29
+ export const collectTscDiagnostics = (diagnostics, rootDir) => {
30
+ const files = new Set();
31
+ const collected = [];
32
+ let errorCount = 0;
33
+ let warningCount = 0;
34
+ for (const d of diagnostics) {
35
+ let file = NO_FILE;
36
+ let line = 0;
37
+ let column = 0;
38
+ if (d.file) {
39
+ if (!isProjectFile(d.file.fileName, rootDir))
40
+ continue;
41
+ file = relative(rootDir, d.file.fileName);
42
+ const lc = d.file.getLineAndCharacterOfPosition(d.start ?? 0);
43
+ line = lc.line + 1;
44
+ column = lc.character + 1;
45
+ }
46
+ const category = CATEGORY[d.category];
47
+ if (category === 'error')
48
+ errorCount++;
49
+ else if (category === 'warning')
50
+ warningCount++;
51
+ let message = ts.flattenDiagnosticMessageText(d.messageText, ' ');
52
+ if (message.length > MAX_MESSAGE_LENGTH) {
53
+ message = `${message.slice(0, MAX_MESSAGE_LENGTH - 1)}…`;
54
+ }
55
+ files.add(file);
56
+ collected.push({ file, line, column, code: d.code, category, message });
57
+ }
58
+ collected.sort((a, b) => a.file === b.file ? a.line - b.line : a.file < b.file ? -1 : 1);
59
+ return {
60
+ errorCount,
61
+ warningCount,
62
+ fileCount: files.size,
63
+ diagnostics: collected,
64
+ };
65
+ };
66
+ /**
67
+ * Compact, token-frugal render of a type-check result: a one-line header plus
68
+ * one line per diagnostic (no code frames), capped. Used by `--tsc-summary` so
69
+ * AI agents and CI logs get the signal without the flood.
70
+ */
71
+ export const renderTscSummary = (result, maxLines = DEFAULT_MAX_LINES) => {
72
+ if (result.errorCount === 0 && result.warningCount === 0) {
73
+ return 'Type check passed — no errors.';
74
+ }
75
+ const counts = [];
76
+ if (result.errorCount > 0) {
77
+ counts.push(`${result.errorCount} error${result.errorCount === 1 ? '' : 's'}`);
78
+ }
79
+ if (result.warningCount > 0) {
80
+ counts.push(`${result.warningCount} warning${result.warningCount === 1 ? '' : 's'}`);
81
+ }
82
+ const fileLabel = `${result.fileCount} file${result.fileCount === 1 ? '' : 's'}`;
83
+ const lines = [`Type check: ${counts.join(', ')} in ${fileLabel}`];
84
+ const shown = result.diagnostics.slice(0, maxLines);
85
+ for (const d of shown) {
86
+ const at = d.line > 0 ? `${d.file}:${d.line}:${d.column}` : d.file;
87
+ lines.push(` ${at} TS${d.code} ${d.message}`);
88
+ }
89
+ const remaining = result.diagnostics.length - shown.length;
90
+ if (remaining > 0) {
91
+ lines.push(` … and ${remaining} more (run \`pikku all --tsc\` for full output)`);
92
+ }
93
+ return lines.join('\n');
94
+ };
95
+ /**
96
+ * Full render: tsc's own formatter with code frames, filtered to project files.
97
+ */
98
+ export const renderTscFull = (diagnostics, rootDir, formatHost) => {
99
+ const filtered = diagnostics.filter((d) => !d.file || isProjectFile(d.file.fileName, rootDir));
100
+ if (filtered.length === 0)
101
+ return 'Type check passed — no errors.';
102
+ return ts.formatDiagnosticsWithColorAndContext(filtered, formatHost);
103
+ };
104
+ /**
105
+ * Run a real `tsc --noEmit` over the project's own tsconfig — the correct
106
+ * source of truth (lib, paths, strict), unlike the inspector's stripped-down
107
+ * traversal program. Returns both a structured result and the raw diagnostics
108
+ * so the caller can pick the compact (`--tsc-summary`) or full (`--tsc`) render.
109
+ */
110
+ export const runProjectTypecheck = (tsconfigPath, rootDir) => {
111
+ const formatHost = {
112
+ getCanonicalFileName: (f) => f,
113
+ getCurrentDirectory: () => rootDir,
114
+ getNewLine: () => ts.sys.newLine,
115
+ };
116
+ const configFile = ts.readConfigFile(tsconfigPath, ts.sys.readFile);
117
+ if (configFile.error) {
118
+ const diagnostics = [configFile.error];
119
+ return {
120
+ result: collectTscDiagnostics(diagnostics, rootDir),
121
+ diagnostics,
122
+ formatHost,
123
+ };
124
+ }
125
+ const parsed = ts.parseJsonConfigFileContent(configFile.config, ts.sys, dirname(tsconfigPath));
126
+ const program = ts.createProgram({
127
+ rootNames: parsed.fileNames,
128
+ options: {
129
+ ...parsed.options,
130
+ noEmit: true,
131
+ incremental: false,
132
+ tsBuildInfoFile: undefined,
133
+ },
134
+ });
135
+ const diagnostics = [...parsed.errors, ...ts.getPreEmitDiagnostics(program)];
136
+ return {
137
+ result: collectTscDiagnostics(diagnostics, rootDir),
138
+ diagnostics,
139
+ formatHost,
140
+ };
141
+ };