@quonfig/cli 0.4.0 → 0.5.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 (47) hide show
  1. package/README.md +157 -75
  2. package/dist/agent/skill-bundle.generated.d.ts +1 -1
  3. package/dist/agent/skill-bundle.generated.js +4 -4
  4. package/dist/codegen/code-generators/base-typescript-generator.d.ts +1 -1
  5. package/dist/codegen/schema-extractor.d.ts +1 -1
  6. package/dist/commands/config-schema.js +9 -2
  7. package/dist/commands/create.d.ts +3 -1
  8. package/dist/commands/create.js +93 -20
  9. package/dist/commands/get.d.ts +1 -0
  10. package/dist/commands/get.js +40 -3
  11. package/dist/commands/info.js +10 -1
  12. package/dist/commands/set-default.d.ts +2 -0
  13. package/dist/commands/set-default.js +70 -18
  14. package/dist/index.d.ts +6 -0
  15. package/dist/index.js +35 -3
  16. package/dist/interactive-prompt.d.ts +18 -0
  17. package/dist/interactive-prompt.js +11 -2
  18. package/dist/migrate/quonfig-target/operators.js +4 -2
  19. package/dist/migrate/quonfig-target/ruleset.d.ts +48 -0
  20. package/dist/migrate/quonfig-target/ruleset.js +132 -8
  21. package/dist/migrate/sources/flagsmith/translate.js +11 -0
  22. package/dist/migrate/sources/launchdarkly/translate.js +19 -16
  23. package/dist/push/git-pack.d.ts +12 -0
  24. package/dist/push/git-pack.js +27 -3
  25. package/dist/push/run-push.d.ts +2 -1
  26. package/dist/push/run-push.js +1 -2
  27. package/dist/ui/get-string.d.ts +18 -1
  28. package/dist/ui/get-string.js +20 -5
  29. package/dist/ui/get-value.d.ts +3 -1
  30. package/dist/ui/get-value.js +7 -7
  31. package/dist/util/clone-and-stack-push.js +8 -3
  32. package/dist/util/encryption.js +11 -20
  33. package/dist/util/git-ops.d.ts +4 -2
  34. package/dist/util/git-ops.js +4 -2
  35. package/dist/util/redact.d.ts +56 -0
  36. package/dist/util/redact.js +98 -0
  37. package/dist/util/value-source.d.ts +30 -0
  38. package/dist/util/value-source.js +81 -0
  39. package/dist/verify/regex-dialect.d.ts +39 -0
  40. package/dist/verify/regex-dialect.js +278 -0
  41. package/dist/verify/standalone.d.ts +3 -1
  42. package/dist/verify/standalone.js +4 -19
  43. package/dist/verify/validate.js +20 -0
  44. package/dist/version.d.ts +1 -1
  45. package/dist/version.js +1 -1
  46. package/oclif.manifest.json +236 -192
  47. package/package.json +18 -16
