@pikku/cli 0.12.20 → 0.12.21

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 (99) hide show
  1. package/dist/.pikku/agent/pikku-agent-types.gen.d.ts +1 -1
  2. package/dist/.pikku/channel/pikku-channel-types.gen.d.ts +1 -1
  3. package/dist/.pikku/channel/pikku-channel-types.gen.js +1 -1
  4. package/dist/.pikku/cli/pikku-cli-channel.js +1 -1
  5. package/dist/.pikku/cli/pikku-cli-types.gen.d.ts +1 -1
  6. package/dist/.pikku/cli/pikku-cli-types.gen.js +1 -1
  7. package/dist/.pikku/cli/pikku-cli-wirings-meta.gen.js +1 -1
  8. package/dist/.pikku/cli/pikku-cli-wirings-meta.gen.json +10 -1
  9. package/dist/.pikku/cli/pikku-cli-wirings.gen.d.ts +1 -1
  10. package/dist/.pikku/cli/pikku-cli-wirings.gen.js +1 -1
  11. package/dist/.pikku/cli/pikku-cli.gen.d.ts +1 -1
  12. package/dist/.pikku/cli/pikku-cli.gen.js +1 -1
  13. package/dist/.pikku/console/pikku-node-types.gen.d.ts +1 -1
  14. package/dist/.pikku/function/pikku-function-types.gen.d.ts +8 -2
  15. package/dist/.pikku/function/pikku-function-types.gen.js +4 -1
  16. package/dist/.pikku/function/pikku-functions-meta.gen.js +1 -1
  17. package/dist/.pikku/function/pikku-functions-meta.gen.json +276 -188
  18. package/dist/.pikku/function/pikku-functions.gen.js +9 -9
  19. package/dist/.pikku/http/pikku-http-types.gen.d.ts +1 -1
  20. package/dist/.pikku/http/pikku-http-types.gen.js +1 -1
  21. package/dist/.pikku/http/pikku-http-wirings-meta.gen.js +1 -1
  22. package/dist/.pikku/http/pikku-http-wirings.gen.d.ts +1 -1
  23. package/dist/.pikku/http/pikku-http-wirings.gen.js +1 -1
  24. package/dist/.pikku/mcp/pikku-mcp-types.gen.d.ts +1 -1
  25. package/dist/.pikku/mcp/pikku-mcp-types.gen.js +1 -1
  26. package/dist/.pikku/pikku-bootstrap.gen.d.ts +1 -1
  27. package/dist/.pikku/pikku-bootstrap.gen.js +1 -1
  28. package/dist/.pikku/pikku-meta-service.gen.d.ts +1 -1
  29. package/dist/.pikku/pikku-meta-service.gen.js +1 -1
  30. package/dist/.pikku/pikku-services.gen.d.ts +1 -1
  31. package/dist/.pikku/pikku-types.gen.d.ts +1 -1
  32. package/dist/.pikku/pikku-types.gen.js +1 -1
  33. package/dist/.pikku/queue/pikku-queue-types.gen.d.ts +1 -1
  34. package/dist/.pikku/queue/pikku-queue-types.gen.js +1 -1
  35. package/dist/.pikku/rpc/pikku-rpc-wirings-meta.internal.gen.js +1 -1
  36. package/dist/.pikku/rpc/pikku-rpc-wirings-meta.internal.gen.json +11 -7
  37. package/dist/.pikku/scheduler/pikku-scheduler-types.gen.d.ts +1 -1
  38. package/dist/.pikku/scheduler/pikku-scheduler-types.gen.js +1 -1
  39. package/dist/.pikku/schemas/register.gen.js +15 -11
  40. package/dist/.pikku/schemas/schemas/PikkuCommandHTTPOutput.schema.json +1 -0
  41. package/dist/.pikku/schemas/schemas/PikkuCommandQueueOutput.schema.json +1 -0
  42. package/dist/.pikku/schemas/schemas/WorkflowRunStatus.schema.json +1 -1
  43. package/dist/.pikku/secrets/pikku-secret-types.gen.d.ts +1 -1
  44. package/dist/.pikku/secrets/pikku-secret-types.gen.js +1 -1
  45. package/dist/.pikku/secrets/pikku-secrets.gen.d.ts +1 -1
  46. package/dist/.pikku/secrets/pikku-secrets.gen.js +1 -1
  47. package/dist/.pikku/trigger/pikku-trigger-types.gen.d.ts +1 -1
  48. package/dist/.pikku/trigger/pikku-trigger-types.gen.js +1 -1
  49. package/dist/.pikku/variables/pikku-variable-types.gen.d.ts +1 -1
  50. package/dist/.pikku/variables/pikku-variable-types.gen.js +1 -1
  51. package/dist/.pikku/variables/pikku-variables.gen.d.ts +1 -1
  52. package/dist/.pikku/variables/pikku-variables.gen.js +1 -1
  53. package/dist/.pikku/workflow/meta/allWorkflow.gen.json +9 -9
  54. package/dist/.pikku/workflow/pikku-workflow-types.gen.d.ts +1 -1
  55. package/dist/.pikku/workflow/pikku-workflow-types.gen.js +1 -1
  56. package/dist/.pikku/workflow/pikku-workflow-wirings-meta.gen.js +1 -1
  57. package/dist/.pikku/workflow/pikku-workflow-wirings.gen.js +1 -1
  58. package/dist/src/cli.wiring.js +12 -2
  59. package/dist/src/deploy/analyzer/manifest.d.ts +10 -0
  60. package/dist/src/deploy/build-pipeline.d.ts +2 -0
  61. package/dist/src/deploy/build-pipeline.js +44 -6
  62. package/dist/src/deploy/bundler/bundler.d.ts +2 -1
  63. package/dist/src/deploy/bundler/bundler.js +28 -5
  64. package/dist/src/deploy/bundler/dep-extractor.d.ts +5 -2
  65. package/dist/src/deploy/bundler/dep-extractor.js +103 -23
  66. package/dist/src/deploy/bundler/types.d.ts +5 -1
  67. package/dist/src/deploy/codegen/per-unit-codegen.d.ts +3 -1
  68. package/dist/src/deploy/codegen/per-unit-codegen.js +3 -1
  69. package/dist/src/deploy/plan/planner.js +25 -3
  70. package/dist/src/deploy/plan/provider.d.ts +2 -0
  71. package/dist/src/functions/commands/deploy-apply.js +6 -4
  72. package/dist/src/functions/commands/deploy-plan.js +7 -1
  73. package/dist/src/functions/commands/pikku-command-summary.js +4 -1
  74. package/dist/src/functions/commands/versions-update.js +4 -2
  75. package/dist/src/functions/wirings/channels/pikku-command-channels.d.ts +1 -1
  76. package/dist/src/functions/wirings/channels/pikku-command-channels.js +1 -1
  77. package/dist/src/functions/wirings/console/pikku-command-console-functions.js +5 -1
  78. package/dist/src/functions/wirings/functions/serialize-function-types.js +31 -1
  79. package/dist/src/functions/wirings/http/pikku-command-http-routes.d.ts +1 -1
  80. package/dist/src/functions/wirings/http/pikku-command-http-routes.js +1 -1
  81. package/dist/src/functions/wirings/queue/pikku-command-queue-map.d.ts +1 -1
  82. package/dist/src/functions/wirings/queue/pikku-command-queue-map.js +1 -1
  83. package/dist/src/functions/wirings/queue/pikku-command-queue.d.ts +1 -1
  84. package/dist/src/functions/wirings/queue/pikku-command-queue.js +1 -1
  85. package/dist/src/functions/wirings/rpc/pikku-command-public-rpc.js +5 -1
  86. package/dist/src/functions/wirings/rpc/pikku-command-remote-rpc.js +5 -1
  87. package/dist/src/functions/wirings/rpc/serialize-typed-rpc-map.js +16 -2
  88. package/dist/src/functions/wirings/workflow/serialize-workflow-routes.js +42 -0
  89. package/dist/src/functions/workflows/all.workflow.js +4 -4
  90. package/dist/src/scaffold/rpc-remote.gen.js +1 -1
  91. package/dist/src/scaffold/workflow-routes.gen.js +33 -1
  92. package/dist/src/services/cli-logger.service.d.ts +22 -2
  93. package/dist/src/services/cli-logger.service.js +97 -21
  94. package/dist/src/services.js +8 -1
  95. package/dist/tsconfig.tsbuildinfo +1 -1
  96. package/package.json +3 -3
  97. package/dist/.pikku/cli/pikku-cli-client.gen.d.ts +0 -10
  98. package/dist/.pikku/cli/pikku-cli-client.gen.js +0 -44
  99. /package/dist/.pikku/schemas/schemas/{PikkuChannelsOutput.schema.json → PikkuCommandChannelsOutput.schema.json} +0 -0
