@pikku/cli 0.12.19 → 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.
- package/console-app/assets/{index-DAQHIRK3.js → index-DXLy-_D4.js} +181 -181
- package/console-app/index.html +1 -1
- package/dist/.pikku/agent/pikku-agent-types.gen.d.ts +1 -1
- package/dist/.pikku/channel/pikku-channel-types.gen.d.ts +1 -1
- package/dist/.pikku/channel/pikku-channel-types.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli-channel.js +6 -1
- package/dist/.pikku/cli/pikku-cli-types.gen.d.ts +1 -1
- package/dist/.pikku/cli/pikku-cli-types.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli-wirings-meta.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli-wirings-meta.gen.json +32 -2
- package/dist/.pikku/cli/pikku-cli-wirings.gen.d.ts +1 -1
- package/dist/.pikku/cli/pikku-cli-wirings.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli.gen.d.ts +1 -1
- package/dist/.pikku/cli/pikku-cli.gen.js +1 -1
- package/dist/.pikku/console/pikku-node-types.gen.d.ts +1 -1
- package/dist/.pikku/function/pikku-function-types.gen.d.ts +8 -2
- package/dist/.pikku/function/pikku-function-types.gen.js +4 -1
- package/dist/.pikku/function/pikku-functions-meta.gen.js +1 -1
- package/dist/.pikku/function/pikku-functions-meta.gen.json +223 -121
- package/dist/.pikku/function/pikku-functions.gen.js +13 -9
- package/dist/.pikku/http/pikku-http-types.gen.d.ts +1 -1
- package/dist/.pikku/http/pikku-http-types.gen.js +1 -1
- package/dist/.pikku/http/pikku-http-wirings-meta.gen.js +1 -1
- package/dist/.pikku/http/pikku-http-wirings.gen.d.ts +1 -1
- package/dist/.pikku/http/pikku-http-wirings.gen.js +1 -1
- package/dist/.pikku/mcp/pikku-mcp-types.gen.d.ts +1 -1
- package/dist/.pikku/mcp/pikku-mcp-types.gen.js +1 -1
- package/dist/.pikku/pikku-bootstrap.gen.d.ts +1 -1
- package/dist/.pikku/pikku-bootstrap.gen.js +1 -1
- package/dist/.pikku/pikku-meta-service.gen.d.ts +1 -1
- package/dist/.pikku/pikku-meta-service.gen.js +1 -1
- package/dist/.pikku/pikku-services.gen.d.ts +2 -1
- package/dist/.pikku/pikku-services.gen.js +1 -0
- package/dist/.pikku/pikku-types.gen.d.ts +1 -1
- package/dist/.pikku/pikku-types.gen.js +1 -1
- package/dist/.pikku/queue/pikku-queue-types.gen.d.ts +1 -1
- package/dist/.pikku/queue/pikku-queue-types.gen.js +1 -1
- package/dist/.pikku/rpc/pikku-rpc-wirings-meta.internal.gen.js +1 -1
- package/dist/.pikku/rpc/pikku-rpc-wirings-meta.internal.gen.json +15 -9
- package/dist/.pikku/scheduler/pikku-scheduler-types.gen.d.ts +1 -1
- package/dist/.pikku/scheduler/pikku-scheduler-types.gen.js +1 -1
- package/dist/.pikku/schemas/register.gen.js +13 -7
- package/dist/.pikku/schemas/schemas/DevInput.schema.json +1 -0
- package/dist/.pikku/schemas/schemas/PikkuCommandHTTPOutput.schema.json +1 -0
- package/dist/.pikku/schemas/schemas/PikkuCommandQueueOutput.schema.json +1 -0
- package/dist/.pikku/schemas/schemas/WorkflowRunStatus.schema.json +1 -1
- package/dist/.pikku/secrets/pikku-secret-types.gen.d.ts +1 -1
- package/dist/.pikku/secrets/pikku-secret-types.gen.js +1 -1
- package/dist/.pikku/secrets/pikku-secrets.gen.d.ts +1 -1
- package/dist/.pikku/secrets/pikku-secrets.gen.js +1 -1
- package/dist/.pikku/trigger/pikku-trigger-types.gen.d.ts +1 -1
- package/dist/.pikku/trigger/pikku-trigger-types.gen.js +1 -1
- package/dist/.pikku/variables/pikku-variable-types.gen.d.ts +1 -1
- package/dist/.pikku/variables/pikku-variable-types.gen.js +1 -1
- package/dist/.pikku/variables/pikku-variables.gen.d.ts +1 -1
- package/dist/.pikku/variables/pikku-variables.gen.js +1 -1
- package/dist/.pikku/workflow/meta/allWorkflow.gen.json +623 -154
- package/dist/.pikku/workflow/pikku-workflow-types.gen.d.ts +1 -1
- package/dist/.pikku/workflow/pikku-workflow-types.gen.js +1 -1
- package/dist/.pikku/workflow/pikku-workflow-wirings-meta.gen.js +1 -1
- package/dist/.pikku/workflow/pikku-workflow-wirings.gen.js +1 -1
- package/dist/src/cli.wiring.js +32 -2
- package/dist/src/deploy/analyzer/manifest.d.ts +10 -0
- package/dist/src/deploy/build-pipeline.d.ts +2 -0
- package/dist/src/deploy/build-pipeline.js +44 -6
- package/dist/src/deploy/bundler/bundler.d.ts +2 -1
- package/dist/src/deploy/bundler/bundler.js +28 -5
- package/dist/src/deploy/bundler/dep-extractor.d.ts +5 -2
- package/dist/src/deploy/bundler/dep-extractor.js +103 -23
- package/dist/src/deploy/bundler/types.d.ts +5 -1
- package/dist/src/deploy/codegen/per-unit-codegen.d.ts +3 -1
- package/dist/src/deploy/codegen/per-unit-codegen.js +3 -1
- package/dist/src/deploy/plan/planner.js +25 -3
- package/dist/src/deploy/plan/provider.d.ts +2 -0
- package/dist/src/functions/commands/deploy-apply.js +6 -4
- package/dist/src/functions/commands/deploy-plan.js +7 -1
- package/dist/src/functions/commands/dev.d.ts +13 -0
- package/dist/src/functions/commands/dev.js +187 -0
- package/dist/src/functions/commands/pikku-command-summary.js +4 -1
- package/dist/src/functions/commands/versions-update.js +9 -3
- package/dist/src/functions/wirings/channels/pikku-command-channels.d.ts +1 -1
- package/dist/src/functions/wirings/channels/pikku-command-channels.js +1 -1
- package/dist/src/functions/wirings/console/pikku-command-console-functions.js +5 -1
- package/dist/src/functions/wirings/functions/serialize-function-types.js +31 -1
- package/dist/src/functions/wirings/http/pikku-command-http-routes.d.ts +1 -1
- package/dist/src/functions/wirings/http/pikku-command-http-routes.js +1 -1
- package/dist/src/functions/wirings/queue/pikku-command-queue-map.d.ts +1 -1
- package/dist/src/functions/wirings/queue/pikku-command-queue-map.js +1 -1
- package/dist/src/functions/wirings/queue/pikku-command-queue.d.ts +1 -1
- package/dist/src/functions/wirings/queue/pikku-command-queue.js +1 -1
- package/dist/src/functions/wirings/rpc/pikku-command-public-rpc.js +5 -1
- package/dist/src/functions/wirings/rpc/pikku-command-remote-rpc.js +5 -1
- package/dist/src/functions/wirings/rpc/serialize-typed-rpc-map.js +16 -2
- package/dist/src/functions/wirings/workflow/serialize-workflow-routes.js +44 -2
- package/dist/src/functions/workflows/all.workflow.js +5 -10
- package/dist/src/scaffold/rpc-remote.gen.js +1 -1
- package/dist/src/scaffold/workflow-routes.gen.js +36 -4
- package/dist/src/services/cli-logger.service.d.ts +22 -2
- package/dist/src/services/cli-logger.service.js +97 -21
- package/dist/src/services.js +8 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +8 -4
- package/dist/.pikku/cli/pikku-cli-client.gen.d.ts +0 -10
- package/dist/.pikku/cli/pikku-cli-client.gen.js +0 -44
- /package/dist/.pikku/schemas/schemas/{PikkuChannelsOutput.schema.json → PikkuCommandChannelsOutput.schema.json} +0 -0
package/dist/src/cli.wiring.js
CHANGED
|
@@ -11,6 +11,7 @@ import { all } from './functions/commands/all.js';
|
|
|
11
11
|
import { bootstrap } from './functions/commands/bootstrap.js';
|
|
12
12
|
import { watch } from './functions/commands/watch.js';
|
|
13
13
|
import { consoleCommand } from './functions/commands/console.js';
|
|
14
|
+
import { dev } from './functions/commands/dev.js';
|
|
14
15
|
import { pikkuVersionsInit } from './functions/commands/versions-init.js';
|
|
15
16
|
import { pikkuVersionsCheck } from './functions/commands/versions-check.js';
|
|
16
17
|
import { pikkuVersionsUpdate } from './functions/commands/versions-update.js';
|
|
@@ -24,11 +25,11 @@ import { enableRpc, enableConsole, enableAgent, enableWorkflow, } from './functi
|
|
|
24
25
|
import { deployPlan } from './functions/commands/deploy-plan.js';
|
|
25
26
|
import { deployApply } from './functions/commands/deploy-apply.js';
|
|
26
27
|
import { deployInfo } from './functions/commands/deploy-info.js';
|
|
27
|
-
|
|
28
|
+
import { defaultCLIRenderer } from './services.js';
|
|
28
29
|
wireCLI({
|
|
29
30
|
program: 'pikku',
|
|
30
31
|
description: 'Pikku CLI - Code generation tool for type-safe backend development',
|
|
31
|
-
|
|
32
|
+
render: defaultCLIRenderer,
|
|
32
33
|
options: {
|
|
33
34
|
config: {
|
|
34
35
|
description: 'Path to pikku.config.json file',
|
|
@@ -39,6 +40,16 @@ wireCLI({
|
|
|
39
40
|
default: 'info',
|
|
40
41
|
short: 'l',
|
|
41
42
|
},
|
|
43
|
+
output: {
|
|
44
|
+
description: 'Output format (json emits NDJSON)',
|
|
45
|
+
choices: ['text', 'json'],
|
|
46
|
+
default: 'text',
|
|
47
|
+
},
|
|
48
|
+
json: {
|
|
49
|
+
description: 'Alias for --output json',
|
|
50
|
+
default: false,
|
|
51
|
+
short: 'j',
|
|
52
|
+
},
|
|
42
53
|
userSessionType: {
|
|
43
54
|
description: 'Specify which UserSession type to use (when multiple exist)',
|
|
44
55
|
},
|
|
@@ -102,6 +113,25 @@ wireCLI({
|
|
|
102
113
|
},
|
|
103
114
|
},
|
|
104
115
|
}),
|
|
116
|
+
dev: pikkuCLICommand({
|
|
117
|
+
func: dev,
|
|
118
|
+
description: 'Start a local development server with all services wired',
|
|
119
|
+
options: {
|
|
120
|
+
port: {
|
|
121
|
+
description: 'Port for the dev server',
|
|
122
|
+
default: '3000',
|
|
123
|
+
short: 'p',
|
|
124
|
+
},
|
|
125
|
+
watch: {
|
|
126
|
+
description: 'Watch for file changes and regenerate',
|
|
127
|
+
default: true,
|
|
128
|
+
},
|
|
129
|
+
hmr: {
|
|
130
|
+
description: 'Enable hot module reload',
|
|
131
|
+
default: true,
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
}),
|
|
105
135
|
console: pikkuCLICommand({
|
|
106
136
|
func: consoleCommand,
|
|
107
137
|
description: 'Start the Pikku Console UI with live file watching',
|
|
@@ -44,6 +44,16 @@ export interface DeploymentUnit {
|
|
|
44
44
|
/** What runtime handlers this unit needs to export */
|
|
45
45
|
handlers: DeploymentHandler[];
|
|
46
46
|
tags: string[];
|
|
47
|
+
/** SHA-256 of final bundled artifact (set by build pipeline) */
|
|
48
|
+
bundleHash?: string;
|
|
49
|
+
/** Final bundle size in bytes (set by build pipeline) */
|
|
50
|
+
bundleSizeBytes?: number;
|
|
51
|
+
/** SHA-256 of sorted exact dependency map (set by build pipeline) */
|
|
52
|
+
exactDependenciesHash?: string;
|
|
53
|
+
/** Top-level exact runtime dependency versions for this unit (set by build pipeline) */
|
|
54
|
+
exactDependencies?: Record<string, string>;
|
|
55
|
+
/** Top-level exact optional runtime dependency versions for this unit (set by build pipeline) */
|
|
56
|
+
exactOptionalDependencies?: Record<string, string>;
|
|
47
57
|
}
|
|
48
58
|
export interface QueueDefinition {
|
|
49
59
|
name: string;
|
|
@@ -16,6 +16,8 @@ export interface BuildLogger {
|
|
|
16
16
|
export interface BuildPipelineResult {
|
|
17
17
|
manifest: DeploymentManifest;
|
|
18
18
|
providerDir: string;
|
|
19
|
+
deploymentManifestPath: string;
|
|
20
|
+
infraPath: string | null;
|
|
19
21
|
projectId: string;
|
|
20
22
|
bundled: BundleResult[];
|
|
21
23
|
bundleErrors: Array<{
|
|
@@ -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:
|
|
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 =
|
|
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:
|
|
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(
|
|
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
|
-
|
|
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 =
|
|
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
|
-
? {
|
|
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
|
|
136
|
-
const packageJson = generateMinimalPackageJson(unit.name,
|
|
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
|
-
|
|
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 =
|
|
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<
|
|
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',
|
|
47
|
-
'
|
|
48
|
-
'
|
|
49
|
-
'
|
|
50
|
-
'
|
|
51
|
-
'
|
|
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
|
|
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.
|
|
86
|
-
if (!(k in
|
|
87
|
-
|
|
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,
|
|
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
|
|
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
|
|
174
|
-
if (
|
|
175
|
-
return
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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
|
|
262
|
+
const exactDependencies = {};
|
|
263
|
+
const exactOptionalDependencies = {};
|
|
197
264
|
for (const pkg of [...externalPackages].sort()) {
|
|
198
|
-
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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:
|
|
33
|
-
|
|
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
|
}
|