@pikku/cli 0.12.43 → 0.12.45

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 (114) hide show
  1. package/cli.schema.json +1 -1
  2. package/console-app/assets/index-CRLT8CXr.js +254 -0
  3. package/console-app/assets/{index-VleHndkw.css → index-DwyRdRuZ.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 +11 -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 +5 -0
  12. package/dist/.pikku/cli/pikku-cli-contracts-meta.gen.js +5 -0
  13. package/dist/.pikku/cli/pikku-cli-contracts-meta.gen.json +474 -0
  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 +55 -0
  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 +14 -1
  24. package/dist/.pikku/function/pikku-function-types.gen.js +17 -1
  25. package/dist/.pikku/function/pikku-functions-meta.gen.js +1 -1
  26. package/dist/.pikku/function/pikku-functions-meta.gen.json +174 -136
  27. package/dist/.pikku/function/pikku-functions.gen.js +1 -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/http/pikku-http-wirings-meta.gen.js +1 -1
  31. package/dist/.pikku/http/pikku-http-wirings.gen.d.ts +1 -1
  32. package/dist/.pikku/http/pikku-http-wirings.gen.js +1 -1
  33. package/dist/.pikku/mcp/pikku-mcp-types.gen.d.ts +1 -1
  34. package/dist/.pikku/mcp/pikku-mcp-types.gen.js +1 -1
  35. package/dist/.pikku/pikku-bootstrap.gen.d.ts +1 -1
  36. package/dist/.pikku/pikku-bootstrap.gen.js +1 -1
  37. package/dist/.pikku/pikku-meta-service.gen.d.ts +1 -1
  38. package/dist/.pikku/pikku-meta-service.gen.js +1 -1
  39. package/dist/.pikku/pikku-services.gen.d.ts +1 -1
  40. package/dist/.pikku/pikku-types.gen.d.ts +1 -1
  41. package/dist/.pikku/pikku-types.gen.js +1 -1
  42. package/dist/.pikku/queue/pikku-queue-types.gen.d.ts +1 -1
  43. package/dist/.pikku/queue/pikku-queue-types.gen.js +1 -1
  44. package/dist/.pikku/queue/pikku-queue-workers-wirings-meta.gen.js +1 -1
  45. package/dist/.pikku/queue/pikku-queue-workers-wirings-meta.gen.json +8 -8
  46. package/dist/.pikku/queue/pikku-queue-workers-wirings.gen.d.ts +1 -1
  47. package/dist/.pikku/queue/pikku-queue-workers-wirings.gen.js +1 -1
  48. package/dist/.pikku/rpc/pikku-rpc-wirings-meta.internal.gen.js +1 -1
  49. package/dist/.pikku/rpc/pikku-rpc-wirings-meta.internal.gen.json +6 -4
  50. package/dist/.pikku/scheduler/pikku-scheduler-types.gen.d.ts +1 -1
  51. package/dist/.pikku/scheduler/pikku-scheduler-types.gen.js +1 -1
  52. package/dist/.pikku/schemas/register.gen.js +13 -5
  53. package/dist/.pikku/schemas/schemas/FabricAddInput.schema.json +1 -0
  54. package/dist/.pikku/schemas/schemas/FabricAddOutput.schema.json +1 -0
  55. package/dist/.pikku/schemas/schemas/FabricPublishInput.schema.json +1 -0
  56. package/dist/.pikku/schemas/schemas/FabricPublishOutput.schema.json +1 -0
  57. package/dist/.pikku/schemas/schemas/PikkuCLIConfig.schema.json +1 -1
  58. package/dist/.pikku/secrets/pikku-secret-types.gen.d.ts +1 -1
  59. package/dist/.pikku/secrets/pikku-secret-types.gen.js +1 -1
  60. package/dist/.pikku/secrets/pikku-secrets.gen.d.ts +1 -1
  61. package/dist/.pikku/secrets/pikku-secrets.gen.js +1 -1
  62. package/dist/.pikku/trigger/pikku-trigger-types.gen.d.ts +1 -1
  63. package/dist/.pikku/trigger/pikku-trigger-types.gen.js +1 -1
  64. package/dist/.pikku/variables/pikku-variable-types.gen.d.ts +1 -1
  65. package/dist/.pikku/variables/pikku-variable-types.gen.js +1 -1
  66. package/dist/.pikku/variables/pikku-variables.gen.d.ts +1 -1
  67. package/dist/.pikku/variables/pikku-variables.gen.js +1 -1
  68. package/dist/.pikku/workflow/meta/allWorkflow.gen.json +15 -15
  69. package/dist/.pikku/workflow/pikku-workflow-types.gen.d.ts +1 -1
  70. package/dist/.pikku/workflow/pikku-workflow-types.gen.js +1 -1
  71. package/dist/.pikku/workflow/pikku-workflow-wirings-meta.gen.js +1 -1
  72. package/dist/.pikku/workflow/pikku-workflow-wirings.gen.js +1 -1
  73. package/dist/bin/pikku-bin.mjs +2 -2
  74. package/dist/src/cli.wiring.js +12 -0
  75. package/dist/src/fabric/fabric-commands.d.ts +61 -3
  76. package/dist/src/fabric/fabric-commands.js +27 -1
  77. package/dist/src/fabric/functions/add.function.d.ts +50 -0
  78. package/dist/src/fabric/functions/add.function.js +144 -0
  79. package/dist/src/fabric/functions/publish.function.d.ts +45 -0
  80. package/dist/src/fabric/functions/publish.function.js +85 -0
  81. package/dist/src/fabric/functions/validate-core.d.ts +1 -1
  82. package/dist/src/fabric/functions/validate.function.d.ts +4 -4
  83. package/dist/src/fabric/functions/validate.function.js +119 -0
  84. package/dist/src/functions/commands/pikku-command-summary.js +3 -2
  85. package/dist/src/functions/commands/versions-update.js +10 -4
  86. package/dist/src/functions/commands/workspace-validate.d.ts +3 -3
  87. package/dist/src/functions/db/better-auth-schema.js +15 -2
  88. package/dist/src/functions/db/sqlite/sqlite-kysely.js +11 -3
  89. package/dist/src/functions/validate/workspace-validate.d.ts +2 -2
  90. package/dist/src/functions/validate/workspace-validate.js +4 -0
  91. package/dist/src/functions/wirings/channels/pikku-channels.js +20 -11
  92. package/dist/src/functions/wirings/channels/pikku-command-channels-map.js +2 -2
  93. package/dist/src/functions/wirings/channels/pikku-command-channels.js +20 -6
  94. package/dist/src/functions/wirings/channels/serialize-typed-channel-map.d.ts +1 -1
  95. package/dist/src/functions/wirings/channels/serialize-typed-channel-map.js +59 -11
  96. package/dist/src/functions/wirings/cli/pikku-command-cli.js +20 -6
  97. package/dist/src/functions/wirings/emails/pikku-command-emails.js +48 -19
  98. package/dist/src/functions/wirings/functions/pikku-command-function-types-split.js +7 -1
  99. package/dist/src/functions/wirings/functions/serialize-addon-refs.d.ts +14 -0
  100. package/dist/src/functions/wirings/functions/serialize-addon-refs.js +129 -0
  101. package/dist/src/functions/wirings/http/pikku-command-http-routes.js +21 -6
  102. package/dist/src/functions/wirings/http/serialize-typed-http-map.js +12 -0
  103. package/dist/src/functions/workflows/all.workflow.js +7 -0
  104. package/dist/src/scaffold/rpc-remote.gen.js +1 -1
  105. package/dist/src/services/cli-logger-forwarder.service.d.ts +4 -1
  106. package/dist/src/services/cli-logger-forwarder.service.js +20 -2
  107. package/dist/src/services/cli-logger.service.d.ts +16 -2
  108. package/dist/src/services/cli-logger.service.js +33 -5
  109. package/dist/src/services.js +7 -0
  110. package/dist/src/utils/pikku-cli-config.js +18 -0
  111. package/dist/tsconfig.tsbuildinfo +1 -1
  112. package/package.json +3 -3
  113. package/skills/pikku-emails/SKILL.md +157 -0
  114. package/console-app/assets/index-AwGnKyWe.js +0 -254
@@ -8,18 +8,57 @@ import { serializeEmailsModule } from './serialize-emails.js';
8
8
  function sha256(input) {
9
9
  return createHash('sha256').update(input).digest('hex');
10
10
  }
11
- function extractVariables(...sources) {
11
+ function resolveLocaleString(strings, dottedPath) {
12
+ let current = strings;
13
+ for (const segment of dottedPath.split('.')) {
14
+ if (!current || typeof current !== 'object' || !(segment in current))
15
+ return undefined;
16
+ current = current[segment];
17
+ }
18
+ return typeof current === 'string' ? current : undefined;
19
+ }
20
+ // Variables a template actually references — scoped to the locale keys and
21
+ // partials it uses (transitively), not every string in the shared locale file.
22
+ function collectTemplateVariables(rootSources, locales, partials) {
12
23
  const variables = new Set();
13
- for (const source of sources) {
24
+ const visited = new Set();
25
+ const queue = [...rootSources];
26
+ while (queue.length > 0) {
27
+ const source = queue.shift();
28
+ if (!source)
29
+ continue;
14
30
  for (const match of source.matchAll(/\{\{\s*([^}]+?)\s*\}\}/g)) {
15
31
  const key = String(match[1]).trim();
16
- if (!key || key === 'content' || key.startsWith('>'))
32
+ if (!key)
33
+ continue;
34
+ if (key.startsWith('>')) {
35
+ const name = key.slice(1).trim();
36
+ if (!visited.has(`partial:${name}`)) {
37
+ visited.add(`partial:${name}`);
38
+ if (partials[name])
39
+ queue.push(partials[name]);
40
+ }
17
41
  continue;
42
+ }
18
43
  const rootKey = key.split('.')[0];
19
- if (rootKey === 't' || rootKey === 'theme' || rootKey === 'locale')
44
+ if (rootKey === 'content' ||
45
+ rootKey === 'subject' ||
46
+ rootKey === 'theme' ||
47
+ rootKey === 'locale') {
20
48
  continue;
21
- if (rootKey === 'subject')
49
+ }
50
+ if (rootKey === 't') {
51
+ const path = key.split('.').slice(1).join('.');
52
+ if (!path || visited.has(`t:${path}`))
53
+ continue;
54
+ visited.add(`t:${path}`);
55
+ for (const strings of Object.values(locales)) {
56
+ const resolved = resolveLocaleString(strings, path);
57
+ if (resolved)
58
+ queue.push(resolved);
59
+ }
22
60
  continue;
61
+ }
23
62
  variables.add(rootKey);
24
63
  }
25
64
  }
@@ -37,18 +76,6 @@ function stableStringify(value) {
37
76
  }
38
77
  return JSON.stringify(value);
39
78
  }
40
- function collectStringLeaves(value) {
41
- if (typeof value === 'string') {
42
- return [value];
43
- }
44
- if (Array.isArray(value)) {
45
- return value.flatMap((item) => collectStringLeaves(item));
46
- }
47
- if (value && typeof value === 'object') {
48
- return Object.values(value).flatMap((nested) => collectStringLeaves(nested));
49
- }
50
- return [];
51
- }
52
79
  export const pikkuEmails = pikkuSessionlessFunc({
53
80
  func: async ({ logger, config }) => {
54
81
  return generateEmailsArtifacts(logger, config);
@@ -116,8 +143,10 @@ export async function generateEmailsArtifacts(logger, config) {
116
143
  readFile(join(emailDir, 'templates', `${templateName}.subject.txt`), 'utf8'),
117
144
  readFile(join(emailDir, 'templates', `${templateName}.text.txt`), 'utf8').catch(() => ''),
118
145
  ]);
119
- const localeSources = Object.values(locales).flatMap((strings) => collectStringLeaves(strings));
120
- const variables = extractVariables(html, subject, text, ...Object.values(partials), ...localeSources);
146
+ // layout.html wraps every template at render time (see serialize-emails),
147
+ // so include it as a discovery root or layout-only variables get dropped.
148
+ const layout = partials.layout ?? '';
149
+ const variables = collectTemplateVariables([html, subject, text, layout], locales, partials);
121
150
  const hashes = Object.fromEntries(Object.entries(locales).map(([locale, strings]) => {
122
151
  const localePayload = stableStringify({
123
152
  templateName,
@@ -4,6 +4,7 @@ import { checkRequiredTypes } from '../../../utils/check-required-types.js';
4
4
  import { writeFileInDir } from '../../../utils/file-writer.js';
5
5
  import { logCommandInfoAndTime } from '../../../middleware/log-command-info-and-time.js';
6
6
  import { serializeFunctionTypes } from './serialize-function-types.js';
7
+ import { serializeAddonRefs } from './serialize-addon-refs.js';
7
8
  export const pikkuFunctionTypesSplit = pikkuSessionlessFunc({
8
9
  func: async ({ logger, config, getInspectorState }, data) => {
9
10
  const visitState = await getInspectorState(false, true, data?.bootstrap ?? false);
@@ -22,7 +23,12 @@ export const pikkuFunctionTypesSplit = pikkuSessionlessFunc({
22
23
  ? `import type { ${pikkuConfigType.type} } from '${getFileImportRelativePath(functionTypesFile, pikkuConfigType.typePath, packageMappings)}'`
23
24
  : '// Config type not found, will use fallback';
24
25
  const content = serializeFunctionTypes(`import type { ${userSessionType.type} } from '${getFileImportRelativePath(functionTypesFile, userSessionType.typePath, packageMappings)}'`, userSessionType.type, `import type { ${singletonServicesType.type} } from '${getFileImportRelativePath(functionTypesFile, singletonServicesType.typePath, packageMappings)}'`, singletonServicesType.type, `import type { ${wireServicesType.type} } from '${getFileImportRelativePath(functionTypesFile, wireServicesType.typePath, packageMappings)}'`, wireServicesType.type, `import type { TypedPikkuRPC, FlattenedRPCMap } from '${getFileImportRelativePath(functionTypesFile, rpcInternalMapDeclarationFile, packageMappings)}'`, `import type { RequiredSingletonServices, RequiredWireServices } from '${getFileImportRelativePath(functionTypesFile, servicesFile, packageMappings)}'`, configTypeImport, config.addonName, undefined, typeof config.addon === 'object' ? config.addon?.categories : undefined);
25
- await writeFileInDir(logger, functionTypesFile, content);
26
+ const addonRefs = serializeAddonRefs({
27
+ addonHttp: visitState.exportedContracts.addonHttp,
28
+ addonChannel: visitState.exportedContracts.addonChannel,
29
+ addonCli: visitState.exportedContracts.addonCli,
30
+ });
31
+ await writeFileInDir(logger, functionTypesFile, `${content}\n${addonRefs}`);
26
32
  },
27
33
  middleware: [
28
34
  logCommandInfoAndTime({
@@ -0,0 +1,14 @@
1
+ interface AddonContracts {
2
+ addonHttp: Record<string, Record<string, any>>;
3
+ addonChannel: Record<string, Record<string, any>>;
4
+ addonCli: Record<string, Record<string, any>>;
5
+ }
6
+ /**
7
+ * Emit the addon contract reference helpers (refHTTP / refChannel / refCLI)
8
+ * mirroring `ref`. Each helper indexes a generated const map whose functions
9
+ * are pre-bound to ref('namespace:fn') RPC proxies, so type-checking and
10
+ * runtime wiring resolve from the same artifact. basePath can be overridden
11
+ * per HTTP reference; otherwise the addon contract's own basePath is preserved.
12
+ */
13
+ export declare const serializeAddonRefs: (state: AddonContracts) => string;
14
+ export {};
@@ -0,0 +1,129 @@
1
+ const refFunc = (pikkuFuncId) => `ref(${JSON.stringify(pikkuFuncId)})`;
2
+ const isHttpRouteConfig = (value) => value &&
3
+ typeof value === 'object' &&
4
+ 'method' in value &&
5
+ 'route' in value &&
6
+ 'func' in value;
7
+ const isHttpRouteGroup = (value) => value &&
8
+ typeof value === 'object' &&
9
+ 'routes' in value &&
10
+ !('method' in value);
11
+ const serializeHttpRouteMap = (routes) => {
12
+ const entries = Object.entries(routes).map(([key, value]) => {
13
+ if (isHttpRouteConfig(value)) {
14
+ const parts = [];
15
+ for (const [field, fieldValue] of Object.entries(value)) {
16
+ if (field === 'func') {
17
+ parts.push(`func: ${refFunc(fieldValue.pikkuFuncId)}`);
18
+ continue;
19
+ }
20
+ if (fieldValue === null || fieldValue === undefined)
21
+ continue;
22
+ parts.push(`${JSON.stringify(field)}: ${JSON.stringify(fieldValue)}`);
23
+ }
24
+ return `${JSON.stringify(key)}: { ${parts.join(', ')} }`;
25
+ }
26
+ if (isHttpRouteGroup(value)) {
27
+ const parts = [];
28
+ if (value.basePath) {
29
+ parts.push(`basePath: ${JSON.stringify(value.basePath)}`);
30
+ }
31
+ if (Array.isArray(value.tags) && value.tags.length > 0) {
32
+ parts.push(`tags: ${JSON.stringify(value.tags)}`);
33
+ }
34
+ if (value.auth !== undefined && value.auth !== null) {
35
+ parts.push(`auth: ${JSON.stringify(value.auth)}`);
36
+ }
37
+ parts.push(`routes: ${serializeHttpRouteMap(value.routes)}`);
38
+ return `${JSON.stringify(key)}: { ${parts.join(', ')} }`;
39
+ }
40
+ return `${JSON.stringify(key)}: ${serializeHttpRouteMap(value)}`;
41
+ });
42
+ return `{ ${entries.join(', ')} }`;
43
+ };
44
+ const serializeHttpContract = (contract) => {
45
+ const parts = [];
46
+ if (contract.basePath) {
47
+ parts.push(`basePath: ${JSON.stringify(contract.basePath)}`);
48
+ }
49
+ if (Array.isArray(contract.tags) && contract.tags.length > 0) {
50
+ parts.push(`tags: ${JSON.stringify(contract.tags)}`);
51
+ }
52
+ if (contract.auth !== undefined && contract.auth !== null) {
53
+ parts.push(`auth: ${JSON.stringify(contract.auth)}`);
54
+ }
55
+ parts.push(`routes: ${serializeHttpRouteMap(contract.routes ?? {})}`);
56
+ return `{ ${parts.join(', ')} }`;
57
+ };
58
+ const serializeChannelContract = (contract) => {
59
+ const entries = Object.entries(contract).map(([action, value]) => `${JSON.stringify(action)}: { func: ${refFunc(value.pikkuFuncId)} }`);
60
+ return `{ ${entries.join(', ')} }`;
61
+ };
62
+ const serializeCliContract = (contract) => {
63
+ const entries = Object.entries(contract).map(([command, value]) => {
64
+ const parts = [`func: ${refFunc(value.pikkuFuncId)}`];
65
+ if (Array.isArray(value.positionals) && value.positionals.length > 0) {
66
+ parts.push(`positionals: ${JSON.stringify(value.positionals)}`);
67
+ }
68
+ parts.push(`options: ${JSON.stringify(value.options ?? {})}`);
69
+ return `${JSON.stringify(command)}: { ${parts.join(', ')} }`;
70
+ });
71
+ return `{ ${entries.join(', ')} }`;
72
+ };
73
+ const serializeMap = (namespaces, serializeContract) => {
74
+ const entries = [];
75
+ for (const [namespace, contracts] of Object.entries(namespaces)) {
76
+ for (const [contractName, contract] of Object.entries(contracts)) {
77
+ const key = `${namespace}:${contractName}`;
78
+ entries.push(` ${JSON.stringify(key)}: ${serializeContract(contract)},`);
79
+ }
80
+ }
81
+ return entries.length > 0 ? `{\n${entries.join('\n')}\n}` : '{}';
82
+ };
83
+ /**
84
+ * Emit the addon contract reference helpers (refHTTP / refChannel / refCLI)
85
+ * mirroring `ref`. Each helper indexes a generated const map whose functions
86
+ * are pre-bound to ref('namespace:fn') RPC proxies, so type-checking and
87
+ * runtime wiring resolve from the same artifact. basePath can be overridden
88
+ * per HTTP reference; otherwise the addon contract's own basePath is preserved.
89
+ */
90
+ export const serializeAddonRefs = (state) => {
91
+ const exported = state ?? {
92
+ addonHttp: {},
93
+ addonChannel: {},
94
+ addonCli: {},
95
+ };
96
+ const httpMap = serializeMap(exported.addonHttp ?? {}, serializeHttpContract);
97
+ const channelMap = serializeMap(exported.addonChannel ?? {}, serializeChannelContract);
98
+ const cliMap = serializeMap(exported.addonCli ?? {}, serializeCliContract);
99
+ return `
100
+ /**
101
+ * Addon contract references. Generated from each wired addon's published
102
+ * contract metadata — no addon source is imported. Functions are proxied via
103
+ * ref() (RPC) exactly like ref('namespace:fn').
104
+ */
105
+ const __addonHttp = ${httpMap} as const
106
+ const __addonChannel = ${channelMap} as const
107
+ const __addonCli = ${cliMap} as const
108
+
109
+ export const refHTTP = <Name extends keyof typeof __addonHttp>(
110
+ name: Name,
111
+ options?: { basePath?: string }
112
+ ): (typeof __addonHttp)[Name] => {
113
+ const contract = __addonHttp[name] as any
114
+ return (
115
+ options?.basePath !== undefined
116
+ ? { ...contract, basePath: options.basePath }
117
+ : contract
118
+ ) as (typeof __addonHttp)[Name]
119
+ }
120
+
121
+ export const refChannel = <Name extends keyof typeof __addonChannel>(
122
+ name: Name
123
+ ): (typeof __addonChannel)[Name] => __addonChannel[name]
124
+
125
+ export const refCLI = <Name extends keyof typeof __addonCli>(
126
+ name: Name
127
+ ): (typeof __addonCli)[Name] => __addonCli[name]
128
+ `;
129
+ };
@@ -7,21 +7,36 @@ import { stripVerboseFields, hasVerboseFields, } from '../../../utils/strip-verb
7
7
  export const pikkuCommandHTTP = pikkuSessionlessFunc({
8
8
  func: async ({ logger, config, getInspectorState }) => {
9
9
  const visitState = await getInspectorState();
10
- const { httpWiringsFile, httpWiringMetaFile, httpWiringMetaJsonFile, packageMappings, schema, } = config;
11
- const { http } = visitState;
12
- if (http.files.size === 0 || Object.keys(http.meta).length === 0) {
10
+ const { httpWiringsFile, httpWiringMetaFile, httpWiringMetaJsonFile, httpContractsMetaJsonFile, httpContractsMetaFile, packageMappings, schema, } = config;
11
+ const { http, exportedContracts } = visitState;
12
+ const hasHTTPContracts = Object.keys(exportedContracts.http).length > 0;
13
+ if ((http.files.size === 0 || Object.keys(http.meta).length === 0) &&
14
+ !hasHTTPContracts) {
13
15
  return undefined;
14
16
  }
17
+ // The bootstrap imports httpWiringsFile and httpWiringMetaFile whenever
18
+ // this command reports HTTP as active (truthy return), so both must
19
+ // always be written once past the guard above — including the
20
+ // contracts-only or synthetic-route case where there are no local
21
+ // wireHTTP source files (http.files is empty). Skipping either leaves the
22
+ // bootstrap importing a file that was never generated and the per-unit
23
+ // deploy bundle fails to resolve it.
15
24
  await writeFileInDir(logger, httpWiringsFile, serializeFileImports('wireHTTP', httpWiringsFile, http.files, packageMappings));
16
- // Write minimal JSON (runtime-only fields)
17
25
  const minimalMeta = stripVerboseFields(http.meta);
18
26
  await writeFileInDir(logger, httpWiringMetaJsonFile, JSON.stringify(minimalMeta, null, 2));
19
- // Write verbose JSON only if it has additional fields
20
27
  if (hasVerboseFields(http.meta)) {
21
28
  const verbosePath = httpWiringMetaJsonFile.replace(/\.gen\.json$/, '-verbose.gen.json');
22
29
  await writeFileInDir(logger, verbosePath, JSON.stringify(http.meta, null, 2));
23
30
  }
24
- // Generate TypeScript file that imports the minimal JSON
31
+ await writeFileInDir(logger, httpContractsMetaJsonFile, JSON.stringify(exportedContracts.http, null, 2));
32
+ if (hasHTTPContracts) {
33
+ const contractsJsonImportPath = getFileImportRelativePath(httpContractsMetaFile, httpContractsMetaJsonFile, packageMappings);
34
+ const supportsImportAttributes = schema?.supportsImportAttributes ?? false;
35
+ const contractsImportStatement = supportsImportAttributes
36
+ ? `import contractsMeta from '${contractsJsonImportPath}' with { type: 'json' }`
37
+ : `import contractsMeta from '${contractsJsonImportPath}'`;
38
+ await writeFileInDir(logger, httpContractsMetaFile, `${contractsImportStatement}\nexport default contractsMeta`);
39
+ }
25
40
  const jsonImportPath = getFileImportRelativePath(httpWiringMetaFile, httpWiringMetaJsonFile, packageMappings);
26
41
  const supportsImportAttributes = schema?.supportsImportAttributes ?? false;
27
42
  const importStatement = supportsImportAttributes
@@ -58,6 +58,18 @@ function generateHTTPWirings(routesMeta, resolvedIOTypes, requiredTypes) {
58
58
  }
59
59
  const resolved = resolvedIOTypes[pikkuFuncId];
60
60
  if (!resolved) {
61
+ if (meta.packageName) {
62
+ // Addon functions aren't in the consumer's local resolvedIOTypes, but
63
+ // their real types are reachable through the generated FlattenedRPCMap
64
+ // (the addon's RPC map is merged in under its namespace). Adding these
65
+ // to requiredTypes triggers the FlattenedRPCMap import below.
66
+ const inputType = `FlattenedRPCMap['${pikkuFuncId}']['input']`;
67
+ const outputType = `FlattenedRPCMap['${pikkuFuncId}']['output']`;
68
+ requiredTypes.add(inputType);
69
+ requiredTypes.add(outputType);
70
+ routesObj[route][method] = { inputType, outputType };
71
+ continue;
72
+ }
61
73
  throw new Error(`Function ${pikkuFuncId} not found in resolvedIOTypes. Please check your configuration.`);
62
74
  }
63
75
  requiredTypes.add(resolved.inputType);
@@ -189,6 +189,13 @@ export const allWorkflow = pikkuWorkflowComplexFunc({
189
189
  allImports.push(config.triggersWiringMetaFile, config.triggerSourcesMetaFile, config.triggersWiringFile);
190
190
  }
191
191
  }
192
+ if (config.addon) {
193
+ await Promise.all([
194
+ workflow.do('HTTP', 'pikkuCommandHTTP', null),
195
+ workflow.do('Channels', 'pikkuCommandChannels', null),
196
+ workflow.do('CLI', 'pikkuCLI', null),
197
+ ]);
198
+ }
192
199
  const hasFunctionRegistrations = await workflow.do('Functions', 'pikkuFunctions', null);
193
200
  allImports.push(config.functionsMetaFile);
194
201
  if (hasFunctionRegistrations) {
@@ -1,5 +1,5 @@
1
1
  /**
2
- * This file was generated by @pikku/cli@0.12.43
2
+ * This file was generated by @pikku/cli@0.12.45
3
3
  */
4
4
  /**
5
5
  * Auto-generated remote internal RPC queue worker and HTTP endpoint
@@ -1,7 +1,7 @@
1
1
  import type { Logger } from '@pikku/core/services';
2
2
  import { LogLevel } from '@pikku/core/services';
3
3
  import type { PikkuChannel } from '@pikku/core/channel';
4
- import type { ErrorCode } from '@pikku/inspector';
4
+ import type { ErrorCode, CodedDiagnostic, DiagnosticSeverity } from '@pikku/inspector';
5
5
  /**
6
6
  * Log message structure sent through the channel
7
7
  */
@@ -29,6 +29,9 @@ export declare class CLILoggerForwarder implements Logger {
29
29
  warn(messageOrObj: string | Record<string, any>, ..._meta: any[]): void;
30
30
  debug(message: string, ..._meta: any[]): void;
31
31
  trace(message: string, ..._meta: any[]): void;
32
+ diagnostic({ severity, code, message }: CodedDiagnostic): void;
32
33
  critical(code: ErrorCode, message: string): void;
33
34
  hasCriticalErrors(): boolean;
35
+ hasBlockingDiagnostics(): boolean;
36
+ blockingSeverities(): DiagnosticSeverity[];
34
37
  }
@@ -59,13 +59,31 @@ export class CLILoggerForwarder {
59
59
  trace(message, ..._meta) {
60
60
  this.log('trace', LogLevel.trace, message);
61
61
  }
62
- critical(code, message) {
62
+ diagnostic({ severity, code, message }) {
63
+ // Delegate tracking to the underlying CLILogger so the build gate still
64
+ // sees diagnostics emitted through the forwarder, then forward to the
65
+ // channel for display.
66
+ ;
67
+ this.logger.diagnostic?.({ severity, code, message });
63
68
  const url = `https://pikku.dev/docs/pikku-cli/errors/${code.toLowerCase()}`;
64
69
  const formattedMessage = `[${code}] ${message}\n → ${url}`;
65
- this.error(formattedMessage);
70
+ if (severity === 'warn')
71
+ this.warn(formattedMessage);
72
+ else
73
+ this.error(formattedMessage);
74
+ }
75
+ critical(code, message) {
76
+ this.diagnostic({ severity: 'critical', code, message });
66
77
  }
67
78
  hasCriticalErrors() {
68
79
  // The underlying logger (CLILogger) tracks critical errors
69
80
  return this.logger.hasCriticalErrors?.() ?? false;
70
81
  }
82
+ hasBlockingDiagnostics() {
83
+ // The underlying logger (CLILogger) owns failOn + diagnostic tracking
84
+ return this.logger.hasBlockingDiagnostics?.() ?? false;
85
+ }
86
+ blockingSeverities() {
87
+ return this.logger.blockingSeverities?.() ?? [];
88
+ }
71
89
  }
@@ -1,11 +1,12 @@
1
1
  import type { Logger } from '@pikku/core/services';
2
2
  import { LogLevel } from '@pikku/core/services';
3
- import type { ErrorCode } from '@pikku/inspector';
3
+ import type { ErrorCode, DiagnosticSeverity, CodedDiagnostic } from '@pikku/inspector';
4
4
  export type CLIOutputMode = 'text' | 'json';
5
5
  export declare class CLILogger implements Logger {
6
6
  private silent;
7
7
  private level;
8
- private criticalErrors;
8
+ private diagnostics;
9
+ private failOn;
9
10
  private outputMode;
10
11
  private jsonFlushHookRegistered;
11
12
  constructor({ logLogo, silent, }: {
@@ -42,7 +43,20 @@ export declare class CLILogger implements Logger {
42
43
  type?: string;
43
44
  data?: Record<string, unknown>;
44
45
  }): void;
46
+ diagnostic({ severity, code, message }: CodedDiagnostic): void;
47
+ /** Sugar for `diagnostic({ severity: 'critical', code, message })`. */
45
48
  critical(code: ErrorCode, message: string): void;
49
+ /**
50
+ * Configure which severities fail the build. Critical is always included.
51
+ */
52
+ setFailOn(severities: Iterable<DiagnosticSeverity>): void;
46
53
  hasCriticalErrors(): boolean;
54
+ /**
55
+ * True if any tracked diagnostic matches a severity configured to fail the
56
+ * build (default: critical only).
57
+ */
58
+ hasBlockingDiagnostics(): boolean;
59
+ /** Distinct severities among tracked diagnostics that would fail the build. */
60
+ blockingSeverities(): DiagnosticSeverity[];
47
61
  logLogo(): void;
48
62
  }
@@ -9,7 +9,12 @@ const ANSI_ESCAPE_REGEX = /\x1B\[[0-?]*[ -/]*[@-~]/g;
9
9
  export class CLILogger {
10
10
  silent;
11
11
  level = LogLevel.info; // default to info level
12
- criticalErrors = [];
12
+ diagnostics = [];
13
+ // Severities that should fail the build. Critical always blocks; error/warn
14
+ // are opt-in via --fail-on-error / --fail-on-warn.
15
+ failOn = new Set([
16
+ 'critical',
17
+ ]);
13
18
  outputMode = 'text';
14
19
  jsonFlushHookRegistered = false;
15
20
  constructor({ logLogo, silent = false, }) {
@@ -138,14 +143,37 @@ export class CLILogger {
138
143
  const data = typeof message === 'string' ? undefined : message.data;
139
144
  this.emit('debug', msg, type, undefined, data);
140
145
  }
141
- critical(code, message) {
146
+ diagnostic({ severity, code, message }) {
142
147
  const url = `${BASE_ERROR_URL}/${code.toLowerCase()}`;
143
148
  const formattedMessage = `[${code}] ${message}\n → ${url}`;
144
- this.criticalErrors.push(formattedMessage);
145
- this.emit('critical', formattedMessage, undefined, code);
149
+ this.diagnostics.push({ severity, code, message });
150
+ // critical → bold red, error → red, warn → yellow. Always printed so the
151
+ // issue surfaces even when it doesn't fail the build.
152
+ this.emit(severity, formattedMessage, undefined, code);
153
+ }
154
+ /** Sugar for `diagnostic({ severity: 'critical', code, message })`. */
155
+ critical(code, message) {
156
+ this.diagnostic({ severity: 'critical', code, message });
157
+ }
158
+ /**
159
+ * Configure which severities fail the build. Critical is always included.
160
+ */
161
+ setFailOn(severities) {
162
+ this.failOn = new Set(['critical', ...severities]);
146
163
  }
147
164
  hasCriticalErrors() {
148
- return this.criticalErrors.length > 0;
165
+ return this.diagnostics.some((d) => d.severity === 'critical');
166
+ }
167
+ /**
168
+ * True if any tracked diagnostic matches a severity configured to fail the
169
+ * build (default: critical only).
170
+ */
171
+ hasBlockingDiagnostics() {
172
+ return this.diagnostics.some((d) => this.failOn.has(d.severity));
173
+ }
174
+ /** Distinct severities among tracked diagnostics that would fail the build. */
175
+ blockingSeverities() {
176
+ return [...this.failOn].filter((s) => this.diagnostics.some((d) => d.severity === s));
149
177
  }
150
178
  logLogo() {
151
179
  if (this.silent || this.outputMode === 'json')
@@ -79,6 +79,13 @@ export const createConfig = async (_variablesService, data) => {
79
79
  }
80
80
  logger.setLevel(logLevel);
81
81
  logger.setSilent(isSilent);
82
+ // Build gate: critical always fails; --fail-on-warn implies --fail-on-error.
83
+ const extraFailOn = [];
84
+ if (data.failOnWarn)
85
+ extraFailOn.push('warn', 'error');
86
+ else if (data.failOnError)
87
+ extraFailOn.push('error');
88
+ logger.setFailOn(extraFailOn);
82
89
  // Display logo unless in silent mode
83
90
  if (!isSilent && outputMode !== 'json') {
84
91
  logger.logLogo();
@@ -137,6 +137,12 @@ const _getPikkuCLIConfig = async (logger, configFile = undefined, requiredFields
137
137
  if (!result.httpWiringMetaJsonFile) {
138
138
  result.httpWiringMetaJsonFile = join(httpDir, 'pikku-http-wirings-meta.gen.json');
139
139
  }
140
+ if (!result.httpContractsMetaJsonFile) {
141
+ result.httpContractsMetaJsonFile = join(httpDir, 'pikku-http-contracts-meta.gen.json');
142
+ }
143
+ if (!result.httpContractsMetaFile) {
144
+ result.httpContractsMetaFile = join(httpDir, 'pikku-http-contracts-meta.gen.ts');
145
+ }
140
146
  if (!result.httpMapDeclarationFile) {
141
147
  result.httpMapDeclarationFile = join(httpDir, 'pikku-http-wirings-map.gen.d.ts');
142
148
  }
@@ -158,6 +164,12 @@ const _getPikkuCLIConfig = async (logger, configFile = undefined, requiredFields
158
164
  if (!result.channelsWiringMetaJsonFile) {
159
165
  result.channelsWiringMetaJsonFile = join(channelDir, 'pikku-channels-meta.gen.json');
160
166
  }
167
+ if (!result.channelContractsMetaJsonFile) {
168
+ result.channelContractsMetaJsonFile = join(channelDir, 'pikku-channel-contracts-meta.gen.json');
169
+ }
170
+ if (!result.channelContractsMetaFile) {
171
+ result.channelContractsMetaFile = join(channelDir, 'pikku-channel-contracts-meta.gen.ts');
172
+ }
161
173
  if (!result.channelsMapDeclarationFile) {
162
174
  result.channelsMapDeclarationFile = join(channelDir, 'pikku-channels-map.gen.d.ts');
163
175
  }
@@ -348,6 +360,12 @@ const _getPikkuCLIConfig = async (logger, configFile = undefined, requiredFields
348
360
  if (!result.cliWiringMetaJsonFile) {
349
361
  result.cliWiringMetaJsonFile = join(cliDir, 'pikku-cli-wirings-meta.gen.json');
350
362
  }
363
+ if (!result.cliContractsMetaJsonFile) {
364
+ result.cliContractsMetaJsonFile = join(cliDir, 'pikku-cli-contracts-meta.gen.json');
365
+ }
366
+ if (!result.cliContractsMetaFile) {
367
+ result.cliContractsMetaFile = join(cliDir, 'pikku-cli-contracts-meta.gen.ts');
368
+ }
351
369
  if (!result.cliBootstrapFile) {
352
370
  result.cliBootstrapFile = join(cliDir, 'pikku-cli-bootstrap.gen.ts');
353
371
  }