flingit 0.0.70 → 0.0.71

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 (185) hide show
  1. package/dist/cli/commands/cron.d.ts +1 -1
  2. package/dist/cli/commands/cron.d.ts.map +1 -1
  3. package/dist/cli/commands/cron.js +2 -2
  4. package/dist/cli/commands/cron.js.map +1 -1
  5. package/dist/cli/commands/dev.js.map +1 -1
  6. package/dist/cli/commands/email.d.ts +23 -1
  7. package/dist/cli/commands/email.d.ts.map +1 -1
  8. package/dist/cli/commands/email.js +123 -17
  9. package/dist/cli/commands/email.js.map +1 -1
  10. package/dist/cli/commands/feedback.d.ts +1 -1
  11. package/dist/cli/commands/feedback.d.ts.map +1 -1
  12. package/dist/cli/commands/feedback.js.map +1 -1
  13. package/dist/cli/commands/launch.js.map +1 -1
  14. package/dist/cli/commands/login.d.ts +1 -1
  15. package/dist/cli/commands/login.d.ts.map +1 -1
  16. package/dist/cli/commands/login.js +33 -19
  17. package/dist/cli/commands/login.js.map +1 -1
  18. package/dist/cli/commands/logout.js.map +1 -1
  19. package/dist/cli/commands/logs.d.ts +3 -4
  20. package/dist/cli/commands/logs.d.ts.map +1 -1
  21. package/dist/cli/commands/logs.js.map +1 -1
  22. package/dist/cli/commands/plugin.d.ts +2 -2
  23. package/dist/cli/commands/plugin.d.ts.map +1 -1
  24. package/dist/cli/commands/plugin.js +14 -5
  25. package/dist/cli/commands/plugin.js.map +1 -1
  26. package/dist/cli/commands/project.d.ts +20 -1
  27. package/dist/cli/commands/project.d.ts.map +1 -1
  28. package/dist/cli/commands/project.js +41 -17
  29. package/dist/cli/commands/project.js.map +1 -1
  30. package/dist/cli/commands/push.d.ts +2 -1
  31. package/dist/cli/commands/push.d.ts.map +1 -1
  32. package/dist/cli/commands/push.js +249 -87
  33. package/dist/cli/commands/push.js.map +1 -1
  34. package/dist/cli/commands/secret.d.ts.map +1 -1
  35. package/dist/cli/commands/secret.js +18 -3
  36. package/dist/cli/commands/secret.js.map +1 -1
  37. package/dist/cli/commands/signup.js.map +1 -1
  38. package/dist/cli/commands/verify.d.ts.map +1 -1
  39. package/dist/cli/commands/verify.js +8 -3
  40. package/dist/cli/commands/verify.js.map +1 -1
  41. package/dist/cli/commands/whoami.d.ts.map +1 -1
  42. package/dist/cli/commands/whoami.js +11 -2
  43. package/dist/cli/commands/whoami.js.map +1 -1
  44. package/dist/cli/commands/workflow.d.ts +1 -1
  45. package/dist/cli/commands/workflow.d.ts.map +1 -1
  46. package/dist/cli/commands/workflow.js +2 -2
  47. package/dist/cli/commands/workflow.js.map +1 -1
  48. package/dist/cli/deploy/bundler.d.ts +5 -0
  49. package/dist/cli/deploy/bundler.d.ts.map +1 -1
  50. package/dist/cli/deploy/bundler.js +29 -0
  51. package/dist/cli/deploy/bundler.js.map +1 -1
  52. package/dist/cli/index.d.ts +51 -1
  53. package/dist/cli/index.d.ts.map +1 -1
  54. package/dist/cli/index.js +71 -10
  55. package/dist/cli/index.js.map +1 -1
  56. package/dist/cli/utils/cli-io-impl.d.ts.map +1 -1
  57. package/dist/cli/utils/cli-io-impl.js +1 -1
  58. package/dist/cli/utils/cli-io-impl.js.map +1 -1
  59. package/dist/cli/utils/cli-io.d.ts +15 -5
  60. package/dist/cli/utils/cli-io.d.ts.map +1 -1
  61. package/dist/cli/utils/config.d.ts +22 -0
  62. package/dist/cli/utils/config.d.ts.map +1 -1
  63. package/dist/cli/utils/config.js +26 -0
  64. package/dist/cli/utils/config.js.map +1 -1
  65. package/dist/cli/utils/create-project.d.ts +58 -0
  66. package/dist/cli/utils/create-project.d.ts.map +1 -0
  67. package/dist/cli/utils/create-project.js +77 -0
  68. package/dist/cli/utils/create-project.js.map +1 -0
  69. package/dist/cli/utils/environment.d.ts +1 -1
  70. package/dist/cli/utils/environment.d.ts.map +1 -1
  71. package/dist/cli/utils/format.js.map +1 -1
  72. package/dist/cli/utils/node-version.js.map +1 -1
  73. package/dist/cli/utils/project-name.js.map +1 -1
  74. package/dist/cli/utils/prompt-new-project.d.ts +2 -2
  75. package/dist/cli/utils/prompt-new-project.d.ts.map +1 -1
  76. package/dist/cli/utils/prompt-new-project.js.map +1 -1
  77. package/dist/cli/utils/retry.d.ts +20 -2
  78. package/dist/cli/utils/retry.d.ts.map +1 -1
  79. package/dist/cli/utils/retry.js +37 -12
  80. package/dist/cli/utils/retry.js.map +1 -1
  81. package/dist/repo-shared/constants.d.ts.map +1 -1
  82. package/dist/repo-shared/cost.d.ts +13 -0
  83. package/dist/repo-shared/cost.d.ts.map +1 -1
  84. package/dist/repo-shared/cost.js.map +1 -1
  85. package/dist/repo-shared/deploy-protocol.d.ts +34 -0
  86. package/dist/repo-shared/deploy-protocol.d.ts.map +1 -0
  87. package/dist/repo-shared/deploy-protocol.js +168 -0
  88. package/dist/repo-shared/deploy-protocol.js.map +1 -0
  89. package/dist/repo-shared/package-metadata.js +1 -1
  90. package/dist/repo-shared/package-metadata.js.map +1 -1
  91. package/dist/repo-shared/slug-validation.d.ts.map +1 -1
  92. package/dist/repo-shared/slug-validation.js +7 -0
  93. package/dist/repo-shared/slug-validation.js.map +1 -1
  94. package/dist/runtime/auth.d.ts.map +1 -1
  95. package/dist/runtime/auth.js +2 -1
  96. package/dist/runtime/auth.js.map +1 -1
  97. package/dist/runtime/db.d.ts.map +1 -1
  98. package/dist/runtime/discord.d.ts.map +1 -1
  99. package/dist/runtime/email-send.d.ts.map +1 -1
  100. package/dist/runtime/entry.js.map +1 -1
  101. package/dist/runtime/log.d.ts.map +1 -1
  102. package/dist/runtime/log.js.map +1 -1
  103. package/dist/runtime/secrets.d.ts.map +1 -1
  104. package/dist/runtime/slack.d.ts.map +1 -1
  105. package/dist/runtime/storage.js +1 -1
  106. package/dist/runtime/storage.js.map +1 -1
  107. package/dist/shared/non-json-response.d.ts +90 -0
  108. package/dist/shared/non-json-response.d.ts.map +1 -0
  109. package/dist/shared/non-json-response.js +211 -0
  110. package/dist/shared/non-json-response.js.map +1 -0
  111. package/dist/worker-runtime/auth.d.ts.map +1 -1
  112. package/dist/worker-runtime/auth.js +59 -18
  113. package/dist/worker-runtime/auth.js.map +1 -1
  114. package/dist/worker-runtime/d1-event-store.d.ts.map +1 -1
  115. package/dist/worker-runtime/discord.d.ts.map +1 -1
  116. package/dist/worker-runtime/email-send.d.ts.map +1 -1
  117. package/dist/worker-runtime/entry-extract.d.ts +38 -6
  118. package/dist/worker-runtime/entry-extract.d.ts.map +1 -1
  119. package/dist/worker-runtime/entry-extract.js +30 -18
  120. package/dist/worker-runtime/entry-extract.js.map +1 -1
  121. package/dist/worker-runtime/entry.d.ts +1 -0
  122. package/dist/worker-runtime/entry.d.ts.map +1 -1
  123. package/dist/worker-runtime/entry.js +89 -1
  124. package/dist/worker-runtime/entry.js.map +1 -1
  125. package/dist/worker-runtime/index.d.ts.map +1 -1
  126. package/dist/worker-runtime/slack.d.ts.map +1 -1
  127. package/dist/worker-runtime/types.d.ts +2 -0
  128. package/dist/worker-runtime/types.d.ts.map +1 -1
  129. package/dist/workflow/runtime.d.ts +22 -1
  130. package/dist/workflow/runtime.d.ts.map +1 -1
  131. package/dist/workflow/runtime.js +25 -16
  132. package/dist/workflow/runtime.js.map +1 -1
  133. package/node_modules/better-sqlite3/deps/defines.gypi +1 -0
  134. package/node_modules/better-sqlite3/deps/download.sh +2 -1
  135. package/node_modules/better-sqlite3/deps/sqlite3/sqlite3.c +7696 -4295
  136. package/node_modules/better-sqlite3/deps/sqlite3/sqlite3.h +482 -103
  137. package/node_modules/better-sqlite3/deps/sqlite3/sqlite3ext.h +10 -1
  138. package/node_modules/better-sqlite3/package.json +3 -3
  139. package/node_modules/better-sqlite3/src/better_sqlite3.cpp +1 -1
  140. package/node_modules/better-sqlite3/src/util/helpers.cpp +1 -1
  141. package/node_modules/better-sqlite3/src/util/macros.cpp +8 -1
  142. package/node_modules/flingflow/biome.json +15 -0
  143. package/node_modules/flingflow/dist/backoff.js +1 -1
  144. package/node_modules/flingflow/dist/backoff.js.map +1 -1
  145. package/node_modules/flingflow/dist/engine.js +0 -1
  146. package/node_modules/flingflow/dist/engine.js.map +1 -1
  147. package/node_modules/flingflow/dist/store-memory.js +0 -1
  148. package/node_modules/flingflow/dist/store-memory.js.map +1 -1
  149. package/node_modules/flingflow/dist/store-sqlite.js +0 -1
  150. package/node_modules/flingflow/dist/store-sqlite.js.map +1 -1
  151. package/node_modules/flingflow/dist/stress.js +7 -8
  152. package/node_modules/flingflow/dist/stress.js.map +1 -1
  153. package/node_modules/flingflow/dist/transitions.js.map +1 -1
  154. package/node_modules/flingflow/package.json +7 -10
  155. package/node_modules/flingflow/src/backoff.ts +1 -1
  156. package/node_modules/flingflow/src/engine.ts +1 -2
  157. package/node_modules/flingflow/src/store-memory.ts +0 -1
  158. package/node_modules/flingflow/src/store-sqlite.ts +0 -1
  159. package/node_modules/flingflow/src/stress.ts +9 -10
  160. package/node_modules/flingflow/test/engine.test.ts +16 -15
  161. package/node_modules/flingflow/test/recovery.test.ts +5 -4
  162. package/node_modules/flingflow/test/simulation.test.ts +21 -20
  163. package/node_modules/flingflow/test/store-conformance.ts +25 -21
  164. package/node_modules/flingflow/tsconfig.typecheck.json +1 -1
  165. package/node_modules/semver/README.md +22 -6
  166. package/node_modules/semver/bin/semver.js +15 -11
  167. package/node_modules/semver/classes/range.js +24 -4
  168. package/node_modules/semver/classes/semver.js +19 -2
  169. package/node_modules/semver/functions/diff.js +1 -1
  170. package/node_modules/semver/functions/truncate.js +48 -0
  171. package/node_modules/semver/index.js +2 -0
  172. package/node_modules/semver/internal/re.js +3 -3
  173. package/node_modules/semver/package.json +4 -4
  174. package/node_modules/semver/range.bnf +5 -4
  175. package/node_modules/semver/ranges/subset.js +3 -3
  176. package/package.json +36 -32
  177. package/templates/default/dot-claude/skills/fling/.hash +1 -1
  178. package/templates/default/dot-claude/skills/fling/SKILL.md +0 -1
  179. package/templates/default/package.json +9 -9
  180. package/dist/cli/commands/tunnel.d.ts +0 -41
  181. package/dist/cli/commands/tunnel.d.ts.map +0 -1
  182. package/dist/cli/commands/tunnel.js +0 -211
  183. package/dist/cli/commands/tunnel.js.map +0 -1
  184. package/node_modules/flingflow/eslint.config.js +0 -94
  185. package/node_modules/flingflow/tsconfig.lint.json +0 -5
