@object-ui/plugin-form 17.5.0 → 17.7.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 (78) hide show
  1. package/CHANGELOG.md +4841 -0
  2. package/README.md +772 -77
  3. package/dist/DrawerForm.d.ts +30 -2
  4. package/dist/DrawerForm.d.ts.map +1 -1
  5. package/dist/EmbeddableForm.d.ts.map +1 -1
  6. package/dist/FormAnalytics.d.ts.map +1 -1
  7. package/dist/LineItemsPanel.d.ts +6 -3
  8. package/dist/LineItemsPanel.d.ts.map +1 -1
  9. package/dist/MasterDetailForm.d.ts +31 -10
  10. package/dist/MasterDetailForm.d.ts.map +1 -1
  11. package/dist/ModalForm.d.ts +48 -4
  12. package/dist/ModalForm.d.ts.map +1 -1
  13. package/dist/ObjectForm.d.ts +18 -2
  14. package/dist/ObjectForm.d.ts.map +1 -1
  15. package/dist/SplitForm.d.ts +37 -2
  16. package/dist/SplitForm.d.ts.map +1 -1
  17. package/dist/TabbedForm.d.ts +65 -3
  18. package/dist/TabbedForm.d.ts.map +1 -1
  19. package/dist/WizardForm.d.ts +149 -11
  20. package/dist/WizardForm.d.ts.map +1 -1
  21. package/dist/autoLayout.d.ts +12 -3
  22. package/dist/autoLayout.d.ts.map +1 -1
  23. package/dist/closedAffordanceNotice.d.ts +14 -0
  24. package/dist/closedAffordanceNotice.d.ts.map +1 -0
  25. package/dist/customFieldsMerge.d.ts +29 -0
  26. package/dist/customFieldsMerge.d.ts.map +1 -0
  27. package/dist/deriveMasterDetail.d.ts +33 -9
  28. package/dist/deriveMasterDetail.d.ts.map +1 -1
  29. package/dist/fieldGroups.d.ts +176 -0
  30. package/dist/fieldGroups.d.ts.map +1 -1
  31. package/dist/fieldWriteGate.d.ts +116 -0
  32. package/dist/fieldWriteGate.d.ts.map +1 -0
  33. package/dist/flatFields.d.ts +30 -0
  34. package/dist/flatFields.d.ts.map +1 -0
  35. package/dist/formChrome.d.ts +55 -0
  36. package/dist/formChrome.d.ts.map +1 -0
  37. package/dist/index.d.ts +131 -25
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/index.js +3676 -1967
  40. package/dist/index.umd.cjs +1 -1
  41. package/dist/initialRecord.d.ts +72 -0
  42. package/dist/initialRecord.d.ts.map +1 -0
  43. package/dist/loadFailure.d.ts +28 -0
  44. package/dist/loadFailure.d.ts.map +1 -0
  45. package/dist/masterDetailTx.d.ts.map +1 -1
  46. package/dist/occSave.d.ts +6 -1
  47. package/dist/occSave.d.ts.map +1 -1
  48. package/dist/recordInvalidation.d.ts +33 -0
  49. package/dist/recordInvalidation.d.ts.map +1 -0
  50. package/dist/recordReadGate.d.ts +34 -0
  51. package/dist/recordReadGate.d.ts.map +1 -0
  52. package/dist/sanitize.d.ts +187 -5
  53. package/dist/sanitize.d.ts.map +1 -1
  54. package/dist/schemaDefaults.d.ts +51 -30
  55. package/dist/schemaDefaults.d.ts.map +1 -1
  56. package/dist/sectionFields.d.ts +65 -2
  57. package/dist/sectionFields.d.ts.map +1 -1
  58. package/dist/sectionGroups.d.ts +62 -0
  59. package/dist/sectionGroups.d.ts.map +1 -0
  60. package/dist/sectionPredicateDiagnostic.d.ts +38 -0
  61. package/dist/sectionPredicateDiagnostic.d.ts.map +1 -0
  62. package/dist/submitRedirect.d.ts +148 -0
  63. package/dist/submitRedirect.d.ts.map +1 -0
  64. package/dist/submitRedirectNavigation.d.ts +82 -0
  65. package/dist/submitRedirectNavigation.d.ts.map +1 -0
  66. package/dist/submitTarget.d.ts +119 -0
  67. package/dist/submitTarget.d.ts.map +1 -0
  68. package/dist/successBehavior.d.ts +44 -8
  69. package/dist/successBehavior.d.ts.map +1 -1
  70. package/dist/thankYouRedirectCountdown.d.ts +17 -0
  71. package/dist/thankYouRedirectCountdown.d.ts.map +1 -0
  72. package/dist/thankYouRedirectNavigation.d.ts +111 -0
  73. package/dist/thankYouRedirectNavigation.d.ts.map +1 -0
  74. package/dist/uploadGate.d.ts +35 -0
  75. package/dist/uploadGate.d.ts.map +1 -0
  76. package/dist/writePayload.d.ts +68 -0
  77. package/dist/writePayload.d.ts.map +1 -0
  78. package/package.json +13 -12
