@mizchi/k1c 0.1.0

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 (77) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +150 -0
  3. package/dist/canary/dispatcher-template.d.ts +17 -0
  4. package/dist/canary/dispatcher-template.js +42 -0
  5. package/dist/canary/effects-cloudflare.d.ts +9 -0
  6. package/dist/canary/effects-cloudflare.js +66 -0
  7. package/dist/canary/rollout-command.d.ts +15 -0
  8. package/dist/canary/rollout-command.js +92 -0
  9. package/dist/canary/runtime.d.ts +59 -0
  10. package/dist/canary/runtime.js +138 -0
  11. package/dist/canary/state-machine.d.ts +72 -0
  12. package/dist/canary/state-machine.js +161 -0
  13. package/dist/cli/args.d.ts +51 -0
  14. package/dist/cli/args.js +239 -0
  15. package/dist/cli/canary-integration.d.ts +11 -0
  16. package/dist/cli/canary-integration.js +101 -0
  17. package/dist/cli/format.d.ts +4 -0
  18. package/dist/cli/format.js +44 -0
  19. package/dist/cli/main.d.ts +3 -0
  20. package/dist/cli/main.js +158 -0
  21. package/dist/cli/run.d.ts +16 -0
  22. package/dist/cli/run.js +246 -0
  23. package/dist/manifest/lower.d.ts +22 -0
  24. package/dist/manifest/lower.js +913 -0
  25. package/dist/manifest/parse.d.ts +22 -0
  26. package/dist/manifest/parse.js +106 -0
  27. package/dist/manifest/schemas.d.ts +10359 -0
  28. package/dist/manifest/schemas.js +309 -0
  29. package/dist/manifest/types.d.ts +246 -0
  30. package/dist/manifest/types.js +12 -0
  31. package/dist/providers/configmap.d.ts +8 -0
  32. package/dist/providers/configmap.js +29 -0
  33. package/dist/providers/custom-domain.d.ts +11 -0
  34. package/dist/providers/custom-domain.js +120 -0
  35. package/dist/providers/d1-database.d.ts +9 -0
  36. package/dist/providers/d1-database.js +106 -0
  37. package/dist/providers/dispatch-namespace.d.ts +8 -0
  38. package/dist/providers/dispatch-namespace.js +100 -0
  39. package/dist/providers/dns-record.d.ts +14 -0
  40. package/dist/providers/dns-record.js +136 -0
  41. package/dist/providers/errors.d.ts +8 -0
  42. package/dist/providers/errors.js +64 -0
  43. package/dist/providers/hyperdrive.d.ts +27 -0
  44. package/dist/providers/hyperdrive.js +168 -0
  45. package/dist/providers/index.d.ts +6 -0
  46. package/dist/providers/index.js +36 -0
  47. package/dist/providers/kv-namespace.d.ts +8 -0
  48. package/dist/providers/kv-namespace.js +90 -0
  49. package/dist/providers/logpush-job.d.ts +17 -0
  50. package/dist/providers/logpush-job.js +181 -0
  51. package/dist/providers/queue.d.ts +10 -0
  52. package/dist/providers/queue.js +124 -0
  53. package/dist/providers/r2-bucket.d.ts +11 -0
  54. package/dist/providers/r2-bucket.js +98 -0
  55. package/dist/providers/registry.d.ts +9 -0
  56. package/dist/providers/registry.js +22 -0
  57. package/dist/providers/secret.d.ts +8 -0
  58. package/dist/providers/secret.js +30 -0
  59. package/dist/providers/types.d.ts +69 -0
  60. package/dist/providers/types.js +12 -0
  61. package/dist/providers/vectorize.d.ts +11 -0
  62. package/dist/providers/vectorize.js +110 -0
  63. package/dist/providers/worker.d.ts +106 -0
  64. package/dist/providers/worker.js +430 -0
  65. package/dist/providers/workflow.d.ts +10 -0
  66. package/dist/providers/workflow.js +103 -0
  67. package/dist/reconciler/apply.d.ts +10 -0
  68. package/dist/reconciler/apply.js +114 -0
  69. package/dist/reconciler/fake-provider.d.ts +48 -0
  70. package/dist/reconciler/fake-provider.js +83 -0
  71. package/dist/reconciler/plan.d.ts +6 -0
  72. package/dist/reconciler/plan.js +124 -0
  73. package/dist/reconciler/topo.d.ts +10 -0
  74. package/dist/reconciler/topo.js +53 -0
  75. package/dist/reconciler/types.d.ts +54 -0
  76. package/dist/reconciler/types.js +8 -0
  77. package/package.json +61 -0
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Pure state-machine for canary rollouts. Given the current KV-stored RolloutState,
3
+ * the desired content hash, the rollout's `canary.steps[]`, and the current time,
4
+ * compute the next RolloutState and the side-effects to execute.
5
+ *
6
+ * No I/O. All state is in inputs and outputs. Wiring (KV reads, API calls) lives
7
+ * in the apply layer.
8
+ */
9
+ export interface RolloutState {
10
+ readonly status: 'idle' | 'progressing' | 'paused';
11
+ readonly currentStepIndex: number;
12
+ /** ISO8601 timestamp when the current canary started, or null when idle. */
13
+ readonly startedAt: string | null;
14
+ /** ISO8601 timestamp when the last setWeight took effect, or null when idle. */
15
+ readonly lastAdvanceAt: string | null;
16
+ /** Script name of the stable variant (e.g. `k1c--default--api--stable`). */
17
+ readonly stableScript: string;
18
+ /** Script name of the canary variant, or null when no canary in flight. */
19
+ readonly canaryScript: string | null;
20
+ /** 0-100, percentage routed to canary. 0 when idle / paused at step 0. */
21
+ readonly weight: number;
22
+ /** Hash of the deployed stable content. */
23
+ readonly stableHash: string;
24
+ /** Hash of the canary content currently being rolled out, or null when idle. */
25
+ readonly canaryHash: string | null;
26
+ }
27
+ export type CanaryStep = {
28
+ readonly setWeight: number;
29
+ } | {
30
+ readonly pause: {
31
+ readonly duration?: string;
32
+ };
33
+ };
34
+ export interface AdvanceInput {
35
+ /** Current state from KV; null on first apply. */
36
+ readonly state: RolloutState | null;
37
+ /** Hash of the desired stable content the user wants live. */
38
+ readonly desiredHash: string;
39
+ /** The Rollout's strategy.canary.steps[]. */
40
+ readonly steps: ReadonlyArray<CanaryStep>;
41
+ /** Stable script name (k1c naming convention, deterministic). */
42
+ readonly stableScriptName: string;
43
+ /** Canary script name. */
44
+ readonly canaryScriptName: string;
45
+ /** Current time. */
46
+ readonly now: Date;
47
+ }
48
+ export type Action = {
49
+ readonly kind: 'init-stable';
50
+ readonly hash: string;
51
+ } | {
52
+ readonly kind: 'upload-canary';
53
+ readonly hash: string;
54
+ } | {
55
+ readonly kind: 'set-weight';
56
+ readonly weight: number;
57
+ } | {
58
+ readonly kind: 'promote-canary';
59
+ readonly hash: string;
60
+ } | {
61
+ readonly kind: 'remove-canary';
62
+ } | {
63
+ readonly kind: 'wait';
64
+ readonly reason: 'duration-not-elapsed' | 'manual-promote-needed';
65
+ };
66
+ export interface AdvanceOutput {
67
+ readonly nextState: RolloutState;
68
+ readonly actions: ReadonlyArray<Action>;
69
+ }
70
+ export declare function advance(input: AdvanceInput): AdvanceOutput;
71
+ export declare function parseDurationMs(input: string): number;
72
+ //# sourceMappingURL=state-machine.d.ts.map
@@ -0,0 +1,161 @@
1
+ /**
2
+ * Pure state-machine for canary rollouts. Given the current KV-stored RolloutState,
3
+ * the desired content hash, the rollout's `canary.steps[]`, and the current time,
4
+ * compute the next RolloutState and the side-effects to execute.
5
+ *
6
+ * No I/O. All state is in inputs and outputs. Wiring (KV reads, API calls) lives
7
+ * in the apply layer.
8
+ */
9
+ export function advance(input) {
10
+ const { state, desiredHash, steps, stableScriptName, canaryScriptName, now } = input;
11
+ // First apply: no prior state. Upload as stable, mark idle.
12
+ if (state === null) {
13
+ return {
14
+ nextState: {
15
+ status: 'idle',
16
+ currentStepIndex: 0,
17
+ startedAt: null,
18
+ lastAdvanceAt: null,
19
+ stableScript: stableScriptName,
20
+ canaryScript: null,
21
+ weight: 0,
22
+ stableHash: desiredHash,
23
+ canaryHash: null,
24
+ },
25
+ actions: [{ kind: 'init-stable', hash: desiredHash }],
26
+ };
27
+ }
28
+ // Workload changed mid-canary: restart the canary with the new content.
29
+ if (state.canaryHash !== null && state.canaryHash !== desiredHash) {
30
+ return startCanary(state, desiredHash, canaryScriptName, steps, now);
31
+ }
32
+ // No active canary, but desired matches stable: nothing to do.
33
+ if (state.status === 'idle' && state.stableHash === desiredHash) {
34
+ return { nextState: state, actions: [] };
35
+ }
36
+ // No active canary, desired differs from stable: start a new canary.
37
+ if (state.status === 'idle' && state.stableHash !== desiredHash) {
38
+ return startCanary(state, desiredHash, canaryScriptName, steps, now);
39
+ }
40
+ // Canary in progress (status === 'progressing' or 'paused'). Try to advance.
41
+ return advanceStep(state, steps, now, canaryScriptName);
42
+ }
43
+ function startCanary(state, desiredHash, canaryScriptName, steps, now) {
44
+ if (steps.length === 0) {
45
+ // Edge case: empty steps → immediate cutover (treat as 100%).
46
+ return {
47
+ nextState: {
48
+ ...state,
49
+ stableHash: desiredHash,
50
+ canaryScript: null,
51
+ canaryHash: null,
52
+ weight: 0,
53
+ status: 'idle',
54
+ currentStepIndex: 0,
55
+ },
56
+ actions: [{ kind: 'upload-canary', hash: desiredHash }, { kind: 'promote-canary', hash: desiredHash }],
57
+ };
58
+ }
59
+ const firstStep = steps[0];
60
+ const initialWeight = 'setWeight' in firstStep ? firstStep.setWeight : 0;
61
+ const actions = [
62
+ { kind: 'upload-canary', hash: desiredHash },
63
+ { kind: 'set-weight', weight: initialWeight },
64
+ ];
65
+ return {
66
+ nextState: {
67
+ ...state,
68
+ canaryScript: canaryScriptName,
69
+ canaryHash: desiredHash,
70
+ weight: initialWeight,
71
+ status: 'progressing',
72
+ currentStepIndex: 'setWeight' in firstStep ? 1 : 0,
73
+ startedAt: now.toISOString(),
74
+ lastAdvanceAt: now.toISOString(),
75
+ },
76
+ actions,
77
+ };
78
+ }
79
+ function advanceStep(state, steps, now, canaryScriptName) {
80
+ const idx = state.currentStepIndex;
81
+ // Finished all steps without an explicit setWeight 100 → promote.
82
+ if (idx >= steps.length) {
83
+ return promote(state);
84
+ }
85
+ const step = steps[idx];
86
+ if ('setWeight' in step) {
87
+ const w = step.setWeight;
88
+ const advanced = {
89
+ ...state,
90
+ weight: w,
91
+ currentStepIndex: idx + 1,
92
+ lastAdvanceAt: now.toISOString(),
93
+ status: 'progressing',
94
+ };
95
+ if (w >= 100) {
96
+ return promote(advanced);
97
+ }
98
+ return {
99
+ nextState: advanced,
100
+ actions: [{ kind: 'set-weight', weight: w }],
101
+ };
102
+ }
103
+ // pause step
104
+ const duration = step.pause.duration;
105
+ if (duration === undefined) {
106
+ return {
107
+ nextState: { ...state, status: 'paused' },
108
+ actions: [{ kind: 'wait', reason: 'manual-promote-needed' }],
109
+ };
110
+ }
111
+ const elapsedMs = now.getTime() - new Date(state.lastAdvanceAt ?? state.startedAt ?? now).getTime();
112
+ if (elapsedMs >= parseDurationMs(duration)) {
113
+ // Pause complete, advance past it and recurse to consume the next step.
114
+ return advanceStep({ ...state, currentStepIndex: idx + 1, lastAdvanceAt: now.toISOString() }, steps, now, canaryScriptName);
115
+ }
116
+ return {
117
+ nextState: state,
118
+ actions: [{ kind: 'wait', reason: 'duration-not-elapsed' }],
119
+ };
120
+ }
121
+ function promote(state) {
122
+ if (state.canaryHash === null) {
123
+ return { nextState: state, actions: [] };
124
+ }
125
+ return {
126
+ nextState: {
127
+ ...state,
128
+ stableHash: state.canaryHash,
129
+ canaryScript: null,
130
+ canaryHash: null,
131
+ weight: 0,
132
+ status: 'idle',
133
+ currentStepIndex: 0,
134
+ },
135
+ actions: [{ kind: 'promote-canary', hash: state.canaryHash }, { kind: 'remove-canary' }],
136
+ };
137
+ }
138
+ const DURATION_RE = /^(\d+)(ms|s|m|h|d)$/;
139
+ export function parseDurationMs(input) {
140
+ const match = DURATION_RE.exec(input.trim());
141
+ if (!match) {
142
+ throw new Error(`invalid duration: ${input}`);
143
+ }
144
+ const n = Number(match[1]);
145
+ const unit = match[2];
146
+ switch (unit) {
147
+ case 'ms':
148
+ return n;
149
+ case 's':
150
+ return n * 1000;
151
+ case 'm':
152
+ return n * 60 * 1000;
153
+ case 'h':
154
+ return n * 60 * 60 * 1000;
155
+ case 'd':
156
+ return n * 24 * 60 * 60 * 1000;
157
+ default:
158
+ throw new Error(`invalid duration unit: ${unit}`);
159
+ }
160
+ }
161
+ //# sourceMappingURL=state-machine.js.map
@@ -0,0 +1,51 @@
1
+ export type OutputFormat = 'text' | 'json';
2
+ export interface ApplyArgs {
3
+ readonly kind: 'apply';
4
+ readonly file: string;
5
+ readonly dryRun: boolean;
6
+ readonly watch: boolean;
7
+ }
8
+ export interface DiffArgs {
9
+ readonly kind: 'diff';
10
+ readonly file: string;
11
+ readonly output: OutputFormat;
12
+ }
13
+ export interface RolloutArgs {
14
+ readonly kind: 'rollout';
15
+ readonly subCommand: 'status' | 'promote' | 'abort';
16
+ readonly target: string;
17
+ readonly dispatch: string;
18
+ }
19
+ export interface GetArgs {
20
+ readonly kind: 'get';
21
+ readonly resourceKind: string;
22
+ readonly name?: string;
23
+ readonly namespace?: string;
24
+ readonly output: OutputFormat;
25
+ }
26
+ export interface DescribeArgs {
27
+ readonly kind: 'describe';
28
+ readonly resourceKind: string;
29
+ readonly name: string;
30
+ readonly namespace?: string;
31
+ readonly output: OutputFormat;
32
+ }
33
+ export interface VersionArgs {
34
+ readonly kind: 'version';
35
+ }
36
+ export interface DeleteArgs {
37
+ readonly kind: 'delete';
38
+ readonly file: string;
39
+ readonly cascade: boolean;
40
+ }
41
+ export interface HelpArgs {
42
+ readonly kind: 'help';
43
+ }
44
+ export interface ErrorArgs {
45
+ readonly kind: 'error';
46
+ readonly message: string;
47
+ }
48
+ export type ParsedArgs = ApplyArgs | DiffArgs | RolloutArgs | GetArgs | DescribeArgs | DeleteArgs | VersionArgs | HelpArgs | ErrorArgs;
49
+ export declare function parseArgs(argv: ReadonlyArray<string>): ParsedArgs;
50
+ export declare const USAGE = "k1c \u2014 apply a subset of Kubernetes manifests to Cloudflare\n\nusage:\n k1c apply -f <manifest.yaml> [--dry-run | --watch]\n k1c diff -f <manifest.yaml> [-o text|json]\n k1c delete -f <manifest.yaml> [--cascade]\n k1c get <kind> [name] [-n <namespace>] [-o text|json]\n k1c describe <kind> <name> [-n <namespace>] [-o text|json]\n k1c rollout {status|promote|abort} <ns>/<name> --dispatch <name>\n k1c version\n\nenvironment:\n K1C_ACCOUNT_ID Cloudflare account id\n CLOUDFLARE_API_TOKEN API token with Workers Edit + R2 + KV permissions\n";
51
+ //# sourceMappingURL=args.d.ts.map
@@ -0,0 +1,239 @@
1
+ export function parseArgs(argv) {
2
+ if (argv.length === 0)
3
+ return { kind: 'help' };
4
+ const first = argv[0];
5
+ if (first === '--help' || first === '-h')
6
+ return { kind: 'help' };
7
+ if (first === '--version' || first === '-V' || first === 'version') {
8
+ return { kind: 'version' };
9
+ }
10
+ if (first === 'apply')
11
+ return parseApply(argv.slice(1));
12
+ if (first === 'diff')
13
+ return parseDiff(argv.slice(1));
14
+ if (first === 'rollout')
15
+ return parseRollout(argv.slice(1));
16
+ if (first === 'get')
17
+ return parseGet(argv.slice(1));
18
+ if (first === 'describe')
19
+ return parseDescribe(argv.slice(1));
20
+ if (first === 'delete')
21
+ return parseDelete(argv.slice(1));
22
+ return { kind: 'error', message: `unknown command: ${first}` };
23
+ }
24
+ function parseOutput(value) {
25
+ if (value === undefined)
26
+ return { error: '--output requires a value (text|json)' };
27
+ if (value === 'text' || value === 'json')
28
+ return value;
29
+ return { error: `--output must be one of: text, json (got "${value}")` };
30
+ }
31
+ function parseGet(rest) {
32
+ const resourceKind = rest[0];
33
+ if (resourceKind === undefined || resourceKind.startsWith('-')) {
34
+ return { kind: 'error', message: 'get requires a resource kind (e.g. Worker, R2Bucket)' };
35
+ }
36
+ let name;
37
+ let namespace;
38
+ let output = 'text';
39
+ let i = 1;
40
+ if (rest[1] !== undefined && !rest[1].startsWith('-')) {
41
+ name = rest[1];
42
+ i = 2;
43
+ }
44
+ for (; i < rest.length; i += 1) {
45
+ const arg = rest[i];
46
+ if (arg === '-n' || arg === '--namespace') {
47
+ const value = rest[i + 1];
48
+ if (value === undefined)
49
+ return { kind: 'error', message: `${arg} requires a value` };
50
+ namespace = value;
51
+ i += 1;
52
+ continue;
53
+ }
54
+ if (arg === '-o' || arg === '--output') {
55
+ const parsed = parseOutput(rest[i + 1]);
56
+ if (typeof parsed === 'object')
57
+ return { kind: 'error', message: parsed.error };
58
+ output = parsed;
59
+ i += 1;
60
+ continue;
61
+ }
62
+ return { kind: 'error', message: `unknown flag for get: ${arg}` };
63
+ }
64
+ return {
65
+ kind: 'get',
66
+ resourceKind,
67
+ output,
68
+ ...(name !== undefined ? { name } : {}),
69
+ ...(namespace !== undefined ? { namespace } : {}),
70
+ };
71
+ }
72
+ function parseDescribe(rest) {
73
+ const resourceKind = rest[0];
74
+ if (resourceKind === undefined || resourceKind.startsWith('-')) {
75
+ return { kind: 'error', message: 'describe requires a resource kind' };
76
+ }
77
+ const name = rest[1];
78
+ if (name === undefined || name.startsWith('-')) {
79
+ return { kind: 'error', message: 'describe requires a resource name' };
80
+ }
81
+ let namespace;
82
+ let output = 'text';
83
+ for (let i = 2; i < rest.length; i += 1) {
84
+ const arg = rest[i];
85
+ if (arg === '-n' || arg === '--namespace') {
86
+ const value = rest[i + 1];
87
+ if (value === undefined)
88
+ return { kind: 'error', message: `${arg} requires a value` };
89
+ namespace = value;
90
+ i += 1;
91
+ continue;
92
+ }
93
+ if (arg === '-o' || arg === '--output') {
94
+ const parsed = parseOutput(rest[i + 1]);
95
+ if (typeof parsed === 'object')
96
+ return { kind: 'error', message: parsed.error };
97
+ output = parsed;
98
+ i += 1;
99
+ continue;
100
+ }
101
+ return { kind: 'error', message: `unknown flag for describe: ${arg}` };
102
+ }
103
+ return {
104
+ kind: 'describe',
105
+ resourceKind,
106
+ name,
107
+ output,
108
+ ...(namespace !== undefined ? { namespace } : {}),
109
+ };
110
+ }
111
+ function parseDelete(rest) {
112
+ let file;
113
+ let cascade = false;
114
+ for (let i = 0; i < rest.length; i += 1) {
115
+ const arg = rest[i];
116
+ if (arg === '-f' || arg === '--file') {
117
+ const value = rest[i + 1];
118
+ if (value === undefined)
119
+ return { kind: 'error', message: `${arg} requires a value` };
120
+ file = value;
121
+ i += 1;
122
+ continue;
123
+ }
124
+ if (arg === '--cascade') {
125
+ cascade = true;
126
+ continue;
127
+ }
128
+ return { kind: 'error', message: `unknown flag for delete: ${arg}` };
129
+ }
130
+ if (file === undefined)
131
+ return { kind: 'error', message: 'delete requires -f / --file' };
132
+ return { kind: 'delete', file, cascade };
133
+ }
134
+ function parseRollout(rest) {
135
+ const sub = rest[0];
136
+ if (sub === undefined) {
137
+ return { kind: 'error', message: 'rollout requires a subcommand: status | promote | abort' };
138
+ }
139
+ if (sub !== 'status' && sub !== 'promote' && sub !== 'abort') {
140
+ return { kind: 'error', message: `unknown rollout subcommand: ${sub}` };
141
+ }
142
+ const target = rest[1];
143
+ if (target === undefined || target.startsWith('-')) {
144
+ return { kind: 'error', message: `rollout ${sub} requires a target <namespace>/<name>` };
145
+ }
146
+ let dispatch;
147
+ for (let i = 2; i < rest.length; i += 1) {
148
+ const arg = rest[i];
149
+ if (arg === '--dispatch') {
150
+ const value = rest[i + 1];
151
+ if (value === undefined)
152
+ return { kind: 'error', message: '--dispatch requires a value' };
153
+ dispatch = value;
154
+ i += 1;
155
+ continue;
156
+ }
157
+ return { kind: 'error', message: `unknown flag for rollout: ${arg}` };
158
+ }
159
+ if (dispatch === undefined) {
160
+ return { kind: 'error', message: `rollout ${sub} requires --dispatch <name>` };
161
+ }
162
+ return { kind: 'rollout', subCommand: sub, target, dispatch };
163
+ }
164
+ function parseApply(rest) {
165
+ let file;
166
+ let dryRun = false;
167
+ let watch = false;
168
+ for (let i = 0; i < rest.length; i += 1) {
169
+ const arg = rest[i];
170
+ if (arg === '-f' || arg === '--file') {
171
+ const value = rest[i + 1];
172
+ if (value === undefined)
173
+ return { kind: 'error', message: `${arg} requires a value` };
174
+ file = value;
175
+ i += 1;
176
+ continue;
177
+ }
178
+ if (arg === '--dry-run') {
179
+ dryRun = true;
180
+ continue;
181
+ }
182
+ if (arg === '--watch' || arg === '-w') {
183
+ watch = true;
184
+ continue;
185
+ }
186
+ return { kind: 'error', message: `unknown flag for apply: ${arg}` };
187
+ }
188
+ if (file === undefined) {
189
+ return { kind: 'error', message: 'apply requires -f / --file' };
190
+ }
191
+ if (dryRun && watch) {
192
+ return { kind: 'error', message: '--dry-run and --watch are mutually exclusive' };
193
+ }
194
+ return { kind: 'apply', file, dryRun, watch };
195
+ }
196
+ function parseDiff(rest) {
197
+ let file;
198
+ let output = 'text';
199
+ for (let i = 0; i < rest.length; i += 1) {
200
+ const arg = rest[i];
201
+ if (arg === '-f' || arg === '--file') {
202
+ const value = rest[i + 1];
203
+ if (value === undefined)
204
+ return { kind: 'error', message: `${arg} requires a value` };
205
+ file = value;
206
+ i += 1;
207
+ continue;
208
+ }
209
+ if (arg === '-o' || arg === '--output') {
210
+ const parsed = parseOutput(rest[i + 1]);
211
+ if (typeof parsed === 'object')
212
+ return { kind: 'error', message: parsed.error };
213
+ output = parsed;
214
+ i += 1;
215
+ continue;
216
+ }
217
+ return { kind: 'error', message: `unknown flag for diff: ${arg}` };
218
+ }
219
+ if (file === undefined) {
220
+ return { kind: 'error', message: 'diff requires -f / --file' };
221
+ }
222
+ return { kind: 'diff', file, output };
223
+ }
224
+ export const USAGE = `k1c — apply a subset of Kubernetes manifests to Cloudflare
225
+
226
+ usage:
227
+ k1c apply -f <manifest.yaml> [--dry-run | --watch]
228
+ k1c diff -f <manifest.yaml> [-o text|json]
229
+ k1c delete -f <manifest.yaml> [--cascade]
230
+ k1c get <kind> [name] [-n <namespace>] [-o text|json]
231
+ k1c describe <kind> <name> [-n <namespace>] [-o text|json]
232
+ k1c rollout {status|promote|abort} <ns>/<name> --dispatch <name>
233
+ k1c version
234
+
235
+ environment:
236
+ K1C_ACCOUNT_ID Cloudflare account id
237
+ CLOUDFLARE_API_TOKEN API token with Workers Edit + R2 + KV permissions
238
+ `;
239
+ //# sourceMappingURL=args.js.map
@@ -0,0 +1,11 @@
1
+ import type { K1cResource } from '../manifest/types.ts';
2
+ import type { DesiredResource } from '../reconciler/types.ts';
3
+ import type { ProviderContext } from '../providers/types.ts';
4
+ export interface CanaryIntegrationDeps {
5
+ readonly providerCtx: ProviderContext;
6
+ readonly out: (msg: string) => void;
7
+ readonly err: (msg: string) => void;
8
+ readonly now: () => Date;
9
+ }
10
+ export declare function advanceCanaryRolloutsForApply(parsed: ReadonlyArray<K1cResource>, desired: ReadonlyArray<DesiredResource>, deps: CanaryIntegrationDeps): Promise<void>;
11
+ //# sourceMappingURL=canary-integration.d.ts.map
@@ -0,0 +1,101 @@
1
+ import { runCanaryAdvance, } from "../canary/runtime.js";
2
+ import { buildCloudflareEffects } from "../canary/effects-cloudflare.js";
3
+ const CANARY_ANNOTATION = 'cloudflare.com/dispatch-namespace';
4
+ export async function advanceCanaryRolloutsForApply(parsed, desired, deps) {
5
+ const annotatedRollouts = parsed.filter((r) => r.kind === 'Rollout' &&
6
+ typeof r.metadata.annotations?.[CANARY_ANNOTATION] === 'string');
7
+ if (annotatedRollouts.length === 0)
8
+ return;
9
+ const inputs = await Promise.all(annotatedRollouts.map((rollout) => buildInputForRollout(rollout, desired, deps)));
10
+ const valid = inputs.filter((i) => i !== null);
11
+ if (valid.length === 0)
12
+ return;
13
+ const stateClient = await buildStateClient(annotatedRollouts, deps);
14
+ if (stateClient === null) {
15
+ deps.err(`canary advance skipped: rollout-state KV not yet provisioned (re-run \`k1c apply\` after the standard apply finishes)`);
16
+ return;
17
+ }
18
+ const effects = buildCloudflareEffects({
19
+ cloudflare: deps.providerCtx.cloudflare,
20
+ accountId: deps.providerCtx.accountId,
21
+ managedByLabel: deps.providerCtx.managedByLabel,
22
+ });
23
+ deps.out('');
24
+ deps.out('[canary]');
25
+ const reports = await runCanaryAdvance(valid, {
26
+ state: stateClient,
27
+ effects,
28
+ now: deps.now,
29
+ });
30
+ for (const r of reports) {
31
+ deps.out(` ${r.label}: ${r.previousStatus} → ${r.nextStatus} (weight=${r.nextWeight}%)` +
32
+ (r.actions.length ? `, actions: ${r.actions.map((a) => a.kind).join(', ')}` : ''));
33
+ }
34
+ }
35
+ async function buildInputForRollout(rollout, desired, deps) {
36
+ const ns = rollout.metadata.namespace ?? 'default';
37
+ const name = rollout.metadata.name;
38
+ const stableLabel = `${ns}/${name}--stable`;
39
+ const stable = desired.find((d) => d.resourceType === 'Worker' && d.label === stableLabel);
40
+ if (!stable) {
41
+ deps.err(`canary: stable DesiredResource for ${ns}/${name} not found in lowered output`);
42
+ return null;
43
+ }
44
+ const stableProps = stable.properties;
45
+ const entrypointContent = await readEntrypoint(stableProps, deps);
46
+ return { rollout, stableProps, entrypointContent };
47
+ }
48
+ async function readEntrypoint(props, deps) {
49
+ if (props.entrypointContent !== undefined) {
50
+ return new TextEncoder().encode(props.entrypointContent);
51
+ }
52
+ const reader = deps.providerCtx.readFile ??
53
+ (async (p) => {
54
+ const fs = await import('node:fs/promises');
55
+ return fs.readFile(p);
56
+ });
57
+ return reader(props.entrypoint);
58
+ }
59
+ async function buildStateClient(rollouts, deps) {
60
+ // For v0.1.2, we assume a single dispatch namespace per apply (the common case).
61
+ const dispatch = rollouts[0]?.metadata.annotations?.[CANARY_ANNOTATION];
62
+ if (dispatch === undefined)
63
+ return null;
64
+ const expected = `k1c/rollout-state/${dispatch}`;
65
+ let stateKvId = null;
66
+ for await (const ns of deps.providerCtx.cloudflare.kv.namespaces.list({
67
+ account_id: deps.providerCtx.accountId,
68
+ })) {
69
+ if (ns.title === expected) {
70
+ stateKvId = ns.id;
71
+ break;
72
+ }
73
+ }
74
+ if (stateKvId === null)
75
+ return null;
76
+ const accountId = deps.providerCtx.accountId;
77
+ const cf = deps.providerCtx.cloudflare;
78
+ return {
79
+ async read(key) {
80
+ try {
81
+ const resp = await cf.kv.namespaces.values.get(stateKvId, key, {
82
+ account_id: accountId,
83
+ });
84
+ return await resp.text();
85
+ }
86
+ catch (e) {
87
+ if (e !== null && typeof e === 'object' && e.status === 404) {
88
+ return null;
89
+ }
90
+ throw e;
91
+ }
92
+ },
93
+ async write(key, value) {
94
+ await cf.kv.namespaces.values.update(stateKvId, key, {
95
+ account_id: accountId,
96
+ value,
97
+ });
98
+ },
99
+ };
100
+ }
101
+ //# sourceMappingURL=canary-integration.js.map
@@ -0,0 +1,4 @@
1
+ import type { ApplyReport, Plan } from '../reconciler/types.ts';
2
+ export declare function formatPlan(plan: Plan): string;
3
+ export declare function formatReport(report: ApplyReport): string;
4
+ //# sourceMappingURL=format.d.ts.map
@@ -0,0 +1,44 @@
1
+ const KIND_WIDTH = 7;
2
+ const TYPE_WIDTH = 12;
3
+ const LABEL_WIDTH = 24;
4
+ function pad(s, width) {
5
+ if (s.length >= width)
6
+ return s;
7
+ return s + ' '.repeat(width - s.length);
8
+ }
9
+ export function formatPlan(plan) {
10
+ if (plan.operations.length === 0)
11
+ return '(no changes)';
12
+ const lines = plan.operations.map((op) => {
13
+ const kind = pad(op.kind.toUpperCase(), KIND_WIDTH);
14
+ const rt = pad(op.resourceType, TYPE_WIDTH);
15
+ return ` ${kind} ${rt} ${op.label}`;
16
+ });
17
+ return lines.join('\n');
18
+ }
19
+ export function formatReport(report) {
20
+ const lines = report.results.map((r) => formatResult(r.op, r.status, r.nativeId, r.error));
21
+ lines.push('');
22
+ lines.push(`summary: ${report.succeeded} ok / ${report.failed} failed / ${report.skipped} skipped`);
23
+ return lines.join('\n');
24
+ }
25
+ function formatResult(op, status, nativeId, error) {
26
+ const tag = pad(statusTag(status), 8);
27
+ const kind = pad(op.kind.toUpperCase(), KIND_WIDTH);
28
+ const rt = pad(op.resourceType, TYPE_WIDTH);
29
+ const label = pad(op.label, LABEL_WIDTH);
30
+ const trail = error
31
+ ? `${error.code}: ${error.message}`
32
+ : nativeId !== undefined
33
+ ? `(${nativeId})`
34
+ : '';
35
+ return ` ${tag} ${kind} ${rt} ${label}${trail ? ' ' + trail : ''}`;
36
+ }
37
+ function statusTag(status) {
38
+ if (status === 'succeeded')
39
+ return 'ok';
40
+ if (status === 'failed')
41
+ return 'FAILED';
42
+ return 'skip';
43
+ }
44
+ //# sourceMappingURL=format.js.map
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=main.d.ts.map