@@ -1,3 +1,4 @@
1
+ import { defined } from "@glideapps/ts-necessities";
1
2
  import { describe, it, expect, beforeEach } from "vitest";
2
3
  import { Engine, UnknownWorkflowError } from "../src/engine.js";
3
4
  import { MemoryEventStore } from "../src/store-memory.js";
@@ -53,12 +54,12 @@ describe("Engine", () => {
53
54
 
54
55
  const events = (await store.getAllEvents(runId, noSim))._unsafeUnwrap();
55
56
  expect(events).toHaveLength(1);
56
- expect(events[0].event_type).toBe("workflow_created");
57
- expect(events[0].payload).toEqual({ workflowName: "single-step", writes: { value: "hello" }, config: { maxAttempts: 5, initialBackoffMS: 1000, backoffMultiplier: 2, maxBackoffMS: 60_000, stepTimeoutMS: 300_000 } });
58
- expect(events[0].seq).toBe(0);
59
- expect(events[0].created_at).toBe(1000);
60
- expect(events[0].run_id).toBe("run-1");
61
- expect(events[0].workflow_id).toBe("wf-single");
57
+ expect(defined(events[0]).event_type).toBe("workflow_created");
58
+ expect(defined(events[0]).payload).toEqual({ workflowName: "single-step", writes: { value: "hello" }, config: { maxAttempts: 5, initialBackoffMS: 1000, backoffMultiplier: 2, maxBackoffMS: 60_000, stepTimeoutMS: 300_000 } });
59
+ expect(defined(events[0]).seq).toBe(0);
60
+ expect(defined(events[0]).created_at).toBe(1000);
61
+ expect(defined(events[0]).run_id).toBe("run-1");
62
+ expect(defined(events[0]).workflow_id).toBe("wf-single");
62
63
  });