@@ -0,0 +1 @@
1
+ {"version":3,"file":"thankYouRedirectCountdown.d.ts","sourceRoot":"","sources":["../src/thankYouRedirectCountdown.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AAGH,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAE5E,wFAAwF;AACxF,eAAO,MAAM,0BAA0B,OAAO,CAAC;AAM/C;;;;;;;;;;;GAWG;AACH,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,uBAAuB,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAmClG"}
@@ -0,0 +1,111 @@
1
+ /**
2
+ * ObjectUI
3
+ * Copyright (c) 2024-present ObjectStack Inc.
4
+ *
5
+ * WHO travels to an ACCEPTED `thankYouPage.redirectUrl` — objectui#5112, the
6
+ * mount-blindness of `EmbeddableForm`'s thank-you redirect.
7
+ *
8
+ * `EmbeddableForm.isRedirectUrlSafe` answers WHETHER a destination may be
9
+ * followed. This module answers who performs the navigation, and it is a
10
+ * separate question for the reason objectui#4989 defect 4 established: a rooted
11
+ * path such as `/thanks` handed to a browser-level navigation resolves against
12
+ * the ORIGIN root, so under a host mounted at a sub-path (the framework CLI
13
+ * configures one for every embedded deployment; the console runs at basename
14
+ * `/_console`) a same-origin in-app destination leaves the application. Only the
15
+ * host knows its mount, and it offers its mount-aware navigate through
16
+ * `HostNavigationContext` (`@object-ui/react`, landed by PR #5111).
17
+ *
18
+ * ## Why this is a sibling of `submitRedirectNavigation.ts` and not a call into it
19
+ *
20
+ * That module serves `submitBehavior.url`, whose accepted set objectstack#7496
21
+ * ruled **relative-only**: every destination it ever holds is already in-app, so
22
+ * it hands the seam whatever it was given. This key is deliberately more
23
+ * permissive — same-origin OR a host in the author's `allowedRedirectHosts`
24
+ * allowlist — because a public form's thank-you page is often a real external
25
+ * marketing page. So this key needs an arm the other one does not have, and
26
+ * putting that arm inside the shared hook would make every `submitBehavior`
27
+ * consumer carry a branch its contract can never reach. The acceptance set
28
+ * itself is untouched here (objectui#5112 is explicitly not the card that
29
+ * revisits it, and objectstack#7496's relative-only rule is NOT imported onto
30
+ * this key).
31
+ *
32
+ * ## The arm split, and the invariant it buys
33
+ *
34
+ * - **an app-relative destination** goes through the host's navigate when a
35
+ * host supplied one, and falls back to today's browser navigation when none
36
+ * was supplied;
37
+ * - **anything else** — an external destination admitted by the allowlist, and
38
+ * equally an ABSOLUTE same-origin URL the author spelled out in full — keeps
39
+ * browser-level navigation unconditionally.
40
+ *
41
+ * The second arm is not a conservatism, it is the seam's own declared input
42
+ * contract: `HostNavigationValue.navigate` documents `to` as "an already-resolved,
43
+ * application-relative path, never an absolute URL: a renderer that holds an
44
+ * external destination must not launder it through the host's router". A host
45
+ * navigate is a client-side router transition; an absolute URL is not a path a
46
+ * router can match, and a cross-origin one is not the router's to attempt at
47
+ * all. So the discriminant is the seam's admission rule restated as a predicate
48
+ * on the string, which yields the invariant worth stating out loud and pinning:
49
+ * **the host seam can never be handed a cross-origin URL**, because a relative
50
+ * reference cannot carry an authority (RFC 3986) and therefore always resolves
51
+ * to the base's origin.
52
+ *
53
+ * An absolute same-origin URL landing in the browser arm is a deliberate call,
54
+ * not an oversight. It is the one destination shape the card's two named arms do
55
+ * not cover, and routing it through the seam would mean this package rewriting
56
+ * `https://forms.example/thanks` into `/thanks` for a router that would then
57
+ * place it at `/_console/thanks` — a DIFFERENT address from the one the author
58
+ * wrote. An author who spelled the whole address asked for that address; the
59
+ * mount-blindness this card fixes is the one where the author wrote no origin at
60
+ * all and had no way to express the mount.
61
+ *
62
+ * ## Absent seam is not a degraded mode
63
+ *
64
+ * With no provider the behaviour is byte-for-byte what it was: one
65
+ * `window.location.href` assignment of the authored destination after the
66
+ * declared delay. A host with no router has no basename, so origin-rooted
67
+ * resolution is already correct there.
68
+ */
69
+ /**
70
+ * An accepted thank-you destination plus the delay declared with it, captured at
71
+ * the moment the write was accepted (objectui#5049) — see the state's own
72
+ * comment in `EmbeddableForm`.
73
+ */
74
+ export interface PendingThankYouRedirect {
75
+ url: string;
76
+ delayMs: number;
77
+ }
78
+ /**
79
+ * Is `rawUrl` an application-relative reference — i.e. a destination the host's
80
+ * router can be asked to place?
81
+ *
82
+ * True for `/thanks`, `thanks`, `?ok=1`, `#done`. False for `https://x/y`,
83
+ * `//x/y` (protocol-relative carries an authority), `javascript:…`, and for a
84
+ * same-origin ABSOLUTE URL — see the module docblock for why that last one is
85
+ * deliberate.
86
+ *
87
+ * The question is answered by the URL parser rather than by a hand-written
88
+ * scheme grammar, so leading whitespace and C0 controls are handled exactly the
89
+ * way the parser handles them and there is no second spelling of RFC 3986 in
90
+ * this repo to drift from the first.
91
+ *
92
+ * This predicate does NOT judge whether a destination may be followed —
93
+ * `isRedirectUrlSafe` already did that, and this runs only on values it
94
+ * accepted. It picks who travels, nothing more.
95
+ */
96
+ export declare function isAppRelativeDestination(rawUrl: string): boolean;
97
+ /**
98
+ * Own the delayed leg of an accepted thank-you redirect: wait the declared
99
+ * `delayMs`, then travel to `pending.url` through the host's navigate if one was
100
+ * injected AND the destination is app-relative, else through
101
+ * `window.location.href`.
102
+ *
103
+ * Unmounting, or dropping the destination, cancels the wait — the property
104
+ * objectui#5049 bought, preserved here unchanged: this hook is the effect
105
+ * `EmbeddableForm` used to declare inline, with the seam added and nothing else
106
+ * moved.
107
+ *
108
+ * Pass `null` when nothing is pending.
109
+ */
110
+ export declare function useThankYouRedirectNavigation(pending: PendingThankYouRedirect | null): void;
111
+ //# sourceMappingURL=thankYouRedirectNavigation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"thankYouRedirectNavigation.d.ts","sourceRoot":"","sources":["../src/thankYouRedirectNavigation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmEG;AAKH;;;;GAIG;AACH,MAAM,WAAW,uBAAuB;IACtC,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;CACjB;AAaD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAYhE;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,6BAA6B,CAAC,OAAO,EAAE,uBAAuB,GAAG,IAAI,GAAG,IAAI,CA+B3F"}
@@ -0,0 +1,35 @@
1
+ import { default as React } from 'react';
2
+ import { useUploadingScope } from '@object-ui/fields';
3
+ export interface UploadGate {
4
+ /** True while at least one upload inside this form is still in flight. */
5
+ uploading: boolean;
6
+ /** Mount around the form body so its upload widgets report in. */
7
+ scope: ReturnType<typeof useUploadingScope>;
8
+ /** The Save button's label while `uploading`. */
9
+ busyLabel: string;
10
+ /** Why Save is unavailable — shown, and used as the refusal message. */
11
+ reason: string;
12
+ }
13
+ /**
14
+ * Own the gate for one record form. The host renders its form body inside
15
+ * {@link UploadGateProvider} with the returned `scope`.
16
+ */
17
+ export declare function useUploadGate(): UploadGate;
18
+ /** Re-exported under this package's own name so hosts import one module. */
19
+ export declare function UploadGateProvider({ gate, children, }: {
20
+ gate: UploadGate;
21
+ children: React.ReactNode;
22
+ }): React.ReactElement;
23
+ /**
24
+ * The visible reason. Rendered beside the action row, not as a toast: the
25
+ * explanation has to be readable at the moment the user looks at the control
26
+ * that will not respond, and a toast is gone by then.
27
+ *
28
+ * `role="status"` (polite) rather than `alert`: nothing has gone wrong, an
29
+ * upload is simply still running, and an assertive live region would interrupt
30
+ * a screen-reader user mid-sentence every time a file is picked.
31
+ */
32
+ export declare function UploadInFlightNotice({ gate }: {
33
+ gate: UploadGate;
34
+ }): React.ReactElement | null;
35
+ //# sourceMappingURL=uploadGate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"uploadGate.d.ts","sourceRoot":"","sources":["../src/uploadGate.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,EAAE,iBAAiB,EAA0B,MAAM,mBAAmB,CAAC;AAgD9E,MAAM,WAAW,UAAU;IACzB,0EAA0E;IAC1E,SAAS,EAAE,OAAO,CAAC;IACnB,kEAAkE;IAClE,KAAK,EAAE,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC;IAC5C,iDAAiD;IACjD,SAAS,EAAE,MAAM,CAAC;IAClB,wEAAwE;IACxE,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,wBAAgB,aAAa,IAAI,UAAU,CAS1C;AAED,4EAA4E;AAC5E,wBAAgB,kBAAkB,CAAC,EACjC,IAAI,EACJ,QAAQ,GACT,EAAE;IACD,IAAI,EAAE,UAAU,CAAC;IACjB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B,GAAG,KAAK,CAAC,YAAY,CAErB;AAED;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,EAAE,IAAI,EAAE,EAAE;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,GAAG,KAAK,CAAC,YAAY,GAAG,IAAI,CAO9F"}
@@ -0,0 +1,68 @@
1
+ import { LoadedRecordSnapshot } from './sanitize.js';
2
+ import { FieldWriteGate } from './fieldWriteGate.js';
3
+ /** The facts of a form schema the outbound sequence reads. */
4
+ export interface FormWriteTarget {
5
+ mode?: string;
6
+ objectName: string;
7
+ recordId?: string | number | null;
8
+ /** Inline field definitions (`object-form.customFields`). */
9
+ customFields?: readonly unknown[] | null;
10
+ }
11
+ /** Where the outbound sequence gets its answers from. */
12
+ export interface FormWriteSources {
13
+ /** The object definition the form loaded, or `null` when it has none. */
14
+ objectSchema: {
15
+ fields?: Record<string, Record<string, unknown> | undefined>;
16
+ } | null | undefined;
17
+ /** The caller's field-level write verdict — {@link FieldWriteGate}. */
18
+ canEdit: FieldWriteGate | undefined;
19
+ /** The record this form read for the record it edits (objectui#10156). */
20
+ snapshot: LoadedRecordSnapshot | null | undefined;
21
+ }
22
+ export interface FormWritePayloads {
23
+ /**
24
+ * Every value the form may write, as the form now holds it: stripped of what
25
+ * a form never writes and, on a create, of the fields the producer owns.
26
+ */
27
+ payload: Record<string, unknown>;
28
+ /**
29
+ * What the save sends — on BOTH routes, the host `submitHandler` and the
30
+ * form's own create / OCC-guarded update. On an edit, the fields of
31
+ * {@link FormWritePayloads.payload} that differ from the record the form
32
+ * read; otherwise `payload` itself.
33
+ */
34
+ writePayload: Record<string, unknown>;
35
+ }
36
+ /**
37
+ * The ONE outbound sequence of a record form: what a save writes, built from
38
+ * the values the form collected (objectui#10563).
39
+ *
40
+ * 1. `sanitizeFormData` drops what a form never writes — server-owned columns
41
+ * (objectui#10108), computed / formula / read-only columns, keys the object
42
+ * does not declare, and every field the caller's field-level security
43
+ * refuses (`canEdit`, `80c54122e`).
44
+ * 2. On a create, `omitServerResolvedDefaults` drops an empty field whose
45
+ * runtime default the producer resolves (#4069).
46
+ * 3. On an edit, `dirtyEditPayload` keeps only the fields that differ from the
47
+ * record the form read (objectui#10156), and sends the whole of step 1's
48
+ * result when it cannot settle that.
49
+ *
50
+ * ## Why it is one function
51
+ *
52
+ * `ObjectForm` routes a record save through several layouts, and each layout
53
+ * owns its save handler. When the sequence was spelled inline in each handler,
54
+ * three of them — `TabbedForm`, `SplitForm` and `WizardForm` (which also renders
55
+ * a simple form's mobile `stepper`) — carried none of it and wrote every value
56
+ * they held, `id`, `owner_id`, the formula columns and the field-level-refused
57
+ * ones included. ⛔ Do not spell any step of it beside a call to this function.
58
+ *
59
+ * ## The inline-members rule
60
+ *
61
+ * With a non-empty `customFields` the object definition is NOT handed to steps
62
+ * 1 and 2: an inline member may name a field the object never declares (it is
63
+ * appended after the generated set, objectui#9778), and the unknown-key strip
64
+ * would drop the value the author asked for. The server-owned roster and the
65
+ * field-level verdict still apply, because neither needs a definition.
66
+ */
67
+ export declare function formWritePayload(values: Record<string, unknown>, form: FormWriteTarget, { objectSchema, canEdit, snapshot }: FormWriteSources): FormWritePayloads;
68
+ //# sourceMappingURL=writePayload.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"writePayload.d.ts","sourceRoot":"","sources":["../src/writePayload.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAGL,KAAK,oBAAoB,EAC1B,MAAM,YAAY,CAAC;AAEpB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAEvD,8DAA8D;AAC9D,MAAM,WAAW,eAAe;IAC9B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAClC,6DAA6D;IAC7D,YAAY,CAAC,EAAE,SAAS,OAAO,EAAE,GAAG,IAAI,CAAC;CAC1C;AAED,yDAAyD;AACzD,MAAM,WAAW,gBAAgB;IAC/B,yEAAyE;IACzE,YAAY,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC,CAAA;KAAE,GAAG,IAAI,GAAG,SAAS,CAAC;IAClG,uEAAuE;IACvE,OAAO,EAAE,cAAc,GAAG,SAAS,CAAC;IACpC,0EAA0E;IAC1E,QAAQ,EAAE,oBAAoB,GAAG,IAAI,GAAG,SAAS,CAAC;CACnD;AAED,MAAM,WAAW,iBAAiB;IAChC;;;OAGG;IACH,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC;;;;;OAKG;IACH,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACvC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,IAAI,EAAE,eAAe,EACrB,EAAE,YAAY,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,gBAAgB,GACpD,iBAAiB,CAanB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@object-ui/plugin-form",
3
- "version": "17.5.0",
3
+ "version": "17.7.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "Form plugin for Object UI",
@@ -15,15 +15,15 @@
15
15
  }
