@kody-ade/kody-engine 0.4.239 → 0.4.241

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.
@@ -1,551 +0,0 @@
1
- /**
2
- * Types shared by the generic executor and executables.
3
- *
4
- * The executor reads a Profile, validates the user's CLI args against
5
- * Profile.inputs, then runs the declared preflight scripts → agent →
6
- * postflight scripts. The executor knows nothing about any specific role
7
- * (build, review, plan, etc.) — it only executes what the profile declares.
8
- */
9
-
10
- import type { AgentResult } from "../agent.js"
11
- import type { KodyConfig, ReasoningEffort } from "../config.js"
12
- import type { Phase } from "../state.js"
13
-
14
- // ────────────────────────────────────────────────────────────────────────────
15
- // Profile shape (mirrors the JSON on disk).
16
- // ────────────────────────────────────────────────────────────────────────────
17
-
18
- export type CapabilityKind = "observe" | "act" | "verify"
19
-
20
- export interface Profile {
21
- name: string
22
- /**
23
- * Public action name owned by a duty. A user may type `@kody <action>`;
24
- * dispatch resolves that action to the duty, then the duty selects the
25
- * implementation executable. Absent → the duty slug/name is the action.
26
- */
27
- action?: string
28
- /**
29
- * Optional agent this executable runs *as*. When set, the executor
30
- * loads `.kody/agents/<agent>.md` and injects that agent (authoritative
31
- * identity) ahead of the executable's own system-prompt append. This is the
32
- * unification hook: a "duty" is just an executable + an agent. Absent →
33
- * runs with no agent (unchanged legacy behaviour). A declared-but-missing
34
- * agent file is fatal at run time (see src/agents.ts).
35
- */
36
- agent?: string
37
- describe: string
38
- /**
39
- * Author-facing capability promise for a duty/executable. This classifies the
40
- * shape of result it should return; it does not change executor control flow.
41
- */
42
- capabilityKind?: CapabilityKind
43
- /**
44
- * Semantic role — what this executable IS, not when it runs.
45
- * - primitive: single-step agent executor (flow → agent → verify → commit → PR).
46
- * - orchestrator: no-agent, drives primitives via a postflight transition table
47
- * (comment-based, one GHA run per step).
48
- * - container: no-agent, runs declared `children` sequentially in-process
49
- * (one GHA run for the whole flow). Routing is done by per-child
50
- * `next` maps over action types — no @kody comments dispatched.
51
- * - watch: scheduled observer that inspects repo state and may trigger other executables.
52
- * - utility: no-agent, one-off administrative work (scaffolding, release, etc.).
53
- *
54
- * Roles enforce shape at profile-load time and let help/dispatch treat
55
- * executables differently by category.
56
- */
57
- role: "primitive" | "orchestrator" | "container" | "watch" | "utility"
58
- /**
59
- * A duty is the WHY/WHEN layer: it references an executable (the HOW) by name
60
- * rather than embedding it. When set, the loader resolves that executable's
61
- * full profile (claudeCode/scripts/prompt/agents) and overlays this duty's
62
- * name + agent (WHO) + every (WHEN) + mentions. Absent → this profile IS an
63
- * executable (defines its own how). executable = how, agent = who, duty = why.
64
- */
65
- executable?: string
66
- /**
67
- * Execution model — orthogonal to `role`.
68
- * `oneshot` (default): single invocation on demand.
69
- * `scheduled`: fires periodically via an external cron (typically GHA
70
- * `schedule:`). Scheduled profiles must declare a `schedule` cron string.
71
- */
72
- kind: "oneshot" | "scheduled"
73
- /**
74
- * Recurrence cadence for a duty that runs on a timer (unified successor to a
75
- * markdown duty's `every:` metadata). One of the ScheduleEvery values
76
- * ("15m".."7d" | "manual"). Present → the duty-scheduler fires a one-shot run
77
- * when due (no target). Absent → on-demand only (runs against an issue/PR).
78
- * This is what makes "scheduled" just a field on the one duty shape.
79
- */
80
- every?: string
81
- /**
82
- * Locked-toolbox palette (unified successor to a markdown duty's `tools:`
83
- * metadata). When non-empty, loadDutyState sets ctx.data.dutyTools so the
84
- * executor spins up the in-process kody-duty MCP server and the agent runs
85
- * MCP-only (Bash/Read revoked unless also in claudeCode.tools). Absent →
86
- * normal SDK tools.
87
- */
88
- dutyTools?: string[]
89
- /**
90
- * GitHub logins (no leading `@`) this duty's output should mention. Rendered
91
- * to `@a @b` and exposed to the prompt as {{mentions}} (and as the duty-MCP
92
- * operator mention), mirroring a markdown duty's `mentions:` metadata.
93
- */
94
- mentions?: string[]
95
- /** Cron expression for scheduled profiles (e.g. "0 8 * * MON"). */
96
- schedule?: string
97
- /**
98
- * Task-state phase label emitted when this executable completes successfully.
99
- * Failing actions always set phase to "failed" regardless. Omitted → "idle".
100
- * Lets state.ts stay generic — phase semantics live on the profile.
101
- */
102
- phase?: Phase
103
- inputs: InputSpec[]
104
- claudeCode: ClaudeCodeSpec
105
- cliTools: CliToolSpec[]
106
- /**
107
- * Optional lifecycle macro. When set, the profile loader applies a
108
- * predefined preflight/postflight wrapper around `scripts.preflight` and
109
- * `scripts.postflight` before returning the profile. Registry of lifecycles
110
- * lives in src/lifecycles/. Unknown values are rejected at load time.
111
- *
112
- * Lifecycles exist to consolidate orchestration boilerplate (label,
113
- * context loading, verify, commit, comment) that recurs across many
114
- * executables. Per-executable specifics still go in `scripts.preflight`
115
- * and `scripts.postflight` — the lifecycle wraps them, it doesn't
116
- * replace them.
117
- */
118
- lifecycle?: string
119
- /**
120
- * Lifecycle-specific configuration. Shape depends on `lifecycle`. Validated
121
- * by each lifecycle expander, not by the generic profile parser.
122
- */
123
- lifecycleConfig?: Record<string, unknown>
124
- scripts: {
125
- preflight: ScriptEntry[]
126
- postflight: ScriptEntry[]
127
- }
128
- outputContract?: OutputContract
129
- /**
130
- * Declared artifacts consumed by this executable. The resolveArtifacts
131
- * preflight loads each into ctx.data.artifacts[name] from the task-state
132
- * comment. If `required: true` and the artifact is absent, the executable
133
- * fails fast.
134
- */
135
- inputArtifacts: InputArtifactSpec[]
136
- /**
137
- * Declared artifacts produced by this executable. The persistArtifacts
138
- * postflight reads the named source field from ctx.data and writes an
139
- * Artifact entry into the task-state comment's `artifacts` map.
140
- */
141
- outputArtifacts: OutputArtifactSpec[]
142
- /**
143
- * Container children — required when role === "container", forbidden otherwise.
144
- * Defines the in-process step sequence and routing map. See ContainerChild.
145
- */
146
- children?: ContainerChild[]
147
- /**
148
- * Whether the container should `git reset --hard HEAD` between
149
- * children to discard tracked-file modifications a prior child left
150
- * behind. Default `true` (preserves the legacy bug-safe behaviour
151
- * — see executor.ts:runContainerLoop notes). Set `false` for
152
- * containers whose children are expected to share intermediate
153
- * state (e.g. bug's `reproduce` writing a failing test that `run`
154
- * then makes pass). Only honoured when `role === "container"`.
155
- */
156
- resetBetweenChildren?: boolean
157
- /**
158
- * Phase 5 in-process handoff: when true, the container's loop runs
159
- * the shared context loaders (loadConventions, loadPriorArt,
160
- * loadMemoryContext, loadCoverageRules) ONCE after its own preflight
161
- * completes, then passes the resulting `ctx.data` snapshot to every
162
- * child via `ExecutorInput.preloadedData`. Each child's loaders take
163
- * their fast path (added in 0.4.63) and skip the redundant
164
- * GitHub/filesystem round-trips.
165
- *
166
- * Default `false` (opt-in) so the change is gated to containers
167
- * that have been verified end-to-end. Only honoured when
168
- * `role === "container"`.
169
- */
170
- preloadContext?: boolean
171
- /** Absolute directory the profile was loaded from. Used to resolve prompt.md. */
172
- dir: string
173
- /**
174
- * Prompt template files captured (by absolute path) at load time, BEFORE any
175
- * preflight runs. composePrompt prefers these over a fresh disk read so the
176
- * template survives working-tree churn from runFlow's branch setup — on the CI
177
- * runner a branch checkout can drop the tracked-but-ignore-negated
178
- * `.kody/executables/<name>/` dir, and reading prompt.md afterwards fails with
179
- * ENOENT even though profile.json (read here, earlier) loaded fine.
180
- */
181
- promptTemplates?: Record<string, string>
182
- /**
183
- * Subagent markdown captured (by declared name) at load time, BEFORE any
184
- * task branch switch — same rationale as promptTemplates. loadSubagents
185
- * prefers this snapshot so a duty's `agents/` surviving only on the default
186
- * checkout (e.g. `.kody/duties/<slug>/agents/` absent on a PR branch) doesn't
187
- * crash a PR-targeted duty. Populated by captureSubagentTemplates.
188
- */
189
- subagentTemplates?: Record<string, string>
190
- }
191
-
192
- /**
193
- * One step in a container's child sequence.
194
- *
195
- * The container executor runs the first child, reads the resulting action
196
- * type from `state.core.lastOutcome`, then looks it up in `next`:
197
- * - exact match → either the name of another child in this container, or
198
- * the literal "done" / "abort"
199
- * - "*" wildcard → fallback when no exact match
200
- * - no match → container aborts
201
- */
202
- export interface ContainerChild {
203
- /** Name of the executable to invoke (must resolve via the registry). */
204
- exec: string
205
- /**
206
- * Where to source the target identifier from when invoking this child.
207
- * - "issue": pass --issue <ctx.args.issue>
208
- * - "pr": parse PR number from state.core.prUrl, pass --pr <N>.
209
- * If state.core.prUrl is not set, the container aborts with
210
- * an AGENT_NOT_RUN action.
211
- */
212
- target: "issue" | "pr"
213
- /**
214
- * Map from action.type → next step. Each value must be the name of another
215
- * child in this container, "done" (exit 0), or "abort" (exit 1). Lookup is
216
- * exact-match first, then "*" as a wildcard fallback.
217
- */
218
- next: Record<string, string>
219
- }
220
-
221
- export interface InputArtifactSpec {
222
- /** Artifact name (the key in state.artifacts). */
223
- name: string
224
- /** If true, the executable fails when this artifact is missing from state. */
225
- required?: boolean
226
- }
227
-
228
- export interface OutputArtifactSpec {
229
- /** Artifact name (the key in state.artifacts). */
230
- name: string
231
- /** Informational format tag ("markdown", "text", …). */
232
- format: string
233
- /** Dotted path into ctx.data to read the payload from (e.g. "prSummary"). */
234
- from: string
235
- }
236
-
237
- export interface InputSpec {
238
- name: string
239
- flag: string
240
- type: "int" | "string" | "bool" | "enum"
241
- /** Allowed values for `type: "enum"`. */
242
- values?: string[]
243
- required?: boolean
244
- /**
245
- * Only required when another input matches one of these values.
246
- * e.g. `{ mode: "run" }` or `{ mode: ["fix", "fix-ci", "resolve"] }`.
247
- */
248
- requiredWhen?: Record<string, string | string[]>
249
- /**
250
- * When true, this input collects any free-text left over from comment
251
- * dispatch after flag/enum/bool parsing. Only one input per profile may
252
- * set this. Used by e.g. `fix.feedback` so `@kody please change X` lands
253
- * "please change X" in `feedback` without hardcoding that in the router.
254
- */
255
- bindsCommentRest?: boolean
256
- describe: string
257
- }
258
-
259
- export interface ClaudeCodeSpec {
260
- /** "inherit" → use KodyConfig.agent.model. Or a concrete "provider/model". */
261
- model: string
262
- permissionMode: "default" | "acceptEdits" | "plan" | "bypassPermissions"
263
- /** null = unbounded. */
264
- maxTurns: number | null
265
- /** Extended-thinking token budget. null = SDK default. */
266
- maxThinkingTokens: number | null
267
- /** User-facing effort level. When set, preferred over maxThinkingTokens. */
268
- reasoningEffort?: ReasoningEffort | null
269
- /**
270
- * Watchdog: abort the agent if no SDK message arrives within this many
271
- * seconds. Per-profile override for the global 600s default. Useful on
272
- * `run`/`fix` stages where a long test suite can leave the SDK silent
273
- * longer than the default. Set to 0 or a negative number to disable
274
- * the watchdog entirely. null/undefined → use the global default.
275
- */
276
- maxTurnTimeoutSec?: number | null
277
- /** Text appended on top of Claude Code's baseline system prompt. */
278
- systemPromptAppend: string | null
279
- /**
280
- * Cross-process prompt caching opt-in. When true, the agent invocation
281
- * sets `systemPrompt.excludeDynamicSections: true` so per-user dynamic
282
- * content (cwd, git status, auto-memory) is stripped from the preset
283
- * and re-injected as the first user message. The remaining preset
284
- * becomes byte-identical across runs and benefits from Anthropic's
285
- * 5-min server-side prompt cache. Recommended for hot-path stages
286
- * (`run`, `fix`, `classify`) where the same workflow fires many
287
- * times in a short window.
288
- *
289
- * Default: false (preserves legacy behaviour). No-op if the SDK does
290
- * not support `excludeDynamicSections` (forward-compatible).
291
- */
292
- cacheable?: boolean
293
- /**
294
- * Phase 3 opt-in: expose an in-process `verify` MCP tool to the agent
295
- * so it can iterate on typecheck/lint/test failures inside one SDK
296
- * session instead of needing a `fix-ci` round trip. The tool is
297
- * bounded by `verifyAttempts` (default 4). The postflight `verify`
298
- * script still runs after the agent finishes as the final ratifier.
299
- * Default false.
300
- */
301
- enableVerifyTool?: boolean
302
- /**
303
- * Opt-in: expose an in-process `submit_state` tool the agent calls to
304
- * persist its next state, instead of relying on a trailing fenced
305
- * `kody-job-next-state` block it must remember to emit. Used by duty-tick.
306
- * The fenced block stays supported as a fallback. Default false.
307
- */
308
- enableSubmitTool?: boolean
309
- /**
310
- * Hard cap on verify-tool invocations per agent session when
311
- * `enableVerifyTool` is true. Default 4 (≈3 fix iterations after the
312
- * first attempt). Set to 0 or omit to use the default.
313
- */
314
- verifyAttempts?: number | null
315
- /** SDK built-in tools this executable is allowed to use (capability pack). */
316
- tools: string[]
317
- /**
318
- * Names of bundled hook configs to load (from src/plugins/hooks/<name>.json).
319
- * Each referenced file is a Claude Code hooks JSON ({ hooks: { PreToolUse: [...] } }).
320
- * Merged into a synthetic plugin at runtime.
321
- */
322
- hooks: string[]
323
- /** Names of bundled skills to load (from src/plugins/skills/<name>/SKILL.md). */
324
- skills: string[]
325
- /** Names of bundled slash commands to load (from src/plugins/commands/<name>.md). */
326
- commands: string[]
327
- /** Names of bundled subagents to load (from src/plugins/agents/<name>.md). */
328
- subagents: string[]
329
- /**
330
- * External plugin directory paths (absolute, or relative to the profile dir).
331
- * Loaded as-is by the SDK via { type: 'local', path }.
332
- */
333
- plugins: string[]
334
- mcpServers: McpServerSpec[]
335
- }
336
-
337
- export interface McpServerSpec {
338
- name: string
339
- command: string
340
- args?: string[]
341
- env?: Record<string, string>
342
- }
343
-
344
- export interface CliToolSpec {
345
- name: string
346
- install: {
347
- required: boolean
348
- checkCommand: string
349
- installCommand?: string
350
- }
351
- verify: string
352
- usage: string
353
- allowedUses: string[]
354
- }
355
-
356
- export interface ScriptEntry {
357
- /**
358
- * Name of a registered TS function in src/scripts/index.ts. Mutually
359
- * exclusive with `shell` — exactly one must be set.
360
- */
361
- script?: string
362
- /**
363
- * Filename of a shell script colocated with the executable
364
- * (e.g. "apply-prefer.sh"). Resolved relative to the profile's
365
- * directory. Invoked via `bash <path> <with-args>` with ctx.args
366
- * exposed as env vars (KODY_ARG_<UPPER_NAME>=<value>). A stdout
367
- * line `KODY_SKIP_AGENT=true` signals the executor to bypass the
368
- * agent. Non-zero exit is treated as a preflight failure.
369
- */
370
- shell?: string
371
- /**
372
- * Optional conditional. Keys are dotted paths into the context (e.g.
373
- * "args.mode"). Values are a single primitive or an array of primitives.
374
- * The script runs only when every key matches. Missing `runWhen` = always.
375
- */
376
- runWhen?: Record<string, string | number | boolean | Array<string | number | boolean>>
377
- /**
378
- * Optional per-call arguments passed to the script as the last positional
379
- * parameter. Used by the orchestrator's transition table so the same
380
- * dispatcher script can be reused with different `next` targets.
381
- */
382
- with?: Record<string, string | number | boolean>
383
- /**
384
- * Optional shell-script timeout in seconds. Only honored on `shell` entries.
385
- * Falls back to `KODY_SHELL_TIMEOUT_SEC` env var, then the 300s default.
386
- * Long-running shells (release publish, large repo verify) should declare
387
- * a higher value rather than relying on the default and getting SIGKILLed
388
- * with an opaque "exited -1".
389
- */
390
- timeoutSec?: number
391
- }
392
-
393
- export interface OutputContract {
394
- finalMessage?: {
395
- onSuccess?: string[]
396
- onFailure?: string[]
397
- }
398
- }
399
-
400
- export interface CapabilityAlert {
401
- level?: "info" | "warning" | "error"
402
- message: string
403
- }
404
-
405
- export interface CapabilitySuggestedAction {
406
- action: string
407
- args?: Record<string, unknown>
408
- reason?: string
409
- }
410
-
411
- export interface CapabilityResourceRef {
412
- type: string
413
- id?: string | number
414
- number?: number
415
- url?: string
416
- name?: string
417
- }
418
-
419
- export interface CapabilityEvidenceItem {
420
- source?: string
421
- message: string
422
- url?: string
423
- }
424
-
425
- export interface ObserveResult {
426
- kind: "observe"
427
- facts?: Record<string, unknown>
428
- alerts?: CapabilityAlert[]
429
- suggestedActions?: CapabilitySuggestedAction[]
430
- evidence?: Record<string, unknown>
431
- }
432
-
433
- export interface ActResult {
434
- kind: "act"
435
- status: "created" | "changed" | "triggered" | "skipped" | "failed"
436
- changedResources?: CapabilityResourceRef[]
437
- createdResources?: CapabilityResourceRef[]
438
- actionResult?: Record<string, unknown>
439
- evidence?: Record<string, unknown>
440
- }
441
-
442
- export interface VerifyResult {
443
- kind: "verify"
444
- passed: boolean
445
- evidence?: CapabilityEvidenceItem[]
446
- blockers?: string[]
447
- facts?: Record<string, unknown>
448
- }
449
-
450
- export type CapabilityResult = ObserveResult | ActResult | VerifyResult
451
-
452
- // ────────────────────────────────────────────────────────────────────────────
453
- // Run-time context passed to every script.
454
- // ────────────────────────────────────────────────────────────────────────────
455
-
456
- export interface Context {
457
- /** Validated CLI args, keyed by input `name`. */
458
- args: Record<string, unknown>
459
- /** Project root. */
460
- cwd: string
461
- /** Loaded kody.config.json. */
462
- config: KodyConfig
463
- /** Stream-output verbosity. */
464
- verbose?: boolean
465
- quiet?: boolean
466
- /** Opaque bag scripts populate during preflight (issue, pr, diff, logs, …). */
467
- data: Record<string, unknown>
468
- /** Final output the executor returns. */
469
- output: {
470
- exitCode: number
471
- prUrl?: string
472
- reason?: string
473
- /**
474
- * In-process hand-off to the next stage. A stage (e.g. `classify`) sets
475
- * this so the orchestrator runs the chosen sub-orchestrator
476
- * (feature/bug/spec/chore) in the same process — instead of posting an
477
- * `@kody <next>` comment, which is silently ignored when Kody comments as
478
- * a GitHub App (bot author), stalling the pipeline at classify.
479
- */
480
- nextDispatch?: { action?: string; duty?: string; executable?: string; cliArgs: Record<string, unknown> }
481
- /** In-process hand-off to a full Job, preserving job identity in task state. */
482
- nextJob?: Job
483
- /** Where to return after nextJob succeeds. Used by task-jobs to keep draining pending work. */
484
- afterNextJob?: { action?: string; duty?: string; executable?: string; cliArgs: Record<string, unknown> }
485
- }
486
- /**
487
- * If a preflight script sets this to true, the executor skips the agent
488
- * invocation and proceeds straight to postflight. Used by e.g. the
489
- * clean-merge resolve path.
490
- */
491
- skipAgent?: boolean
492
- }
493
-
494
- // ────────────────────────────────────────────────────────────────────────────
495
- // Script signatures. Two phases, two contracts.
496
- // ────────────────────────────────────────────────────────────────────────────
497
-
498
- export type ScriptArgs = Record<string, string | number | boolean>
499
-
500
- export type PreflightScript = (ctx: Context, profile: Profile, args?: ScriptArgs) => Promise<void>
501
-
502
- export type PostflightScript = (
503
- ctx: Context,
504
- profile: Profile,
505
- agentResult: AgentResult | null,
506
- args?: ScriptArgs,
507
- ) => Promise<void>
508
-
509
- /** A registered script may be either phase; registry looks it up by name. */
510
- export type AnyScript = PreflightScript | PostflightScript
511
-
512
- // ────────────────────────────────────────────────────────────────────────────
513
- // Job — the unified work request (task-state jobs collect run attempts).
514
- //
515
- // A Job is the required work the engine tries to execute, regardless of how it
516
- // was triggered. It must reference a duty/action (why). The executable is only
517
- // the duty-selected implementation detail (how), never a standalone request.
518
- // Task state stores this durable job separately from individual run attempts.
519
- // Two flavors:
520
- // - "instant" — run once now (an `@kody <verb>` comment or a manual dispatch)
521
- // - "scheduled" — fired on `schedule` (cron) by the tick path
522
- //
523
- // `runJob` (src/job.ts) lowers a Job onto the private executor after resolving
524
- // the duty, and seeds both stable job metadata and per-run metadata.
525
- // ────────────────────────────────────────────────────────────────────────────
526
-
527
- export type JobFlavor = "instant" | "scheduled"
528
-
529
- export interface Job {
530
- /** Public action the user/operator invoked. Mirrors the duty action. */
531
- action?: string
532
- /** How: implementation profile selected by the duty. Not valid by itself. */
533
- executable?: string
534
- /** Why (referenced): a duty slug whose intent drives the run. */
535
- duty?: string
536
- /** Why (inline): free-text intent, e.g. an `@kody` comment body. Untrusted —
537
- * fenced where it enters a prompt, not here. */
538
- why?: string
539
- /** Who: an agent identity slug. */
540
- agent?: string
541
- /** When: cron expression. Set for scheduled jobs, absent for instant. */
542
- schedule?: string
543
- /** The issue/PR number this job acts on, when applicable. */
544
- target?: number
545
- /** Args passed through to the executable (mirrors DispatchResult.cliArgs). */
546
- cliArgs: Record<string, unknown>
547
- /** Run once now ("instant") or on the schedule ("scheduled"). */
548
- flavor: JobFlavor
549
- /** Manual force-run (bypass cadence) for a scheduled job. */
550
- force?: boolean
551
- }
@@ -1,43 +0,0 @@
1
- # syntax=docker/dockerfile:1.7
2
- #
3
- # Bundled default Dockerfile.preview — DEV-MODE variant.
4
- #
5
- # Previews run `next dev`, NOT `next build` + `next start`. This skips
6
- # the 5–10 min webpack production compile entirely. Trade-offs:
7
- # - First request to each route lags 2–5s (compile-on-demand)
8
- # - Subsequent requests are fast
9
- # - Memory hungry at runtime (webpack alive)
10
- # For PR previews this is the right trade — reviewers click a handful
11
- # of pages, never need prod optimizations. Production stays on Vercel.
12
- #
13
- # When BASE_IMAGE is set, deps are inherited from the per-repo base
14
- # image; the install layer just verifies the lockfile.
15
-
16
- ARG BASE_IMAGE=node:22-alpine
17
-
18
- FROM ${BASE_IMAGE}
19
- WORKDIR /app
20
-
21
- RUN corepack enable 2>/dev/null || true
22
-
23
- COPY package.json pnpm-lock.yaml* package-lock.json* yarn.lock* ./
24
- RUN --mount=type=cache,id=kp-pnpm-store,target=/root/.local/share/pnpm/store \
25
- --mount=type=cache,id=kp-npm-cache,target=/root/.npm \
26
- if [ -f pnpm-lock.yaml ]; then pnpm install --frozen-lockfile --ignore-scripts; \
27
- elif [ -f package-lock.json ]; then npm ci --ignore-scripts; \
28
- elif [ -f yarn.lock ]; then yarn install --frozen-lockfile --ignore-scripts; \
29
- else npm install --ignore-scripts; fi
30
-
31
- # Overwrite source files with the PR's version. node_modules and any
32
- # prior .next directory from the base image are preserved as warm
33
- # caches for the dev server's first compile.
34
- COPY . ./
35
- RUN mkdir -p public
36
-
37
- ENV NEXT_TELEMETRY_DISABLED=1
38
- ENV NODE_ENV=development
39
- ENV PORT=8080
40
- ENV HOSTNAME=0.0.0.0
41
-
42
- EXPOSE 8080
43
- CMD ["sh", "-c", "if [ -f pnpm-lock.yaml ]; then pnpm exec next dev -H 0.0.0.0 -p 8080; else npx next dev -H 0.0.0.0 -p 8080; fi"]
@@ -1,40 +0,0 @@
1
- # syntax=docker/dockerfile:1.7
2
- #
3
- # Bundled default Dockerfile.preview — PROD-MODE variant.
4
- #
5
- # Runs `next build` + `next start` — same shape as Vercel. Slower
6
- # first build (~5–10 min webpack) but instant per-page response.
7
- # Pick this mode for repos where reviewers test production-only
8
- # behaviour (SSG, static optimization, edge runtime parity).
9
-
10
- ARG BASE_IMAGE=node:22-alpine
11
-
12
- FROM ${BASE_IMAGE}
13
- WORKDIR /app
14
-
15
- RUN corepack enable 2>/dev/null || true
16
-
17
- COPY package.json pnpm-lock.yaml* package-lock.json* yarn.lock* ./
18
- RUN --mount=type=cache,id=kp-pnpm-store,target=/root/.local/share/pnpm/store \
19
- --mount=type=cache,id=kp-npm-cache,target=/root/.npm \
20
- if [ -f pnpm-lock.yaml ]; then pnpm install --frozen-lockfile --ignore-scripts; \
21
- elif [ -f package-lock.json ]; then npm ci --ignore-scripts; \
22
- elif [ -f yarn.lock ]; then yarn install --frozen-lockfile --ignore-scripts; \
23
- else npm install --ignore-scripts; fi
24
-
25
- COPY . ./
26
- RUN mkdir -p public
27
-
28
- ENV NEXT_TELEMETRY_DISABLED=1
29
- ENV NODE_ENV=production
30
- ENV PORT=8080
31
- ENV HOSTNAME=0.0.0.0
32
- ENV NODE_OPTIONS="--max-old-space-size=7168"
33
-
34
- RUN --mount=type=cache,id=kp-next-cache,target=/app/.next/cache \
35
- if [ -f pnpm-lock.yaml ]; then pnpm exec next build; \
36
- else npx next build; \
37
- fi
38
-
39
- EXPOSE 8080
40
- CMD ["sh", "-c", "if [ -f pnpm-lock.yaml ]; then pnpm exec next start -H 0.0.0.0 -p 8080; else npx next start -H 0.0.0.0 -p 8080; fi"]