@pikku/cli 0.12.0 → 0.12.1

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 (185) hide show
  1. package/cli.schema.json +1 -1
  2. package/console-app/assets/index-DiYPTQU_.js +676 -0
  3. package/console-app/index.html +6 -1
  4. package/dist/.pikku/agent/pikku-agent-types.gen.d.ts +30 -3
  5. package/dist/.pikku/agent/pikku-agent-types.gen.js +13 -0
  6. package/dist/.pikku/agent/pikku-agent-wirings-meta.gen.js +2 -2
  7. package/dist/.pikku/agent/pikku-agent-wirings.gen.d.ts +1 -1
  8. package/dist/.pikku/agent/pikku-agent-wirings.gen.js +1 -1
  9. package/dist/.pikku/channel/pikku-channel-types.gen.d.ts +1 -1
  10. package/dist/.pikku/channel/pikku-channel-types.gen.js +1 -1
  11. package/dist/.pikku/channel/pikku-channels-meta.gen.js +2 -2
  12. package/dist/.pikku/channel/pikku-channels.gen.d.ts +1 -1
  13. package/dist/.pikku/channel/pikku-channels.gen.js +1 -1
  14. package/dist/.pikku/cli/pikku-cli-channel.js +57 -3
  15. package/dist/.pikku/cli/pikku-cli-client.gen.d.ts +1 -1
  16. package/dist/.pikku/cli/pikku-cli-client.gen.js +1 -1
  17. package/dist/.pikku/cli/pikku-cli-types.gen.d.ts +1 -1
  18. package/dist/.pikku/cli/pikku-cli-types.gen.js +1 -1
  19. package/dist/.pikku/cli/pikku-cli-wirings-meta.gen.js +2 -2
  20. package/dist/.pikku/cli/pikku-cli-wirings-meta.gen.json +228 -10
  21. package/dist/.pikku/cli/pikku-cli-wirings.gen.d.ts +1 -1
  22. package/dist/.pikku/cli/pikku-cli-wirings.gen.js +1 -1
  23. package/dist/.pikku/cli/pikku-cli.gen.d.ts +1 -1
  24. package/dist/.pikku/cli/pikku-cli.gen.js +1 -1
  25. package/dist/.pikku/console/pikku-node-types.gen.d.ts +1 -1
  26. package/dist/.pikku/function/pikku-function-types.gen.d.ts +14 -11
  27. package/dist/.pikku/function/pikku-function-types.gen.js +25 -13
  28. package/dist/.pikku/function/pikku-functions-meta.gen.js +2 -2
  29. package/dist/.pikku/function/pikku-functions-meta.gen.json +359 -105
  30. package/dist/.pikku/function/pikku-functions.gen.js +3 -3
  31. package/dist/.pikku/http/pikku-http-types.gen.d.ts +1 -1
  32. package/dist/.pikku/http/pikku-http-types.gen.js +1 -1
  33. package/dist/.pikku/http/pikku-http-wirings-meta.gen.js +2 -2
  34. package/dist/.pikku/http/pikku-http-wirings.gen.d.ts +1 -1
  35. package/dist/.pikku/http/pikku-http-wirings.gen.js +1 -1
  36. package/dist/.pikku/mcp/pikku-mcp-types.gen.d.ts +1 -1
  37. package/dist/.pikku/mcp/pikku-mcp-types.gen.js +1 -1
  38. package/dist/.pikku/mcp/pikku-mcp-wirings-meta.gen.js +2 -2
  39. package/dist/.pikku/mcp/pikku-mcp-wirings.gen.d.ts +1 -1
  40. package/dist/.pikku/mcp/pikku-mcp-wirings.gen.js +1 -1
  41. package/dist/.pikku/pikku-bootstrap.gen.js +2 -2
  42. package/dist/.pikku/pikku-services.gen.d.ts +2 -1
  43. package/dist/.pikku/pikku-services.gen.js +1 -0
  44. package/dist/.pikku/pikku-types.gen.d.ts +1 -1
  45. package/dist/.pikku/pikku-types.gen.js +1 -1
  46. package/dist/.pikku/pikku-websocket.gen.d.ts +1 -1
  47. package/dist/.pikku/pikku-websocket.gen.js +1 -1
  48. package/dist/.pikku/queue/pikku-queue-types.gen.d.ts +1 -1
  49. package/dist/.pikku/queue/pikku-queue-types.gen.js +1 -1
  50. package/dist/.pikku/queue/pikku-queue-workers-wirings-meta.gen.js +2 -2
  51. package/dist/.pikku/queue/pikku-queue-workers-wirings.gen.d.ts +1 -1
  52. package/dist/.pikku/queue/pikku-queue-workers-wirings.gen.js +1 -1
  53. package/dist/.pikku/rpc/pikku-remote-rpc-workers.gen.js +1 -1
  54. package/dist/.pikku/rpc/pikku-rpc-wirings-meta.internal.gen.js +2 -2
  55. package/dist/.pikku/rpc/pikku-rpc-wirings-meta.internal.gen.json +15 -6
  56. package/dist/.pikku/scheduler/pikku-scheduler-types.gen.d.ts +1 -1
  57. package/dist/.pikku/scheduler/pikku-scheduler-types.gen.js +1 -1
  58. package/dist/.pikku/scheduler/pikku-schedulers-wirings-meta.gen.js +2 -2
  59. package/dist/.pikku/scheduler/pikku-schedulers-wirings.gen.d.ts +1 -1
  60. package/dist/.pikku/scheduler/pikku-schedulers-wirings.gen.js +1 -1
  61. package/dist/.pikku/schemas/register.gen.js +15 -5
  62. package/dist/.pikku/schemas/schemas/PikkuCLIConfig.schema.json +1 -1
  63. package/dist/.pikku/schemas/schemas/PikkuNewAddonInput.schema.json +1 -0
  64. package/dist/.pikku/schemas/schemas/PikkuNewFunctionInput.schema.json +1 -0
  65. package/dist/.pikku/schemas/schemas/PikkuNewMiddlewareInput.schema.json +1 -0
  66. package/dist/.pikku/schemas/schemas/PikkuNewPermissionInput.schema.json +1 -0
  67. package/dist/.pikku/schemas/schemas/PikkuNewWiringInput.schema.json +1 -0
  68. package/dist/.pikku/secrets/pikku-secret-types.gen.d.ts +1 -1
  69. package/dist/.pikku/secrets/pikku-secret-types.gen.js +1 -1
  70. package/dist/.pikku/secrets/pikku-secrets.gen.d.ts +1 -1
  71. package/dist/.pikku/secrets/pikku-secrets.gen.js +1 -1
  72. package/dist/.pikku/trigger/pikku-trigger-types.gen.d.ts +1 -1
  73. package/dist/.pikku/trigger/pikku-trigger-types.gen.js +1 -1
  74. package/dist/.pikku/variables/pikku-variable-types.gen.d.ts +1 -1
  75. package/dist/.pikku/variables/pikku-variable-types.gen.js +1 -1
  76. package/dist/.pikku/variables/pikku-variables.gen.d.ts +1 -1
  77. package/dist/.pikku/variables/pikku-variables.gen.js +1 -1
  78. package/dist/.pikku/workflow/pikku-workflow-types.gen.d.ts +1 -1
  79. package/dist/.pikku/workflow/pikku-workflow-types.gen.js +1 -1
  80. package/dist/.pikku/workflow/pikku-workflow-wirings-meta.gen.js +2 -2
  81. package/dist/.pikku/workflow/pikku-workflow-wirings.gen.d.ts +1 -1
  82. package/dist/.pikku/workflow/pikku-workflow-wirings.gen.js +1 -1
  83. package/dist/src/cli.wiring.js +190 -9
  84. package/dist/src/functions/commands/all.js +6 -6
  85. package/dist/src/functions/commands/info.d.ts +9 -0
  86. package/dist/src/functions/commands/info.js +283 -0
  87. package/dist/src/functions/commands/new-addon.d.ts +34 -0
  88. package/dist/src/functions/commands/new-addon.js +636 -0
  89. package/dist/src/functions/commands/new-function.d.ts +10 -0
  90. package/dist/src/functions/commands/new-function.js +79 -0
  91. package/dist/src/functions/commands/new-middleware.d.ts +10 -0
  92. package/dist/src/functions/commands/new-middleware.js +48 -0
  93. package/dist/src/functions/commands/new-permission.d.ts +10 -0
  94. package/dist/src/functions/commands/new-permission.js +45 -0
  95. package/dist/src/functions/commands/new-wiring.d.ts +10 -0
  96. package/dist/src/functions/commands/new-wiring.js +102 -0
  97. package/dist/src/functions/commands/pikku-command-bootstrap.js +11 -40
  98. package/dist/src/functions/commands/versions-check.js +85 -3
  99. package/dist/src/functions/commands/versions-update.js +1 -1
  100. package/dist/src/functions/runtimes/nextjs/serialize-nextjs-backend-wrapper.js +0 -4
  101. package/dist/src/functions/wirings/ai-agent/pikku-command-ai-agent-types.js +3 -2
  102. package/dist/src/functions/wirings/ai-agent/pikku-command-ai-agent.js +5 -5
  103. package/dist/src/functions/wirings/ai-agent/serialize-agent-map.d.ts +1 -1
  104. package/dist/src/functions/wirings/ai-agent/serialize-ai-agent-types.d.ts +1 -1
  105. package/dist/src/functions/wirings/ai-agent/serialize-ai-agent-types.js +48 -3
  106. package/dist/src/functions/wirings/ai-agent/serialize-public-agent.js +30 -52
  107. package/dist/src/functions/wirings/channels/pikku-channels.js +1 -1
  108. package/dist/src/functions/wirings/channels/pikku-command-channel-types.js +2 -2
  109. package/dist/src/functions/wirings/channels/pikku-command-channels.js +1 -1
  110. package/dist/src/functions/wirings/channels/serialize-typed-channel-map.d.ts +4 -4
  111. package/dist/src/functions/wirings/cli/pikku-command-cli.js +1 -1
  112. package/dist/src/functions/wirings/cli/serialize-channel-cli-client.d.ts +2 -2
  113. package/dist/src/functions/wirings/cli/serialize-channel-cli.d.ts +1 -1
  114. package/dist/src/functions/wirings/cli/serialize-local-cli-bootstrap.d.ts +1 -1
  115. package/dist/src/functions/wirings/console/pikku-command-console-functions.js +2 -1
  116. package/dist/src/functions/wirings/console/pikku-command-nodes-meta.js +1 -1
  117. package/dist/src/functions/wirings/console/serialize-console-functions.d.ts +1 -1
  118. package/dist/src/functions/wirings/console/serialize-console-functions.js +18 -157
  119. package/dist/src/functions/wirings/functions/pikku-command-addon-types.d.ts +1 -0
  120. package/dist/src/functions/wirings/functions/pikku-command-addon-types.js +33 -0
  121. package/dist/src/functions/wirings/functions/pikku-command-function-types-split.js +1 -1
  122. package/dist/src/functions/wirings/functions/pikku-command-function-types.js +4 -4
  123. package/dist/src/functions/wirings/functions/pikku-command-functions.js +8 -14
  124. package/dist/src/functions/wirings/functions/schemas.js +1 -1
  125. package/dist/src/functions/wirings/functions/serialize-addon-types.d.ts +1 -0
  126. package/dist/src/functions/wirings/functions/{serialize-external-types.js → serialize-addon-types.js} +16 -15
  127. package/dist/src/functions/wirings/functions/serialize-function-imports.d.ts +3 -3
  128. package/dist/src/functions/wirings/functions/serialize-function-imports.js +3 -3
  129. package/dist/src/functions/wirings/functions/serialize-function-types.js +28 -14
  130. package/dist/src/functions/wirings/functions/serialize-pikku-types-hub.d.ts +1 -1
  131. package/dist/src/functions/wirings/functions/serialize-pikku-types-hub.js +2 -2
  132. package/dist/src/functions/wirings/http/pikku-command-http-routes.js +1 -1
  133. package/dist/src/functions/wirings/http/pikku-http-routes.js +1 -1
  134. package/dist/src/functions/wirings/http/serialize-typed-http-map.d.ts +3 -3
  135. package/dist/src/functions/wirings/mcp/pikku-command-mcp.js +1 -1
  136. package/dist/src/functions/wirings/package/pikku-command-package.js +5 -5
  137. package/dist/src/functions/wirings/package/serialize-package.js +2 -2
  138. package/dist/src/functions/wirings/permissions/pikku-command-permissions.js +0 -5
  139. package/dist/src/functions/wirings/queue/serialize-queue-map.d.ts +2 -2
  140. package/dist/src/functions/wirings/queue/serialize-queue-meta.d.ts +1 -1
  141. package/dist/src/functions/wirings/rpc/pikku-command-rpc-map.js +6 -6
  142. package/dist/src/functions/wirings/rpc/pikku-command-rpc.js +2 -4
  143. package/dist/src/functions/wirings/rpc/serialize-public-rpc.js +25 -16
  144. package/dist/src/functions/wirings/rpc/serialize-rpc-wrapper.js +43 -7
  145. package/dist/src/functions/wirings/rpc/serialize-typed-rpc-map.d.ts +8 -3
  146. package/dist/src/functions/wirings/rpc/serialize-typed-rpc-map.js +41 -31
  147. package/dist/src/functions/wirings/scheduler/serialize-scheduler-meta.d.ts +1 -1
  148. package/dist/src/functions/wirings/secrets/serialize-secrets-types.d.ts +2 -2
  149. package/dist/src/functions/wirings/triggers/serialize-trigger-meta.d.ts +1 -1
  150. package/dist/src/functions/wirings/triggers/serialize-trigger-meta.js +2 -2
  151. package/dist/src/functions/wirings/variables/serialize-variables-types.d.ts +2 -2
  152. package/dist/src/functions/wirings/workflow/pikku-command-workflow.js +2 -2
  153. package/dist/src/functions/wirings/workflow/serialize-workflow-map.d.ts +2 -2
  154. package/dist/src/functions/wirings/workflow/serialize-workflow-meta.js +2 -2
  155. package/dist/src/services/cli-logger-forwarder.service.d.ts +4 -3
  156. package/dist/src/services/cli-logger.service.d.ts +3 -2
  157. package/dist/src/services.d.ts +4 -3
  158. package/dist/src/services.js +2 -3
  159. package/dist/src/utils/check-required-types.d.ts +1 -1
  160. package/dist/src/utils/contract-versions.d.ts +1 -1
  161. package/dist/src/utils/file-writer.d.ts +6 -1
  162. package/dist/src/utils/file-writer.js +14 -1
  163. package/dist/src/utils/generate-bootstrap-file.d.ts +2 -2
  164. package/dist/src/utils/openapi/codegen.d.ts +19 -0
  165. package/dist/src/utils/openapi/codegen.js +288 -0
  166. package/dist/src/utils/openapi/naming.d.ts +30 -0
  167. package/dist/src/utils/openapi/naming.js +167 -0
  168. package/dist/src/utils/openapi/parse-openapi.d.ts +36 -0
  169. package/dist/src/utils/openapi/parse-openapi.js +196 -0
  170. package/dist/src/utils/openapi/zod-codegen.d.ts +53 -0
  171. package/dist/src/utils/openapi/zod-codegen.js +251 -0
  172. package/dist/src/utils/pikku-cli-config.d.ts +2 -2
  173. package/dist/src/utils/pikku-cli-config.js +8 -14
  174. package/dist/src/utils/pikku-files-and-methods.d.ts +1 -1
  175. package/dist/src/utils/pikku-files-and-methods.js +1 -1
  176. package/dist/src/utils/serialize-import-map.d.ts +2 -2
  177. package/dist/src/utils/serialize-import-map.js +1 -1
  178. package/dist/src/utils/serialize-meta-ts.js +1 -1
  179. package/dist/src/utils/serialize-schemas.d.ts +2 -2
  180. package/dist/tsconfig.tsbuildinfo +1 -1
  181. package/package.json +3 -3
  182. package/console-app/assets/index-C19L3UJu.js +0 -637
  183. package/dist/src/functions/wirings/functions/pikku-command-external-types.d.ts +0 -1
  184. package/dist/src/functions/wirings/functions/pikku-command-external-types.js +0 -33
  185. package/dist/src/functions/wirings/functions/serialize-external-types.d.ts +0 -1