@@ -0,0 +1,39 @@
1
+ /**
2
+ * "Quonfig regex v1": the regex dialect of PROP_MATCHES and PROP_DOES_NOT_MATCH
3
+ * (qfg-ktkr.2). Spec: project/plans/2026-10-12-regex-write-time-check.md
4
+ * section 2, approved 2026-10-16 (qfg-goi1.10.10). Fixture of record:
5
+ * integration-test-data tests/regex/dialect.yaml (tag v2026.10.22.1; \_, \v
6
+ * and \x80-\xFF rejected per qfg-ktkr.7), which
7
+ * test/verify/regex-dialect.test.ts holds this file to, row by row.
8
+ *
9
+ * The dialect is the intersection of what every SDK's engine (go RE2, node
10
+ * RegExp, python re, ruby Onigmo, java.util.regex, .NET Regex) accepts and
11
+ * reads the same way, plus a ReDoS safety rule. SDKs do not validate; the
12
+ * writers (this check, app-quonfig's copy, and the Git hook built from this
13
+ * repo) are the only gate.
14
+ *
15
+ * No imports: src/verify/ is bundled into the standalone qfg-verify hook.
16
+ */
17
+ export declare const REGEX_V1_LIMITS: {
18
+ readonly maxLength: 256;
19
+ readonly maxRepeat: 1000;
20
+ readonly maxUnboundedQuantifiers: 2;
21
+ };
22
+ /** The canonical docs section the error links to (qfg-ktkr.5 owns the page). */
23
+ export declare const REGEX_V1_DOCS_URL = "https://docs.quonfig.com/docs/explanations/features/rules-and-segmentation#quonfig-regex-v1";
24
+ export type RegexV1Reason = 'backref' | 'bad-repeat' | 'class-syntax' | 'group-extension' | 'inline-flag' | 'lookaround' | 'named-group' | 'nested-quantifier' | 'octal' | 'possessive' | 'syntax' | 'too-long' | 'too-many-unbounded' | 'unicode-shorthand' | 'unsupported-escape';
25
+ /** Fix-it hints, verbatim from the fixture's `hints` map. */
26
+ export declare const REGEX_V1_HINTS: Record<RegexV1Reason, string>;
27
+ export interface RegexV1Violation {
28
+ hint: string;
29
+ reason: RegexV1Reason;
30
+ }
31
+ /**
32
+ * Check one pattern against Quonfig regex v1. Returns null when the pattern is
33
+ * in the dialect, or the reason and fix-it hint for the first violation.
34
+ *
35
+ * Precedence (fixture header): too-long first; then one left-to-right scan
36
+ * reports the first offending construct (an unclosed ( or [ is found at the
37
+ * end); then too-many-unbounded, which needs the whole pattern.
38
+ */
39
+ export declare function checkRegexV1(pattern: string): null | RegexV1Violation;
@@ -0,0 +1,278 @@
1
+ /**
2
+ * "Quonfig regex v1": the regex dialect of PROP_MATCHES and PROP_DOES_NOT_MATCH
3
+ * (qfg-ktkr.2). Spec: project/plans/2026-10-12-regex-write-time-check.md
4
+ * section 2, approved 2026-10-16 (qfg-goi1.10.10). Fixture of record:
5
+ * integration-test-data tests/regex/dialect.yaml (tag v2026.10.22.1; \_, \v
6
+ * and \x80-\xFF rejected per qfg-ktkr.7), which
7
+ * test/verify/regex-dialect.test.ts holds this file to, row by row.
8
+ *
9
+ * The dialect is the intersection of what every SDK's engine (go RE2, node
10
+ * RegExp, python re, ruby Onigmo, java.util.regex, .NET Regex) accepts and
11
+ * reads the same way, plus a ReDoS safety rule. SDKs do not validate; the
12
+ * writers (this check, app-quonfig's copy, and the Git hook built from this
13
+ * repo) are the only gate.
14
+ *
15
+ * No imports: src/verify/ is bundled into the standalone qfg-verify hook.
16
+ */
17
+ export const REGEX_V1_LIMITS = { maxLength: 256, maxRepeat: 1000, maxUnboundedQuantifiers: 2 };
18
+ /** The canonical docs section the error links to (qfg-ktkr.5 owns the page). */
19
+ export const REGEX_V1_DOCS_URL = 'https://docs.quonfig.com/docs/explanations/features/rules-and-segmentation#quonfig-regex-v1';
20
+ /** Fix-it hints, verbatim from the fixture's `hints` map. */
21
+ export const REGEX_V1_HINTS = {
22
+ 'too-long': 'Regex patterns are limited to 256 characters.',
23
+ syntax: 'This is not a valid regular expression. Check for an unbalanced ( ) or [ ], a trailing \\, a reversed range such as [z-a], or a quantifier with nothing before it (a quantifier cannot follow ^, $, |, ( or another quantifier).',
24
+ lookaround: 'Lookahead and lookbehind ((?=, (?!, (?<=, (?<!) are not supported. Rewrite the pattern without them, or split the rule into two criteria.',
25
+ 'named-group': "Named groups ((?<name>...), (?P<name>...), (?'name'...)) are not supported. Use a plain group ( ) or (?: ).",
26
+ 'inline-flag': 'Inline flags such as (?i) are not supported. Spell out both cases with a class, for example [Hh]ello.',
27
+ 'group-extension': 'Only ( ) and (?: ) groups are supported. Atomic groups (?>...), comments (?#...), conditionals and other (? forms are not.',
28
+ backref: 'Backreferences (\\1 to \\9, \\k<name>) are not supported. Repeat the text explicitly instead.',
29
+ octal: 'Octal escapes and \\0 are not supported. Write the character as \\xHH, for example \\x00.',
30
+ possessive: 'Possessive quantifiers (*+, ++, ?+, {n}+) are not supported. Use the plain form (a+) or the lazy form (a+?).',
31
+ 'bad-repeat': 'Write repeat counts as {n}, {n,} or {n,m} with n <= m <= 1000. {,m} is not portable: write {0,m}. Escape a literal brace as \\{.',
32
+ 'class-syntax': 'Inside [...] use only literals, ranges and escapes. POSIX classes such as [:digit:], a nested [, &&, --, ~~ and || are not supported. Escape a literal [ or ] as \\[ or \\], and do not start a class with ].',
33
+ 'unicode-shorthand': '\\d, \\w, \\s, \\b and their negations match different characters in different SDKs. Use [0-9], [A-Za-z0-9_] or [ \\t\\r\\n\\f\\x0B].',
34
+ 'unsupported-escape': 'This escape is not supported. The supported escapes are \\ followed by ASCII punctuation other than _, \\t \\n \\r \\f, and \\xHH with exactly two hex digits from \\x00 to \\x7F. Write _ unescaped instead of \\_, \\x0B instead of \\v, and a character above \\x7F as the character itself.',
35
+ 'nested-quantifier': 'A quantifier cannot apply to a group that contains a quantifier or a |, as in (a+)+ or (a|b)*. Such patterns can take exponential time. Quantify the inner part only, or use a class: [ab]* instead of (a|b)*.',
36
+ 'too-many-unbounded': 'A pattern can have at most 2 unbounded quantifiers (*, +, {n,}). Bound the extra ones with {n,m}, or split the rule into two criteria.',
37
+ };
38
+ // qfg-ktkr.7: no \_ (.NET throws), no \v (java reads a vertical-whitespace
39
+ // class), and \xHH only up to \x7F (ruby rejects \x80-\xFF).
40
+ const ASCII_PUNCT = '!"#$%&\'()*+,-./:;<=>?@[\\]^`{|}~';
41
+ const SIMPLE_ESCAPES = { f: 0x0c, n: 0x0a, r: 0x0d, t: 0x09 };
42
+ const MAX_HEX_ESCAPE = 0x7f;
43
+ const SHORTHANDS = 'dDwWsSbB';
44
+ const HEX = /^[\dA-Fa-f]{2}$/;
45
+ class Violation {
46
+ reason;
47
+ constructor(reason) {
48
+ this.reason = reason;
49
+ }
50
+ }
51
+ const fail = (reason) => {
52
+ throw new Violation(reason);
53
+ };
54
+ /**
55
+ * Check one pattern against Quonfig regex v1. Returns null when the pattern is
56
+ * in the dialect, or the reason and fix-it hint for the first violation.
57
+ *
58
+ * Precedence (fixture header): too-long first; then one left-to-right scan
59
+ * reports the first offending construct (an unclosed ( or [ is found at the
60
+ * end); then too-many-unbounded, which needs the whole pattern.
61
+ */
62
+ export function checkRegexV1(pattern) {
63
+ const cps = [...pattern];
64
+ try {
65
+ if (cps.length > REGEX_V1_LIMITS.maxLength)
66
+ fail('too-long');
67
+ const unbounded = scan(cps);
68
+ if (unbounded > REGEX_V1_LIMITS.maxUnboundedQuantifiers)
69
+ fail('too-many-unbounded');
70
+ return null;
71
+ }
72
+ catch (error) {
73
+ if (error instanceof Violation)
74
+ return { hint: REGEX_V1_HINTS[error.reason], reason: error.reason };
75
+ throw error;
76
+ }
77
+ }
78
+ /** Scan the pattern; returns the unbounded-quantifier count or throws a Violation. */
79
+ function scan(p) {
80
+ // One frame per open group: does it (transitively) contain a quantifier or |?
81
+ const groups = [];
82
+ let last = 'none';
83
+ let lastGroupRisky = false;
84
+ let unbounded = 0;
85
+ let i = 0;
86
+ const markRisky = () => {
87
+ if (groups.length > 0)
88
+ groups[groups.length - 1] = true;
89
+ };
90
+ /** At p[i] === '{': parse a repeat. Returns [end index, unbounded] or throws. */
91
+ const parseRepeat = () => {
92
+ let j = i + 1;
93
+ let n = '';
94
+ while (j < p.length && p[j] >= '0' && p[j] <= '9')
95
+ n += p[j++];
96
+ let m;
97
+ let comma = false;
98
+ if (p[j] === ',') {
99
+ comma = true;
100
+ j++;
101
+ m = '';
102
+ while (j < p.length && p[j] >= '0' && p[j] <= '9')
103
+ m += p[j++];
104
+ }
105
+ if (p[j] !== '}' || n === '')
106
+ fail('bad-repeat'); // {,m}, a bare {, x{foo}
107
+ const lo = Number(n);
108
+ const hi = comma && m !== '' ? Number(m) : undefined;
109
+ if (lo > REGEX_V1_LIMITS.maxRepeat || (hi !== undefined && (hi > REGEX_V1_LIMITS.maxRepeat || hi < lo)))
110
+ fail('bad-repeat');
111
+ return [j, comma && m === ''];
112
+ };
113
+ /** At p[i] === '\\': validate one escape, return its code point (or -1 for a non-literal). */
114
+ const parseEscape = () => {
115
+ const c = p[i + 1];
116
+ if (c === undefined)
117
+ fail('syntax');
118
+ if (c >= '1' && c <= '9')
119
+ fail('backref');
120
+ if (c === 'k')
121
+ fail('backref');
122
+ if (c === '0')
123
+ fail('octal');
124
+ if (SHORTHANDS.includes(c))
125
+ fail('unicode-shorthand');
126
+ if (c in SIMPLE_ESCAPES) {
127
+ i += 2;
128
+ return SIMPLE_ESCAPES[c];
129
+ }
130
+ if (c === 'x') {
131
+ const hex = (p[i + 2] ?? '') + (p[i + 3] ?? '');
132
+ if (!HEX.test(hex))
133
+ fail('unsupported-escape');
134
+ const cp = Number.parseInt(hex, 16);
135
+ if (cp > MAX_HEX_ESCAPE)
136
+ fail('unsupported-escape');
137
+ i += 4;
138
+ return cp;
139
+ }
140
+ if (c.length === 1 && ASCII_PUNCT.includes(c)) {
141
+ i += 2;
142
+ return c.codePointAt(0);
143
+ }
144
+ return fail('unsupported-escape');
145
+ };
146
+ const parseClass = () => {
147
+ // p[i] === '['
148
+ i++;
149
+ if (p[i] === '^')
150
+ i++;
151
+ if (p[i] === ']')
152
+ fail('class-syntax'); // []a] / [^]a] read differently in node
153
+ const start = i;
154
+ let prev; // code point of the previous single literal, for ranges
155
+ for (;;) {
156
+ const c = p[i];
157
+ if (c === undefined)
158
+ fail('syntax'); // unclosed [
159
+ if (c === ']') {
160
+ const body = p.slice(start, i);
161
+ if (body.length >= 2 && body[0] === ':' && body.at(-1) === ':')
162
+ fail('class-syntax'); // [:alpha:]
163
+ i++;
164
+ return;
165
+ }
166
+ if (c === '[')
167
+ fail('class-syntax'); // nested [ and [[:x:]]
168
+ if ('&-~|'.includes(c) && p[i + 1] === c)
169
+ fail('class-syntax'); // && -- ~~ ||
170
+ if (c === '-' && prev !== undefined && p[i + 1] !== ']' && p[i + 1] !== undefined) {
171
+ i++;
172
+ if (p[i] === '[')
173
+ fail('class-syntax');
174
+ const hi = p[i] === '\\' ? parseEscape() : p[i++].codePointAt(0);
175
+ if (hi < prev)
176
+ fail('syntax'); // [z-a]
177
+ prev = undefined;
178
+ continue;
179
+ }
180
+ prev = c === '\\' ? parseEscape() : p[i++].codePointAt(0);
181
+ }
182
+ };
183
+ while (i < p.length) {
184
+ const c = p[i];
185
+ switch (c) {
186
+ case '\\': {
187
+ parseEscape();
188
+ last = 'atom';
189
+ continue;
190
+ }
191
+ case '[': {
192
+ parseClass();
193
+ last = 'atom';
194
+ continue;
195
+ }
196
+ case '(': {
197
+ if (p[i + 1] === '?') {
198
+ const a = p[i + 2];
199
+ const b = p[i + 3];
200
+ if (a === ':') {
201
+ i += 3;
202
+ }
203
+ else if (a === '=' || a === '!' || (a === '<' && (b === '=' || b === '!'))) {
204
+ fail('lookaround');
205
+ }
206
+ else if (a === '<' || a === 'P' || a === "'") {
207
+ fail('named-group');
208
+ }
209
+ else if (a !== undefined && /[A-Za-z-]/.test(a)) {
210
+ fail('inline-flag');
211
+ }
212
+ else if (a === undefined) {
213
+ fail('syntax');
214
+ }
215
+ else {
216
+ fail('group-extension');
217
+ }
218
+ }
219
+ else {
220
+ i++;
221
+ }
222
+ groups.push(false);
223
+ last = 'none';
224
+ continue;
225
+ }
226
+ case ')': {
227
+ if (groups.length === 0)
228
+ fail('syntax');
229
+ lastGroupRisky = groups.pop();
230
+ if (lastGroupRisky)
231
+ markRisky(); // "contains" is transitive
232
+ last = 'group';
233
+ i++;
234
+ continue;
235
+ }
236
+ case '|': {
237
+ markRisky();
238
+ last = 'none';
239
+ break;
240
+ }
241
+ case '^':
242
+ case '$': {
243
+ last = 'none';
244
+ break;
245
+ }
246
+ case '*':
247
+ case '+':
248
+ case '?':
249
+ case '{': {
250
+ let end = i;
251
+ let isUnbounded = c === '*' || c === '+';
252
+ if (c === '{')
253
+ [end, isUnbounded] = parseRepeat();
254
+ if (last === 'none' || last === 'quantifier')
255
+ fail('syntax');
256
+ if (last === 'group' && lastGroupRisky)
257
+ fail('nested-quantifier');
258
+ markRisky();
259
+ if (isUnbounded)
260
+ unbounded++;
261
+ i = end + 1;
262
+ if (p[i] === '?')
263
+ i++; // lazy
264
+ else if (p[i] === '+')
265
+ fail('possessive');
266
+ last = 'quantifier';
267
+ continue;
268
+ }
269
+ default: {
270
+ last = 'atom';
271
+ }
272
+ }
273
+ i++;
274
+ }
275
+ if (groups.length > 0)
276
+ fail('syntax'); // unclosed (
277
+ return unbounded;
278
+ }
@@ -35,7 +35,9 @@ export declare function hookVersion(env?: HookEnv): string;
35
35
  * old oid that is an ancestor of the new oid. A delete and a create are both
