@pikku/cli 0.8.0 → 0.8.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 (43) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/bin/pikku-all.ts +122 -64
  3. package/bin/pikku-fetch.ts +5 -1
  4. package/bin/pikku-nextjs.ts +6 -2
  5. package/bin/pikku-openapi.ts +3 -1
  6. package/bin/pikku-queue-service.ts +5 -1
  7. package/bin/pikku-websocket.ts +5 -1
  8. package/cli.schema.json +113 -10
  9. package/dist/bin/pikku-all.js +51 -50
  10. package/dist/bin/pikku-fetch.js +5 -1
  11. package/dist/bin/pikku-nextjs.js +6 -2
  12. package/dist/bin/pikku-openapi.js +3 -1
  13. package/dist/bin/pikku-queue-service.js +5 -1
  14. package/dist/bin/pikku-websocket.js +5 -1
  15. package/dist/src/events/functions/pikku-command-services.d.ts +3 -0
  16. package/dist/src/events/functions/pikku-command-services.js +73 -0
  17. package/dist/src/events/http/serialize-typed-http-map.d.ts +0 -1
  18. package/dist/src/events/http/serialize-typed-http-map.js +1 -14
  19. package/dist/src/events/rpc/pikku-command-rpc-client.d.ts +2 -0
  20. package/dist/src/events/rpc/pikku-command-rpc-client.js +12 -0
  21. package/dist/src/events/rpc/serialize-rpc-wrapper.d.ts +1 -0
  22. package/dist/src/events/rpc/serialize-rpc-wrapper.js +29 -0
  23. package/dist/src/events/rpc/serialize-typed-rpc-map.js +1 -1
  24. package/dist/src/inspector-glob.js +1 -1
  25. package/dist/src/pikku-cli-config.d.ts +4 -1
  26. package/dist/src/pikku-cli-config.js +26 -14
  27. package/dist/src/schema-generator.js +2 -2
  28. package/dist/src/utils.d.ts +3 -0
  29. package/dist/src/utils.js +9 -3
  30. package/dist/tsconfig.tsbuildinfo +1 -1
  31. package/package.json +3 -3
  32. package/src/events/functions/pikku-command-services.ts +125 -0
  33. package/src/events/http/serialize-typed-http-map.ts +1 -18
  34. package/src/events/rpc/pikku-command-rpc-client.ts +33 -0
  35. package/src/events/rpc/serialize-rpc-wrapper.ts +29 -0
  36. package/src/events/rpc/serialize-typed-rpc-map.ts +1 -1
  37. package/src/inspector-glob.ts +1 -1
  38. package/src/pikku-cli-config.ts +33 -17
  39. package/src/schema-generator.ts +2 -2
  40. package/src/utils.ts +13 -3
  41. package/dist/src/events/http/pikku-command-nextjs.d.ts +0 -2
  42. package/dist/src/events/http/pikku-command-nextjs.js +0 -36
  43. package/src/events/http/pikku-command-nextjs.ts +0 -111
@@ -9,46 +9,37 @@ import { pikkuChannels } from '../src/events/channels/pikku-command-channels.js'
9
9
  import { inspectorGlob } from '../src/inspector-glob.js';
10
10
  import chokidar from 'chokidar';
11
11
  import { pikkuFunctions } from '../src/events/functions/pikku-command-functions.js';
12
+ import { pikkuServices } from '../src/events/functions/pikku-command-services.js';
12
13
  import { pikkuRPC } from '../src/events/rpc/pikku-command-rpc.js';
13
14
  import { pikkuRPCMap } from '../src/events/rpc/pikku-command-rpc-map.js';
14
- import { PikkuEventTypes } from '@pikku/core';
15
15
  import { pikkuQueue } from '../src/events/queue/pikku-command-queue.js';
16
16
  import { pikkuQueueMap } from '../src/events/queue/pikku-command-queue-map.js';
17
17
  import { pikkuFetch } from '../src/events/fetch/index.js';
18
+ import { pikkuRPCClient } from '../src/events/rpc/pikku-command-rpc-client.js';
18
19
  import { pikkuWebSocketTyped } from '../src/events/channels/pikku-command-websocket-typed.js';
19
- import { pikkuNext } from '../src/events/http/pikku-command-nextjs.js';
20
20
  import { pikkuOpenAPI } from '../src/events/http/pikku-command-openapi.js';
21
21
  import { pikkuMCP } from '../src/events/mcp/pikku-command-mcp.js';
22
22
  import { pikkuQueueService } from '../src/events/queue/pikku-command-queue-service.js';
23
23
  import { pikkuScheduler } from '../src/events/scheduler/pikku-command-scheduler.js';
24
24
  import { pikkuSchemas } from '../src/schemas.js';
25
25
  import { pikkuMCPJSON } from '../src/events/mcp/pikku-command-mcp-json.js';