@@ -0,0 +1,636 @@
1
+ import { existsSync } from 'fs';
2
+ import { join } from 'path';
3
+ import { mkdir, writeFile } from 'fs/promises';
4
+ import { pikkuSessionlessFunc } from '#pikku';
5
+ import { parseOpenAPISpec } from '../../utils/openapi/parse-openapi.js';
6
+ import { generateAddonFromOpenAPI } from '../../utils/openapi/codegen.js';
7
+ function toCamelCase(str) {
8
+ return str.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
9
+ }
10
+ function toPascalCase(str) {
11
+ const camel = toCamelCase(str);
12
+ return camel.charAt(0).toUpperCase() + camel.slice(1);
13
+ }
14
+ function toScreamingSnake(str) {
15
+ return str.replace(/-/g, '_').toUpperCase();
16
+ }
17
+ function getAddonFiles(vars, flags) {
18
+ const { name, camelName, pascalName, screamingName, displayName, description, category, } = vars;
19
+ const files = {};
20
+ // package.json
21
+ files['package.json'] = JSON.stringify({
22
+ name: `@pikku/addon-${name}`,
23
+ version: '0.0.1',
24
+ type: 'module',
25
+ imports: {
26
+ '#pikku': './.pikku/pikku-types.gen.ts',
27
+ '#pikku/*': './.pikku/*',
28
+ },
29
+ exports: {
30
+ '.': {
31
+ types: './dist/src/index.d.ts',
32
+ import: './dist/src/index.js',
33
+ },
34
+ './.pikku/*': './.pikku/*',
35
+ './.pikku/pikku-metadata.gen.json': './.pikku/pikku-metadata.gen.json',
36
+ './.pikku/rpc/pikku-rpc-wirings-map.internal.gen.js': {
37
+ types: './.pikku/rpc/pikku-rpc-wirings-map.internal.gen.d.ts',
38
+ },
39
+ },
40
+ files: ['dist', '.pikku'],
41
+ scripts: {
42
+ prebuild: 'pikku all',
43
+ build: 'tsc && cp -r .pikku dist/',
44
+ pikku: 'pikku all',
45
+ },
46
+ peerDependencies: {
47
+ '@pikku/core': '*',
48
+ zod: '^4',
49
+ },
50
+ devDependencies: {
51
+ '@pikku/cli': '*',
52
+ '@pikku/core': '*',
53
+ typescript: '^5.7.2',
54
+ zod: '^4',
55
+ },
56
+ }, null, 2);
57
+ // pikku.config.json
58
+ files['pikku.config.json'] = JSON.stringify({
59
+ $schema: 'https://raw.githubusercontent.com/pikkujs/pikku/refs/heads/main/packages/cli/cli.schema.json',
60
+ tsconfig: './tsconfig.json',
61
+ srcDirectories: ['src', 'types'],
62
+ outDir: './.pikku',
63
+ addon: true,
64
+ node: {
65
+ displayName,
66
+ description,
67
+ categories: [category],
68
+ icon: `./${name}.svg`,
69
+ },
70
+ }, null, 2);
71
+ // tsconfig.json
72
+ files['tsconfig.json'] = JSON.stringify({
73
+ compilerOptions: {
74
+ target: 'ES2021',
75
+ module: 'NodeNext',
76
+ moduleResolution: 'NodeNext',
77
+ rootDir: '.',
78
+ outDir: './dist',
79
+ declaration: true,
80
+ declarationMap: true,
81
+ sourceMap: true,
82
+ strict: true,
83
+ esModuleInterop: true,
84
+ skipLibCheck: true,
85
+ forceConsistentCasingInFileNames: true,
86
+ resolveJsonModule: true,
87
+ paths: {
88
+ '#pikku': ['./.pikku/pikku-types.gen.ts'],
89
+ },
90
+ },
91
+ include: ['src/**/*', 'types/**/*', '.pikku/**/*.ts'],
92
+ exclude: ['node_modules', 'dist', '.pikku/**/*.d.ts'],
93
+ }, null, 2);
94
+ // README.md
95
+ files['README.md'] = `# @pikku/addon-${name}
96
+
97
+ ${description}
98
+
99
+ ## Setup
100
+
101
+ 1. Add icon SVG at \`${name}.svg\`
102
+ 2. Update secret schema with required fields
103
+ 3. Implement API service methods
104
+ 4. Create function files for each operation
105
+ 5. Export functions in \`src/index.ts\`
106
+ 6. Build: \`yarn install && yarn pikku && yarn build\`
107
+ `;
108
+ // src/index.ts
109
+ files['src/index.ts'] = `// ${displayName} functions
110
+ // export { ${camelName}Operation } from './functions/operation.function.js'
111
+ `;
112
+ // src/services.ts
113
+ if (flags.oauth) {
114
+ files['src/services.ts'] =
115
+ `import { ${pascalName}Service } from './${name}-api.service.js'
116
+ import { pikkuAddonServices } from '#pikku'
117
+
118
+ export const createSingletonServices = pikkuAddonServices(async (
119
+ config,
120
+ { secrets }
121
+ ) => {
122
+ const ${camelName} = new ${pascalName}Service(secrets)
123
+
124
+ return { ${camelName} }
125
+ })
126
+ `;
127
+ }
128
+ else if (flags.secret) {
129
+ files['src/services.ts'] =
130
+ `import { ${pascalName}Service } from './${name}-api.service.js'
131
+ import type { ${pascalName}Secrets } from './${name}.secret.js'
132
+ import { pikkuAddonServices } from '#pikku'
133
+
134
+ export const createSingletonServices = pikkuAddonServices(async (
135
+ config,
136
+ { secrets }
137
+ ) => {
138
+ const creds = await secrets.getSecretJSON<${pascalName}Secrets>('${screamingName}_CREDENTIALS')
139
+ const ${camelName} = new ${pascalName}Service(creds)
140
+
141
+ return { ${camelName} }
142
+ })
143
+ `;
144
+ }
145
+ else {
146
+ files['src/services.ts'] =
147
+ `import { ${pascalName}Service } from './${name}-api.service.js'
148
+ import { pikkuAddonServices } from '#pikku'
149
+
150
+ export const createSingletonServices = pikkuAddonServices(async (
151
+ config,
152
+ ) => {
153
+ const ${camelName} = new ${pascalName}Service()
154
+
155
+ return { ${camelName} }
156
+ })
157
+ `;
158
+ }
159
+ // src/{name}-api.service.ts
160
+ if (flags.oauth) {
161
+ files[`src/${name}-api.service.ts`] =
162
+ `import { OAuth2Client } from '@pikku/core/oauth2'
163
+ import type { TypedSecretService } from '#pikku/secrets/pikku-secrets.gen.js'
164
+
165
+ const BASE_URL = 'https://api.example.com/v1'
166
+
167
+ export const ${screamingName}_OAUTH2_CONFIG = {
168
+ tokenSecretId: '${screamingName}_TOKENS',
169
+ authorizationUrl: 'https://example.com/oauth2/authorize',
170
+ tokenUrl: 'https://example.com/oauth2/token',
171
+ scopes: ['read', 'write'],
172
+ }
173
+
174
+ export interface RequestOptions {
175
+ body?: unknown
176
+ qs?: Record<string, string | number | boolean | undefined>
177
+ }
178
+
179
+ export class ${pascalName}Service {
180
+ private oauth: OAuth2Client
181
+
182
+ constructor(secrets: TypedSecretService) {
183
+ this.oauth = new OAuth2Client(
184
+ ${screamingName}_OAUTH2_CONFIG,
185
+ '${screamingName}_APP_CREDENTIALS',
186
+ secrets
187
+ )
188
+ }
189
+
190
+ async request<T>(
191
+ method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH',
192
+ endpoint: string,
193
+ options?: RequestOptions
194
+ ): Promise<T> {
195
+ const url = new URL(\`\${BASE_URL}\${endpoint}\`)
196
+
197
+ if (options?.qs) {
198
+ for (const [key, value] of Object.entries(options.qs)) {
199
+ if (value !== undefined) {
200
+ url.searchParams.set(key, String(value))
201
+ }
202
+ }
203
+ }
204
+
205
+ const response = await this.oauth.request(url.toString(), {
206
+ method,
207
+ headers: {
208
+ 'Content-Type': 'application/json',
209
+ },
210
+ body: options?.body ? JSON.stringify(options.body) : undefined,
211
+ })
212
+
213
+ if (!response.ok) {
214
+ const errorText = await response.text()
215
+ throw new Error(\`${displayName} API error (\${response.status}): \${errorText}\`)
216
+ }
217
+
218
+ const text = await response.text()
219
+ if (!text) {
220
+ return {} as T
221
+ }
222
+ return JSON.parse(text) as T
223
+ }
224
+ }
225
+ `;
226
+ }
227
+ else if (flags.secret) {
228
+ files[`src/${name}-api.service.ts`] =
229
+ `import type { ${pascalName}Secrets } from './${name}.secret.js'
230
+
231
+ const BASE_URL = 'https://api.example.com/v1'
232
+
233
+ export interface RequestOptions {
234
+ body?: unknown
235
+ qs?: Record<string, string | number | boolean | undefined>
236
+ }
237
+
238
+ export class ${pascalName}Service {
239
+ constructor(private creds: ${pascalName}Secrets) {}
240
+
241
+ async request<T>(
242
+ method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH',
243
+ endpoint: string,
244
+ options?: RequestOptions
245
+ ): Promise<T> {
246
+ const url = new URL(endpoint, BASE_URL)
247
+
248
+ if (options?.qs) {
249
+ for (const [key, value] of Object.entries(options.qs)) {
250
+ if (value !== undefined) {
251
+ url.searchParams.set(key, String(value))
252
+ }
253
+ }
254
+ }
255
+
256
+ const response = await fetch(url.toString(), {
257
+ method,
258
+ headers: {
259
+ 'Content-Type': 'application/json',
260
+ 'Authorization': \`Bearer \${this.creds.apiKey}\`,
261
+ },
262
+ body: options?.body ? JSON.stringify(options.body) : undefined,
263
+ })
264
+
265
+ if (!response.ok) {
266
+ const errorText = await response.text()
267
+ throw new Error(\`${displayName} API error (\${response.status}): \${errorText}\`)
268
+ }
269
+
270
+ return response.json() as Promise<T>
271
+ }
272
+ }
273
+ `;
274
+ }
275
+ else {
276
+ files[`src/${name}-api.service.ts`] =
277
+ `const BASE_URL = 'https://api.example.com/v1'
278
+
279
+ export interface RequestOptions {
280
+ body?: unknown
281
+ qs?: Record<string, string | number | boolean | undefined>
282
+ }
283
+
284
+ export class ${pascalName}Service {
285
+ async request<T>(
286
+ method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH',
287
+ endpoint: string,
288
+ options?: RequestOptions
289
+ ): Promise<T> {
290
+ const url = new URL(endpoint, BASE_URL)
291
+
292
+ if (options?.qs) {
293
+ for (const [key, value] of Object.entries(options.qs)) {
294
+ if (value !== undefined) {
295
+ url.searchParams.set(key, String(value))
296
+ }
297
+ }
298
+ }
299
+
300
+ const response = await fetch(url.toString(), {
301
+ method,
302
+ headers: {
303
+ 'Content-Type': 'application/json',
304
+ },
305
+ body: options?.body ? JSON.stringify(options.body) : undefined,
306
+ })
307
+
308
+ if (!response.ok) {
309
+ const errorText = await response.text()
310
+ throw new Error(\`${displayName} API error (\${response.status}): \${errorText}\`)
311
+ }
312
+
313
+ return response.json() as Promise<T>
314
+ }
315
+ }
316
+ `;
317
+ }
318
+ // src/{name}.types.ts
319
+ files[`src/${name}.types.ts`] = `import { z } from 'zod'
320
+
321
+ // Define Zod schemas for API types
322
+
323
+ export const ${pascalName}ResourceSchema = z.object({
324
+ id: z.string(),
325
+ name: z.string(),
326
+ // Add fields based on API response
327
+ })
328
+
329
+ export type ${pascalName}Resource = z.infer<typeof ${pascalName}ResourceSchema>
330
+ `;
331
+ // types/application-types.d.ts
332
+ files['types/application-types.d.ts'] = `import type {
333
+ CoreConfig,
334
+ CoreServices,
335
+ CoreSingletonServices,
336
+ CoreUserSession,
337
+ } from '@pikku/core'
338
+ import type { ${pascalName}Service } from '../src/${name}-api.service.js'
339
+
340
+ export interface Config extends CoreConfig {}
341
+
342
+ export interface UserSession extends CoreUserSession {}
343
+
344
+ export interface SingletonServices extends CoreSingletonServices<Config> {
345
+ ${camelName}: ${pascalName}Service
346
+ }
347
+
348
+ export interface Services extends CoreServices<SingletonServices> {}
349
+ `;
350
+ // Conditional: secret file
351
+ if (flags.oauth) {
352
+ files[`src/${name}.secret.ts`] =
353
+ `import { wireOAuth2Credential } from '@pikku/core/oauth2'
354
+
355
+ wireOAuth2Credential({
356
+ name: '${camelName}OAuth',
357
+ displayName: '${displayName} OAuth2',
358
+ description: '${displayName} OAuth2 credentials',
359
+ secretId: '${screamingName}_APP_CREDENTIALS',
360
+ tokenSecretId: '${screamingName}_TOKENS',
361
+ authorizationUrl: 'https://example.com/oauth2/authorize',
362
+ tokenUrl: 'https://example.com/oauth2/token',
363
+ scopes: ['read', 'write'],
364
+ })
365
+ `;
366
+ }
367
+ else if (flags.secret) {
368
+ files[`src/${name}.secret.ts`] = `import { z } from 'zod'
369
+ import { wireSecret } from '@pikku/core/secret'
370
+
371
+ export const ${camelName}SecretsSchema = z.object({
372
+ apiKey: z.string().describe('${displayName} API key'),
373
+ // Add other secret fields as needed
374
+ })
375
+
376
+ export type ${pascalName}Secrets = z.infer<typeof ${camelName}SecretsSchema>
377
+
378
+ wireSecret({
379
+ name: '${camelName}',
380
+ displayName: '${displayName} API',
381
+ description: '${description}',
382
+ secretId: '${screamingName}_CREDENTIALS',
383
+ schema: ${camelName}SecretsSchema,
384
+ })
385
+ `;
386
+ }
387
+ // Conditional: variable file
388
+ if (flags.variable) {
389
+ files[`src/${name}.variable.ts`] = `import { z } from 'zod'
390
+ import { wireVariable } from '@pikku/core/variable'
391
+
392
+ export const ${camelName}VariableSchema = z.string().optional().describe('TODO: describe this variable')
393
+
394
+ wireVariable({
395
+ name: '${camelName}_variable',
396
+ displayName: '${displayName} Variable',
397
+ description: 'TODO: describe this variable',
398
+ variableId: '${screamingName}_VARIABLE',
399
+ schema: ${camelName}VariableSchema,
400
+ })
401
+ `;
402
+ }
403
+ return files;
404
+ }
405
+ function getTestFiles(vars) {
406
+ const { name, camelName, pascalName, screamingName } = vars;
407
+ const files = {};
408
+ // test/package.json
409
+ files['package.json'] = JSON.stringify({
410
+ name: `@pikku/test-${name}`,
411
+ private: true,
412
+ type: 'module',
413
+ imports: {
414
+ '#pikku': './.pikku/pikku-types.gen.ts',
415
+ '#pikku/*': './.pikku/*',
416
+ },
417
+ scripts: {
418
+ pretest: 'pikku all',
419
+ test: 'node --import tsx --test src/**/*.test.ts',
420
+ },
421
+ dependencies: {
422
+ '@pikku/core': '*',
423
+ [`@pikku/addon-${name}`]: 'file:..',
424
+ },
425
+ devDependencies: {
426
+ '@pikku/cli': '*',
427
+ '@types/node': '^24',
428
+ tsx: '^4',
429
+ typescript: '^5.9',
430
+ zod: '^4',
431
+ },
432
+ }, null, 2);
433
+ // test/pikku.config.json
434
+ files['pikku.config.json'] = JSON.stringify({
435
+ $schema: 'https://raw.githubusercontent.com/pikkujs/pikku/refs/heads/main/packages/cli/cli.schema.json',
436
+ srcDirectories: ['./src', './types'],
437
+ outDir: './.pikku',
438
+ tsconfig: './tsconfig.json',
439
+ }, null, 2);
440
+ // test/tsconfig.json
441
+ files['tsconfig.json'] = JSON.stringify({
442
+ compilerOptions: {
443
+ target: 'ES2021',
444
+ module: 'NodeNext',
445
+ moduleResolution: 'NodeNext',
446
+ declaration: true,
447
+ strict: true,
448
+ noEmit: true,
449
+ skipLibCheck: true,
450
+ resolveJsonModule: true,
451
+ types: ['node'],
452
+ paths: {
453
+ '#pikku': ['./.pikku/pikku-types.gen.ts'],
454
+ },
455
+ },
456
+ include: ['src/*', '.pikku/**/*', 'types/**/*'],
457
+ exclude: ['node_modules', '.pikku/**/*.d.ts'],
458
+ }, null, 2);
459
+ // test/src/addons.ts
460
+ files['src/addons.ts'] = `import { wireAddon } from '#pikku'
461
+
462
+ wireAddon({ name: '${name}', package: '@pikku/addon-${name}' })
463
+ `;
464
+ // test/src/services.ts
465
+ files['src/services.ts'] = `import type {
466
+ SingletonServices,
467
+ } from '../types/application-types.js'
468
+ import {
469
+ CreateSingletonServices,
470
+ } from '@pikku/core'
471
+ import {
472
+ ConsoleLogger,
473
+ LocalVariablesService,
474
+ LocalSecretService,
475
+ } from '@pikku/core/services'
476
+
477
+ import '../.pikku/pikku-bootstrap.gen.js'
478
+
479
+ export const createSingletonServices: CreateSingletonServices<
480
+ {},
481
+ SingletonServices
482
+ > = async (_config, existingServices) => {
483
+ const variables = existingServices?.variables ?? new LocalVariablesService(process.env)
484
+ const secrets = existingServices?.secrets ?? new LocalSecretService(variables)
485
+
486
+ return {
487
+ logger: existingServices?.logger ?? new ConsoleLogger(),
488
+ variables,
489
+ secrets,
490
+ }
491
+ }
492
+ `;
493
+ // test/src/{name}-tests.function.ts
494
+ files[`src/${name}-tests.function.ts`] =
495
+ `import assert from 'node:assert/strict'
496
+ import { pikkuSessionlessFunc } from '#pikku'
497
+
498
+ export type Test${pascalName}Input = {}
499
+ export type Test${pascalName}Output = { passed: number; failed: string[] }
500
+
501
+ export const test${pascalName} = pikkuSessionlessFunc<Test${pascalName}Input, Test${pascalName}Output>({
502
+ func: async (_services, _data, { rpc }) => {
503
+ let passed = 0
504
+ const failed: string[] = []
505
+
506
+ const run = async (name: string, fn: () => Promise<void>) => {
507
+ try {
508
+ await fn()
509
+ passed++
510
+ } catch (e: any) {
511
+ failed.push(\`\${name}: \${e.message}\`)
512
+ }
513
+ }
514
+
515
+ // Add test cases here:
516
+ // await run('test name', async () => {
517
+ // const result = await rpc.invoke('${camelName}:functionName', { ... })
518
+ // assert.equal(result.someField, expectedValue)
519
+ // })
520
+
521
+ return { passed, failed }
522
+ }
523
+ })
524
+ `;
525
+ // test/src/{name}.test.ts
526
+ files[`src/${name}.test.ts`] = `import '../.pikku/pikku-bootstrap.gen.js'
527
+
528
+ import { test } from 'node:test'
529
+ import assert from 'node:assert/strict'
530
+ import { stopSingletonServices } from '@pikku/core'
531
+ import { rpcService } from '@pikku/core/rpc'
532
+ import { LocalSecretService } from '@pikku/core/services'
533
+ import { createSingletonServices } from './services.js'
534
+
535
+ test('${name} addon', async () => {
536
+ const secrets = new LocalSecretService()
537
+ // Set up secrets for the service
538
+ // await secrets.setSecretJSON('${screamingName}_CREDENTIALS', { ... })
539
+
540
+ const singletonServices = await createSingletonServices({}, { secrets })
541
+ const rpc = rpcService.getContextRPCService(singletonServices as any, {})
542
+
543
+ try {
544
+ const { passed, failed } = await rpc.invoke('test${pascalName}', {})
545
+
546
+ console.log(\`\\n \${passed} passed\`)
547
+ if (failed.length > 0) {
548
+ console.log(\` \${failed.length} failed:\`)
549
+ for (const f of failed) console.log(\` \\u2717 \${f}\`)
550
+ }
551
+
552
+ assert.equal(failed.length, 0, \`Failed tests:\\n\${failed.join('\\n')}\`)
553
+ } finally {
554
+ await stopSingletonServices()
555
+ }
556
+ })
557
+ `;
558
+ // test/types/application-types.d.ts
559
+ files['types/application-types.d.ts'] = `import type {
560
+ CoreConfig,
561
+ CoreServices,
562
+ CoreSingletonServices,
563
+ CoreUserSession,
564
+ } from '@pikku/core'
565
+
566
+ export interface Config extends CoreConfig {}
567
+ export interface UserSession extends CoreUserSession {}
568
+ export interface SingletonServices extends CoreSingletonServices<Config> {}
569
+ export interface Services extends CoreServices<SingletonServices> {}
570
+ `;
571
+ return files;
572
+ }
573
+ async function writeFiles(baseDir, files) {
574
+ const written = [];
575
+ for (const [relativePath, content] of Object.entries(files)) {
576
+ const fullPath = join(baseDir, relativePath);
577
+ await mkdir(join(fullPath, '..'), { recursive: true });
578
+ await writeFile(fullPath, content, 'utf-8');
579
+ written.push(fullPath);
580
+ }
581
+ return written;
582
+ }
583
+ export const pikkuNewAddon = pikkuSessionlessFunc({
584
+ func: async ({ logger, config }, { name, displayName, description, category = 'General', dir, secret = false, variable = false, oauth = false, test = true, openapi, }) => {
585
+ if (!/^[a-z][a-z0-9_-]*$/.test(name)) {
586
+ logger.error(`Invalid addon name "${name}": must start with a lowercase letter and contain only lowercase alphanumerics, hyphens, and underscores`);
587
+ process.exit(1);
588
+ }
589
+ const pascalName = toPascalCase(name);
590
+ const resolvedDisplayName = displayName || pascalName;
591
+ const resolvedDescription = description || `${resolvedDisplayName} integration for Pikku`;
592
+ // Resolve target directory
593
+ const baseDir = dir || config.scaffold?.addonDir || process.cwd();
594
+ const addonDir = join(baseDir, name);
595
+ if (existsSync(addonDir)) {
596
+ logger.error(`Directory already exists: ${addonDir}`);
597
+ process.exit(1);
598
+ }
599
+ const vars = {
600
+ name,
601
+ camelName: toCamelCase(name),
602
+ pascalName,
603
+ screamingName: toScreamingSnake(name),
604
+ displayName: resolvedDisplayName,
605
+ description: resolvedDescription,
606
+ category,
607
+ };
608
+ // oauth implies secret
609
+ const addonFiles = getAddonFiles(vars, {
610
+ secret: secret || oauth,
611
+ variable,
612
+ oauth,
613
+ });
614
+ // If openapi spec provided, generate typed files and merge over scaffold
615
+ if (openapi) {
616
+ const spec = await parseOpenAPISpec(openapi);
617
+ const openapiFiles = generateAddonFromOpenAPI(spec, vars, {
618
+ oauth,
619
+ secret: secret || oauth,
620
+ });
621
+ Object.assign(addonFiles, openapiFiles);
622
+ }
623
+ const written = await writeFiles(addonDir, addonFiles);
624
+ // Test harness
625
+ if (test) {
626
+ const testFiles = getTestFiles(vars);
627
+ const testWritten = await writeFiles(join(addonDir, 'test'), testFiles);
628
+ written.push(...testWritten);
629
+ }
630
+ logger.info(`Created addon at ${addonDir}`);
631
+ for (const f of written) {
632
+ logger.debug({ message: ` ${f}`, type: 'success' });
633
+ }
634
+ console.log(addonDir);
635
+ },
636
+ });
@@ -0,0 +1,10 @@
1
+ export declare const pikkuNewFunction: import("#pikku").PikkuFunctionConfig<{
2
+ name: string;
3
+ type?: string;
4
+ }, void, "session" | "rpc", import("#pikku").PikkuFunctionSessionless<{
5
+ name: string;
6
+ type?: string;
7
+ }, void, "session" | "rpc", import("#pikku").Services> | import("#pikku").PikkuFunction<{
8
+ name: string;
9
+ type?: string;
10
+ }, void, "session" | "rpc", import("#pikku").Services>, undefined, undefined>;