36
36
  * rejected (provisioning creates `main` with auto_init; no writer creates it by
37
37
  * push, and the zero-oid door is the only way to delete-then-recreate).
38
- * Other refs are untouched. See plan 2026-09-17-tree-derived-cache 5.6.
38
+ * Other refs are untouched. Enforced on every repo, unconditionally: the
39
+ * QUONFIG_HOOK_FF_ENFORCE rollout switch is gone (qfg-jxml.29), so no env var
40
+ * can turn the rule off. See plan 2026-09-17-tree-derived-cache 5.6.
39
41
  */
40
42
  export declare function checkAppendOnlyMain(refs: readonly RefUpdate[], env: HookEnv, cwd?: string): {
41
43
  bypassReason?: string;
@@ -127,11 +127,13 @@ const REWRITE_OPTION = 'quonfig-rewrite=';
127
127
  * old oid that is an ancestor of the new oid. A delete and a create are both
128
128
  * rejected (provisioning creates `main` with auto_init; no writer creates it by
129
129
  * push, and the zero-oid door is the only way to delete-then-recreate).
130
- * Other refs are untouched. See plan 2026-09-17-tree-derived-cache 5.6.
130
+ * Other refs are untouched. Enforced on every repo, unconditionally: the
131
+ * QUONFIG_HOOK_FF_ENFORCE rollout switch is gone (qfg-jxml.29), so no env var
132
+ * can turn the rule off. See plan 2026-09-17-tree-derived-cache 5.6.
131
133
  */
132
134
  export function checkAppendOnlyMain(refs, env, cwd) {
133
135
  const mainUpdates = refs.filter((ref) => ref.refName === MAIN_REF);
134
- if (mainUpdates.length === 0 || !ffEnforcedForRepo(env))
136
+ if (mainUpdates.length === 0)
135
137
  return { errors: [] };
136
138
  const bypassReason = operatorBypassReason(env);
137
139
  if (bypassReason)
@@ -159,23 +161,6 @@ export function checkAppendOnlyMain(refs, env, cwd) {
159
161
  }
160
162
  return { errors };
161
163
  }
162
- /** Rollout switch: enforce only for repos named in the allowlist; `*` = all. */
163
- function ffEnforcedForRepo(env) {
164
- const allowlist = (env.QUONFIG_HOOK_FF_ENFORCE ?? '')
165
- .split(',')
166
- .map((entry) => entry.trim().toLowerCase())
167
- .filter(Boolean);
168
- if (allowlist.length === 0)
169
- return false;
170
- if (allowlist.includes('*'))
171
- return true;
172
- // Gitea 1.25 hands the hook its repo via these (modules/repository/env.go).
173
- const owner = (env.GITEA_REPO_USER_NAME ?? '').trim().toLowerCase();
174
- const name = (env.GITEA_REPO_NAME ?? '').trim().toLowerCase();
175
- if (!owner || !name)
176
- return false;
177
- return allowlist.includes(`${owner}/${name}`);
178
- }
179
164
  /**
180
165
  * Operator bypass, two keys, BOTH required: the pusher is the operator admin
181
166
  * account (the app pushes as admin too, so identity only excludes customers)
@@ -28,6 +28,7 @@
28
28
  import { z } from 'zod';
29
29
  import { findMapKeyOrderWarnings, navigate, scanOrderedJson } from './map-key-order.js';
30
30
  import { compileSchema, validateAgainstSchema } from './schema-validator.js';
31
+ import { checkRegexV1, REGEX_V1_DOCS_URL } from './regex-dialect.js';
31
32
  import { DURATION_FORMAT_HINT, INT_RANGE_FORMAT_HINT, isValidIntRange, isValidIsoDuration, isValidSemver, isValidTimestamp, parseDoubleValue, parseIntValue, SEMVER_FORMAT_HINT, TIMESTAMP_FORMAT_HINT, } from './value-grammar.js';
32
33
  // Inlined to keep this directory self-contained for the standalone bun-compile
33
34
  // build that runs as the qfg-verify pre-receive hook in app-gitea — that build
@@ -1341,6 +1342,25 @@ function criterionValueShapeIssue(criterion) {
1341
1342
  return { hint: SEMVER_FORMAT_HINT, problem: `needs a version string; got ${vtm.type} ${shown}` };
1342
1343
  return undefined;
1343
1344
  }
1345
+ case 'PROP_DOES_NOT_MATCH':
1346
+ case 'PROP_MATCHES': {
1347
+ // Quonfig regex v1 (qfg-ktkr.2): an out-of-dialect pattern compiles in
1348
+ // some SDKs and not others (or runs in exponential time), so the rule
1349
+ // silently diverges across SDKs. One check here covers qfg verify, the
1350
+ // Git hook and both push engines.
1351
+ if (vtm.type !== 'string' || typeof vtm.value !== 'string')
1352
+ return {
1353
+ hint: `Store the pattern as {"type": "string", "value": "<pattern>"}. See ${REGEX_V1_DOCS_URL}`,
1354
+ problem: `needs a string pattern; got ${vtm.type} ${shown}`,
1355
+ };
1356
+ const violation = checkRegexV1(vtm.value);
1357
+ if (violation)
1358
+ return {
1359
+ hint: `${violation.hint} See ${REGEX_V1_DOCS_URL}`,
1360
+ problem: `pattern ${shown} is outside Quonfig regex v1 (${violation.reason})`,
1361
+ };
1362
+ return undefined;
1363
+ }
1344
1364
  default: {
1345
1365
  return undefined;
1346
1366
  }
package/dist/version.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- declare const _default: "0.4.0";
1
+ declare const _default: "0.5.0";
2
2
  export default _default;
package/dist/version.js CHANGED
@@ -1,2 +1,2 @@
1
1
  // AUTO-GENERATED from package.json by scripts/generate-version.mjs — do not edit.
2
- export default '0.4.0';
2
+ export default '0.5.0';