16
16
  },
17
17
  "dependencies": {
18
- "@objectstack/spec": "^17.0.0-rc.6",
19
- "lucide-react": "^1.29.0",
20
- "@object-ui/components": "17.5.0",
21
- "@object-ui/core": "17.5.0",
22
- "@object-ui/fields": "17.5.0",
23
- "@object-ui/i18n": "17.5.0",
24
- "@object-ui/permissions": "17.5.0",
25
- "@object-ui/react": "17.5.0",
26
- "@object-ui/types": "17.5.0"
18
+ "@objectstack/spec": "^17.0.0",
19
+ "lucide-react": "^1.43.0",
20
+ "@object-ui/components": "17.7.0",
21
+ "@object-ui/core": "17.7.0",
22
+ "@object-ui/fields": "17.7.0",
23
+ "@object-ui/i18n": "17.7.0",
24
+ "@object-ui/permissions": "17.7.0",
25
+ "@object-ui/react": "17.7.0",
26
+ "@object-ui/types": "17.7.0"
27
27
  },
28
28
  "peerDependencies": {
29
29
  "react": "^18.0.0 || ^19.0.0",
@@ -35,7 +35,8 @@
35
35
  "typescript": "^6.0.3",
36
36
  "vite": "^8.2.1",
37
37
  "vite-plugin-dts": "^5.0.3",
38
- "@object-ui/data-objectstack": "17.5.0"
38
+ "@object-ui/data-objectstack": "17.7.0",
39
+ "@object-ui/test-support": "17.4.0"
39
40
  },
40
41
  "keywords": [
41
42
  "objectui",
@@ -71,7 +72,7 @@
71
72
  ],
72
73
  "scripts": {
73
74
  "build": "vite build",
74
- "test": "vitest run",
75
+ "test": "vitest run --root ../.. packages/plugin-form/",
75
76
  "type-check": "tsc --noEmit && tsc -p tsconfig.test.json",
76
77
  "lint": "eslint ."
77
78
  }