polen 0.10.0-next.3 → 0.10.0-next.4
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.
- package/README.md +49 -376
- package/build/api/api.d.ts +1 -0
- package/build/api/api.d.ts.map +1 -1
- package/build/api/api.js +1 -0
- package/build/api/api.js.map +1 -1
- package/build/api/static/index.d.ts +2 -0
- package/build/api/static/index.d.ts.map +1 -0
- package/build/api/static/index.js +2 -0
- package/build/api/static/index.js.map +1 -0
- package/build/api/static/manifest.d.ts +18 -0
- package/build/api/static/manifest.d.ts.map +1 -0
- package/build/api/static/manifest.js +13 -0
- package/build/api/static/manifest.js.map +1 -0
- package/build/api/static/rebase.d.ts +14 -0
- package/build/api/static/rebase.d.ts.map +1 -0
- package/build/api/static/rebase.js +110 -0
- package/build/api/static/rebase.js.map +1 -0
- package/build/api/static/static.d.ts +3 -0
- package/build/api/static/static.d.ts.map +1 -0
- package/build/api/static/static.js +3 -0
- package/build/api/static/static.js.map +1 -0
- package/build/api/vite/plugins/build.d.ts.map +1 -1
- package/build/api/vite/plugins/build.js +22 -1
- package/build/api/vite/plugins/build.js.map +1 -1
- package/build/cli/commands/static/$default.d.ts +3 -0
- package/build/cli/commands/static/$default.d.ts.map +1 -0
- package/build/cli/commands/static/$default.js +38 -0
- package/build/cli/commands/static/$default.js.map +1 -0
- package/build/cli/commands/static/rebase.d.ts +2 -0
- package/build/cli/commands/static/rebase.d.ts.map +1 -0
- package/build/cli/commands/static/rebase.js +26 -0
- package/build/cli/commands/static/rebase.js.map +1 -0
- package/build/cli/commands/static.d.ts +3 -0
- package/build/cli/commands/static.d.ts.map +1 -0
- package/build/cli/commands/static.js +5 -0
- package/build/cli/commands/static.js.map +1 -0
- package/build/lib/demos/builder.d.ts +83 -0
- package/build/lib/demos/builder.d.ts.map +1 -0
- package/build/lib/demos/builder.js +237 -0
- package/build/lib/demos/builder.js.map +1 -0
- package/build/lib/demos/config-schema.d.ts +243 -0
- package/build/lib/demos/config-schema.d.ts.map +1 -0
- package/build/lib/demos/config-schema.js +52 -0
- package/build/lib/demos/config-schema.js.map +1 -0
- package/build/lib/demos/config.d.ts +40 -0
- package/build/lib/demos/config.d.ts.map +1 -0
- package/build/lib/demos/config.js +180 -0
- package/build/lib/demos/config.js.map +1 -0
- package/build/lib/demos/index.d.ts +9 -0
- package/build/lib/demos/index.d.ts.map +1 -0
- package/build/lib/demos/index.js +8 -0
- package/build/lib/demos/index.js.map +1 -0
- package/build/lib/demos/ui/components.d.ts +33 -0
- package/build/lib/demos/ui/components.d.ts.map +1 -0
- package/build/lib/demos/ui/components.js +699 -0
- package/build/lib/demos/ui/components.js.map +1 -0
- package/build/lib/demos/ui/data-collector.d.ts +88 -0
- package/build/lib/demos/ui/data-collector.d.ts.map +1 -0
- package/build/lib/demos/ui/data-collector.js +174 -0
- package/build/lib/demos/ui/data-collector.js.map +1 -0
- package/build/lib/demos/ui/landing-page-cli.d.ts +3 -0
- package/build/lib/demos/ui/landing-page-cli.d.ts.map +1 -0
- package/build/lib/demos/ui/landing-page-cli.js +21 -0
- package/build/lib/demos/ui/landing-page-cli.js.map +1 -0
- package/build/lib/demos/ui/landing-page.d.ts +32 -0
- package/build/lib/demos/ui/landing-page.d.ts.map +1 -0
- package/build/lib/demos/ui/landing-page.js +83 -0
- package/build/lib/demos/ui/landing-page.js.map +1 -0
- package/build/lib/demos/ui/page-renderer.d.ts +26 -0
- package/build/lib/demos/ui/page-renderer.d.ts.map +1 -0
- package/build/lib/demos/ui/page-renderer.js +104 -0
- package/build/lib/demos/ui/page-renderer.js.map +1 -0
- package/build/lib/demos/utils.d.ts +14 -0
- package/build/lib/demos/utils.d.ts.map +1 -0
- package/build/lib/demos/utils.js +37 -0
- package/build/lib/demos/utils.js.map +1 -0
- package/build/lib/deployment/$$.d.ts +3 -0
- package/build/lib/deployment/$$.d.ts.map +1 -0
- package/build/lib/deployment/$$.js +3 -0
- package/build/lib/deployment/$$.js.map +1 -0
- package/build/lib/deployment/$.d.ts +2 -0
- package/build/lib/deployment/$.d.ts.map +1 -0
- package/build/lib/deployment/$.js +2 -0
- package/build/lib/deployment/$.js.map +1 -0
- package/build/lib/deployment/metadata.d.ts +32 -0
- package/build/lib/deployment/metadata.d.ts.map +1 -0
- package/build/lib/deployment/metadata.js +37 -0
- package/build/lib/deployment/metadata.js.map +1 -0
- package/build/lib/deployment/path-manager.d.ts +41 -0
- package/build/lib/deployment/path-manager.d.ts.map +1 -0
- package/build/lib/deployment/path-manager.js +157 -0
- package/build/lib/deployment/path-manager.js.map +1 -0
- package/build/lib/github-actions/git-controller.d.ts +50 -0
- package/build/lib/github-actions/git-controller.d.ts.map +1 -0
- package/build/lib/github-actions/git-controller.js +90 -0
- package/build/lib/github-actions/git-controller.js.map +1 -0
- package/build/lib/github-actions/github-actions.d.ts +7 -0
- package/build/lib/github-actions/github-actions.d.ts.map +1 -0
- package/build/lib/github-actions/github-actions.js +7 -0
- package/build/lib/github-actions/github-actions.js.map +1 -0
- package/build/lib/github-actions/index.d.ts +2 -0
- package/build/lib/github-actions/index.d.ts.map +1 -0
- package/build/lib/github-actions/index.js +2 -0
- package/build/lib/github-actions/index.js.map +1 -0
- package/build/lib/github-actions/lib/get-pr-deployments.d.ts +12 -0
- package/build/lib/github-actions/lib/get-pr-deployments.d.ts.map +1 -0
- package/build/lib/github-actions/lib/get-pr-deployments.js +51 -0
- package/build/lib/github-actions/lib/get-pr-deployments.js.map +1 -0
- package/build/lib/github-actions/pr-controller.d.ts +39 -0
- package/build/lib/github-actions/pr-controller.d.ts.map +1 -0
- package/build/lib/github-actions/pr-controller.js +122 -0
- package/build/lib/github-actions/pr-controller.js.map +1 -0
- package/build/lib/github-actions/run-step-cli.d.ts +9 -0
- package/build/lib/github-actions/run-step-cli.d.ts.map +1 -0
- package/build/lib/github-actions/run-step-cli.js +71 -0
- package/build/lib/github-actions/run-step-cli.js.map +1 -0
- package/build/lib/github-actions/runner.d.ts +17 -0
- package/build/lib/github-actions/runner.d.ts.map +1 -0
- package/build/lib/github-actions/runner.js +195 -0
- package/build/lib/github-actions/runner.js.map +1 -0
- package/build/lib/github-actions/schemas/context.d.ts +933 -0
- package/build/lib/github-actions/schemas/context.d.ts.map +1 -0
- package/build/lib/github-actions/schemas/context.js +407 -0
- package/build/lib/github-actions/schemas/context.js.map +1 -0
- package/build/lib/github-actions/schemas/index.d.ts +5 -0
- package/build/lib/github-actions/schemas/index.d.ts.map +1 -0
- package/build/lib/github-actions/schemas/index.js +5 -0
- package/build/lib/github-actions/schemas/index.js.map +1 -0
- package/build/lib/github-actions/search-module.d.ts +38 -0
- package/build/lib/github-actions/search-module.d.ts.map +1 -0
- package/build/lib/github-actions/search-module.js +40 -0
- package/build/lib/github-actions/search-module.js.map +1 -0
- package/build/lib/github-actions/step.d.ts +163 -0
- package/build/lib/github-actions/step.d.ts.map +1 -0
- package/build/lib/github-actions/step.js +121 -0
- package/build/lib/github-actions/step.js.map +1 -0
- package/build/lib/helpers.d.ts.map +1 -1
- package/build/lib/helpers.js +5 -3
- package/build/lib/helpers.js.map +1 -1
- package/build/lib/kit-temp.d.ts +54 -0
- package/build/lib/kit-temp.d.ts.map +1 -1
- package/build/lib/kit-temp.js +80 -14
- package/build/lib/kit-temp.js.map +1 -1
- package/build/lib/kit-temp.test-d.d.ts +2 -0
- package/build/lib/kit-temp.test-d.d.ts.map +1 -0
- package/build/lib/kit-temp.test-d.js +75 -0
- package/build/lib/kit-temp.test-d.js.map +1 -0
- package/build/lib/mask/$$.d.ts +3 -0
- package/build/lib/mask/$$.d.ts.map +1 -0
- package/build/lib/mask/$$.js +3 -0
- package/build/lib/mask/$$.js.map +1 -0
- package/build/lib/mask/$.d.ts +2 -0
- package/build/lib/mask/$.d.ts.map +1 -0
- package/build/lib/mask/$.js +2 -0
- package/build/lib/mask/$.js.map +1 -0
- package/build/lib/mask/apply.d.ts +86 -0
- package/build/lib/mask/apply.d.ts.map +1 -0
- package/build/lib/mask/apply.js +86 -0
- package/build/lib/mask/apply.js.map +1 -0
- package/build/lib/mask/mask.d.ts +124 -0
- package/build/lib/mask/mask.d.ts.map +1 -0
- package/build/lib/mask/mask.js +137 -0
- package/build/lib/mask/mask.js.map +1 -0
- package/build/lib/mask/mask.test-d.d.ts +2 -0
- package/build/lib/mask/mask.test-d.d.ts.map +1 -0
- package/build/lib/mask/mask.test-d.js +102 -0
- package/build/lib/mask/mask.test-d.js.map +1 -0
- package/build/lib/task/$$.d.ts +3 -0
- package/build/lib/task/$$.d.ts.map +1 -0
- package/build/lib/task/$$.js +3 -0
- package/build/lib/task/$$.js.map +1 -0
- package/build/lib/task/$.d.ts +2 -0
- package/build/lib/task/$.d.ts.map +1 -0
- package/build/lib/task/$.js +2 -0
- package/build/lib/task/$.js.map +1 -0
- package/build/lib/task/report.d.ts +28 -0
- package/build/lib/task/report.d.ts.map +1 -0
- package/build/lib/task/report.js +33 -0
- package/build/lib/task/report.js.map +1 -0
- package/build/lib/task/task.d.ts +44 -0
- package/build/lib/task/task.d.ts.map +1 -0
- package/build/lib/task/task.js +63 -0
- package/build/lib/task/task.js.map +1 -0
- package/build/lib/version-history/index.d.ts +3 -0
- package/build/lib/version-history/index.d.ts.map +1 -0
- package/build/lib/version-history/index.js +2 -0
- package/build/lib/version-history/index.js.map +1 -0
- package/build/lib/version-history/types.d.ts +64 -0
- package/build/lib/version-history/types.d.ts.map +1 -0
- package/build/lib/version-history/types.js +5 -0
- package/build/lib/version-history/types.js.map +1 -0
- package/build/lib/version-history/version-history.d.ts +85 -0
- package/build/lib/version-history/version-history.d.ts.map +1 -0
- package/build/lib/version-history/version-history.js +248 -0
- package/build/lib/version-history/version-history.js.map +1 -0
- package/build/sandbox.d.ts +2 -0
- package/build/sandbox.d.ts.map +1 -0
- package/build/sandbox.js +3 -0
- package/build/sandbox.js.map +1 -0
- package/build/template/components/Link.jsx +1 -1
- package/package.json +16 -9
- package/src/api/api.ts +1 -0
- package/src/api/singletons/markdown/markdown.test.ts +1 -1
- package/src/api/static/index.ts +1 -0
- package/src/api/static/manifest.test.ts +106 -0
- package/src/api/static/manifest.ts +16 -0
- package/src/api/static/rebase.test.ts +229 -0
- package/src/api/static/rebase.ts +140 -0
- package/src/api/static/static.ts +2 -0
- package/src/api/utils/asset-url/asset-url.test.ts +4 -4
- package/src/api/vite/plugins/build.ts +25 -1
- package/src/api/vite/plugins/core.ts +1 -1
- package/src/cli/commands/static/$default.ts +43 -0
- package/src/cli/commands/static/rebase.ts +37 -0
- package/src/cli/commands/static.ts +6 -0
- package/src/lib/demos/builder.ts +298 -0
- package/src/lib/demos/config-schema.ts +56 -0
- package/src/lib/demos/config.test.ts +193 -0
- package/src/lib/demos/config.ts +205 -0
- package/src/lib/demos/index.ts +9 -0
- package/src/lib/demos/ui/components.ts +739 -0
- package/src/lib/demos/ui/data-collector.ts +246 -0
- package/src/lib/demos/ui/landing-page-cli.ts +23 -0
- package/src/lib/demos/ui/landing-page.ts +126 -0
- package/src/lib/demos/ui/page-renderer.ts +124 -0
- package/src/lib/demos/utils.ts +43 -0
- package/src/lib/deployment/$$.ts +2 -0
- package/src/lib/deployment/$.test.ts +53 -0
- package/src/lib/deployment/$.ts +1 -0
- package/src/lib/deployment/metadata.ts +40 -0
- package/src/lib/deployment/path-manager.ts +186 -0
- package/src/lib/github-actions/git-controller.ts +151 -0
- package/src/lib/github-actions/github-actions.ts +6 -0
- package/src/lib/github-actions/index.ts +1 -0
- package/src/lib/github-actions/lib/get-pr-deployments.ts +76 -0
- package/src/lib/github-actions/pr-controller.test.ts +172 -0
- package/src/lib/github-actions/pr-controller.ts +183 -0
- package/src/lib/github-actions/run-step-cli.ts +84 -0
- package/src/lib/github-actions/runner.test.ts +192 -0
- package/src/lib/github-actions/runner.ts +226 -0
- package/src/lib/github-actions/schemas/context.ts +424 -0
- package/src/lib/github-actions/schemas/index.ts +5 -0
- package/src/lib/github-actions/search-module.test.ts +110 -0
- package/src/lib/github-actions/search-module.ts +76 -0
- package/src/lib/github-actions/step.test.ts +149 -0
- package/src/lib/github-actions/step.ts +232 -0
- package/src/lib/helpers.ts +4 -3
- package/src/lib/kit-temp.test-d.ts +115 -0
- package/src/lib/kit-temp.test.ts +127 -0
- package/src/lib/kit-temp.ts +126 -14
- package/src/lib/mask/$$.ts +2 -0
- package/src/lib/mask/$.test.ts +248 -0
- package/src/lib/mask/$.ts +1 -0
- package/src/lib/mask/apply.ts +134 -0
- package/src/lib/mask/mask.test-d.ts +144 -0
- package/src/lib/mask/mask.ts +244 -0
- package/src/lib/shiki/shiki.test.ts +1 -1
- package/src/lib/task/$$.ts +2 -0
- package/src/lib/task/$.test.ts +209 -0
- package/src/lib/task/$.ts +1 -0
- package/src/lib/task/report.ts +72 -0
- package/src/lib/task/task.ts +112 -0
- package/src/lib/version-history/index.test.ts +188 -0
- package/src/lib/version-history/index.ts +4 -0
- package/src/lib/version-history/types.ts +68 -0
- package/src/lib/version-history/version-history.ts +293 -0
- package/src/sandbox.ts +1 -0
- package/src/template/components/Link.tsx +1 -1
@@ -0,0 +1,244 @@
|
|
1
|
+
//
|
2
|
+
//
|
3
|
+
//
|
4
|
+
//
|
5
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ • Mask
|
6
|
+
//
|
7
|
+
//
|
8
|
+
|
9
|
+
/**
|
10
|
+
* A mask that can either hide/show data entirely (BinaryMask) or
|
11
|
+
* selectively hide/show object properties (PropertiesMask).
|
12
|
+
*
|
13
|
+
* @template $Data - The data type being masked
|
14
|
+
*/
|
15
|
+
export type Mask<$Data = any> = BinaryMask<$Data> | PropertiesMask<$Data extends object ? $Data : object>
|
16
|
+
|
17
|
+
/**
|
18
|
+
* Create a mask based on the provided options.
|
19
|
+
*
|
20
|
+
* @param options - Mask configuration:
|
21
|
+
* - `boolean`: Creates a binary mask (true = show, false = hide)
|
22
|
+
* - `string[]`: Creates a properties mask that allows only the specified keys
|
23
|
+
* - `object`: Creates a properties mask based on true/false values per key
|
24
|
+
*
|
25
|
+
* @returns A mask that can be applied to data
|
26
|
+
*
|
27
|
+
* @example
|
28
|
+
* ```ts
|
29
|
+
* // Binary mask
|
30
|
+
* const showAll = create(true)
|
31
|
+
* const hideAll = create(false)
|
32
|
+
*
|
33
|
+
* // Properties mask with array
|
34
|
+
* const allowMask = create<User>(['name', 'email'])
|
35
|
+
*
|
36
|
+
* // Properties mask with object
|
37
|
+
* const objectMask = create<User>({
|
38
|
+
* name: true,
|
39
|
+
* email: true,
|
40
|
+
* password: false
|
41
|
+
* })
|
42
|
+
* ```
|
43
|
+
*/
|
44
|
+
export const create = <$Data = unknown>(
|
45
|
+
options: InferOptions<$Data>,
|
46
|
+
): Mask<$Data> => {
|
47
|
+
if (typeof options === 'boolean') {
|
48
|
+
return createBinary(options) as any
|
49
|
+
}
|
50
|
+
|
51
|
+
// Array input -> PropertiesMask with 'allow' mode
|
52
|
+
if (Array.isArray(options)) {
|
53
|
+
return createProperties('allow', options as any) as any
|
54
|
+
}
|
55
|
+
|
56
|
+
// Object input -> PropertiesMask based on true/false values
|
57
|
+
const entries = Object.entries(options)
|
58
|
+
|
59
|
+
const allowedKeys = entries
|
60
|
+
.filter(([_, include]) => include === true)
|
61
|
+
.map(([key]) => key)
|
62
|
+
|
63
|
+
const deniedKeys = entries
|
64
|
+
.filter(([_, include]) => include === false)
|
65
|
+
.map(([key]) => key)
|
66
|
+
|
67
|
+
// If we have denied keys, use deny mode
|
68
|
+
if (deniedKeys.length > 0 && allowedKeys.length === 0) {
|
69
|
+
return createProperties('deny', deniedKeys) as any
|
70
|
+
}
|
71
|
+
|
72
|
+
// Default to allow mode with allowed keys
|
73
|
+
return createProperties('allow', allowedKeys) as any
|
74
|
+
}
|
75
|
+
|
76
|
+
/**
|
77
|
+
* Valid options for creating a mask for the given data type.
|
78
|
+
*
|
79
|
+
* @template $Data - The data type to be masked
|
80
|
+
*/
|
81
|
+
export type InferOptions<$Data> = unknown extends $Data ? boolean | string[] | Record<string, boolean>
|
82
|
+
: $Data extends object ? (
|
83
|
+
| boolean
|
84
|
+
| (keyof $Data)[]
|
85
|
+
| Partial<
|
86
|
+
{
|
87
|
+
[K in keyof $Data]: boolean
|
88
|
+
}
|
89
|
+
>
|
90
|
+
)
|
91
|
+
: boolean
|
92
|
+
|
93
|
+
//
|
94
|
+
//
|
95
|
+
//
|
96
|
+
//
|
97
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ • PropertiesMask
|
98
|
+
//
|
99
|
+
//
|
100
|
+
|
101
|
+
/**
|
102
|
+
* A mask that selectively shows or hides object properties.
|
103
|
+
*
|
104
|
+
* @template $Data - The object type being masked
|
105
|
+
*/
|
106
|
+
export interface PropertiesMask<$Data extends object = object> {
|
107
|
+
type: 'properties'
|
108
|
+
/** Whether to allow only specified properties or deny them */
|
109
|
+
mode: 'allow' | 'deny'
|
110
|
+
/** The list of property keys to allow or deny */
|
111
|
+
properties: (keyof $Data)[]
|
112
|
+
}
|
113
|
+
|
114
|
+
/**
|
115
|
+
* Create a properties mask.
|
116
|
+
*
|
117
|
+
* @param mode - 'allow' to show only specified properties, 'deny' to hide them
|
118
|
+
* @param properties - Array of property keys to allow or deny
|
119
|
+
* @returns A PropertiesMask
|
120
|
+
*/
|
121
|
+
export const createProperties = <$Data extends object = object>(
|
122
|
+
mode: 'allow' | 'deny',
|
123
|
+
properties: (keyof $Data)[],
|
124
|
+
): PropertiesMask<$Data> => ({
|
125
|
+
type: 'properties',
|
126
|
+
mode,
|
127
|
+
properties,
|
128
|
+
})
|
129
|
+
|
130
|
+
//
|
131
|
+
//
|
132
|
+
//
|
133
|
+
//
|
134
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ • BinaryMask
|
135
|
+
//
|
136
|
+
//
|
137
|
+
|
138
|
+
/**
|
139
|
+
* A mask that either shows or hides data entirely.
|
140
|
+
*
|
141
|
+
* @template _$Data - The data type being masked (used for type inference)
|
142
|
+
*/
|
143
|
+
export type BinaryMask<_$Data = any> = {
|
144
|
+
type: 'binary'
|
145
|
+
/** Whether to show (true) or hide (false) the data */
|
146
|
+
show: boolean
|
147
|
+
}
|
148
|
+
|
149
|
+
/**
|
150
|
+
* Create a binary mask.
|
151
|
+
*
|
152
|
+
* @param show - Whether to show (true) or hide (false) the data
|
153
|
+
* @returns A BinaryMask
|
154
|
+
*/
|
155
|
+
export const createBinary = <$Data = any>(show: boolean): BinaryMask<$Data> => ({
|
156
|
+
type: 'binary',
|
157
|
+
show,
|
158
|
+
})
|
159
|
+
|
160
|
+
//
|
161
|
+
//
|
162
|
+
//
|
163
|
+
//
|
164
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ • Convenience Constructors with Semantic Names
|
165
|
+
//
|
166
|
+
//
|
167
|
+
|
168
|
+
/**
|
169
|
+
* Create a mask that shows all data.
|
170
|
+
* @returns A BinaryMask with show=true
|
171
|
+
*/
|
172
|
+
export const show = (): BinaryMask => ({
|
173
|
+
type: 'binary',
|
174
|
+
show: true,
|
175
|
+
})
|
176
|
+
|
177
|
+
/**
|
178
|
+
* Create a mask that hides all data.
|
179
|
+
* @returns A BinaryMask with show=false
|
180
|
+
*/
|
181
|
+
export const hide = (): BinaryMask => ({
|
182
|
+
type: 'binary',
|
183
|
+
show: false,
|
184
|
+
})
|
185
|
+
|
186
|
+
/**
|
187
|
+
* Create a mask that shows only the specified properties.
|
188
|
+
*
|
189
|
+
* @param properties - Array of property keys to show
|
190
|
+
* @returns A PropertiesMask in 'allow' mode
|
191
|
+
*
|
192
|
+
* @example
|
193
|
+
* ```ts
|
194
|
+
* const userMask = pick<User>(['name', 'email'])
|
195
|
+
* // Only 'name' and 'email' will be shown
|
196
|
+
* ```
|
197
|
+
*/
|
198
|
+
export const pick = <$Data extends object = object>(
|
199
|
+
properties: (keyof $Data)[],
|
200
|
+
): PropertiesMask<$Data> => ({
|
201
|
+
type: 'properties',
|
202
|
+
mode: 'allow',
|
203
|
+
properties,
|
204
|
+
})
|
205
|
+
|
206
|
+
/**
|
207
|
+
* Create a mask that hides the specified properties.
|
208
|
+
*
|
209
|
+
* @param properties - Array of property keys to hide
|
210
|
+
* @returns A PropertiesMask in 'deny' mode
|
211
|
+
*
|
212
|
+
* @example
|
213
|
+
* ```ts
|
214
|
+
* const userMask = omit<User>(['password', 'ssn'])
|
215
|
+
* // Everything except 'password' and 'ssn' will be shown
|
216
|
+
* ```
|
217
|
+
*/
|
218
|
+
export const omit = <$Data extends object = object>(
|
219
|
+
properties: (keyof $Data)[],
|
220
|
+
): PropertiesMask<$Data> => ({
|
221
|
+
type: 'properties',
|
222
|
+
mode: 'deny',
|
223
|
+
properties,
|
224
|
+
})
|
225
|
+
|
226
|
+
//
|
227
|
+
//
|
228
|
+
//
|
229
|
+
//
|
230
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ • Utilities
|
231
|
+
//
|
232
|
+
//
|
233
|
+
|
234
|
+
/**
|
235
|
+
* Extract the data type from a mask.
|
236
|
+
*
|
237
|
+
* @template $Mask - The mask type
|
238
|
+
* @returns The data type the mask is designed for
|
239
|
+
*/
|
240
|
+
// dprint-ignore
|
241
|
+
export type GetDataType<$Mask extends Mask<any>> =
|
242
|
+
$Mask extends BinaryMask<infer $Data> ? $Data :
|
243
|
+
$Mask extends PropertiesMask<infer $Data> ? $Data
|
244
|
+
: never
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import { expect, test } from 'vitest'
|
2
|
-
import { getHighlighter, highlightCode } from './shiki.
|
2
|
+
import { getHighlighter, highlightCode } from './shiki.ts'
|
3
3
|
|
4
4
|
test(`getHighlighter returns singleton instance`, async () => {
|
5
5
|
const highlighter1 = await getHighlighter()
|
@@ -0,0 +1,209 @@
|
|
1
|
+
import * as fc from 'fast-check'
|
2
|
+
import { describe, expect, test, vi } from 'vitest'
|
3
|
+
import { Task } from './$.ts'
|
4
|
+
|
5
|
+
describe('create', () => {
|
6
|
+
test('executes async function and captures timing', async () => {
|
7
|
+
const double = Task.create(
|
8
|
+
async (x: number) => x * 2,
|
9
|
+
{ name: 'double' },
|
10
|
+
)
|
11
|
+
|
12
|
+
const report = await double(5)
|
13
|
+
|
14
|
+
expect(report.task.name).toBe('double')
|
15
|
+
expect(report.execution.input).toBe(5)
|
16
|
+
expect(report.execution.output).toBe(10)
|
17
|
+
expect(report.execution.timings.duration).toBeGreaterThan(0)
|
18
|
+
})
|
19
|
+
|
20
|
+
test('captures errors', async () => {
|
21
|
+
const failing = Task.create(
|
22
|
+
async () => {
|
23
|
+
throw new Error('Failed')
|
24
|
+
},
|
25
|
+
{ name: 'failing' },
|
26
|
+
)
|
27
|
+
|
28
|
+
const report = await failing(null)
|
29
|
+
|
30
|
+
expect(report.execution.output).toBeInstanceOf(Error)
|
31
|
+
expect((report.execution.output as Error).message).toBe('Failed')
|
32
|
+
})
|
33
|
+
})
|
34
|
+
|
35
|
+
describe('formatReport', () => {
|
36
|
+
test('masks sensitive data', async () => {
|
37
|
+
const createUser = Task.create(
|
38
|
+
async (data: any) => ({
|
39
|
+
email: data.email,
|
40
|
+
token: 'abc123',
|
41
|
+
}),
|
42
|
+
{
|
43
|
+
name: 'create-user',
|
44
|
+
mask: {
|
45
|
+
input: { password: false }, // Hide password
|
46
|
+
output: { token: false }, // Hide token
|
47
|
+
},
|
48
|
+
},
|
49
|
+
)
|
50
|
+
|
51
|
+
const report = await createUser({
|
52
|
+
email: 'user@example.com',
|
53
|
+
password: 'secret',
|
54
|
+
})
|
55
|
+
|
56
|
+
// Uses mask from task definition by default
|
57
|
+
const formatted = Task.formatReport(report)
|
58
|
+
|
59
|
+
expect(formatted).toContain('create-user')
|
60
|
+
expect(formatted).toContain('user@example.com')
|
61
|
+
expect(formatted).not.toContain('secret')
|
62
|
+
expect(formatted).not.toContain('abc123')
|
63
|
+
})
|
64
|
+
|
65
|
+
test('debug mode reveals all data', async () => {
|
66
|
+
const createUser = Task.create(
|
67
|
+
async (data: any) => ({
|
68
|
+
email: data.email,
|
69
|
+
token: 'abc123',
|
70
|
+
}),
|
71
|
+
{
|
72
|
+
name: 'create-user',
|
73
|
+
mask: {
|
74
|
+
input: { password: false }, // Hide password
|
75
|
+
output: { token: false }, // Hide token
|
76
|
+
},
|
77
|
+
},
|
78
|
+
)
|
79
|
+
|
80
|
+
const report = await createUser({
|
81
|
+
email: 'user@example.com',
|
82
|
+
password: 'secret',
|
83
|
+
})
|
84
|
+
|
85
|
+
const formatted = Task.formatReport(report, { debug: true })
|
86
|
+
|
87
|
+
expect(formatted).toContain('secret')
|
88
|
+
expect(formatted).toContain('abc123')
|
89
|
+
})
|
90
|
+
})
|
91
|
+
|
92
|
+
describe('exitWithReport', () => {
|
93
|
+
test('exits with code 0 on success', async () => {
|
94
|
+
const mockExit = vi.spyOn(process, 'exit').mockImplementation(() => {
|
95
|
+
throw new Error('process.exit called')
|
96
|
+
})
|
97
|
+
const mockLog = vi.spyOn(console, 'log').mockImplementation(() => {})
|
98
|
+
|
99
|
+
const success = Task.create(async () => 'result')
|
100
|
+
const report = await success(null)
|
101
|
+
|
102
|
+
expect(() => Task.exitWithReport(report)).toThrow('process.exit called')
|
103
|
+
expect(mockExit).toHaveBeenCalledWith(0)
|
104
|
+
expect(mockLog).toHaveBeenCalled()
|
105
|
+
|
106
|
+
mockExit.mockRestore()
|
107
|
+
mockLog.mockRestore()
|
108
|
+
})
|
109
|
+
|
110
|
+
test('exits with code 1 on error', async () => {
|
111
|
+
const mockExit = vi.spyOn(process, 'exit').mockImplementation(() => {
|
112
|
+
throw new Error('process.exit called')
|
113
|
+
})
|
114
|
+
const mockLog = vi.spyOn(console, 'log').mockImplementation(() => {})
|
115
|
+
|
116
|
+
const failing = Task.create(async () => {
|
117
|
+
throw new Error('Failed')
|
118
|
+
})
|
119
|
+
const report = await failing(null)
|
120
|
+
|
121
|
+
expect(() => Task.exitWithReport(report)).toThrow('process.exit called')
|
122
|
+
expect(mockExit).toHaveBeenCalledWith(1)
|
123
|
+
expect(mockLog).toHaveBeenCalled()
|
124
|
+
|
125
|
+
mockExit.mockRestore()
|
126
|
+
mockLog.mockRestore()
|
127
|
+
})
|
128
|
+
})
|
129
|
+
|
130
|
+
describe('runAndExit', () => {
|
131
|
+
test('combines create, execute, and exit into one call', async () => {
|
132
|
+
const mockExit = vi.spyOn(process, 'exit').mockImplementation(() => {
|
133
|
+
throw new Error('process.exit called')
|
134
|
+
})
|
135
|
+
const mockLog = vi.spyOn(console, 'log').mockImplementation(() => {})
|
136
|
+
|
137
|
+
const double = async (x: number) => x * 2
|
138
|
+
|
139
|
+
await expect(() => Task.runAndExit(double, 5, { name: 'double' })).rejects.toThrow('process.exit called')
|
140
|
+
|
141
|
+
expect(mockExit).toHaveBeenCalledWith(0)
|
142
|
+
expect(mockLog).toHaveBeenCalled()
|
143
|
+
|
144
|
+
// Verify the logged output contains expected content
|
145
|
+
const loggedOutput = mockLog.mock.calls[0]?.[0]
|
146
|
+
expect(loggedOutput).toContain('double')
|
147
|
+
expect(loggedOutput).toContain('10') // Result of 5 * 2
|
148
|
+
|
149
|
+
mockExit.mockRestore()
|
150
|
+
mockLog.mockRestore()
|
151
|
+
})
|
152
|
+
|
153
|
+
test('exits with error code on failure', async () => {
|
154
|
+
const mockExit = vi.spyOn(process, 'exit').mockImplementation(() => {
|
155
|
+
throw new Error('process.exit called')
|
156
|
+
})
|
157
|
+
const mockLog = vi.spyOn(console, 'log').mockImplementation(() => {})
|
158
|
+
|
159
|
+
const failing = async () => {
|
160
|
+
throw new Error('Task failed')
|
161
|
+
}
|
162
|
+
|
163
|
+
await expect(() => Task.runAndExit(failing, null, { name: 'failing' })).rejects.toThrow('process.exit called')
|
164
|
+
|
165
|
+
expect(mockExit).toHaveBeenCalledWith(1)
|
166
|
+
expect(mockLog).toHaveBeenCalled()
|
167
|
+
|
168
|
+
mockExit.mockRestore()
|
169
|
+
mockLog.mockRestore()
|
170
|
+
})
|
171
|
+
})
|
172
|
+
|
173
|
+
describe('property-based tests', () => {
|
174
|
+
test('duration is always positive', async () => {
|
175
|
+
await fc.assert(
|
176
|
+
fc.asyncProperty(
|
177
|
+
fc.integer(),
|
178
|
+
async (input) => {
|
179
|
+
const identity = Task.create(
|
180
|
+
async (x: any) => x,
|
181
|
+
{ name: 'identity' },
|
182
|
+
)
|
183
|
+
|
184
|
+
const report = await identity(input)
|
185
|
+
|
186
|
+
expect(report.execution.timings.duration).toBeGreaterThanOrEqual(0)
|
187
|
+
expect(report.execution.output).toBe(input)
|
188
|
+
},
|
189
|
+
),
|
190
|
+
)
|
191
|
+
})
|
192
|
+
|
193
|
+
test('timing fields are consistent', async () => {
|
194
|
+
await fc.assert(
|
195
|
+
fc.asyncProperty(
|
196
|
+
fc.anything(),
|
197
|
+
async (input) => {
|
198
|
+
const noop = Task.create(async (_: any) => null)
|
199
|
+
const report = await noop(input)
|
200
|
+
|
201
|
+
const { start, end, duration } = report.execution.timings
|
202
|
+
|
203
|
+
expect(end).toBeGreaterThanOrEqual(start)
|
204
|
+
expect(duration).toBeCloseTo(end - start, 5)
|
205
|
+
},
|
206
|
+
),
|
207
|
+
)
|
208
|
+
})
|
209
|
+
})
|
@@ -0,0 +1 @@
|
|
1
|
+
export * as Task from './$$.ts'
|
@@ -0,0 +1,72 @@
|
|
1
|
+
import { Mask } from '#lib/mask'
|
2
|
+
import { Err, Str } from '@wollybeard/kit'
|
3
|
+
import type { Definition, MaskOptions } from './task.ts'
|
4
|
+
|
5
|
+
export interface Report<$Input, $Output> {
|
6
|
+
task: Definition<$Input, $Output>
|
7
|
+
execution: {
|
8
|
+
input: $Input
|
9
|
+
output: $Output | Error
|
10
|
+
timings: {
|
11
|
+
start: number
|
12
|
+
end: number
|
13
|
+
duration: number
|
14
|
+
}
|
15
|
+
}
|
16
|
+
}
|
17
|
+
|
18
|
+
export interface FormatOptions<$Input, $Output> {
|
19
|
+
/**
|
20
|
+
* Force masks to reveal all data
|
21
|
+
*/
|
22
|
+
debug?: boolean
|
23
|
+
/**
|
24
|
+
* A mask to apply to the input (if any) and successful return (if any).
|
25
|
+
*
|
26
|
+
* @default Uses the mask bundled with the task, if any.
|
27
|
+
*/
|
28
|
+
mask?: MaskOptions<$Input, $Output>
|
29
|
+
}
|
30
|
+
|
31
|
+
export const formatReport = <$Input, $Output>(
|
32
|
+
report: Report<$Input, $Output>,
|
33
|
+
options?: FormatOptions<$Input, $Output>,
|
34
|
+
): string => {
|
35
|
+
const maskOptions = options?.debug
|
36
|
+
? undefined
|
37
|
+
: (options?.mask ?? report.task.mask)
|
38
|
+
|
39
|
+
// Apply masks
|
40
|
+
const maskedInput = maskOptions?.input
|
41
|
+
? Mask.apply(report.execution.input, Mask.create(maskOptions.input))
|
42
|
+
: report.execution.input
|
43
|
+
|
44
|
+
const maskedOutput = report.execution.output instanceof Error || !maskOptions?.output
|
45
|
+
? report.execution.output
|
46
|
+
: Mask.apply(report.execution.output, Mask.create(maskOptions.output))
|
47
|
+
|
48
|
+
// Format report
|
49
|
+
const s = Str.Builder()
|
50
|
+
|
51
|
+
s`Task: ${report.task.name}`
|
52
|
+
s`Duration: ${report.execution.timings.duration.toFixed(2)}ms`
|
53
|
+
s`Input: ${JSON.stringify(maskedInput, null, 2)}`
|
54
|
+
|
55
|
+
if (report.execution.output instanceof Error) {
|
56
|
+
s(Err.inspect(report.execution.output))
|
57
|
+
} else {
|
58
|
+
s`Output: ${JSON.stringify(maskedOutput, null, 2)}`
|
59
|
+
}
|
60
|
+
|
61
|
+
return s.render()
|
62
|
+
}
|
63
|
+
|
64
|
+
export const exitWithReport = <$Input, $Output>(
|
65
|
+
report: Report<$Input, $Output>,
|
66
|
+
options?: FormatOptions<$Input, $Output>,
|
67
|
+
): never => {
|
68
|
+
const isError = Err.is(report.execution.output)
|
69
|
+
const exitCode = isError ? 1 : 0
|
70
|
+
console.log(formatReport(report, options))
|
71
|
+
process.exit(exitCode)
|
72
|
+
}
|
@@ -0,0 +1,112 @@
|
|
1
|
+
import type { Mask } from '#lib/mask'
|
2
|
+
import type { Report } from './report.ts'
|
3
|
+
import { exitWithReport } from './report.ts'
|
4
|
+
|
5
|
+
export interface Definition<$Input = any, $Output = any> {
|
6
|
+
name: string
|
7
|
+
mask?: MaskOptions<$Input, $Output>
|
8
|
+
}
|
9
|
+
|
10
|
+
export interface Task<$Input, $Output> {
|
11
|
+
(input: $Input): Promise<Report<$Input, $Output>>
|
12
|
+
definition: Definition<$Input, $Output>
|
13
|
+
}
|
14
|
+
|
15
|
+
export interface MaskOptions<$Input, $Output> {
|
16
|
+
/**
|
17
|
+
* @default true
|
18
|
+
*/
|
19
|
+
input?: Mask.InferOptions<$Input>
|
20
|
+
/**
|
21
|
+
* @default true
|
22
|
+
*/
|
23
|
+
output?: Mask.InferOptions<$Output>
|
24
|
+
}
|
25
|
+
|
26
|
+
export const create = <$Input, $Output>(
|
27
|
+
fn: (input: $Input) => Promise<$Output>,
|
28
|
+
options?: {
|
29
|
+
/**
|
30
|
+
* @default the fn name
|
31
|
+
*/
|
32
|
+
name?: string
|
33
|
+
/**
|
34
|
+
* An optional default mask to use for successfully returned values.
|
35
|
+
*/
|
36
|
+
mask?: MaskOptions<$Input, $Output>
|
37
|
+
},
|
38
|
+
): Task<$Input, $Output> => {
|
39
|
+
const definition: Definition<$Input, $Output> = {
|
40
|
+
name: options?.name ?? (fn.name || 'anonymous'),
|
41
|
+
mask: options?.mask,
|
42
|
+
}
|
43
|
+
|
44
|
+
const task = async (input: $Input): Promise<Report<$Input, $Output>> => {
|
45
|
+
const start = performance.now()
|
46
|
+
|
47
|
+
try {
|
48
|
+
const output = await fn(input)
|
49
|
+
const end = performance.now()
|
50
|
+
|
51
|
+
return {
|
52
|
+
task: definition,
|
53
|
+
execution: {
|
54
|
+
input,
|
55
|
+
output,
|
56
|
+
timings: {
|
57
|
+
start,
|
58
|
+
end,
|
59
|
+
duration: end - start,
|
60
|
+
},
|
61
|
+
},
|
62
|
+
}
|
63
|
+
} catch (error) {
|
64
|
+
const end = performance.now()
|
65
|
+
|
66
|
+
return {
|
67
|
+
task: definition,
|
68
|
+
execution: {
|
69
|
+
input,
|
70
|
+
output: error as Error,
|
71
|
+
timings: {
|
72
|
+
start,
|
73
|
+
end,
|
74
|
+
duration: end - start,
|
75
|
+
},
|
76
|
+
},
|
77
|
+
}
|
78
|
+
}
|
79
|
+
}
|
80
|
+
|
81
|
+
task.definition = definition
|
82
|
+
|
83
|
+
return task
|
84
|
+
}
|
85
|
+
|
86
|
+
/**
|
87
|
+
* Sugar method that creates a task, executes it, and exits with the report.
|
88
|
+
* Equivalent to: create(fn, options) -> task(input) -> exitWithReport(report)
|
89
|
+
*
|
90
|
+
* @param fn - The function to wrap as a task
|
91
|
+
* @param input - Input to pass to the task
|
92
|
+
* @param options - Combined task creation and format options
|
93
|
+
*/
|
94
|
+
export const runAndExit = async <$Input, $Output>(
|
95
|
+
fn: (input: $Input) => Promise<$Output>,
|
96
|
+
input: $Input,
|
97
|
+
options?: {
|
98
|
+
name?: string
|
99
|
+
mask?: MaskOptions<$Input, $Output>
|
100
|
+
debug?: boolean
|
101
|
+
},
|
102
|
+
): Promise<never> => {
|
103
|
+
const task = create(fn, {
|
104
|
+
name: options?.name,
|
105
|
+
mask: options?.mask,
|
106
|
+
})
|
107
|
+
const report = await task(input)
|
108
|
+
return exitWithReport(report, {
|
109
|
+
debug: options?.debug,
|
110
|
+
mask: options?.mask,
|
111
|
+
})
|
112
|
+
}
|