@pikku/cli 0.12.15 → 0.12.16

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 (160) hide show
  1. package/cli.schema.json +1 -1
  2. package/console-app/assets/index-CzMWJFqj.js +700 -0
  3. package/console-app/index.html +1 -1
  4. package/dist/.pikku/agent/pikku-agent-types.gen.d.ts +1 -1
  5. package/dist/.pikku/channel/pikku-channel-types.gen.d.ts +1 -1
  6. package/dist/.pikku/channel/pikku-channel-types.gen.js +1 -1
  7. package/dist/.pikku/cli/pikku-cli-channel.js +16 -1
  8. package/dist/.pikku/cli/pikku-cli-client.gen.d.ts +1 -1
  9. package/dist/.pikku/cli/pikku-cli-client.gen.js +1 -1
  10. package/dist/.pikku/cli/pikku-cli-types.gen.d.ts +1 -1
  11. package/dist/.pikku/cli/pikku-cli-types.gen.js +1 -1
  12. package/dist/.pikku/cli/pikku-cli-wirings-meta.gen.js +1 -1
  13. package/dist/.pikku/cli/pikku-cli-wirings-meta.gen.json +68 -5
  14. package/dist/.pikku/cli/pikku-cli-wirings.gen.d.ts +1 -1
  15. package/dist/.pikku/cli/pikku-cli-wirings.gen.js +1 -1
  16. package/dist/.pikku/cli/pikku-cli.gen.d.ts +1 -1
  17. package/dist/.pikku/cli/pikku-cli.gen.js +1 -1
  18. package/dist/.pikku/console/pikku-node-types.gen.d.ts +1 -1
  19. package/dist/.pikku/function/pikku-function-types.gen.d.ts +16 -19
  20. package/dist/.pikku/function/pikku-function-types.gen.js +15 -19
  21. package/dist/.pikku/function/pikku-functions-meta.gen.js +1 -1
  22. package/dist/.pikku/function/pikku-functions-meta.gen.json +190 -107
  23. package/dist/.pikku/function/pikku-functions.gen.js +6 -2
  24. package/dist/.pikku/http/pikku-http-types.gen.d.ts +1 -1
  25. package/dist/.pikku/http/pikku-http-types.gen.js +1 -1
  26. package/dist/.pikku/http/pikku-http-wirings-meta.gen.js +1 -1
  27. package/dist/.pikku/http/pikku-http-wirings-meta.gen.json +18 -1
  28. package/dist/.pikku/http/pikku-http-wirings.gen.d.ts +2 -2
  29. package/dist/.pikku/http/pikku-http-wirings.gen.js +3 -3
  30. package/dist/.pikku/mcp/pikku-mcp-types.gen.d.ts +1 -1
  31. package/dist/.pikku/mcp/pikku-mcp-types.gen.js +1 -1
  32. package/dist/.pikku/pikku-bootstrap.gen.d.ts +3 -10
  33. package/dist/.pikku/pikku-bootstrap.gen.js +1 -18
  34. package/dist/.pikku/pikku-meta-service.gen.d.ts +7 -0
  35. package/dist/.pikku/pikku-meta-service.gen.js +9 -0
  36. package/dist/.pikku/pikku-services.gen.d.ts +2 -1
  37. package/dist/.pikku/pikku-services.gen.js +1 -0
  38. package/dist/.pikku/pikku-types.gen.d.ts +1 -1
  39. package/dist/.pikku/pikku-types.gen.js +1 -1
  40. package/dist/.pikku/queue/pikku-queue-types.gen.d.ts +1 -1
  41. package/dist/.pikku/queue/pikku-queue-types.gen.js +1 -1
  42. package/dist/.pikku/rpc/pikku-rpc-wirings-meta.internal.gen.js +1 -1
  43. package/dist/.pikku/rpc/pikku-rpc-wirings-meta.internal.gen.json +5 -1
  44. package/dist/.pikku/scheduler/pikku-scheduler-types.gen.d.ts +1 -1
  45. package/dist/.pikku/scheduler/pikku-scheduler-types.gen.js +1 -1
  46. package/dist/.pikku/schemas/register.gen.js +15 -7
  47. package/dist/.pikku/schemas/schemas/DeployApplyInput.schema.json +1 -0
  48. package/dist/.pikku/schemas/schemas/DeployPlanInput.schema.json +1 -0
  49. package/dist/.pikku/schemas/schemas/PikkuCLIConfig.schema.json +1 -1
  50. package/dist/.pikku/schemas/schemas/PikkuNewAddonInput.schema.json +1 -1
  51. package/dist/.pikku/schemas/schemas/PikkuWorkflowRoutesOutput.schema.json +1 -0
  52. package/dist/.pikku/schemas/schemas/RemoteRPCHandlerInput.schema.json +1 -0
  53. package/dist/.pikku/secrets/pikku-secret-types.gen.d.ts +1 -1
  54. package/dist/.pikku/secrets/pikku-secret-types.gen.js +1 -1
  55. package/dist/.pikku/secrets/pikku-secrets.gen.d.ts +1 -1
  56. package/dist/.pikku/secrets/pikku-secrets.gen.js +1 -1
  57. package/dist/.pikku/trigger/pikku-trigger-types.gen.d.ts +1 -1
  58. package/dist/.pikku/trigger/pikku-trigger-types.gen.js +1 -1
  59. package/dist/.pikku/variables/pikku-variable-types.gen.d.ts +1 -1
  60. package/dist/.pikku/variables/pikku-variable-types.gen.js +1 -1
  61. package/dist/.pikku/variables/pikku-variables.gen.d.ts +1 -1
  62. package/dist/.pikku/variables/pikku-variables.gen.js +1 -1
  63. package/dist/.pikku/workflow/pikku-workflow-types.gen.d.ts +3 -24
  64. package/dist/.pikku/workflow/pikku-workflow-types.gen.js +1 -14
  65. package/dist/.pikku/workflow/pikku-workflow-wirings-meta.gen.js +1 -1
  66. package/dist/.pikku/workflow/pikku-workflow-wirings.gen.d.ts +1 -1
  67. package/dist/.pikku/workflow/pikku-workflow-wirings.gen.js +1 -1
  68. package/dist/src/cli.wiring.js +63 -4
  69. package/dist/src/deploy/analyzer/analyzer.d.ts +16 -0
  70. package/dist/src/deploy/analyzer/analyzer.js +557 -0
  71. package/dist/src/deploy/analyzer/index.d.ts +3 -0
  72. package/dist/src/deploy/analyzer/index.js +1 -0
  73. package/dist/src/deploy/analyzer/manifest.d.ts +112 -0
  74. package/dist/src/deploy/analyzer/manifest.js +8 -0
  75. package/dist/src/deploy/build-pipeline.d.ts +39 -0
  76. package/dist/src/deploy/build-pipeline.js +209 -0
  77. package/dist/src/deploy/bundler/bundler.d.ts +30 -0
  78. package/dist/src/deploy/bundler/bundler.js +196 -0
  79. package/dist/src/deploy/bundler/dep-extractor.d.ts +35 -0
  80. package/dist/src/deploy/bundler/dep-extractor.js +213 -0
  81. package/dist/src/deploy/bundler/index.d.ts +3 -0
  82. package/dist/src/deploy/bundler/index.js +2 -0
  83. package/dist/src/deploy/bundler/types.d.ts +21 -0
  84. package/dist/src/deploy/bundler/types.js +5 -0
  85. package/dist/src/deploy/codegen/index.d.ts +2 -0
  86. package/dist/src/deploy/codegen/index.js +1 -0
  87. package/dist/src/deploy/codegen/per-unit-codegen.d.ts +44 -0
  88. package/dist/src/deploy/codegen/per-unit-codegen.js +216 -0
  89. package/dist/src/deploy/plan/executor.d.ts +9 -0
  90. package/dist/src/deploy/plan/executor.js +49 -0
  91. package/dist/src/deploy/plan/formatter.d.ts +4 -0
  92. package/dist/src/deploy/plan/formatter.js +114 -0
  93. package/dist/src/deploy/plan/index.d.ts +5 -0
  94. package/dist/src/deploy/plan/index.js +3 -0
  95. package/dist/src/deploy/plan/planner.d.ts +4 -0
  96. package/dist/src/deploy/plan/planner.js +220 -0
  97. package/dist/src/deploy/plan/provider.d.ts +30 -0
  98. package/dist/src/deploy/plan/provider.js +1 -0
  99. package/dist/src/deploy/plan/types.d.ts +29 -0
  100. package/dist/src/deploy/plan/types.js +1 -0
  101. package/dist/src/deploy/provider-adapter.d.ts +111 -0
  102. package/dist/src/deploy/provider-adapter.js +10 -0
  103. package/dist/src/functions/commands/all.js +6 -2
  104. package/dist/src/functions/commands/deploy-apply.d.ts +22 -0
  105. package/dist/src/functions/commands/deploy-apply.js +206 -0
  106. package/dist/src/functions/commands/deploy-info.d.ts +1 -0
  107. package/dist/src/functions/commands/deploy-info.js +122 -0
  108. package/dist/src/functions/commands/deploy-plan.d.ts +10 -0
  109. package/dist/src/functions/commands/deploy-plan.js +96 -0
  110. package/dist/src/functions/commands/enable.js +1 -1
  111. package/dist/src/functions/commands/new-addon.d.ts +3 -0
  112. package/dist/src/functions/commands/new-addon.js +68 -2
  113. package/dist/src/functions/commands/pikku-command-bootstrap.js +30 -20
  114. package/dist/src/functions/runtimes/nextjs/pikku-command-nextjs.js +7 -3
  115. package/dist/src/functions/wirings/ai-agent/pikku-command-ai-agent.js +7 -5
  116. package/dist/src/functions/wirings/channels/pikku-channels.js +3 -0
  117. package/dist/src/functions/wirings/channels/pikku-command-channels.js +3 -0
  118. package/dist/src/functions/wirings/cli/pikku-command-cli.js +3 -0
  119. package/dist/src/functions/wirings/cli/serialize-channel-cli.js +2 -2
  120. package/dist/src/functions/wirings/console/serialize-console-functions.js +2 -2
  121. package/dist/src/functions/wirings/functions/pikku-command-services.d.ts +1 -1
  122. package/dist/src/functions/wirings/functions/pikku-command-services.js +9 -2
  123. package/dist/src/functions/wirings/functions/serialize-function-imports.js +5 -3
  124. package/dist/src/functions/wirings/functions/serialize-function-types.js +17 -19
  125. package/dist/src/functions/wirings/http/pikku-command-http-routes.js +3 -0
  126. package/dist/src/functions/wirings/http/pikku-http-routes.js +3 -0
  127. package/dist/src/functions/wirings/mcp/pikku-command-mcp.js +6 -0
  128. package/dist/src/functions/wirings/package/pikku-command-package.js +1 -1
  129. package/dist/src/functions/wirings/package/serialize-package.d.ts +1 -1
  130. package/dist/src/functions/wirings/package/serialize-package.js +5 -2
  131. package/dist/src/functions/wirings/queue/pikku-command-queue.js +4 -0
  132. package/dist/src/functions/wirings/queue/pikku-queue.js +4 -0
  133. package/dist/src/functions/wirings/queue/serialize-queue-map.js +4 -1
  134. package/dist/src/functions/wirings/rpc/pikku-command-rpc.js +10 -3
  135. package/dist/src/functions/wirings/rpc/serialize-public-rpc.js +5 -27
  136. package/dist/src/functions/wirings/rpc/serialize-remote-rpc.js +11 -14
  137. package/dist/src/functions/wirings/rpc/serialize-rpc-wrapper.js +28 -3
  138. package/dist/src/functions/wirings/rpc/serialize-typed-rpc-map.js +15 -5
  139. package/dist/src/functions/wirings/scheduler/pikku-command-scheduler.js +4 -0
  140. package/dist/src/functions/wirings/workflow/pikku-command-workflow-routes.d.ts +1 -0
  141. package/dist/src/functions/wirings/workflow/pikku-command-workflow-routes.js +21 -0
  142. package/dist/src/functions/wirings/workflow/pikku-command-workflow.js +10 -9
  143. package/dist/src/functions/wirings/workflow/serialize-workflow-map.d.ts +6 -1
  144. package/dist/src/functions/wirings/workflow/serialize-workflow-map.js +42 -5
  145. package/dist/src/functions/wirings/workflow/serialize-workflow-registration.d.ts +1 -1
  146. package/dist/src/functions/wirings/workflow/serialize-workflow-registration.js +3 -2
  147. package/dist/src/functions/wirings/workflow/serialize-workflow-routes.d.ts +4 -0
  148. package/dist/src/functions/wirings/workflow/serialize-workflow-routes.js +139 -0
  149. package/dist/src/functions/wirings/workflow/serialize-workflow-types.js +4 -51
  150. package/dist/src/scaffold/rpc-remote.gen.d.ts +10 -0
  151. package/dist/src/scaffold/rpc-remote.gen.js +22 -0
  152. package/dist/src/services.js +12 -7
  153. package/dist/src/utils/pikku-cli-config.d.ts +1 -1
  154. package/dist/src/utils/pikku-cli-config.js +30 -28
  155. package/dist/src/utils/strip-verbose-meta.js +2 -0
  156. package/dist/tsconfig.tsbuildinfo +1 -1
  157. package/package.json +8 -4
  158. package/console-app/assets/index-robZPL3O.js +0 -672
  159. package/dist/src/functions/wirings/workflow/serialize-workflow-workers.d.ts +0 -4
  160. package/dist/src/functions/wirings/workflow/serialize-workflow-workers.js +0 -47