26
+ import { pikkuNext } from '../src/runtimes/nextjs/pikku-command-nextjs.js';
27
+ const generateBootstrapFile = async (logger, cliConfig, bootstrapFile, specificImports, schemas) => {
28
+ // Common imports that every bootstrap file needs
29
+ const commonImports = [cliConfig.functionsMetaFile, cliConfig.functionsFile];
30
+ // Add schema if it exists
31
+ if (schemas) {
32
+ commonImports.push(`${cliConfig.schemaDirectory}/register.gen.ts`);
33
+ }
34
+ // Combine common imports with specific imports
35
+ const allImports = [...commonImports, ...specificImports];
36
+ await writeFileInDir(logger, bootstrapFile, allImports
37
+ .map((to) => `import '${getFileImportRelativePath(bootstrapFile, to, cliConfig.packageMappings)}'`)
38
+ .sort((to) => (to.includes('meta') ? -1 : 1)) // Ensure meta files are at the top
39
+ .join('\n'));
40
+ };
26
41
  const runAll = async (logger, cliConfig, options) => {
27
- const boostrapImports = {
28
- all: { meta: [], events: [] },
29
- };
30
- const addImport = (from, type, addTo) => {
31
- if (type === 'meta') {
32
- boostrapImports.all.meta.push(from);
33
- }
34
- else {
35
- boostrapImports.all.events.push(from);
36
- }
37
- for (const transport of Object.keys(PikkuEventTypes)) {
38
- if (!addTo || addTo?.includes(transport)) {
39
- boostrapImports[transport] = boostrapImports[transport] || {
40
- meta: [],
41
- events: [],
42
- };
43
- if (type === 'meta') {
44
- boostrapImports[transport].meta.push(from);
45
- }
46
- else {
47
- boostrapImports[transport].events.push(from);
48
- }
49
- }
50
- }
51
- };
42
+ const allImports = [];
52
43
  let typesDeclarationFileExists = true;
53
44
  let visitState = await inspectorGlob(logger, cliConfig.rootDir, cliConfig.srcDirectories, cliConfig.filters);
54
45
  if (!existsSync(cliConfig.typesDeclarationFile)) {
@@ -65,46 +56,51 @@ const runAll = async (logger, cliConfig, options) => {
65
56
  logger.info(`• No functions found, skipping remaining steps...\x1b[0m`);
66
57
  process.exit(1);
67
58
  }
68
- addImport(cliConfig.functionsMetaFile, 'meta');
69
- addImport(cliConfig.functionsFile, 'events');
59
+ // Base imports for all bootstrap files
60
+ allImports.push(cliConfig.functionsMetaFile, cliConfig.functionsFile);
61
+ // Generate services map
62
+ await pikkuServices(logger, cliConfig, visitState);
70
63
  await pikkuRPC(logger, cliConfig, visitState);
71
64
  await pikkuRPCMap(logger, cliConfig, visitState);
72
- addImport(cliConfig.rpcMetaFile, 'meta', [PikkuEventTypes.rpc]);
65
+ await pikkuRPCClient(logger, cliConfig);
66
+ allImports.push(cliConfig.rpcMetaFile);
73
67
  const schemas = await pikkuSchemas(logger, cliConfig, visitState);
74
68
  if (schemas) {
75
- addImport(`${cliConfig.schemaDirectory}/register.gen.ts`, 'other');
69
+ allImports.push(`${cliConfig.schemaDirectory}/register.gen.ts`);
76
70
  }
71
+ // RPC bootstrap is always generated since RPC is always present
72
+ await generateBootstrapFile(logger, cliConfig, cliConfig.bootstrapFiles.rpc, [cliConfig.rpcMetaFile], schemas);
77
73
  const http = await pikkuHTTP(logger, cliConfig, visitState);
78
74
  if (http) {
79
75
  await pikkuHTTPMap(logger, cliConfig, visitState);
80
76
  await pikkuFetch(logger, cliConfig);
81
- addImport(cliConfig.httpRoutesMetaFile, 'meta', [PikkuEventTypes.http]);
82
- addImport(cliConfig.httpRoutesFile, 'events', [PikkuEventTypes.http]);
77
+ allImports.push(cliConfig.httpRoutesMetaFile, cliConfig.httpRoutesFile);
78
+ await generateBootstrapFile(logger, cliConfig, cliConfig.bootstrapFiles.http, [cliConfig.httpRoutesMetaFile, cliConfig.httpRoutesFile], schemas);
83
79
  }
84
- const scheduled = await pikkuScheduler(logger, cliConfig, visitState);
85
- if (scheduled) {
86
- addImport(cliConfig.schedulersMetaFile, 'meta', [PikkuEventTypes.scheduled]);
87
- addImport(cliConfig.schedulersFile, 'events', [PikkuEventTypes.scheduled]);
80
+ const scheduler = await pikkuScheduler(logger, cliConfig, visitState);
81
+ if (scheduler) {
82
+ allImports.push(cliConfig.schedulersMetaFile, cliConfig.schedulersFile);
83
+ await generateBootstrapFile(logger, cliConfig, cliConfig.bootstrapFiles.scheduler, [cliConfig.schedulersMetaFile, cliConfig.schedulersFile], schemas);
88
84
  }
89
85
  const queues = await pikkuQueue(logger, cliConfig, visitState);
90
86
  if (queues) {
91
87
  await pikkuQueueMap(logger, cliConfig, visitState);
92
88
  await pikkuQueueService(logger, cliConfig);
93
- addImport(cliConfig.queueWorkersMetaFile, 'meta', [PikkuEventTypes.queue]);
94
- addImport(cliConfig.queueWorkersFile, 'events', [PikkuEventTypes.queue]);
89
+ allImports.push(cliConfig.queueWorkersMetaFile, cliConfig.queueWorkersFile);
90
+ await generateBootstrapFile(logger, cliConfig, cliConfig.bootstrapFiles.queue, [cliConfig.queueWorkersMetaFile, cliConfig.queueWorkersFile], schemas);
95
91
  }
96
92
  const channels = await pikkuChannels(logger, cliConfig, visitState);
97
93
  if (channels) {
98
94
  await pikkuChannelsMap(logger, cliConfig, visitState);
99
95
  await pikkuWebSocketTyped(logger, cliConfig);
100
- addImport(cliConfig.channelsMetaFile, 'meta', [PikkuEventTypes.channel]);
101
- addImport(cliConfig.channelsFile, 'events', [PikkuEventTypes.channel]);
96
+ allImports.push(cliConfig.channelsMetaFile, cliConfig.channelsFile);
97
+ await generateBootstrapFile(logger, cliConfig, cliConfig.bootstrapFiles.channel, [cliConfig.channelsMetaFile, cliConfig.channelsFile], schemas);
102
98
  }
103
99
  const mcp = await pikkuMCP(logger, cliConfig, visitState);
104
100
  if (mcp) {
105
101
  await pikkuMCPJSON(logger, cliConfig, visitState);
106
- addImport(cliConfig.mcpEndpointsMetaFile, 'meta', [PikkuEventTypes.mcp]);
107
- addImport(cliConfig.mcpEndpointsFile, 'events', [PikkuEventTypes.mcp]);
102
+ allImports.push(cliConfig.mcpEndpointsMetaFile, cliConfig.mcpEndpointsFile);
103
+ await generateBootstrapFile(logger, cliConfig, cliConfig.bootstrapFiles.mcp, [cliConfig.mcpEndpointsMetaFile, cliConfig.mcpEndpointsFile], schemas);
108
104
  }
109
105
  if (cliConfig.nextBackendFile || cliConfig.nextHTTPFile) {
110
106
  await pikkuNext(logger, cliConfig, visitState, options);
@@ -114,12 +110,11 @@ const runAll = async (logger, cliConfig, options) => {
114
110
  visitState = await inspectorGlob(logger, cliConfig.rootDir, cliConfig.srcDirectories, cliConfig.filters);
115
111
  await pikkuOpenAPI(logger, cliConfig, visitState);
116
112
  }
117
- for (const [type, { meta, events }] of Object.entries(boostrapImports)) {
118
- const bootstrapFile = type === 'all' ? cliConfig.bootstrapFile : cliConfig.bootstrapFiles[type];
119
- await writeFileInDir(logger, bootstrapFile, [...meta, ...events]
120
- .map((to) => `import '${getFileImportRelativePath(bootstrapFile, to, cliConfig.packageMappings)}'`)
121
- .join('\n'));
122
- }
113
+ // Generate main bootstrap file (pass all imports directly since this is the main file)
114
+ await writeFileInDir(logger, cliConfig.bootstrapFile, allImports
115
+ .map((to) => `import '${getFileImportRelativePath(cliConfig.bootstrapFile, to, cliConfig.packageMappings)}'`)
116
+ .sort((to) => (to.includes('meta') ? -1 : 1)) // Ensure meta files are at the top
117
+ .join('\n'));
123
118
  };
124
119
  const watch = (logger, cliConfig, options) => {
125
120
  const configWatcher = chokidar.watch(cliConfig.srcDirectories, {
@@ -162,7 +157,11 @@ const watch = (logger, cliConfig, options) => {
162
157
  };
163
158
  export const action = async (options) => {
164
159
  const logger = new CLILogger({ logLogo: true });
165
- const cliConfig = await getPikkuCLIConfig(options.config, [], options.tags, true);
160
+ const cliConfig = await getPikkuCLIConfig(options.config, [], {
161
+ tags: options.tags,
162
+ types: options.types,
163
+ directories: options.directories,
164
+ }, true);
166
165
  if (options.watch) {
167
166
  watch(logger, cliConfig, options);
168
167
  }
@@ -179,6 +178,8 @@ export const all = (program) => {
179
178
  .option('-se | --session-services-factory-type', 'The type of your session services factory')
180
179
  .option('-c | --config <string>', 'The path to pikku cli config file')
181
180
  .option('-t | --tags <tags...>', 'Which tags to filter by')
181
+ .option('--types <types...>', 'Which types to filter by (http, channel, queue, scheduler, rpc, mcp)')
182
+ .option('--directories <directories...>', 'Which directories to filter by')
182
183
  .option('-w | --watch', 'Whether to watch file changes')
183
184
  .action(action);
184
185
  };
@@ -3,7 +3,11 @@ import { pikkuFetch } from '../src/events/fetch/index.js';
3
3
  import { CLILogger } from '../src/utils.js';
4
4
  export const action = async (options) => {
5
5
  const logger = new CLILogger({ logLogo: true });
6
- const cliConfig = await getPikkuCLIConfig(options.config, ['rootDir', 'schemaDirectory', 'configDir', 'fetchFile'], options.tags, true);
6
+ const cliConfig = await getPikkuCLIConfig(options.config, ['rootDir', 'schemaDirectory', 'configDir', 'fetchFile'], {
7
+ tags: options.tags,
8
+ types: options.types,
9
+ directories: options.directories,
10
+ }, true);
7
11
  await pikkuFetch(logger, cliConfig);
8
12
  };
9
13
  export const fetch = (program) => {
@@ -1,10 +1,14 @@
1
1
  import { CLILogger } from '../src/utils.js';
2
2
  import { getPikkuCLIConfig } from '../src/pikku-cli-config.js';
3
3
  import { inspectorGlob } from '../src/inspector-glob.js';
4
- import { pikkuNext } from '../src/events/http/pikku-command-nextjs.js';
4
+ import { pikkuNext } from '../src/runtimes/nextjs/pikku-command-nextjs.js';
5
5
  export const action = async (options) => {
6
6
  const logger = new CLILogger({ logLogo: true });
7
- const cliConfig = await getPikkuCLIConfig(options.config, ['rootDir', 'schemaDirectory', 'configDir'], options.tags, true);
7
+ const cliConfig = await getPikkuCLIConfig(options.config, ['rootDir', 'schemaDirectory', 'configDir'], {
8
+ tags: options.tags,
9
+ types: options.types,
10
+ directories: options.directories,
11
+ }, true);
8
12
  const visitState = await inspectorGlob(logger, cliConfig.rootDir, cliConfig.srcDirectories, cliConfig.filters);
9
13
  await pikkuNext(logger, cliConfig, visitState, options);
10
14
  };
@@ -4,7 +4,9 @@ import { inspectorGlob } from '../src/inspector-glob.js';
4
4
  import { pikkuOpenAPI } from '../src/events/http/pikku-command-openapi.js';
5
5
  async function action({ config, tags }) {
6
6
  const logger = new CLILogger({ logLogo: true });
7
- const cliConfig = await getPikkuCLIConfig(config, ['rootDir', 'httpRoutesFile', 'openAPI', 'schemaDirectory', 'tsconfig'], tags);
7
+ const cliConfig = await getPikkuCLIConfig(config, ['rootDir', 'httpRoutesFile', 'openAPI', 'schemaDirectory', 'tsconfig'], {
8
+ tags: tags || [],
9
+ });
8
10
  const visitState = await inspectorGlob(logger, cliConfig.rootDir, cliConfig.srcDirectories, cliConfig.filters);
9
11
  await pikkuOpenAPI(logger, cliConfig, visitState);
10
12
  }
@@ -3,7 +3,11 @@ import { CLILogger } from '../src/utils.js';
3
3
  import { pikkuQueueService } from '../src/events/queue/pikku-command-queue-service.js';
4
4
  export const action = async (options) => {
5
5
  const logger = new CLILogger({ logLogo: true });
6
- const cliConfig = await getPikkuCLIConfig(options.config, ['rootDir', 'schemaDirectory', 'configDir', 'queueFile'], options.tags, true);
6
+ const cliConfig = await getPikkuCLIConfig(options.config, ['rootDir', 'schemaDirectory', 'configDir', 'queueFile'], {
7
+ tags: options.tags,
8
+ types: options.types,
9
+ directories: options.directories,
10
+ }, true);
7
11
  await pikkuQueueService(logger, cliConfig);
8
12
  };
9
13
  export const queue = (program) => {
@@ -3,7 +3,11 @@ import { getPikkuCLIConfig } from '../src/pikku-cli-config.js';
3
3
  import { pikkuWebSocketTyped } from '../src/events/channels/pikku-command-websocket-typed.js';
4
4
  export const action = async (options) => {
5
5
  const logger = new CLILogger({ logLogo: true });
6
- const cliConfig = await getPikkuCLIConfig(options.config, ['rootDir', 'schemaDirectory', 'configDir', 'fetchFile'], options.tags, true);
6
+ const cliConfig = await getPikkuCLIConfig(options.config, ['rootDir', 'schemaDirectory', 'configDir', 'fetchFile'], {
7
+ tags: options.tags,
8
+ types: options.types,
9
+ directories: options.directories,
10
+ }, true);
7
11
  await pikkuWebSocketTyped(logger, cliConfig);
8
12
  };
9
13
  export const websocket = (program) => {
@@ -0,0 +1,3 @@
1
+ import { PikkuCommand } from '../../types.js';
2
+ export declare const serializeServicesMap: (functionsMetaData: Record<string, any>, middlewareServices: string[] | undefined, servicesImport: string, sessionServicesImport: string) => string;
3
+ export declare const pikkuServices: PikkuCommand;
@@ -0,0 +1,73 @@
1
+ import { getFileImportRelativePath, getPikkuFilesAndMethods, logCommandInfoAndTime, writeFileInDir, } from '../../utils.js';
2
+ export const serializeServicesMap = (functionsMetaData, middlewareServices = [], servicesImport, sessionServicesImport) => {
3
+ // Extract all unique services from all functions
4
+ const usedServices = new Set();
5
+ // Internal services that are created internally and not via the create service script
6
+ const internalServices = new Set(['rpc', 'mcp', 'channel', 'userSession']);
7
+ for (const funcMeta of Object.values(functionsMetaData)) {
8
+ if (funcMeta.services && Array.isArray(funcMeta.services.services)) {
9
+ funcMeta.services.services.forEach((service) => {
10
+ // Only include services that are not internal
11
+ if (!internalServices.has(service)) {
12
+ usedServices.add(service);
13
+ }
14
+ });
15
+ }
16
+ }
17
+ // Add middleware services that might not be detected from function inspection
18
+ middlewareServices.forEach((service) => {
19
+ if (!internalServices.has(service)) {
20
+ usedServices.add(service);
21
+ }
22
+ });
23
+ // Create a map of services with true for all needed services
24
+ const servicesMap = Object.fromEntries(Array.from(usedServices)
25
+ .sort()
26
+ .map((service) => [service, true]));
27
+ // Generate the TypeScript code
28
+ const serviceKeys = Object.keys(servicesMap).sort();
29
+ // Services that are always required internally by the framework
30
+ const defaultServices = ['config', 'logger', 'variables', 'schema'];
31
+ // Combine default services with detected services
32
+ const allRequiredServices = [
33
+ ...new Set([...defaultServices, ...serviceKeys]),
34
+ ].sort();
35
+ // For RequiredSingletonServices, we need to pick from the actual SingletonServices interface
36
+ // This will be resolved at compile time based on what's actually in the SingletonServices interface
37
+ // We don't need to hardcode which services are singletons beyond the core framework ones
38
+ const code = [
39
+ '/**',
40
+ ' * This file was generated by the @pikku/cli',
41
+ ' */',
42
+ '',
43
+ servicesImport,
44
+ sessionServicesImport,
45
+ "import type { PikkuInteraction } from '@pikku/core'",
46
+ '',
47
+ 'export const singletonServices = {',
48
+ ...Object.keys(servicesMap).map((service) => ` '${service}': true,`),
49
+ '} as const',
50
+ '',
51
+ '// Singleton services (created once at startup)',
52
+ '// Only includes services that are both required and available in SingletonServices',
53
+ `export type RequiredSingletonServices = Pick<SingletonServices, Extract<keyof SingletonServices, ${allRequiredServices.map((key) => `'${key}'`).join(' | ')}>> & Partial<Omit<SingletonServices, ${allRequiredServices.map((key) => `'${key}'`).join(' | ')}>>`,
54
+ '',
55
+ '// Session services (created per request, can access singleton services)',
56
+ '// Omits singleton services and PikkuInteraction (mcp, rpc, http, channel)',
57
+ `export type RequiredSessionServices = Omit<Services, keyof SingletonServices | keyof PikkuInteraction>`,
58
+ '',
59
+ ].join('\n');
60
+ return code;
61
+ };
62
+ export const pikkuServices = async (logger, cliConfig, visitState) => {
63
+ return await logCommandInfoAndTime(logger, 'Generating Pikku services map', 'Generated Pikku services map', [visitState.functions.files.size === 0], async () => {
64
+ const { sessionServicesType, singletonServicesType } = await getPikkuFilesAndMethods(logger, visitState, cliConfig.packageMappings, cliConfig.typesDeclarationFile, {}, {
65
+ sessionServiceType: true,
66
+ singletonServicesType: true,
67
+ });
68
+ const servicesImport = `import type { ${singletonServicesType.type} } from '${getFileImportRelativePath(cliConfig.typesDeclarationFile, singletonServicesType.typePath, cliConfig.packageMappings)}'`;
69
+ const sessionServicesImport = `import type { ${sessionServicesType.type} } from '${getFileImportRelativePath(cliConfig.typesDeclarationFile, sessionServicesType.typePath, cliConfig.packageMappings)}'`;
70
+ const servicesCode = serializeServicesMap(visitState.functions.meta, cliConfig.middlewareServices, servicesImport, sessionServicesImport);
71
+ await writeFileInDir(logger, cliConfig.servicesFile, servicesCode);
72
+ });
73
+ };
@@ -2,4 +2,3 @@ import { HTTPRoutesMeta } from '@pikku/core/http';
2
2
  import { MetaInputTypes, TypesMap } from '@pikku/inspector';
3
3
  import { FunctionsMeta } from '@pikku/core';
4
4
  export declare const serializeTypedRoutesMap: (relativeToPath: string, packageMappings: Record<string, string>, typesMap: TypesMap, functionsMeta: FunctionsMeta, routesMeta: HTTPRoutesMeta, metaTypes: MetaInputTypes) => string;
5
- export declare function generateCustomTypes(typesMap: TypesMap, requiredTypes: Set<string>): string;
@@ -1,4 +1,5 @@
1
1
  import { serializeImportMap } from '../../serialize-import-map.js';
2
+ import { generateCustomTypes } from '../../utils.js';
2
3
  export const serializeTypedRoutesMap = (relativeToPath, packageMappings, typesMap, functionsMeta, routesMeta, metaTypes) => {
3
4
  const requiredTypes = new Set();
4
5
  const serializedCustomTypes = generateCustomTypes(typesMap, requiredTypes);
@@ -30,20 +31,6 @@ export type RoutesWithMethod<Method extends string> = {
30
31
  }[keyof RoutesMap];
31
32
  `;
32
33
  };
33
- export function generateCustomTypes(typesMap, requiredTypes) {
34
- return `
35
- // Custom types are those that are defined directly within generics
36
- // or are broken into simpler types
37
- ${Array.from(typesMap.customTypes.entries())
38
- .map(([name, { type, references }]) => {
39
- references.forEach((name) => {
40
- const originalName = typesMap.getTypeMeta(name).originalName;
41
- requiredTypes.add(originalName);
42
- });
43
- return `export type ${name} = ${type}`;
44
- })
45
- .join('\n')}`;
46
- }
47
34
  function generateRoutes(routesMeta, functionsMeta, typesMap, requiredTypes) {
48
35
  // Initialize an object to collect routes
49
36
  const routesObj = {};
@@ -0,0 +1,2 @@
1
+ import { PikkuCommandWithoutState } from '../../types.js';
2
+ export declare const pikkuRPCClient: PikkuCommandWithoutState;
@@ -0,0 +1,12 @@
1
+ import { serializeRPCWrapper } from './serialize-rpc-wrapper.js';
2
+ import { getFileImportRelativePath, logCommandInfoAndTime, writeFileInDir, } from '../../utils.js';
3
+ export const pikkuRPCClient = async (logger, { rpcFile, rpcMapDeclarationFile, packageMappings }) => {
4
+ return await logCommandInfoAndTime(logger, 'Generating RPC wrapper', 'Generated RPC wrapper', [rpcFile === undefined, "rpcFile isn't set in the pikku config"], async () => {
5
+ if (!rpcFile) {
6
+ throw new Error("rpcFile isn't set in the pikku config");
7
+ }
8
+ const rpcMapDeclarationPath = getFileImportRelativePath(rpcFile, rpcMapDeclarationFile, packageMappings);
9
+ const content = [serializeRPCWrapper(rpcMapDeclarationPath)];
10
+ await writeFileInDir(logger, rpcFile, content.join('\n'));
11
+ });
12
+ };
@@ -0,0 +1 @@
1
+ export declare const serializeRPCWrapper: (rpcMapPath: string) => string;
@@ -0,0 +1,29 @@
1
+ export const serializeRPCWrapper = (rpcMapPath) => {
2
+ return `
3
+ import { PikkuFetch } from "./pikku-fetch.gen.js"
4
+ import type { RPCInvoke } from '${rpcMapPath}'
5
+
6
+ export class PikkuRPC {
7
+ pikkuFetch = new PikkuFetch()
8
+
9
+ setPikkuFetch(pikkuFetch: PikkuFetch): void {
10
+ this.pikkuFetch = pikkuFetch
11
+ }
12
+
13
+ setServerUrl(serverUrl: string): void {
14
+ this.pikkuFetch.setServerUrl(serverUrl)
15
+ }
16
+
17
+ setAuthorizationJWT(jwt: string | null): void {
18
+ this.pikkuFetch.setAuthorizationJWT(jwt)
19
+ }
20
+
21
+ // Generic RPC invoke method
22
+ invoke: RPCInvoke = async (name, data) => {
23
+ return await this.pikkuFetch.post('/rpc', { name, data })
24
+ }
25
+ }
26
+
27
+ export const pikkuRPC = new PikkuRPC();
28
+ `;
29
+ };
@@ -19,7 +19,7 @@ interface RPCHandler<I, O> {
19
19
 
20
20
  ${serializedRPCs}
21
21
 
22
- type RPCInvoke = <Name extends keyof RPCMap>(
22
+ export type RPCInvoke = <Name extends keyof RPCMap>(
23
23
  name: Name,
24
24
  data: RPCMap[Name]['input'],
25
25
  options?: {
@@ -6,7 +6,7 @@ export const inspectorGlob = async (logger, rootDir, srcDirectories, filters) =>
6
6
  let result;
7
7
  await logCommandInfoAndTime(logger, 'Inspecting codebase', 'Inspected codebase', [false], async () => {
8
8
  const routeFiles = (await Promise.all(srcDirectories.map((dir) => glob(`${path.join(rootDir, dir)}/**/*.ts`)))).flat();
9
- result = await inspect(routeFiles, filters);
9
+ result = await inspect(logger, routeFiles, filters);
10
10
  });
11
11
  return result;
12
12
  };
@@ -22,6 +22,7 @@ export interface PikkuCLICoreOutputFiles {
22
22
  queueMapDeclarationFile: string;
23
23
  mcpEndpointsFile: string;
24
24
  mcpEndpointsMetaFile: string;
25
+ servicesFile: string;
25
26
  bootstrapFile: string;
26
27
  bootstrapFiles: Record<PikkuEventTypes, string>;
27
28
  }
@@ -38,13 +39,15 @@ export type PikkuCLIConfig = {
38
39
  nextHTTPFile?: string;
39
40
  fetchFile?: string;
40
41
  websocketFile?: string;
42
+ rpcFile?: string;
41
43
  queueFile?: string;
42
44
  mcpJsonFile?: string;
43
45
  openAPI?: {
44
46
  outputFile: string;
45
47
  additionalInfo: OpenAPISpecInfo;
46
48
  };
49
+ middlewareServices?: string[];
47
50
  filters: InspectorFilters;
48
51
  } & PikkuCLICoreOutputFiles;
49
- export declare const getPikkuCLIConfig: (configFile: string | undefined, requiredFields: Array<keyof PikkuCLIConfig>, tags?: string[], exitProcess?: boolean) => Promise<PikkuCLIConfig>;
52
+ export declare const getPikkuCLIConfig: (configFile: string | undefined, requiredFields: Array<keyof PikkuCLIConfig>, filters?: InspectorFilters, exitProcess?: boolean) => Promise<PikkuCLIConfig>;
50
53
  export declare const validateCLIConfig: (cliConfig: PikkuCLIConfig, required: Array<keyof PikkuCLIConfig>) => void;
@@ -6,14 +6,15 @@ const CONFIG_DIR_FILES = [
6
6
  'nextHTTPFile',
7
7
  'fetchFile',
8
8
  'websocketFile',
9
+ 'rpcFile',
9
10
  'queueFile',
10
11
  'mcpJsonFile',
11
12
  ];
12
- export const getPikkuCLIConfig = async (configFile = undefined, requiredFields, tags = [], exitProcess = false) => {
13
- const config = await _getPikkuCLIConfig(configFile, requiredFields, tags, exitProcess);
13
+ export const getPikkuCLIConfig = async (configFile = undefined, requiredFields, filters = {}, exitProcess = false) => {
14
+ const config = await _getPikkuCLIConfig(configFile, requiredFields, filters, exitProcess);
14
15
  return config;
15
16
  };
16
- const _getPikkuCLIConfig = async (configFile = undefined, requiredFields, tags = [], exitProcess = false) => {
17
+ const _getPikkuCLIConfig = async (configFile = undefined, requiredFields, filters = {}, exitProcess = false) => {
17
18
  if (!configFile) {
18
19
  let execDirectory = process.cwd();
19
20
  const files = await readdir(execDirectory);
@@ -34,7 +35,7 @@ const _getPikkuCLIConfig = async (configFile = undefined, requiredFields, tags =
34
35
  const configDir = dirname(configFile);
35
36
  const config = JSON.parse(file);
36
37
  if (config.extends) {
37
- const extendedConfig = await getPikkuCLIConfig(resolve(configDir, config.extends), [], tags, exitProcess);
38
+ const extendedConfig = await getPikkuCLIConfig(resolve(configDir, config.extends), [], filters, exitProcess);
38
39
  result = {
39
40
  ...extendedConfig,
40
41
  ...config,
@@ -57,8 +58,9 @@ const _getPikkuCLIConfig = async (configFile = undefined, requiredFields, tags =
57
58
  }
58
59
  if (result.outDir) {
59
60
  // Create transport/event directories
61
+ const functionDir = join(result.outDir, 'function');
60
62
  const httpDir = join(result.outDir, 'http');
61
- const channelsDir = join(result.outDir, 'channels');
63
+ const channelDir = join(result.outDir, 'channel');
62
64
  const rpcDir = join(result.outDir, 'rpc');
63
65
  const schedulerDir = join(result.outDir, 'scheduler');
64
66
  const queueDir = join(result.outDir, 'queue');
@@ -69,10 +71,10 @@ const _getPikkuCLIConfig = async (configFile = undefined, requiredFields, tags =
69
71
  }
70
72
  // Functions
71
73
  if (!result.functionsFile) {
72
- result.functionsFile = join(result.outDir, 'pikku-functions.gen.ts');
74
+ result.functionsFile = join(functionDir, 'pikku-functions.gen.ts');
73
75
  }
74
76
  if (!result.functionsMetaFile) {
75
- result.functionsMetaFile = join(result.outDir, 'pikku-functions-meta.gen.ts');
77
+ result.functionsMetaFile = join(functionDir, 'pikku-functions-meta.gen.ts');
76
78
  }
77
79
  if (!result.typesDeclarationFile) {
78
80
  result.typesDeclarationFile = join(result.outDir, 'pikku-types.gen.ts');
@@ -89,13 +91,13 @@ const _getPikkuCLIConfig = async (configFile = undefined, requiredFields, tags =
89
91
  }
90
92
  // Channels/WebSocket
91
93
  if (!result.channelsFile) {
92
- result.channelsFile = join(channelsDir, 'pikku-channels.gen.ts');
94
+ result.channelsFile = join(channelDir, 'pikku-channels.gen.ts');
93
95
  }
94
96
  if (!result.channelsMetaFile) {
95
- result.channelsMetaFile = join(channelsDir, 'pikku-channels-meta.gen.ts');
97
+ result.channelsMetaFile = join(channelDir, 'pikku-channels-meta.gen.ts');
96
98
  }
97
99
  if (!result.channelsMapDeclarationFile) {
98
- result.channelsMapDeclarationFile = join(channelsDir, 'pikku-channels-map.gen.d.ts');
100
+ result.channelsMapDeclarationFile = join(channelDir, 'pikku-channels-map.gen.d.ts');
99
101
  }
100
102
  // RPC
101
103
  if (!result.rpcMetaFile) {
@@ -106,10 +108,10 @@ const _getPikkuCLIConfig = async (configFile = undefined, requiredFields, tags =
106
108
  }
107
109
  // Scheduler
108
110
  if (!result.schedulersFile) {
109
- result.schedulersFile = join(schedulerDir, 'pikku-schedules.gen.ts');
111
+ result.schedulersFile = join(schedulerDir, 'pikku-scheduler.gen.ts');
110
112
  }
111
113
  if (!result.schedulersMetaFile) {
112
- result.schedulersMetaFile = join(schedulerDir, 'pikku-schedules-meta.gen.ts');
114
+ result.schedulersMetaFile = join(schedulerDir, 'pikku-scheduler-meta.gen.ts');
113
115
  }
114
116
  // Queue
115
117
  if (!result.queueWorkersFile) {
@@ -121,6 +123,10 @@ const _getPikkuCLIConfig = async (configFile = undefined, requiredFields, tags =
121
123
  if (!result.queueMapDeclarationFile) {
122
124
  result.queueMapDeclarationFile = join(queueDir, 'pikku-queue-map.gen.ts');
123
125
  }
126
+ // Services
127
+ if (!result.servicesFile) {
128
+ result.servicesFile = join(result.outDir, 'pikku-services.gen.ts');
129
+ }
124
130
  // Bootstrap files
125
131
  if (!result.bootstrapFile) {
126
132
  result.bootstrapFile = join(result.outDir, 'pikku-bootstrap.gen.ts');
@@ -157,8 +163,14 @@ const _getPikkuCLIConfig = async (configFile = undefined, requiredFields, tags =
157
163
  }
158
164
  }
159
165
  result.filters = result.filters || {};
160
- if (tags.length > 0) {
161
- result.filters.tags = tags;
166
+ if (filters.tags && filters.tags.length > 0) {
167
+ result.filters.tags = filters.tags;
168
+ }
169
+ if (filters.types && filters.types.length > 0) {
170
+ result.filters.types = filters.types;
171
+ }
172
+ if (filters.directories && filters.directories.length > 0) {
173
+ result.filters.directories = filters.directories;
162
174
  }
163
175
  if (!isAbsolute(result.tsconfig)) {
164
176
  result.tsconfig = join(result.rootDir, result.tsconfig);
@@ -43,10 +43,10 @@ export async function generateSchemas(logger, tsconfig, typesMap, functionMeta,
43
43
  catch (e) {
44
44
  // Ignore rootless errors
45
45
  if (e instanceof RootlessError) {
46
- console.error('Error generating schema since it has no root:', schema);
46
+ logger.error(`Error generating schema since it has no root: ${schema}`);
47
47
  return;
48
48
  }
49
- throw e;
49
+ logger.error(`Error generating schema: ${schema}`);
50
50
  }
51
51
  });
52
52
  return schemas;
@@ -8,6 +8,7 @@ export declare class CLILogger {
8
8
  info(message: string): void;
9
9
  error(message: string): void;
10
10
  warn(message: string): void;
11
+ debug(message: string): void;
11
12
  private logPikkuLogo;
12
13
  }
13
14
  export declare const getFileImportRelativePath: (from: string, to: string, packageMappings: Record<string, string>) => string;
@@ -33,6 +34,8 @@ export interface PikkuCLIOptions {
33
34
  singletonServicesFactoryType?: string;
34
35
  sessionServicesFactoryType?: string;
35
36
  tags?: string[];
37
+ types?: string[];
38
+ directories?: string[];
36
39
  }
37
40
  export declare const getPikkuFilesAndMethods: (logger: CLILogger, { singletonServicesTypeImportMap, sessionServicesTypeImportMap, userSessionTypeImportMap, sessionServicesFactories, singletonServicesFactories, configFactories, }: InspectorState, packageMappings: Record<string, string>, outputFile: string, { configFileType, singletonServicesFactoryType, sessionServicesFactoryType, }: PikkuCLIOptions, requires?: Partial<{
38
41
  config: boolean;
package/dist/src/utils.js CHANGED
@@ -1,4 +1,4 @@
1
- import { relative, dirname } from 'path';
1
+ import { relative, dirname, resolve } from 'path';
2
2
  import { mkdir, writeFile } from 'fs/promises';
3
3
  import chalk from 'chalk';
4
4
  import { fileURLToPath } from 'url';
@@ -33,6 +33,11 @@ export class CLILogger {
33
33
  warn(message) {
34
34
  console.error(chalk.yellow(message));
35
35
  }
36
+ debug(message) {
37
+ if (process.env.DEBUG) {
38
+ console.log(chalk.gray(message));
39
+ }
40
+ }
36
41
  logPikkuLogo() {
37
42
  this.primary(logo);
38
43
  const packageJson = JSON.parse(readFileSync(`${dirname(__filename)}/../../package.json`, 'utf-8'));
@@ -44,11 +49,12 @@ export const getFileImportRelativePath = (from, to, packageMappings) => {
44
49
  if (!/^\.+\//.test(filePath)) {
45
50
  filePath = `./${filePath}`;
46
51
  }
52
+ const absolutePath = resolve(dirname(from), to);
47
53
  // let usesPackageName = false
48
54
  for (const [path, packageName] of Object.entries(packageMappings)) {
49
- if (filePath.includes(path)) {
55
+ if (absolutePath.includes(path)) {
50
56
  // usesPackageName = true
51
- filePath = filePath.replace(new RegExp(`.*${path}`), packageName);
57
+ filePath = absolutePath.replace(new RegExp(`.*${path}`), packageName);
52
58
  break;
53
59
  }
54
60
  }