@@ -10,6 +10,24 @@ import { mkdir, writeFile, copyFile } from 'node:fs/promises';
10
10
  import { analyzeDeployment } from './analyzer/index.js';
11
11
  import { generatePerUnitCodegen } from './codegen/per-unit-codegen.js';
12
12
  import { bundleUnits } from './bundler/index.js';
13
+ const MERGED_SERVER_UNIT_NAME = 'pikku-server-container';
14
+ const UNITS_DIR_NAME = 'units';
15
+ const CONTAINER_DIR_NAME = 'container';
16
+ function attachBundleMetadata(manifest, bundled) {
17
+ if (bundled.length === 0)
18
+ return;
19
+ const byUnitName = new Map(bundled.map((b) => [b.unitName, b]));
20
+ for (const unit of manifest.units) {
21
+ const bundle = byUnitName.get(unit.name);
22
+ if (!bundle)
23
+ continue;
24
+ unit.bundleHash = bundle.bundleHash;
25
+ unit.bundleSizeBytes = bundle.bundleSizeBytes;
26
+ unit.exactDependenciesHash = bundle.exactDependenciesHash;
27
+ unit.exactDependencies = bundle.exactDependencies;
28
+ unit.exactOptionalDependencies = bundle.exactOptionalDependencies;
29
+ }
30
+ }
13
31
  function findLockfile(projectDir) {
14
32
  for (const name of ['yarn.lock', 'package-lock.json', 'pnpm-lock.yaml']) {
15
33
  const p = join(projectDir, name);
@@ -30,6 +48,10 @@ export async function runBuildPipeline(options) {
30
48
  let bundled = [];
31
49
  let bundleErrors = [];
32
50
  let codegenErrors = [];
51
+ let infraPath = null;
52
+ const deploymentManifestPath = join(providerDir, 'deployment-manifest.json');
53
+ const unitsDir = join(providerDir, UNITS_DIR_NAME);
54
+ const containerDir = join(providerDir, CONTAINER_DIR_NAME);
33
55
  if (provider.singleUnit) {
34
56
  // Single-unit mode: bundle everything into one unit, use project's .pikku/ directly
35
57
  logger.info('Building standalone bundle...');
@@ -63,6 +85,7 @@ export async function runBuildPipeline(options) {
63
85
  });
64
86
  bundled = bundleResult.results;
65
87
  bundleErrors = bundleResult.errors;
88
+ attachBundleMetadata(manifest, bundled);
66
89
  logger.info(`Bundled standalone${bundleErrors.length > 0 ? ` (${bundleErrors.length} errors)` : ''}`);
67
90
  }
68
91
  else {
@@ -74,6 +97,8 @@ export async function runBuildPipeline(options) {
74
97
  return {
75
98
  manifest,
76
99
  providerDir,
100
+ deploymentManifestPath,
101
+ infraPath,
77
102
  projectId,
78
103
  bundled: [],
79
104
  bundleErrors: [],
@@ -88,7 +113,7 @@ export async function runBuildPipeline(options) {
88
113
  projectDir,
89
114
  manifest: serverlessManifest,
90
115
  inspectorState,
91
- deployDir: providerDir,
116
+ deployDir: unitsDir,
92
117
  onProgress: (unitName, status, error) => {
93
118
  if (status === 'start') {
94
119
  logger.info(` Codegen: ${unitName}...`);
@@ -104,7 +129,7 @@ export async function runBuildPipeline(options) {
104
129
  codegenErrors = serverlessCodegenErrors;
105
130
  // Step 2b: Server units — single codegen pass with all server function IDs
106
131
  if (serverUnits.length > 0) {
107
- const serverUnitName = 'server';
132
+ const serverUnitName = MERGED_SERVER_UNIT_NAME;
108
133
  // Create a merged server unit with all server function IDs
109
134
  const mergedServerUnit = {
110
135
  name: serverUnitName,
@@ -122,7 +147,8 @@ export async function runBuildPipeline(options) {
122
147
  projectDir,
123
148
  manifest: serverManifest,
124
149
  inspectorState,
125
- deployDir: providerDir,
150
+ deployDir: containerDir,
151
+ resolveUnitDir: () => containerDir,
126
152
  onProgress: (unitName, status, error) => {
127
153
  if (status === 'start')
128
154
  logger.info(` Codegen: ${unitName}...`);
@@ -147,7 +173,7 @@ export async function runBuildPipeline(options) {
147
173
  const pikkuDir = unitPikkuDirs.get(unit.name);
148
174
  if (!pikkuDir)
149
175
  continue;
150
- const unitDir = join(providerDir, unit.name);
176
+ const unitDir = unit.target === 'server' ? containerDir : join(unitsDir, unit.name);
151
177
  const entryPath = join(unitDir, 'entry.ts');
152
178
  await mkdir(unitDir, { recursive: true });
153
179
  const ctx = getEntryContext(unitDir, pikkuDir, unit, inspectorState);
@@ -161,9 +187,11 @@ export async function runBuildPipeline(options) {
161
187
  define: provider.getDefine?.(),
162
188
  platform: provider.getPlatform?.(),
163
189
  format: provider.getFormat?.(),
190
+ resolveOutputDir: (unit) => unit.target === 'server' ? containerDir : join(unitsDir, unit.name),
164
191
  });
165
192
  bundled = bundleResult.results;
166
193
  bundleErrors = bundleResult.errors;
194
+ attachBundleMetadata(manifest, bundled);
167
195
  logger.info(`Bundled ${bundled.length} units${bundleErrors.length > 0 ? ` (${bundleErrors.length} failed)` : ''}`);
168
196
  }
169
197
  if (bundleErrors.length > 0) {
@@ -172,9 +200,13 @@ export async function runBuildPipeline(options) {
172
200
  }
173
201
  }
174
202
  // Step 4: Generate configs + infra manifest
203
+ await mkdir(providerDir, { recursive: true });
204
+ await writeFile(deploymentManifestPath, JSON.stringify(manifest, null, 2), 'utf-8');
205
+ logger.info('Generated deployment manifest');
175
206
  const infraContent = provider.generateInfraManifest(manifest);
176
207
  if (infraContent) {
177
- await writeFile(join(providerDir, 'infra.json'), infraContent, 'utf-8');
208
+ infraPath = join(providerDir, 'infra.json');
209
+ await writeFile(infraPath, infraContent, 'utf-8');
178
210
  logger.info('Generated infrastructure manifest');
179
211
  }
180
212
  if (provider.generateProviderConfigs) {
@@ -187,7 +219,11 @@ export async function runBuildPipeline(options) {
187
219
  }
188
220
  const lockfileSrc = findLockfile(projectDir);
189
221
  for (const unit of manifest.units) {
190
- const unitDir = join(providerDir, unit.name);
222
+ const unitDir = provider.singleUnit
223
+ ? join(providerDir, unit.name)
224
+ : unit.target === 'server'
225
+ ? containerDir
226
+ : join(unitsDir, unit.name);
191
227
  await mkdir(unitDir, { recursive: true });
192
228
  const configs = provider.generateUnitConfigs(unit, manifest, projectId);
193
229
  for (const [filename, content] of configs) {
@@ -201,6 +237,8 @@ export async function runBuildPipeline(options) {
201
237
  return {
202
238
  manifest,
203
239
  providerDir,
240
+ deploymentManifestPath,
241
+ infraPath,
204
242
  projectId,
205
243
  bundled,
206
244
  bundleErrors,
@@ -9,7 +9,7 @@
9
9
  * 5. Generates a minimal package.json with exact versions
10
10
  * 6. Writes all artifacts to `<outputDir>/<unit-name>/`
11
11
  */
12
- import type { DeploymentManifest, BundleOutput } from './types.js';
12
+ import type { DeploymentManifest, DeploymentUnit, BundleOutput } from './types.js';
13
13
  /**
14
14
  * Bundles all deployment units defined in a DeploymentManifest.
15
15
  *
@@ -27,4 +27,5 @@ export declare function bundleUnits(projectDir: string, manifest: DeploymentMani
27
27
  define?: Record<string, string>;
28
28
  platform?: 'node' | 'neutral' | 'browser';
29
29
  format?: 'esm' | 'cjs';
30
+ resolveOutputDir?: (unit: DeploymentUnit, baseOutputDir: string) => string;
30
31
  }): Promise<BundleOutput>;
@@ -12,6 +12,7 @@
12
12
  import { build } from 'esbuild';
13
13
  import { writeFile, mkdir, stat, readFile } from 'node:fs/promises';
14
14
  import { join } from 'node:path';
15
+ import { createHash } from 'node:crypto';
15
16
  import { extractDependencies, generateMinimalPackageJson, } from './dep-extractor.js';
16
17
  /**
17
18
  * Mapping of service name -> gen file pattern that should be stubbed
@@ -70,6 +71,7 @@ function createDeadModuleStubPlugin(patterns) {
70
71
  const BUNDLE_FILENAME = 'bundle.js';
71
72
  const METAFILE_FILENAME = 'metafile.json';
72
73
  const PACKAGE_JSON_FILENAME = 'package.json';
74
+ const EXACT_DEPENDENCIES_FILENAME = 'exact-dependencies.json';
73
75
  /**
74
76
  * Bundles a single deployment unit using esbuild.
75
77
  *
@@ -84,6 +86,7 @@ async function bundleUnit(options) {
84
86
  const bundlePath = join(unitOutputDir, BUNDLE_FILENAME);
85
87
  const metafilePath = join(unitOutputDir, METAFILE_FILENAME);
86
88
  const packageJsonPath = join(unitOutputDir, PACKAGE_JSON_FILENAME);
89
+ const exactDependenciesPath = join(unitOutputDir, EXACT_DEPENDENCIES_FILENAME);
87
90
  // Determine which gen files to stub based on per-unit service requirements
88
91
  const deadPatterns = await getDeadGenFilePatterns(unitOutputDir);
89
92
  // Run esbuild — inline everything into a self-contained bundle.
@@ -106,7 +109,9 @@ async function bundleUnit(options) {
106
109
  // The banner shims require via createRequire so CJS builtins resolve.
107
110
  const resolvedFormat = format ?? 'esm';
108
111
  const banner = resolvedFormat === 'esm' && (platform ?? 'node') === 'node'
109
- ? { js: `import { createRequire } from 'module'; const require = createRequire(import.meta.url);` }
112
+ ? {
113
+ js: `import { createRequire } from 'module'; const require = createRequire(import.meta.url);`,
114
+ }
110
115
  : undefined;
111
116
  const result = await build({
112
117
  entryPoints: [entryPath],
@@ -132,18 +137,34 @@ async function bundleUnit(options) {
132
137
  const metafileJson = JSON.stringify(result.metafile, null, 2);
133
138
  await writeFile(metafilePath, metafileJson, 'utf-8');
134
139
  // Extract dependencies and generate minimal package.json
135
- const dependencies = await extractDependencies(result.metafile, projectDir);
136
- const packageJson = generateMinimalPackageJson(unit.name, dependencies);
140
+ const { exactDependencies, exactOptionalDependencies } = await extractDependencies(result.metafile, projectDir);
141
+ const packageJson = generateMinimalPackageJson(unit.name, exactDependencies, exactOptionalDependencies);
137
142
  await writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2), 'utf-8');
143
+ await writeFile(exactDependenciesPath, JSON.stringify({
144
+ dependencies: Object.fromEntries(Object.entries(exactDependencies).sort(([a], [b]) => a.localeCompare(b))),
145
+ optionalDependencies: Object.fromEntries(Object.entries(exactOptionalDependencies).sort(([a], [b]) => a.localeCompare(b))),
146
+ }, null, 2), 'utf-8');
138
147
  // Get bundle size
139
148
  const bundleStat = await stat(bundlePath);
149
+ const bundleContents = await readFile(bundlePath);
150
+ const bundleHash = createHash('sha256').update(bundleContents).digest('hex');
151
+ const exactDependenciesHash = createHash('sha256')
152
+ .update(JSON.stringify({
153
+ dependencies: Object.entries(exactDependencies).sort(([a], [b]) => a.localeCompare(b)),
154
+ optionalDependencies: Object.entries(exactOptionalDependencies).sort(([a], [b]) => a.localeCompare(b)),
155
+ }))
156
+ .digest('hex');
140
157
  return {
141
158
  unitName: unit.name,
142
159
  bundlePath,
143
160
  packageJsonPath,
161
+ exactDependenciesPath,
144
162
  metafilePath,
145
163
  bundleSizeBytes: bundleStat.size,
146
- externalPackages: dependencies,
164
+ bundleHash,
165
+ exactDependenciesHash,
166
+ exactDependencies,
167
+ exactOptionalDependencies,
147
168
  };
148
169
  }
149
170
  /**
@@ -173,7 +194,9 @@ export async function bundleUnits(projectDir, manifest, entryFiles, outputDir, o
173
194
  });
174
195
  continue;
175
196
  }
176
- const unitOutputDir = join(buildDir, unit.name);
197
+ const unitOutputDir = options?.resolveOutputDir
198
+ ? options.resolveOutputDir(unit, buildDir)
199
+ : join(buildDir, unit.name);
177
200
  try {
178
201
  const result = await bundleUnit({
179
202
  unit,
@@ -28,8 +28,11 @@ export declare function parsePackageName(specifier: string): string | null;
28
28
  * Returns a record of package name to exact version, suitable for
29
29
  * writing into a minimal package.json.
30
30
  */
31
- export declare function extractDependencies(metafile: Metafile, projectDir: string): Promise<Record<string, string>>;
31
+ export declare function extractDependencies(metafile: Metafile, projectDir: string): Promise<{
32
+ exactDependencies: Record<string, string>;
33
+ exactOptionalDependencies: Record<string, string>;
34
+ }>;
32
35
  /**
33
36
  * Generates a minimal package.json content object for a unit bundle.
34
37
  */
35
- export declare function generateMinimalPackageJson(unitName: string, dependencies: Record<string, string>): Record<string, unknown>;
38
+ export declare function generateMinimalPackageJson(unitName: string, dependencies: Record<string, string>, optionalDependencies: Record<string, string>): Record<string, unknown>;
@@ -43,12 +43,57 @@ export function parsePackageName(specifier) {
43
43
  return null;
44
44
  }
45
45
  const builtins = new Set([
46
- 'assert', 'buffer', 'child_process', 'cluster', 'console', 'constants',
47
- 'crypto', 'dgram', 'dns', 'domain', 'events', 'fs', 'http', 'http2',
48
- 'https', 'inspector', 'module', 'net', 'os', 'path', 'perf_hooks',
49
- 'process', 'punycode', 'querystring', 'readline', 'repl', 'stream',
50
- 'string_decoder', 'sys', 'timers', 'tls', 'tty', 'url', 'util',
51
- 'v8', 'vm', 'wasi', 'worker_threads', 'zlib',
46
+ 'assert',
47
+ 'buffer',
48
+ 'child_process',
49
+ 'cluster',
50
+ 'console',
51
+ 'constants',
52
+ 'crypto',
53
+ 'dgram',
54
+ 'dns',
55
+ 'domain',
56
+ 'events',
57
+ 'fs',
58
+ 'http',
59
+ 'http2',
60
+ 'https',
61
+ 'inspector',
62
+ 'module',
63
+ 'net',
64
+ 'os',
65
+ 'path',
66
+ 'perf_hooks',
67
+ 'process',
68
+ 'punycode',
69
+ 'querystring',
70
+ 'readline',
71
+ 'repl',
72
+ 'stream',
73
+ 'string_decoder',
74
+ 'sys',
75
+ 'timers',
76
+ 'tls',
77
+ 'tty',
78
+ 'url',
79
+ 'util',
80
+ 'v8',
81
+ 'vm',
82
+ 'wasi',
83
+ 'worker_threads',
84
+ 'zlib',
85
+ 'async_hooks',
86
+ 'child_process',
87
+ 'cluster',
88
+ 'diagnostics_channel',
89
+ 'dns/promises',
90
+ 'fs/promises',
91
+ 'readline/promises',
92
+ 'stream/consumers',
93
+ 'stream/promises',
94
+ 'stream/web',
95
+ 'timers/promises',
96
+ 'util/types',
52
97
  ]);
53
98
  if (builtins.has(specifier.split('/')[0])) {
54
99
  return null;
@@ -68,7 +113,7 @@ export function parsePackageName(specifier) {
68
113
  */
69
114
  async function readProjectDependencies(projectDir) {
70
115
  const dependencies = {};
71
- const devDependencies = {};
116
+ const optionalDependencies = {};
72
117
  // Walk up the directory tree to find all package.json files
73
118
  // (handles monorepo setups where deps are in the root package.json)
74
119
  let dir = projectDir;
@@ -82,9 +127,9 @@ async function readProjectDependencies(projectDir) {
82
127
  if (!(k in dependencies))
83
128
  dependencies[k] = v;
84
129
  }
85
- for (const [k, v] of Object.entries(pkg.devDependencies ?? {})) {
86
- if (!(k in devDependencies))
87
- devDependencies[k] = v;
130
+ for (const [k, v] of Object.entries(pkg.optionalDependencies ?? {})) {
131
+ if (!(k in optionalDependencies))
132
+ optionalDependencies[k] = v;
88
133
  }
89
134
  }
90
135
  catch {
@@ -95,7 +140,7 @@ async function readProjectDependencies(projectDir) {
95
140
  break;
96
141
  dir = parent;
97
142
  }
98
- return { dependencies, devDependencies };
143
+ return { dependencies, optionalDependencies };
99
144
  }
100
145
  /**
101
146
  * Attempts to read a version from yarn.lock for a given package.
@@ -161,7 +206,27 @@ function parseYarnLockKeyLine(line) {
161
206
  * Resolves the exact version for a package, trying yarn.lock first,
162
207
  * then falling back to the version range from package.json.
163
208
  */
164
- function resolveVersion(packageName, projectDeps, yarnLockVersions) {
209
+ async function resolveInstalledPackageVersion(packageName, projectDir) {
210
+ let dir = projectDir;
211
+ while (true) {
212
+ const pkgPath = join(dir, 'node_modules', packageName, 'package.json');
213
+ try {
214
+ const content = await readFile(pkgPath, 'utf-8');
215
+ const pkg = JSON.parse(content);
216
+ if (pkg.version)
217
+ return pkg.version;
218
+ }
219
+ catch {
220
+ // Keep walking up to workspace root
221
+ }
222
+ const parent = join(dir, '..');
223
+ if (parent === dir)
224
+ break;
225
+ dir = parent;
226
+ }
227
+ return null;
228
+ }
229
+ async function resolveVersion(packageName, projectDeps, yarnLockVersions, projectDir) {
165
230
  // Prefer exact version from yarn.lock
166
231
  const locked = yarnLockVersions.get(packageName);
167
232
  if (locked)
@@ -170,12 +235,13 @@ function resolveVersion(packageName, projectDeps, yarnLockVersions) {
170
235
  const fromDeps = projectDeps.dependencies[packageName];
171
236
  if (fromDeps)
172
237
  return fromDeps;
173
- const fromDevDeps = projectDeps.devDependencies[packageName];
174
- if (fromDevDeps)
175
- return fromDevDeps;
176
- // Last resort: use wildcard (will get latest on install)
177
- console.warn(`Could not resolve version for package '${packageName}', defaulting to '*'`);
178
- return '*';
238
+ const fromOptionalDeps = projectDeps.optionalDependencies[packageName];
239
+ if (fromOptionalDeps)
240
+ return fromOptionalDeps;
241
+ const installedVersion = await resolveInstalledPackageVersion(packageName, projectDir);
242
+ if (installedVersion)
243
+ return installedVersion;
244
+ return null;
179
245
  }
180
246
  /**
181
247
  * Given an esbuild metafile, extracts all external packages and resolves
@@ -187,27 +253,41 @@ function resolveVersion(packageName, projectDeps, yarnLockVersions) {
187
253
  export async function extractDependencies(metafile, projectDir) {
188
254
  const externalPackages = extractExternalPackages(metafile);
189
255
  if (externalPackages.size === 0) {
190
- return {};
256
+ return { exactDependencies: {}, exactOptionalDependencies: {} };
191
257
  }
192
258
  const [projectDeps, yarnLockVersions] = await Promise.all([
193
259
  readProjectDependencies(projectDir),
194
260
  readYarnLockVersions(projectDir),
195
261
  ]);
196
- const dependencies = {};
262
+ const exactDependencies = {};
263
+ const exactOptionalDependencies = {};
197
264
  for (const pkg of [...externalPackages].sort()) {
198
- dependencies[pkg] = resolveVersion(pkg, projectDeps, yarnLockVersions);
265
+ const version = await resolveVersion(pkg, projectDeps, yarnLockVersions, projectDir);
266
+ if (!version) {
267
+ // Some packages are optional-at-runtime (e.g. ws acceleration addons).
268
+ // Keep deploy planning deterministic without forcing a hard failure.
269
+ exactOptionalDependencies[pkg] = '*';
270
+ continue;
271
+ }
272
+ if (pkg in projectDeps.optionalDependencies) {
273
+ exactOptionalDependencies[pkg] = version;
274
+ }
275
+ else {
276
+ exactDependencies[pkg] = version;
277
+ }
199
278
  }
200
- return dependencies;
279
+ return { exactDependencies, exactOptionalDependencies };
201
280
  }
202
281
  /**
203
282
  * Generates a minimal package.json content object for a unit bundle.
204
283
  */
205
- export function generateMinimalPackageJson(unitName, dependencies) {
284
+ export function generateMinimalPackageJson(unitName, dependencies, optionalDependencies) {
206
285
  return {
207
286
  name: unitName,
208
287
  private: true,
209
288
  type: 'module',
210
289
  main: 'bundle.js',
211
290
  dependencies,
291
+ optionalDependencies,
212
292
  };
213
293
  }
@@ -7,9 +7,13 @@ export interface BundleResult {
7
7
  unitName: string;
8
8
  bundlePath: string;
9
9
  packageJsonPath: string;
10
+ exactDependenciesPath: string;
10
11
  metafilePath: string;
11
12
  bundleSizeBytes: number;
12
- externalPackages: Record<string, string>;
13
+ bundleHash: string;
14
+ exactDependenciesHash: string;
15
+ exactDependencies: Record<string, string>;
16
+ exactOptionalDependencies: Record<string, string>;
13
17
  }
14
18
  export interface BundleError {
15
19
  unitName: string;
@@ -8,7 +8,7 @@
8
8
  * Uses --stateInput to avoid re-inspecting the codebase for each unit.
9
9
  */
10
10
  import type { InspectorState } from '@pikku/inspector';
11
- import type { DeploymentManifest } from '../analyzer/manifest.js';
11
+ import type { DeploymentManifest, DeploymentUnit } from '../analyzer/manifest.js';
12
12
  export interface PerUnitCodegenOptions {
13
13
  /** Root directory of the project (where pikku.config.json lives) */
14
14
  projectDir: string;
@@ -22,6 +22,8 @@ export interface PerUnitCodegenOptions {
22
22
  pikkuBin?: string;
23
23
  /** Called for each unit as it starts/completes */
24
24
  onProgress?: (unitName: string, status: 'start' | 'done' | 'error', error?: string) => void;
25
+ /** Resolve unit output directory (defaults to <deployDir>/<unit-name>) */
26
+ resolveUnitDir?: (unit: DeploymentUnit, baseDeployDir: string) => string;
25
27
  }
26
28
  export interface PerUnitCodegenResult {
27
29
  /** Map of unit name -> path to the unit's .pikku directory */
@@ -171,7 +171,9 @@ export async function generatePerUnitCodegen(options) {
171
171
  continue;
172
172
  }
173
173
  onProgress?.(unit.name, 'start');
174
- const unitDir = join(baseDir, unit.name);
174
+ const unitDir = options.resolveUnitDir
175
+ ? options.resolveUnitDir(unit, baseDir)
176
+ : join(baseDir, unit.name);
175
177
  const unitPikkuDir = join(unitDir, '.pikku');
176
178
  await mkdir(unitDir, { recursive: true });
177
179
  const namesArg = filterNames.join(',');
@@ -23,14 +23,36 @@ function diffUnits(manifest, current, drainInfo) {
23
23
  });
24
24
  }
25
25
  else if (!arraysEqual(existing.functionIds, desired.functionIds) ||
26
- existing.role !== desired.role) {
26
+ existing.role !== desired.role ||
27
+ (typeof desired.bundleHash === 'string' &&
28
+ typeof existing.bundleHash === 'string' &&
29
+ desired.bundleHash !== existing.bundleHash) ||
30
+ (typeof desired.exactDependenciesHash === 'string' &&
31
+ typeof existing.exactDependenciesHash === 'string' &&
32
+ desired.exactDependenciesHash !== existing.exactDependenciesHash)) {
33
+ const bundleChanged = typeof desired.bundleHash === 'string' &&
34
+ typeof existing.bundleHash === 'string' &&
35
+ desired.bundleHash !== existing.bundleHash;
36
+ const depsChanged = typeof desired.exactDependenciesHash === 'string' &&
37
+ typeof existing.exactDependenciesHash === 'string' &&
38
+ desired.exactDependenciesHash !== existing.exactDependenciesHash;
27
39
  changes.push({
28
40
  action: 'update',
29
41
  resourceType: 'unit',
30
42
  name: desired.name,
31
43
  role: desired.role,
32
- reason: 'code changed',
33
- details: { functionIds: desired.functionIds },
44
+ reason: bundleChanged
45
+ ? depsChanged
46
+ ? 'bundle + dependencies changed'
47
+ : 'bundle changed'
48
+ : depsChanged
49
+ ? 'dependencies changed'
50
+ : 'code changed',
51
+ details: {
52
+ functionIds: desired.functionIds,
53
+ bundleHash: desired.bundleHash,
54
+ exactDependenciesHash: desired.exactDependenciesHash,
55
+ },
34
56
  });
35
57
  }
36
58
  }
@@ -5,6 +5,8 @@ export interface CurrentState {
5
5
  name: string;
6
6
  functionIds: string[];
7
7
  role: string;
8
+ bundleHash?: string;
9
+ exactDependenciesHash?: string;
8
10
  }>;
9
11
  queues: Array<{
10
12
  name: string;
@@ -94,7 +94,6 @@ export async function resolveProvider(config, providerName) {
94
94
  }
95
95
  const ANSI = {
96
96
  green: '\x1b[32m',
97
- red: '\x1b[31m',
98
97
  bold: '\x1b[1m',
99
98
  reset: '\x1b[0m',
100
99
  };
@@ -119,8 +118,12 @@ async function runDeploy(provider, providerDir, logger, resultFile) {
119
118
  deployResult = await provider.deploy({
120
119
  buildDir: providerDir,
121
120
  logger,
122
- onProgress: (_step, _detail) => {
123
- process.stdout.write(` ${ANSI.green}done${ANSI.reset}\n`);
121
+ onProgress: (step, detail) => {
122
+ logger.info({
123
+ message: `[${step}] ${detail}`,
124
+ type: 'progress',
125
+ data: { progress: { step, detail } },
126
+ });
124
127
  },
125
128
  });
126
129
  }
@@ -132,7 +135,6 @@ async function runDeploy(provider, providerDir, logger, resultFile) {
132
135
  };
133
136
  }
134
137
  await writeResultFile(resultFile, deployResult);
135
- console.log('');
136
138
  if (deployResult.success) {
137
139
  logger.info(`${ANSI.green}${ANSI.bold}Deployment complete.${ANSI.reset}`);
138
140
  logger.info(` ${deployResult.workersDeployed?.length ?? 0} units deployed, ${deployResult.resourcesCreated?.length ?? 0} resources created`);
@@ -89,7 +89,13 @@ export const deployPlan = pikkuSessionlessFunc({
89
89
  unitCount: result.bundled.length,
90
90
  totalSizeBytes: totalSize,
91
91
  errors: result.bundleErrors,
92
- manifest: result.manifest,
92
+ codegenErrors: result.codegenErrors,
93
+ summary: plan.summary,
94
+ changeCount: plan.changes.length,
95
+ artifacts: {
96
+ deploymentManifestPath: result.deploymentManifestPath,
97
+ infraPath: result.infraPath,
98
+ },
93
99
  }, null, 2), 'utf-8');
94
100
  }
95
101
  },
@@ -49,7 +49,10 @@ export const pikkuSummary = pikkuSessionlessFunc({
49
49
  summary.set('workflowGraphs', workflowGraphsCount);
50
50
  }
51
51
  if (!logger.isSilent()) {
52
- console.log(summary.format());
52
+ // Route through the logger so JSON mode emits a single NDJSON
53
+ // record instead of raw chalk-coloured text written directly to
54
+ // stdout (which would break NDJSON consumers).
55
+ logger.info({ message: summary.format(), type: 'summary' });
53
56
  }
54
57
  },
55
58
  });
@@ -13,9 +13,11 @@ export const pikkuVersionsUpdate = pikkuSessionlessFunc({
13
13
  const immutabilityErrors = visitState.manifest.errors.filter((e) => e.code === ErrorCode.FUNCTION_VERSION_MODIFIED);
14
14
  if (immutabilityErrors.length > 0) {
15
15
  for (const e of immutabilityErrors) {
16
- logger.warn(`[${e.code}] ${e.message}`);
16
+ logger.critical(ErrorCode.FUNCTION_VERSION_MODIFIED, e.message);
17
+ }
18
+ if (logger.hasCriticalErrors()) {
19
+ process.exit(1);
17
20
  }
18
- logger.warn(`Contract drift detected — version manifest not updated. Run 'pikku versions check' to inspect, or bump versions via code and re-run.`);
19
21
  return;
20
22
  }
21
23
  await saveManifest(manifestPath, visitState.manifest.current);
@@ -1 +1 @@
1
- export declare const pikkuChannels: import("#pikku").PikkuFunctionConfig<void, boolean | undefined, "session" | "rpc", import("#pikku").PikkuFunctionSessionless<void, boolean | undefined, "session" | "rpc", import("#pikku").Services> | import("#pikku").PikkuFunction<void, boolean | undefined, "session" | "rpc", import("#pikku").Services>, undefined, undefined>;
1
+ export declare const pikkuCommandChannels: import("#pikku").PikkuFunctionConfig<void, boolean | undefined, "session" | "rpc", import("#pikku").PikkuFunctionSessionless<void, boolean | undefined, "session" | "rpc", import("#pikku").Services> | import("#pikku").PikkuFunction<void, boolean | undefined, "session" | "rpc", import("#pikku").Services>, undefined, undefined>;
@@ -4,7 +4,7 @@ import { writeFileInDir } from '../../../utils/file-writer.js';
4
4
  import { logCommandInfoAndTime } from '../../../middleware/log-command-info-and-time.js';
5
5
  import { getFileImportRelativePath } from '../../../utils/file-import-path.js';
6
6
  import { stripVerboseFields, hasVerboseFields, } from '../../../utils/strip-verbose-meta.js';
7
- export const pikkuChannels = pikkuSessionlessFunc({
7
+ export const pikkuCommandChannels = pikkuSessionlessFunc({
8
8
  func: async ({ logger, config, getInspectorState }) => {
9
9
  const visitState = await getInspectorState();
10
10
  const { channelsWiringFile, channelsWiringMetaFile, channelsWiringMetaJsonFile, packageMappings, schema, } = config;
@@ -4,7 +4,11 @@ import { writeFileInDir } from '../../../utils/file-writer.js';
4
4
  import { logCommandInfoAndTime } from '../../../middleware/log-command-info-and-time.js';
5
5
  import { serializeConsoleFunctions } from './serialize-console-functions.js';
6
6
  export const pikkuConsoleFunctions = pikkuSessionlessFunc({
7
- func: async ({ logger, config }) => {
7
+ func: async ({ logger, config, variables }) => {
8
+ const deployCodegenFlag = await variables.get('PIKKU_DEPLOY_CODEGEN');
9
+ if (deployCodegenFlag === '1') {
10
+ return false;
11
+ }
8
12
  if (config.scaffold?.console) {
9
13
  const pathToPikkuTypes = getFileImportRelativePath(config.consoleFunctionsFile, config.typesDeclarationFile, config.packageMappings);
10
14
  const pathToAgentTypes = getFileImportRelativePath(config.consoleFunctionsFile, config.agentTypesFile, config.packageMappings);