63
64
 
64
65
  it("rejects unknown workflow names", async () => {
@@ -109,7 +110,7 @@ describe("Engine", () => {
109
110
 
110
111
  const events = (await store.getAllEvents(runId, noSim))._unsafeUnwrap();
111
112
  const lastEvent = events[events.length - 1];
112
- expect(lastEvent.event_type).toBe("workflow_failed");
113
+ expect(defined(lastEvent).event_type).toBe("workflow_failed");
113
114
  });
114
115
  });
115
116
 
@@ -180,8 +181,8 @@ describe("Engine", () => {
180
181
 
181
182
  // Check attempt numbers
182
183
  const starts = events.filter(e => e.event_type === "step_started");
183
- expect((starts[0].payload as any).attempt).toBe(1);
184
- expect((starts[1].payload as any).attempt).toBe(2);
184
+ expect((defined(starts[0]).payload as any).attempt).toBe(1);
185
+ expect((defined(starts[1]).payload as any).attempt).toBe(2);
185
186
  });
186
187
 
187
188
  it("max attempts exceeded → workflow_failed", async () => {
@@ -236,7 +237,7 @@ describe("Engine", () => {
236
237
 
237
238
  const events = (await store.getAllEvents(runId, noSim))._unsafeUnwrap();
238
239
  for (let i = 0; i < events.length; i++) {
239
- expect(events[i].seq).toBe(i);
240
+ expect(defined(events[i]).seq).toBe(i);
240
241
  }
241
242
  });
242
243
 
@@ -267,7 +268,7 @@ describe("Engine", () => {
267
268
  (await engine.runToCompletion(runId, noSim))._unsafeUnwrap();
268
269
 
269
270
  const events = (await store.getAllEvents(runId, noSim))._unsafeUnwrap();
270
- expect(events[0].created_at).toBe(5000); // workflow_created
271
+ expect(defined(events[0]).created_at).toBe(5000); // workflow_created
271
272
  for (const event of events.slice(1)) {
272
273
  expect(event.created_at).toBe(5100); // all others at clock=5100
273
274
  }
@@ -286,8 +287,8 @@ describe("Engine", () => {
286
287
  };
287
288
 
288
289
  for (let i = 0; i < events.length - 1; i++) {
289
- const from = events[i].event_type;
290
- const to = events[i + 1].event_type;
290
+ const from = defined(events[i]).event_type;
291
+ const to = defined(events[i + 1]).event_type;
291
292
  expect(
292
293
  validTransitions[from],
293
294
  `No valid transitions from ${from}`,
@@ -344,8 +345,8 @@ describe("Engine", () => {
344
345
 
345
346
  const events1 = (await store.getAllEvents(runId1, noSim))._unsafeUnwrap();
346
347
  const events2 = (await store.getAllEvents(runId2, noSim))._unsafeUnwrap();
347
- expect(events1[events1.length - 1].event_type).toBe("workflow_completed");
348
- expect(events2[events2.length - 1].event_type).toBe("workflow_completed");
348
+ expect(defined(events1[events1.length - 1]).event_type).toBe("workflow_completed");
349
+ expect(defined(events2[events2.length - 1]).event_type).toBe("workflow_completed");
349
350
  });
350
351
 
351
352
  it("successive runs of same workflow ID get different run IDs", async () => {
@@ -1,3 +1,4 @@
1
+ import { defined } from "@glideapps/ts-necessities";
1
2
  import { describe, it, expect, beforeEach } from "vitest";
2
3
  import { runRecovery } from "../src/recovery.js";
3
4
  import { Engine } from "../src/engine.js";
@@ -52,9 +53,9 @@ describe("recovery", () => {
52
53
  // step_failed should have been appended
53
54
  const events = (await store.getAllEvents("run-1", noSim))._unsafeUnwrap();
54
55
  expect(events).toHaveLength(3);
55
- expect(events[2].event_type).toBe("step_failed");
56
- expect((events[2].payload as any).reason).toBe("timeout");
57
- expect((events[2].payload as any).retryable).toBe(true);
56
+ expect(defined(events[2]).event_type).toBe("step_failed");
57
+ expect((defined(events[2]).payload as any).reason).toBe("timeout");
58
+ expect((defined(events[2]).payload as any).retryable).toBe(true);
58
59
  });
59
60
 
60
61
  it("detects stuck workflow at workflow_created", async () => {
@@ -138,7 +139,7 @@ describe("recovery", () => {
138
139
 
139
140
  const events = (await store.getAllEvents("run-10", noSim))._unsafeUnwrap();
140
141
  const last = events[events.length - 1];
141
- expect(last.event_type).toBe("workflow_completed");
142
+ expect(defined(last).event_type).toBe("workflow_completed");
142
143
  });
143
144
 
144
145
  it("detects step_completed without next_step as needing workflow_completed", async () => {
@@ -1,3 +1,4 @@
1
+ import { defined } from "@glideapps/ts-necessities";
1
2
  import { describe, it, expect } from "vitest";
2
3
  import {
3
4
  SimulationImpl,
@@ -71,24 +72,24 @@ function makeCheck(): EventStoreCheck {
71
72
 
72
73
  // Ascending seq with no gaps
73
74
  for (let i = 0; i < events.length; i++) {
74
- if (events[i].seq !== i) {
75
+ if (defined(events[i]).seq !== i) {
75
76
  throw new Error(
76
- `Check failed [${runId}]: expected seq ${i}, got ${events[i].seq}`,
77
+ `Check failed [${runId}]: expected seq ${i}, got ${defined(events[i]).seq}`,
77
78
  );
78
79
  }
79
80
  }
80
81
 
81
82
  // First event must be workflow_created
82
- if (events[0].event_type !== "workflow_created") {
83
+ if (defined(events[0]).event_type !== "workflow_created") {
83
84
  throw new Error(
84
- `Check failed [${runId}]: first event is ${events[0].event_type}, expected workflow_created`,
85
+ `Check failed [${runId}]: first event is ${defined(events[0]).event_type}, expected workflow_created`,
85
86
  );
86
87
  }
87
88
 
88
89
  // All transitions are valid
89
90
  for (let i = 0; i < events.length - 1; i++) {
90
- const from = events[i].event_type;
91
- const to = events[i + 1].event_type;
91
+ const from = defined(events[i]).event_type;
92
+ const to = defined(events[i + 1]).event_type;
92
93
  const allowed = validTransitions[from];
93
94
  if (!allowed?.includes(to)) {
94
95
  throw new Error(
@@ -114,15 +115,15 @@ function makeCheck(): EventStoreCheck {
114
115
  function validateEventTrace(events: WorkflowEvent[]): void {
115
116
  // Check ascending seq
116
117
  for (let i = 0; i < events.length; i++) {
117
- if (events[i].seq !== i) {
118
- throw new Error(`Expected seq ${i}, got ${events[i].seq}`);
118
+ if (defined(events[i]).seq !== i) {
119
+ throw new Error(`Expected seq ${i}, got ${defined(events[i]).seq}`);
119
120
  }
120
121
  }
121
122
 
122
123
  // Check valid transitions
123
124
  for (let i = 0; i < events.length - 1; i++) {
124
- const from = events[i].event_type;
125
- const to = events[i + 1].event_type;
125
+ const from = defined(events[i]).event_type;
126
+ const to = defined(events[i + 1]).event_type;
126
127
  const allowed = validTransitions[from];
127
128
  if (!allowed?.includes(to)) {
128
129
  throw new Error(
@@ -134,17 +135,17 @@ function validateEventTrace(events: WorkflowEvent[]): void {
134
135
  // Check terminal
135
136
  const last = events[events.length - 1];
136
137
  if (
137
- last.event_type !== "workflow_completed" &&
138
- last.event_type !== "workflow_failed"
138
+ defined(last).event_type !== "workflow_completed" &&
139
+ defined(last).event_type !== "workflow_failed"
139
140
  ) {
140
- throw new Error(`Workflow not terminal: last event is ${last.event_type}`);
141
+ throw new Error(`Workflow not terminal: last event is ${defined(last).event_type}`);
141
142
  }
142
143
  }
143
144
 
144
145
  function simulationTests(
145
146
  name: string,
146
147
  createStore: () => Promise<EventStore>,
147
- ) {
148
+ ): void {
148
149
  describe(`simulation tests (${name})`, () => {
149
150
  it("concurrent workers: exactly one claims each step", async () => {
150
151
  const store = await createStore();
@@ -188,8 +189,8 @@ function simulationTests(
188
189
  validateEventTrace(events);
189
190
 
190
191
  const last = events[events.length - 1];
191
- expect(last.event_type).toBe("workflow_completed");
192
- expect((last.payload as any).result).toEqual({ finalCount: 3 });
192
+ expect(defined(last).event_type).toBe("workflow_completed");
193
+ expect((defined(last).payload as any).result).toEqual({ finalCount: 3 });
193
194
  });
194
195
 
195
196
  it("concurrent workers across multiple seeds", async () => {
@@ -242,7 +243,7 @@ function simulationTests(
242
243
  validateEventTrace(events);
243
244
 
244
245
  const last = events[events.length - 1];
245
- expect(last.event_type).toBe("workflow_completed");
246
+ expect(defined(last).event_type).toBe("workflow_completed");
246
247
  }
247
248
  });
248
249
 
@@ -315,12 +316,12 @@ function simulationTests(
315
316
 
316
317
  if (events.length > 0) {
317
318
  for (let i = 0; i < events.length; i++) {
318
- expect(events[i].seq).toBe(i);
319
+ expect(defined(events[i]).seq).toBe(i);
319
320
  }
320
321
 
321
322
  for (let i = 0; i < events.length - 1; i++) {
322
- const from = events[i].event_type;
323
- const to = events[i + 1].event_type;
323
+ const from = defined(events[i]).event_type;
324
+ const to = defined(events[i + 1]).event_type;
324
325
  const allowed = validTransitions[from];
325
326
  expect(allowed, `No transitions defined from ${from}`).toBeDefined();
326
327
  expect(
@@ -1,3 +1,4 @@
1
+ import { defined } from "@glideapps/ts-necessities";
1
2
  import { describe, it, expect, beforeEach } from "vitest";
2
3
  import { UniqueConstraintError, WorkflowAlreadyActiveError, WorkflowIdMismatchError, type EventStore } from "../src/store.js";
3
4
  import type { WorkflowConfig, WorkflowEvent } from "../src/types.js";
@@ -5,7 +6,7 @@ import { noSim } from "./fixtures.js";
5
6
 
6
7
  const TEST_CONFIG: WorkflowConfig = { maxAttempts: 5, initialBackoffMS: 1000, backoffMultiplier: 2, maxBackoffMS: 60_000, stepTimeoutMS: 300_000 };
7
8
 
8
- export function storeConformanceTests(name: string, createStore: () => Promise<EventStore>) {
9
+ export function storeConformanceTests(name: string, createStore: () => Promise<EventStore>): void {
9
10
  describe(`${name} conformance`, () => {
10
11
  let store: EventStore;
11
12
 
@@ -43,8 +44,8 @@ export function storeConformanceTests(name: string, createStore: () => Promise<E
43
44
  }, noSim))._unsafeUnwrap();
44
45
 
45
46
  const last = (await store.getLastEvent("run-1", noSim))._unsafeUnwrap();
46
- expect(last!.seq).toBe(1);
47
- expect(last!.event_type).toBe("step_started");
47
+ expect(last?.seq).toBe(1);
48
+ expect(last?.event_type).toBe("step_started");
48
49
  });
49
50
 
50
51
  it("returns UniqueConstraintError on duplicate (run_id, seq)", async () => {
@@ -88,7 +89,7 @@ export function storeConformanceTests(name: string, createStore: () => Promise<E
88
89
  // seq=1 should NOT be present since batch is atomic
89
90
  const all = (await store.getAllEvents("run-1", noSim))._unsafeUnwrap();
90
91
  expect(all).toHaveLength(1);
91
- expect(all[0].seq).toBe(0);
92
+ expect(defined(all[0]).seq).toBe(0);
92
93
  });
93
94
 
94
95
  it("getEvent returns specific event", async () => {
@@ -98,9 +99,12 @@ export function storeConformanceTests(name: string, createStore: () => Promise<E
98
99
  }, noSim))._unsafeUnwrap();
99
100
 
100
101
  const event = (await store.getEvent("run-1", 0, noSim))._unsafeUnwrap();
101
- expect(event).not.toBeNull();
102
- expect(event!.event_type).toBe("workflow_created");
103
- expect((event!.payload as any).writes).toEqual({ x: 1 });
102
+ // `defined()` only rules out undefined, and getEvent returns
103
+ // `WorkflowEvent | null` — narrow explicitly so the assertions below run
104
+ // against a real event rather than silently against null.
105
+ if (event === null) throw new Error("getEvent returned null for seq 0");
106
+ expect(event.event_type).toBe("workflow_created");
107
+ expect((event.payload as any).writes).toEqual({ x: 1 });
104
108
  });
105
109
 
106
110
  it("getEvent returns null for missing seq", async () => {
@@ -119,8 +123,8 @@ export function storeConformanceTests(name: string, createStore: () => Promise<E
119
123
 
120
124
  const completed = (await store.getCompletedEvents("run-1", noSim))._unsafeUnwrap();
121
125
  expect(completed).toHaveLength(2);
122
- expect(completed[0].seq).toBe(4); // newest first
123
- expect(completed[1].seq).toBe(2);
126
+ expect(defined(completed[0]).seq).toBe(4); // newest first
127
+ expect(defined(completed[1]).seq).toBe(2);
124
128
  });
125
129
 
126
130
  it("getAllEvents returns events in seq ASC order", async () => {
@@ -131,8 +135,8 @@ export function storeConformanceTests(name: string, createStore: () => Promise<E
131
135
 
132
136
  const all = (await store.getAllEvents("run-1", noSim))._unsafeUnwrap();
133
137
  expect(all).toHaveLength(2);
134
- expect(all[0].seq).toBe(0);
135
- expect(all[1].seq).toBe(1);
138
+ expect(defined(all[0]).seq).toBe(0);
139
+ expect(defined(all[1]).seq).toBe(1);
136
140
  });
137
141
 
138
142
  it("getActiveWorkflows excludes terminal workflows", async () => {
@@ -160,7 +164,7 @@ export function storeConformanceTests(name: string, createStore: () => Promise<E
160
164
 
161
165
  const active = (await store.getActiveWorkflows(noSim))._unsafeUnwrap();
162
166
  expect(active).toHaveLength(1);
163
- expect(active[0].run_id).toBe("run-active");
167
+ expect(defined(active[0]).run_id).toBe("run-active");
164
168
  });
165
169
 
166
170
  it("JSON round-trips payloads correctly", async () => {
@@ -175,8 +179,8 @@ export function storeConformanceTests(name: string, createStore: () => Promise<E
175
179
  }, noSim))._unsafeUnwrap();
176
180
 
177
181
  const retrieved = (await store.getEvent("run-json", 0, noSim))._unsafeUnwrap();
178
- expect(retrieved!.payload).toEqual(payload);
179
- expect(retrieved!.payload).not.toBe(payload);
182
+ expect(retrieved?.payload).toEqual(payload);
183
+ expect(retrieved?.payload).not.toBe(payload);
180
184
  });
181
185
 
182
186
  it("isolates events between different runs", async () => {
@@ -193,8 +197,8 @@ export function storeConformanceTests(name: string, createStore: () => Promise<E
193
197
  const allB = (await store.getAllEvents("run-b", noSim))._unsafeUnwrap();
194
198
  expect(allA).toHaveLength(1);
195
199
  expect(allB).toHaveLength(1);
196
- expect((allA[0].payload as any).workflowName).toBe("a");
197
- expect((allB[0].payload as any).workflowName).toBe("b");
200
+ expect((defined(allA[0]).payload as any).workflowName).toBe("a");
201
+ expect((defined(allB[0]).payload as any).workflowName).toBe("b");
198
202
  });
199
203
 
200
204
  // --- appendIfNoActiveWorkflow tests ---
@@ -268,9 +272,9 @@ export function storeConformanceTests(name: string, createStore: () => Promise<E
268
272
 
269
273
  const runs = (await store.getRecentRuns(10, noSim))._unsafeUnwrap();
270
274
  expect(runs).toHaveLength(3);
271
- expect(runs[0].run_id).toBe("run-new");
272
- expect(runs[1].run_id).toBe("run-mid");
273
- expect(runs[2].run_id).toBe("run-old");
275
+ expect(defined(runs[0]).run_id).toBe("run-new");
276
+ expect(defined(runs[1]).run_id).toBe("run-mid");
277
+ expect(defined(runs[2]).run_id).toBe("run-old");
274
278
  // All should be seq=0 (workflow_created)
275
279
  for (const run of runs) {
276
280
  expect(run.seq).toBe(0);
@@ -294,8 +298,8 @@ export function storeConformanceTests(name: string, createStore: () => Promise<E
294
298
 
295
299
  const runs = (await store.getRecentRuns(2, noSim))._unsafeUnwrap();
296
300
  expect(runs).toHaveLength(2);
297
- expect(runs[0].run_id).toBe("run-3");
298
- expect(runs[1].run_id).toBe("run-2");
301
+ expect(defined(runs[0]).run_id).toBe("run-3");
302
+ expect(defined(runs[1]).run_id).toBe("run-2");
299
303
  });
300
304
 
301
305
  it("getRecentRuns returns empty array when no runs exist", async () => {
@@ -15,6 +15,6 @@
15
15
  "allowUnusedLabels": false,
16
16
  "allowUnreachableCode": false
17
17
  },
18
- "include": ["src/**/*.ts"],
18
+ "include": ["src/**/*.ts", "test/**/*.ts"],
19
19
  "exclude": ["node_modules", "dist"]
20
20
  }
@@ -56,6 +56,7 @@ const semverCompareLoose = require('semver/functions/compare-loose')
56
56
  const semverCompareBuild = require('semver/functions/compare-build')
57
57
  const semverSort = require('semver/functions/sort')
58
58
  const semverRsort = require('semver/functions/rsort')
59
+ const semverTruncate = require('semver/functions/truncate')
59
60
 
60
61
  // low-level comparators between versions
61
62
  const semverGt = require('semver/functions/gt')
@@ -110,8 +111,9 @@ Options:
110
111
  -l --loose
111
112
  Interpret versions and ranges loosely
112
113
 
113
- -n <0|1>
114
- This is the base to be used for the prerelease identifier.
114
+ -n <0|1|false>
115
+ Base number for prerelease identifier (default: 0).
116
+ Use false to omit the number altogether.
115
117
 
116
118
  -p --include-prerelease
117
119
  Always include prerelease versions in range matching
@@ -398,12 +400,19 @@ nr ::= '0' | ['1'-'9'] ( ['0'-'9'] ) *
398
400
  tilde ::= '~' partial
399
401
  caret ::= '^' partial
400
402
  qualifier ::= ( '-' pre )? ( '+' build )?
401
- pre ::= parts
402
- build ::= parts
403
- parts ::= part ( '.' part ) *
404
- part ::= nr | [-0-9A-Za-z]+
403
+ pre ::= prepart ( '.' prepart ) *
404
+ prepart ::= nr | alphanumid
405
+ build ::= buildid ( '.' buildid ) *
406
+ alphanumid ::= ( ['0'-'9'] ) * [-A-Za-z] [-0-9A-Za-z] *
407
+ buildid ::= [-0-9A-Za-z]+
405
408
  ```
406
409
 
410
+ Note: Prerelease identifiers (`pre`) use `nr` for numeric parts, which
411
+ disallows leading zeros (e.g., `1.2.3-00` is invalid). Build metadata
412
+ identifiers (`build`) allow any alphanumeric string including leading
413
+ zeros (e.g., `1.2.3+00` is valid). This matches the
414
+ [SemVer 2.0.0 specification](https://semver.org/#spec-item-9).
415
+
407
416
  ## Functions
408
417
 
409
418
  All methods and classes take a final `options` object argument. All
@@ -448,6 +457,12 @@ strings that they parse.
448
457
  or comparators intersect.
449
458
  * `parse(v)`: Attempt to parse a string as a semantic version, returning either
450
459
  a `SemVer` object or `null`.
460
+ * `truncate(v, releaseType)`: Return the version with components _lower_
461
+ than `releaseType` dropped off, e.g.:
462
+ * `major` removes build & prerelease info and sets minor & patch to 0.
463
+ * `minor` removes build & prerelease info, and sets patch to 0
464
+ * `patch` removes build & prerelease info
465
+ * All prerelease types remove build info only
451
466
 
452
467
  ### Comparison
453
468
 
@@ -649,6 +664,7 @@ The following modules are available:
649
664
  * `require('semver/functions/rsort')`
650
665
  * `require('semver/functions/satisfies')`
651
666
  * `require('semver/functions/sort')`
667
+ * `require('semver/functions/truncate')`
652
668
  * `require('semver/functions/valid')`
653
669
  * `require('semver/ranges/gtr')`
654
670
  * `require('semver/ranges/intersects')`
@@ -46,6 +46,7 @@ const main = () => {
46
46
  a = a.slice(0, indexOfEqualSign)
47
47
  argv.unshift(value)
48
48
  }
49
+
49
50
  switch (a) {
50
51
  case '-rv': case '-rev': case '--rev': case '--reverse':
51
52
  reverse = true
@@ -60,15 +61,10 @@ const main = () => {
60
61
  versions.push(argv.shift())
61
62
  break
62
63
  case '-i': case '--inc': case '--increment':
63
- switch (argv[0]) {
64
- case 'major': case 'minor': case 'patch': case 'prerelease':
65
- case 'premajor': case 'preminor': case 'prepatch':
66
- case 'release':
67
- inc = argv.shift()
68
- break
69
- default:
70
- inc = 'patch'
71
- break
64
+ if (semver.RELEASE_TYPES.includes(argv[0]) || (argv[0] === 'release')) {
65
+ inc = { value: argv.shift(), maybeErrantValue: null, option: a }
66
+ } else {
67
+ inc = { value: 'patch', maybeErrantValue: argv[0], option: a }
72
68
  }
73
69
  break
74
70
  case '--preid':
@@ -102,10 +98,18 @@ const main = () => {
102
98
 
103
99
  options = parseOptions({ loose, includePrerelease, rtl })
104
100
 
101
+ if (
102
+ inc &&
103
+ versions.includes(inc.maybeErrantValue) &&
104
+ !semver.valid(inc.maybeErrantValue, options)
105
+ ) {
106
+ console.warn(`Invalid value for ${inc.option}; defaulting to 'patch'. This may become a failure in future major versions.`)
107
+ }
108
+
105
109
  versions = versions.map((v) => {
106
110
  return coerce ? (semver.coerce(v, options) || { version: v }).version : v
107
111
  }).filter((v) => {
108
- return semver.valid(v)
112
+ return semver.valid(v, options)
109
113
  })
110
114
  if (!versions.length) {
111
115
  return fail()
@@ -125,7 +129,7 @@ const main = () => {
125
129
  versions
126
130
  .sort((a, b) => semver[reverse ? 'rcompare' : 'compare'](a, b, options))
127
131
  .map(v => semver.clean(v, options))
128
- .map(v => inc ? semver.inc(v, inc, options, identifier, identifierBase) : v)
132
+ .map(v => inc ? semver.inc(v, inc.value, options, identifier, identifierBase) : v)
129
133
  .forEach(v => console.log(v))
130
134
  }
131
135
 
@@ -98,6 +98,9 @@ class Range {
98
98
  }
99
99
 
100
100
  parseRange (range) {
101
+ // strip build metadata so it can't bleed into the version
102
+ range = range.replace(BUILDSTRIPRE, '')
103
+
101
104
  // memoize range parsing for performance.
102
105
  // this is a very hot path, and fully deterministic.
103
106
  const memoOpts =
@@ -223,6 +226,7 @@ const debug = require('../internal/debug')
223
226
  const SemVer = require('./semver')
224
227
  const {
225
228
  safeRe: re,
229
+ src,
226
230
  t,
227
231
  comparatorTrimReplace,
228
232
  tildeTrimReplace,
@@ -230,6 +234,9 @@ const {
230
234
  } = require('../internal/re')
231
235
  const { FLAG_INCLUDE_PRERELEASE, FLAG_LOOSE } = require('../internal/constants')
232
236
 
237
+ // unbounded global build-metadata stripper used by parseRange
238
+ const BUILDSTRIPRE = new RegExp(src[t.BUILD], 'g')
239
+
233
240
  const isNullSet = c => c.value === '<0.0.0-0'
234
241
  const isAny = c => c.value === ''
235
242
 
@@ -270,6 +277,11 @@ const parseComparator = (comp, options) => {
270
277
 
271
278
  const isX = id => !id || id.toLowerCase() === 'x' || id === '*'
272
279
 
280
+ const invalidXRangeOrder = (M, m, p) => (
281
+ (isX(M) && !isX(m)) ||
282
+ (isX(m) && p && !isX(p))
283
+ )
284
+
273
285
  // ~, ~> --> * (any, kinda silly)
274
286
  // ~2, ~2.x, ~2.x.x, ~>2, ~>2.x ~>2.x.x --> >=2.0.0 <3.0.0-0
275
287
  // ~2.0, ~2.0.x, ~>2.0, ~>2.0.x --> >=2.0.0 <2.1.0-0
@@ -287,6 +299,10 @@ const replaceTildes = (comp, options) => {
287
299
 
288
300
  const replaceTilde = (comp, options) => {
289
301
  const r = options.loose ? re[t.TILDELOOSE] : re[t.TILDE]
302
+ // if we're including prereleases in the match, then the lower bound is
303
+ // -0, the lowest possible prerelease value, just like x-ranges and carets.
304
+ // this keeps `~1.2` equivalent to the `1.2.x` x-range it's documented as.
305
+ const z = options.includePrerelease ? '-0' : ''
290
306
  return comp.replace(r, (_, M, m, p, pr) => {
291
307
  debug('tilde', comp, _, M, m, p, pr)
292
308
  let ret
@@ -294,10 +310,10 @@ const replaceTilde = (comp, options) => {
294
310
  if (isX(M)) {
295
311
  ret = ''
296
312
  } else if (isX(m)) {
297
- ret = `>=${M}.0.0 <${+M + 1}.0.0-0`
313
+ ret = `>=${M}.0.0${z} <${+M + 1}.0.0-0`
298
314
  } else if (isX(p)) {
299
315
  // ~1.2 == >=1.2.0 <1.3.0-0
300
- ret = `>=${M}.${m}.0 <${M}.${+m + 1}.0-0`
316
+ ret = `>=${M}.${m}.0${z} <${M}.${+m + 1}.0-0`
301
317
  } else if (pr) {
302
318
  debug('replaceTilde pr', pr)
303
319
  ret = `>=${M}.${m}.${p}-${pr
@@ -366,10 +382,10 @@ const replaceCaret = (comp, options) => {
366
382
  if (M === '0') {
367
383
  if (m === '0') {
368
384
  ret = `>=${M}.${m}.${p
369
- }${z} <${M}.${m}.${+p + 1}-0`
385
+ } <${M}.${m}.${+p + 1}-0`
370
386
  } else {
371
387
  ret = `>=${M}.${m}.${p
372
- }${z} <${M}.${+m + 1}.0-0`
388
+ } <${M}.${+m + 1}.0-0`
373
389
  }
374
390
  } else {
375
391
  ret = `>=${M}.${m}.${p
@@ -395,6 +411,10 @@ const replaceXRange = (comp, options) => {
395
411
  const r = options.loose ? re[t.XRANGELOOSE] : re[t.XRANGE]
396
412
  return comp.replace(r, (ret, gtlt, M, m, p, pr) => {
397
413
  debug('xRange', comp, ret, gtlt, M, m, p, pr)
414
+ if (invalidXRangeOrder(M, m, p)) {
415
+ return comp
416
+ }
417
+
398
418
  const xM = isX(M)
399
419
  const xm = xM || isX(m)
400
420
  const xp = xm || isX(p)
@@ -6,6 +6,22 @@ const { safeRe: re, t } = require('../internal/re')
6
6
 
7
7
  const parseOptions = require('../internal/parse-options')
8
8
  const { compareIdentifiers } = require('../internal/identifiers')
9
+
10
+ const isPrereleaseIdentifier = (prerelease, identifier) => {
11
+ const identifiers = identifier.split('.')
12
+ if (identifiers.length > prerelease.length) {
13
+ return false
14
+ }
15
+
16
+ for (let i = 0; i < identifiers.length; i++) {
17
+ if (compareIdentifiers(prerelease[i], identifiers[i]) !== 0) {
18
+ return false
19
+ }
20
+ }
21
+
22
+ return true
23
+ }
24
+
9
25
  class SemVer {
10
26
  constructor (version, options) {
11
27
  options = parseOptions(options)
@@ -309,8 +325,9 @@ class SemVer {
309
325
  if (identifierBase === false) {
310
326
  prerelease = [identifier]
311
327
  }
312
- if (compareIdentifiers(this.prerelease[0], identifier) === 0) {
313
- if (isNaN(this.prerelease[1])) {
328
+ if (isPrereleaseIdentifier(this.prerelease, identifier)) {
329
+ const prereleaseBase = this.prerelease[identifier.split('.').length]
330
+ if (isNaN(prereleaseBase)) {
314
331
  this.prerelease = prerelease
315
332
  }
316
333
  } else {
@@ -53,7 +53,7 @@ const diff = (version1, version2) => {
53
53
  return prefix + 'patch'
54
54
  }
55
55
 
56
- // high and low are preleases
56
+ // high and low are prereleases
57
57
  return 'prerelease'
58
58
  }
59
59
 
@@ -0,0 +1,48 @@
1
+ 'use strict'
2
+
3
+ const parse = require('./parse')
4
+ const constants = require('../internal/constants')
5
+ const SemVer = require('../classes/semver')
6
+
7
+ const truncate = (version, truncation, options) => {
8
+ if (!constants.RELEASE_TYPES.includes(truncation)) {
9
+ return null
10
+ }
11
+
12
+ const clonedVersion = cloneInputVersion(version, options)
13
+ return clonedVersion && doTruncation(clonedVersion, truncation)
14
+ }
15
+
16
+ const cloneInputVersion = (version, options) => {
17
+ const versionStringToParse = (
18
+ version instanceof SemVer ? version.version : version
19
+ )
20
+
21
+ return parse(versionStringToParse, options)
22
+ }
23
+
24
+ const doTruncation = (version, truncation) => {
25
+ if (isPrerelease(truncation)) {
26
+ return version.version
27
+ }
28
+
29
+ version.prerelease = []
30
+
31
+ switch (truncation) {
32
+ case 'major':
33
+ version.minor = 0
34
+ version.patch = 0
35
+ break
36
+ case 'minor':
37
+ version.patch = 0
38
+ break
39
+ }
40
+
41
+ return version.format()
42
+ }
43
+
44
+ const isPrerelease = (type) => {
45
+ return type.startsWith('pre')
46
+ }
47
+
48
+ module.exports = truncate