@@ -0,0 +1,206 @@
1
+ import { basename, join, relative } from 'node:path';
2
+ import { readFile } from 'node:fs/promises';
3
+ import { pikkuSessionlessFunc } from '#pikku';
4
+ import { runBuildPipeline } from '../../deploy/build-pipeline.js';
5
+ function toRelativeImport(fromDir, toFile) {
6
+ let rel = relative(fromDir, toFile).replace(/\\/g, '/');
7
+ if (!rel.startsWith('.'))
8
+ rel = `./${rel}`;
9
+ return rel.replace(/\.ts$/, '.js');
10
+ }
11
+ export function getEntryContext(unitDir, pikkuDir, unit, inspectorState) {
12
+ const bootstrapRelative = relative(unitDir, join(pikkuDir, 'pikku-bootstrap.gen.js'));
13
+ const bootstrapPath = bootstrapRelative.startsWith('./') || bootstrapRelative.startsWith('../')
14
+ ? bootstrapRelative
15
+ : `./${bootstrapRelative}`;
16
+ const { pikkuConfigFactory, singletonServicesFactory, singletonServicesType, } = inspectorState.filesAndMethods;
17
+ if (!pikkuConfigFactory || !singletonServicesFactory) {
18
+ throw new Error('Cannot generate deploy entries: createConfig and createSingletonServices must be defined in your project');
19
+ }
20
+ const configRelative = toRelativeImport(unitDir, pikkuConfigFactory.file);
21
+ const servicesRelative = toRelativeImport(unitDir, singletonServicesFactory.file);
22
+ const singletonServicesImport = singletonServicesType
23
+ ? `import type { ${singletonServicesType.type} } from '${toRelativeImport(unitDir, singletonServicesType.typePath)}'`
24
+ : '';
25
+ const servicesType = singletonServicesType
26
+ ? `Partial<${singletonServicesType.type}>`
27
+ : 'Record<string, unknown>';
28
+ return {
29
+ unit,
30
+ unitDir,
31
+ bootstrapPath,
32
+ configImport: `import { ${pikkuConfigFactory.variable} } from '${configRelative}'`,
33
+ configVar: pikkuConfigFactory.variable,
34
+ servicesImport: `import { ${singletonServicesFactory.variable} } from '${servicesRelative}'`,
35
+ servicesVar: singletonServicesFactory.variable,
36
+ singletonServicesImport,
37
+ servicesType,
38
+ };
39
+ }
40
+ function sanitizeProjectId(raw) {
41
+ return (raw
42
+ .toLowerCase()
43
+ .replace(/[^a-z0-9-]/g, '-')
44
+ .replace(/-+/g, '-')
45
+ .replace(/^-|-$/g, '') || 'pikku-project');
46
+ }
47
+ async function resolveProjectId(projectDir) {
48
+ try {
49
+ const pkg = JSON.parse(await readFile(join(projectDir, 'package.json'), 'utf-8'));
50
+ if (pkg.name) {
51
+ const name = pkg.name.replace(/^@[^/]+\//, '');
52
+ return sanitizeProjectId(name);
53
+ }
54
+ }
55
+ catch (e) {
56
+ const err = e;
57
+ if (err?.code !== 'ENOENT') {
58
+ console.warn(`Warning: failed to read package.json: ${err?.message ?? e}`);
59
+ }
60
+ }
61
+ return sanitizeProjectId(basename(projectDir));
62
+ }
63
+ export async function resolveProvider(config, providerName) {
64
+ const name = providerName ?? config?.deploy?.defaultProvider ?? 'cloudflare';
65
+ const providers = config?.deploy?.providers ?? {
66
+ cloudflare: '@pikku/deploy-cloudflare',
67
+ serverless: '@pikku/deploy-serverless',
68
+ azure: '@pikku/deploy-azure',
69
+ standalone: '@pikku/deploy-standalone',
70
+ };
71
+ const packageName = providers[name];
72
+ if (!packageName) {
73
+ throw new Error(`Unknown deploy provider: '${name}'. Available: ${Object.keys(providers).join(', ')}`);
74
+ }
75
+ const adapterExportName = name.charAt(0).toUpperCase() + name.slice(1) + 'ProviderAdapter';
76
+ try {
77
+ const mod = await import(packageName);
78
+ if (typeof mod.createAdapter === 'function') {
79
+ return mod.createAdapter();
80
+ }
81
+ if (typeof mod[adapterExportName] === 'function') {
82
+ return new mod[adapterExportName]();
83
+ }
84
+ throw new Error(`Deploy provider '${packageName}' does not export createAdapter() or ${adapterExportName}`);
85
+ }
86
+ catch (e) {
87
+ const err = e;
88
+ if (err?.code === 'ERR_MODULE_NOT_FOUND' ||
89
+ err?.code === 'MODULE_NOT_FOUND') {
90
+ throw new Error(`Deploy provider '${packageName}' is not installed. Run: yarn add ${packageName}`);
91
+ }
92
+ throw e;
93
+ }
94
+ }
95
+ const ANSI = {
96
+ green: '\x1b[32m',
97
+ red: '\x1b[31m',
98
+ bold: '\x1b[1m',
99
+ reset: '\x1b[0m',
100
+ };
101
+ async function writeResultFile(resultFile, result) {
102
+ if (resultFile) {
103
+ const { writeFile } = await import('node:fs/promises');
104
+ await writeFile(resultFile, JSON.stringify(result, null, 2), 'utf-8');
105
+ }
106
+ }
107
+ async function runDeploy(provider, providerDir, logger, resultFile) {
108
+ if (typeof provider.deploy !== 'function') {
109
+ logger.error(`Provider '${provider.name}' does not support deploy.`);
110
+ await writeResultFile(resultFile, {
111
+ success: false,
112
+ errors: [{ step: 'provider', error: 'No deploy support' }],
113
+ });
114
+ process.exit(1);
115
+ }
116
+ logger.info(`Deploying via ${provider.name}...`);
117
+ let deployResult;
118
+ try {
119
+ deployResult = await provider.deploy({
120
+ buildDir: providerDir,
121
+ logger,
122
+ onProgress: (_step, _detail) => {
123
+ process.stdout.write(` ${ANSI.green}done${ANSI.reset}\n`);
124
+ },
125
+ });
126
+ }
127
+ catch (err) {
128
+ const message = err instanceof Error ? err.message : String(err);
129
+ deployResult = {
130
+ success: false,
131
+ errors: [{ step: 'deploy', error: message }],
132
+ };
133
+ }
134
+ await writeResultFile(resultFile, deployResult);
135
+ console.log('');
136
+ if (deployResult.success) {
137
+ logger.info(`${ANSI.green}${ANSI.bold}Deployment complete.${ANSI.reset}`);
138
+ logger.info(` ${deployResult.workersDeployed?.length ?? 0} units deployed, ${deployResult.resourcesCreated?.length ?? 0} resources created`);
139
+ }
140
+ else {
141
+ logger.error(`Deployment finished with ${deployResult.errors.length} error(s):`);
142
+ for (const e of deployResult.errors) {
143
+ logger.error(` ${e.step}: ${e.error}`);
144
+ }
145
+ }
146
+ }
147
+ export const deployApply = pikkuSessionlessFunc({
148
+ func: async ({ logger, config, getInspectorState }, data) => {
149
+ const projectDir = config.rootDir;
150
+ const provider = await resolveProvider(config, data?.provider);
151
+ const fromPlan = data?.fromPlan ?? false;
152
+ const resultFile = data?.resultFile;
153
+ if (fromPlan) {
154
+ // Skip build pipeline — deploy from existing plan output
155
+ const { join } = await import('node:path');
156
+ const { existsSync } = await import('node:fs');
157
+ const providerDir = join(projectDir, '.deploy', provider.deployDirName);
158
+ const infraPath = join(providerDir, 'infra.json');
159
+ if (!existsSync(infraPath)) {
160
+ logger.error(`No plan found at ${providerDir}. Run 'pikku deploy plan' first.`);
161
+ await writeResultFile(resultFile, {
162
+ success: false,
163
+ errors: [{ step: 'plan', error: 'No plan found' }],
164
+ });
165
+ process.exit(1);
166
+ }
167
+ await runDeploy(provider, providerDir, logger, resultFile);
168
+ return;
169
+ }
170
+ // Full build + deploy pipeline
171
+ const inspectorState = await getInspectorState(true);
172
+ const projectId = await resolveProjectId(projectDir);
173
+ const buildResult = await runBuildPipeline({
174
+ projectDir,
175
+ projectId,
176
+ provider,
177
+ inspectorState,
178
+ serverlessIncompatible: config.deploy?.serverlessIncompatible,
179
+ getEntryContext,
180
+ logger,
181
+ });
182
+ if (buildResult.manifest.units.length === 0) {
183
+ logger.info('No deployment units found. Nothing to deploy.');
184
+ await writeResultFile(resultFile, {
185
+ success: true,
186
+ workersDeployed: [],
187
+ resourcesCreated: [],
188
+ errors: [],
189
+ });
190
+ return;
191
+ }
192
+ const { providerDir, bundled } = buildResult;
193
+ if (typeof provider.deploy === 'function') {
194
+ await runDeploy(provider, providerDir, logger, resultFile);
195
+ }
196
+ else {
197
+ logger.info(`${ANSI.green}${ANSI.bold}Build complete.${ANSI.reset}`);
198
+ logger.info(` ${bundled.length} functions bundled to ${ANSI.bold}${providerDir}${ANSI.reset}`);
199
+ await writeResultFile(resultFile, {
200
+ success: true,
201
+ buildOnly: true,
202
+ unitCount: bundled.length,
203
+ });
204
+ }
205
+ },
206
+ });
@@ -0,0 +1 @@
1
+ export declare const deployInfo: import("#pikku").PikkuFunctionConfig<void, void, "session" | "rpc", import("#pikku").PikkuFunctionSessionless<void, void, "session" | "rpc", import("#pikku").Services> | import("#pikku").PikkuFunction<void, void, "session" | "rpc", import("#pikku").Services>, undefined, undefined>;
@@ -0,0 +1,122 @@
1
+ import { basename, join } from 'node:path';
2
+ import { readFile } from 'node:fs/promises';
3
+ import { pikkuVoidFunc } from '#pikku';
4
+ import { analyzeDeployment } from '../../deploy/analyzer/index.js';
5
+ const ANSI = {
6
+ green: '\x1b[32m',
7
+ blue: '\x1b[34m',
8
+ yellow: '\x1b[33m',
9
+ dim: '\x1b[2m',
10
+ bold: '\x1b[1m',
11
+ reset: '\x1b[0m',
12
+ };
13
+ const ROLE_COLORS = {
14
+ function: ANSI.blue,
15
+ mcp: '\x1b[35m', // magenta
16
+ agent: '\x1b[33m', // yellow
17
+ channel: ANSI.dim,
18
+ 'workflow-step': '\x1b[34m',
19
+ workflow: '\x1b[34m',
20
+ };
21
+ function padRight(str, len) {
22
+ return str + ' '.repeat(Math.max(0, len - str.length));
23
+ }
24
+ function sanitizeProjectId(raw) {
25
+ return (raw
26
+ .toLowerCase()
27
+ .replace(/[^a-z0-9-]/g, '-')
28
+ .replace(/-+/g, '-')
29
+ .replace(/^-|-$/g, '') || 'pikku-project');
30
+ }
31
+ async function resolveProjectId(projectDir) {
32
+ try {
33
+ const pkg = JSON.parse(await readFile(join(projectDir, 'package.json'), 'utf-8'));
34
+ if (pkg.name) {
35
+ const name = pkg.name.replace(/^@[^/]+\//, '');
36
+ return sanitizeProjectId(name);
37
+ }
38
+ }
39
+ catch { }
40
+ return sanitizeProjectId(basename(projectDir));
41
+ }
42
+ export const deployInfo = pikkuVoidFunc({
43
+ func: async ({ logger, config, getInspectorState }) => {
44
+ logger.info('Analyzing project...');
45
+ const inspectorState = await getInspectorState(true);
46
+ const projectId = await resolveProjectId(config.rootDir);
47
+ const manifest = analyzeDeployment(inspectorState, { projectId });
48
+ console.log('');
49
+ console.log(`${ANSI.bold}Project:${ANSI.reset} ${manifest.projectId}`);
50
+ console.log('');
51
+ // Units
52
+ console.log(`${ANSI.bold}Units (${manifest.units.length}):${ANSI.reset}`);
53
+ for (const u of manifest.units) {
54
+ const color = ROLE_COLORS[u.role] ?? ANSI.dim;
55
+ const fns = u.functionIds.join(', ');
56
+ console.log(` ${color}${padRight(u.role, 22)}${ANSI.reset} ${ANSI.bold}${padRight(u.name, 30)}${ANSI.reset} ${ANSI.dim}[${fns}]${ANSI.reset}`);
57
+ for (const handler of u.handlers) {
58
+ if (handler.type === 'fetch' && handler.routes.length > 0) {
59
+ for (const route of handler.routes) {
60
+ console.log(` ${ANSI.dim}${route.method} ${route.route}${ANSI.reset}`);
61
+ }
62
+ }
63
+ else if (handler.type === 'queue') {
64
+ console.log(` ${ANSI.dim}queue: ${handler.queueName}${ANSI.reset}`);
65
+ }
66
+ else if (handler.type === 'scheduled') {
67
+ console.log(` ${ANSI.dim}cron: ${handler.schedule}${ANSI.reset}`);
68
+ }
69
+ }
70
+ }
71
+ // Queues
72
+ if (manifest.queues.length > 0) {
73
+ console.log('');
74
+ console.log(`${ANSI.bold}Queues (${manifest.queues.length}):${ANSI.reset}`);
75
+ for (const q of manifest.queues) {
76
+ console.log(` ${padRight(q.name, 30)} ${ANSI.dim}-> ${q.consumerUnit}${ANSI.reset}`);
77
+ }
78
+ }
79
+ // Scheduled tasks
80
+ if (manifest.scheduledTasks.length > 0) {
81
+ console.log('');
82
+ console.log(`${ANSI.bold}Scheduled Tasks (${manifest.scheduledTasks.length}):${ANSI.reset}`);
83
+ for (const t of manifest.scheduledTasks) {
84
+ console.log(` ${padRight(t.name, 30)} ${ANSI.dim}${t.schedule} -> ${t.unitName}${ANSI.reset}`);
85
+ }
86
+ }
87
+ // Channels
88
+ if (manifest.channels.length > 0) {
89
+ console.log('');
90
+ console.log(`${ANSI.bold}Channels (${manifest.channels.length}):${ANSI.reset}`);
91
+ for (const c of manifest.channels) {
92
+ console.log(` ${padRight(c.name, 30)} ${ANSI.dim}${c.route} -> ${c.unitName}${ANSI.reset}`);
93
+ }
94
+ }
95
+ // Agents
96
+ if (manifest.agents.length > 0) {
97
+ console.log('');
98
+ console.log(`${ANSI.bold}Agents (${manifest.agents.length}):${ANSI.reset}`);
99
+ for (const a of manifest.agents) {
100
+ const tools = a.toolFunctionIds.join(', ');
101
+ console.log(` ${padRight(a.name, 30)} ${ANSI.dim}[${tools}]${ANSI.reset}`);
102
+ }
103
+ }
104
+ // Secrets
105
+ if (manifest.secrets.length > 0) {
106
+ console.log('');
107
+ console.log(`${ANSI.bold}Required Secrets (${manifest.secrets.length}):${ANSI.reset}`);
108
+ for (const s of manifest.secrets) {
109
+ console.log(` ${ANSI.yellow}${s.secretId}${ANSI.reset}`);
110
+ }
111
+ }
112
+ // Variables
113
+ if (manifest.variables.length > 0) {
114
+ console.log('');
115
+ console.log(`${ANSI.bold}Variables (${manifest.variables.length}):${ANSI.reset}`);
116
+ for (const v of manifest.variables) {
117
+ console.log(` ${v.variableId}${ANSI.dim} (${v.displayName})${ANSI.reset}`);
118
+ }
119
+ }
120
+ console.log('');
121
+ },
122
+ });
@@ -0,0 +1,10 @@
1
+ export declare const deployPlan: import("#pikku").PikkuFunctionConfig<{
2
+ resultFile?: string;
3
+ provider?: string;
4
+ }, void, "session" | "rpc", import("#pikku").PikkuFunctionSessionless<{
5
+ resultFile?: string;
6
+ provider?: string;
7
+ }, void, "session" | "rpc", import("#pikku").Services> | import("#pikku").PikkuFunction<{
8
+ resultFile?: string;
9
+ provider?: string;
10
+ }, void, "session" | "rpc", import("#pikku").Services>, undefined, undefined>;
@@ -0,0 +1,96 @@
1
+ import { basename, join } from 'node:path';
2
+ import { readFile } from 'node:fs/promises';
3
+ import { pikkuSessionlessFunc } from '#pikku';
4
+ import { generatePlan } from '../../deploy/plan/index.js';
5
+ import { formatPlan } from '../../deploy/plan/formatter.js';
6
+ import { runBuildPipeline } from '../../deploy/build-pipeline.js';
7
+ import { resolveProvider, getEntryContext } from './deploy-apply.js';
8
+ function sanitizeProjectId(raw) {
9
+ return (raw
10
+ .toLowerCase()
11
+ .replace(/[^a-z0-9-]/g, '-')
12
+ .replace(/-+/g, '-')
13
+ .replace(/^-|-$/g, '') || 'pikku-project');
14
+ }
15
+ async function resolveProjectId(projectDir) {
16
+ try {
17
+ const pkg = JSON.parse(await readFile(join(projectDir, 'package.json'), 'utf-8'));
18
+ if (pkg.name) {
19
+ const name = pkg.name.replace(/^@[^/]+\//, '');
20
+ return sanitizeProjectId(name);
21
+ }
22
+ }
23
+ catch { }
24
+ return sanitizeProjectId(basename(projectDir));
25
+ }
26
+ function createEmptyProvider() {
27
+ return {
28
+ async getCurrentState() {
29
+ return {
30
+ units: [],
31
+ queues: [],
32
+ scheduledTasks: [],
33
+ secrets: [],
34
+ variables: {},
35
+ };
36
+ },
37
+ async applyChange() {
38
+ throw new Error('Plan mode — apply not available');
39
+ },
40
+ async hasActiveWork() {
41
+ return { active: false, pendingCount: 0 };
42
+ },
43
+ };
44
+ }
45
+ export const deployPlan = pikkuSessionlessFunc({
46
+ func: async ({ logger, config, getInspectorState }, data) => {
47
+ const projectDir = config.rootDir;
48
+ const inspectorState = await getInspectorState(true);
49
+ const projectId = await resolveProjectId(projectDir);
50
+ const provider = await resolveProvider(config, data?.provider);
51
+ const result = await runBuildPipeline({
52
+ projectDir,
53
+ projectId,
54
+ provider,
55
+ inspectorState,
56
+ serverlessIncompatible: config.deploy?.serverlessIncompatible,
57
+ getEntryContext,
58
+ logger,
59
+ });
60
+ if (result.manifest.units.length === 0) {
61
+ logger.info('No deployment units found.');
62
+ return;
63
+ }
64
+ // Show plan
65
+ const deployProvider = createEmptyProvider();
66
+ const currentState = await deployProvider.getCurrentState(result.manifest.projectId);
67
+ const plan = await generatePlan(result.manifest, currentState, deployProvider);
68
+ console.log('');
69
+ console.log(formatPlan(plan));
70
+ console.log('');
71
+ // Summary
72
+ let totalSize = 0;
73
+ for (const b of result.bundled)
74
+ totalSize += b.bundleSizeBytes;
75
+ const formatBytes = (bytes) => bytes < 1024
76
+ ? `${bytes}B`
77
+ : bytes < 1024 * 1024
78
+ ? `${(bytes / 1024).toFixed(1)}KB`
79
+ : `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
80
+ logger.info(`${result.bundled.length} workers bundled (${formatBytes(totalSize)} total)`);
81
+ logger.info(`Output: ${result.providerDir}`);
82
+ // Write result file if --result-file is set (used by Fabric build pipeline)
83
+ const resultFile = data?.resultFile;
84
+ if (resultFile) {
85
+ const { writeFile } = await import('node:fs/promises');
86
+ await writeFile(resultFile, JSON.stringify({
87
+ success: result.bundleErrors.length === 0,
88
+ providerDir: result.providerDir,
89
+ unitCount: result.bundled.length,
90
+ totalSizeBytes: totalSize,
91
+ errors: result.bundleErrors,
92
+ manifest: result.manifest,
93
+ }, null, 2), 'utf-8');
94
+ }
95
+ },
96
+ });
@@ -8,7 +8,7 @@ const FEATURE_DEFAULTS = {
8
8
  workflow: 'auth',
9
9
  };
10
10
  async function enableFeature(feature, logger, config, data) {
11
- const noAuth = data?.['no-auth'] ?? false;
11
+ const noAuth = data?.noAuth ?? false;
12
12
  const configPath = join(config.configDir, 'pikku.config.json');
13
13
  const raw = await readFile(configPath, 'utf-8');
14
14
  const json = JSON.parse(raw);
@@ -11,6 +11,7 @@ export declare const pikkuNewAddon: import("#pikku").PikkuFunctionConfig<{
11
11
  test?: boolean;
12
12
  openapi?: string;
13
13
  mcp?: boolean;
14
+ camelCase?: boolean;
14
15
  }, void, "session" | "rpc", import("#pikku").PikkuFunctionSessionless<{
15
16
  name: string;
16
17
  displayName?: string;
@@ -24,6 +25,7 @@ export declare const pikkuNewAddon: import("#pikku").PikkuFunctionConfig<{
24
25
  test?: boolean;
25
26
  openapi?: string;
26
27
  mcp?: boolean;
28
+ camelCase?: boolean;
27
29
  }, void, "session" | "rpc", import("#pikku").Services> | import("#pikku").PikkuFunction<{
28
30
  name: string;
29
31
  displayName?: string;
@@ -37,4 +39,5 @@ export declare const pikkuNewAddon: import("#pikku").PikkuFunctionConfig<{
37
39
  test?: boolean;
38
40
  openapi?: string;
39
41
  mcp?: boolean;
42
+ camelCase?: boolean;
40
43
  }, void, "session" | "rpc", import("#pikku").Services>, undefined, undefined>;
@@ -5,7 +5,7 @@ import { createEmptyManifest, saveManifest, } from '../../utils/contract-version
5
5
  import { pikkuSessionlessFunc } from '#pikku';
6
6
  import { parseOpenAPISpec, computeContractHash, generateAddonFromOpenAPI, } from '@pikku/openapi-parser';
7
7
  function toCamelCase(str) {
8
- return str.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
8
+ return str.replace(/-([a-z0-9])/g, (_, c) => c.toUpperCase());
9
9
  }
10
10
  function toPascalCase(str) {
11
11
  const camel = toCamelCase(str);
@@ -14,6 +14,69 @@ function toPascalCase(str) {
14
14
  function toScreamingSnake(str) {
15
15
  return str.replace(/-/g, '_').toUpperCase();
16
16
  }
17
+ const TLD_SEGMENTS = new Set([
18
+ 'com',
19
+ 'io',
20
+ 'org',
21
+ 'net',
22
+ 'co',
23
+ 'dev',
24
+ 'app',
25
+ 'us',
26
+ 'uk',
27
+ 'eu',
28
+ 'de',
29
+ 'fr',
30
+ 'nl',
31
+ 'ch',
32
+ 'ca',
33
+ 'au',
34
+ 'gov',
35
+ 'edu',
36
+ 'local',
37
+ 'cloud',
38
+ 'ai',
39
+ 'fm',
40
+ 'tv',
41
+ 'me',
42
+ 'cc',
43
+ 'info',
44
+ 'biz',
45
+ 'xyz',
46
+ 'tech',
47
+ 'space',
48
+ 'online',
49
+ 'site',
50
+ 'store',
51
+ 'ac',
52
+ 'int',
53
+ 'mil',
54
+ 'ninja',
55
+ 'guru',
56
+ ]);
57
+ function sanitizeAddonName(raw) {
58
+ const dotParts = raw.toLowerCase().split('.');
59
+ const kept = [];
60
+ for (const part of dotParts) {
61
+ const clean = part.replace(/^-|-$/g, '');
62
+ if (TLD_SEGMENTS.has(clean))
63
+ continue;
64
+ const hyphenIdx = part.indexOf('-');
65
+ if (hyphenIdx > 0 && TLD_SEGMENTS.has(part.slice(0, hyphenIdx))) {
66
+ kept.push(part.slice(hyphenIdx + 1));
67
+ continue;
68
+ }
69
+ kept.push(part);
70
+ }
71
+ let name = kept
72
+ .join('-')
73
+ .replace(/[^a-z0-9-]/g, '-')
74
+ .replace(/-+/g, '-')
75
+ .replace(/^-|-$/g, '');
76
+ if (name && /^[0-9]/.test(name))
77
+ name = `x${name}`;
78
+ return name || raw;
79
+ }
17
80
  function getAddonFiles(vars, flags) {
18
81
  const { name, camelName, pascalName, screamingName, displayName, description, category, } = vars;
19
82
  const files = {};
@@ -700,7 +763,8 @@ async function writeFiles(baseDir, files) {
700
763
  return written;
701
764
  }
702
765
  export const pikkuNewAddon = pikkuSessionlessFunc({
703
- func: async ({ logger, config }, { name, displayName, description, category = 'General', dir, secret = false, variable = false, oauth = false, credential, test = true, openapi, mcp = false, }) => {
766
+ func: async ({ logger, config }, { name, displayName, description, category = 'General', dir, secret = false, variable = false, oauth = false, credential, test = true, openapi, mcp = false, camelCase = false, }) => {
767
+ name = sanitizeAddonName(name);
704
768
  if (!/^[a-z][a-z0-9_-]*$/.test(name)) {
705
769
  logger.error(`Invalid addon name "${name}": must start with a lowercase letter and contain only lowercase alphanumerics, hyphens, and underscores`);
706
770
  process.exit(1);
@@ -747,6 +811,7 @@ export const pikkuNewAddon = pikkuSessionlessFunc({
747
811
  secret: (secret || effectiveOAuth) && !credentialType,
748
812
  credential: credentialType,
749
813
  mcp,
814
+ camelCase,
750
815
  });
751
816
  Object.assign(addonFiles, openapiFiles);
752
817
  // Inject openapi metadata into pikku.config.json
@@ -757,6 +822,7 @@ export const pikkuNewAddon = pikkuSessionlessFunc({
757
822
  config.addon.openapi = {
758
823
  version: spec.info.version,
759
824
  hash: computeContractHash(spec),
825
+ ...(camelCase ? { camelCase: true } : {}),
760
826
  };
761
827
  addonFiles['pikku.config.json'] = JSON.stringify(config, null, 2);
762
828
  }
@@ -1,3 +1,4 @@
1
+ import { join, dirname } from 'node:path';
1
2
  import { pikkuSessionlessFunc } from '#pikku';
2
3
  import { getFileImportRelativePath } from '../../utils/file-import-path.js';
3
4
  import { writeFileInDir } from '../../utils/file-writer.js';
@@ -13,26 +14,35 @@ export const pikkuBootstrap = pikkuSessionlessFunc({
13
14
  const wireAddonFileImports = Array.from(stateBeforeBootstrap.rpc?.wireAddonFiles ?? []).map((to) => `import '${getFileImportRelativePath(config.bootstrapFile, to, config.packageMappings)}'`);
14
15
  const localImports = allImports.map((to) => `import '${getFileImportRelativePath(config.bootstrapFile, to, config.packageMappings)}'`);
15
16
  const addonImports = addonBootstraps.map((packagePath) => `import '${packagePath}'`);
16
- const packageNameArg = config.addonName ? `'${config.addonName}'` : 'null';
17
- const metaDirRegistration = `import { pikkuState as __pikkuState } from '@pikku/core/internal'
18
- try {
19
- const { fileURLToPath: __fileURLToPath } = await import('url')
20
- const { dirname: __dirname } = await import('path')
21
- __pikkuState(${packageNameArg}, 'package', 'metaDir', __dirname(__fileURLToPath(import.meta.url)))
22
- } catch {}
23
- `;
24
- const allBootstrapImports = metaDirRegistration +
25
- [...localImports, ...wireAddonFileImports, ...addonImports]
26
- .sort((a, b) => {
27
- const aMeta = a.includes('meta');
28
- const bMeta = b.includes('meta');
29
- if (aMeta && !bMeta)
30
- return -1;
31
- if (!aMeta && bMeta)
32
- return 1;
33
- return 0;
34
- })
35
- .join('\n');
17
+ const outDir = dirname(config.bootstrapFile);
18
+ const metaServiceFile = join(outDir, 'pikku-meta-service.gen.ts');
19
+ const escapedOutDir = outDir.replace(/\\/g, '/').replace(/'/g, "\\'");
20
+ const metaServiceContent = [
21
+ `import { LocalMetaService } from '@pikku/core/services/local-meta'`,
22
+ ``,
23
+ `export class PikkuMetaService extends LocalMetaService {`,
24
+ ` constructor() {`,
25
+ ` super('${escapedOutDir}')`,
26
+ ` }`,
27
+ `}`,
28
+ ``,
29
+ ].join('\n');
30
+ await writeFileInDir(logger, metaServiceFile, metaServiceContent);
31
+ const allBootstrapImports = [
32
+ ...localImports,
33
+ ...wireAddonFileImports,
34
+ ...addonImports,
35
+ ]
36
+ .sort((a, b) => {
37
+ const aMeta = a.includes('meta');
38
+ const bMeta = b.includes('meta');
39
+ if (aMeta && !bMeta)
40
+ return -1;
41
+ if (!aMeta && bMeta)
42
+ return 1;
43
+ return 0;
44
+ })
45
+ .join('\n');
36
46
  await writeFileInDir(logger, config.bootstrapFile, allBootstrapImports);
37
47
  },
38
48
  });
@@ -35,10 +35,14 @@ export const pikkuNext = pikkuSessionlessFunc({
35
35
  if (!pikkuConfigFactory || !singletonServicesFactory) {
36
36
  throw new Error('Required types not found');
37
37
  }
38
- const pikkuConfigImport = `import { ${pikkuConfigFactory.variable} as createConfig } from '${getFileImportRelativePath(nextBackendFile, pikkuConfigFactory.file, packageMappings)}'`;
39
- const singletonServicesImport = `import { ${singletonServicesFactory.variable} as createSingletonServices } from '${getFileImportRelativePath(nextBackendFile, singletonServicesFactory.file, packageMappings)}'`;
38
+ const configAlias = pikkuConfigFactory.variable === 'createConfig' ? '' : ' as createConfig';
39
+ const servicesAlias = singletonServicesFactory.variable === 'createSingletonServices'
40
+ ? ''
41
+ : ' as createSingletonServices';
42
+ const pikkuConfigImport = `import { ${pikkuConfigFactory.variable}${configAlias} } from '${getFileImportRelativePath(nextBackendFile, pikkuConfigFactory.file, packageMappings)}'`;
43
+ const singletonServicesImport = `import { ${singletonServicesFactory.variable}${servicesAlias} } from '${getFileImportRelativePath(nextBackendFile, singletonServicesFactory.file, packageMappings)}'`;
40
44
  const wireServicesImport = wireServicesFactory
41
- ? `import { ${wireServicesFactory.variable} as createWireServices } from '${getFileImportRelativePath(nextBackendFile, wireServicesFactory.file, packageMappings)}'`
45
+ ? `import { ${wireServicesFactory.variable}${wireServicesFactory.variable === 'createWireServices' ? '' : ' as createWireServices'} } from '${getFileImportRelativePath(nextBackendFile, wireServicesFactory.file, packageMappings)}'`
42
46
  : '';
43
47
  const bootstrapPath = getFileImportRelativePath(nextBackendFile, config.bootstrapFile, packageMappings);
44
48
  const routesMapDeclarationPath = getFileImportRelativePath(nextBackendFile, httpMapDeclarationFile, packageMappings);