@hashsome/integration.home-assistant 0.0.0-stage → 0.2.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.
- package/LICENSE +674 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/browse.d.ts +19 -0
- package/dist/browse.js +45 -0
- package/dist/commands.d.ts +8 -0
- package/dist/commands.js +177 -0
- package/dist/forecast.d.ts +27 -0
- package/dist/forecast.js +50 -0
- package/dist/history.d.ts +20 -0
- package/dist/history.js +23 -0
- package/dist/index.d.ts +79 -0
- package/dist/index.js +246 -0
- package/dist/logbook.d.ts +28 -0
- package/dist/logbook.js +78 -0
- package/dist/mappers/climate.d.ts +6 -0
- package/dist/mappers/climate.js +55 -0
- package/dist/mappers/common.d.ts +24 -0
- package/dist/mappers/common.js +72 -0
- package/dist/mappers/index.d.ts +14 -0
- package/dist/mappers/index.js +37 -0
- package/dist/mappers/light.d.ts +3 -0
- package/dist/mappers/light.js +49 -0
- package/dist/mappers/media-player.d.ts +3 -0
- package/dist/mappers/media-player.js +69 -0
- package/dist/mappers/simple.d.ts +8 -0
- package/dist/mappers/simple.js +38 -0
- package/dist/mappers/weather.d.ts +5 -0
- package/dist/mappers/weather.js +40 -0
- package/dist/mock.d.ts +11 -0
- package/dist/mock.js +114 -0
- package/package.json +38 -3
- package/README.md +0 -3
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { baseOf, num, strings } from './common.js';
|
|
2
|
+
const COLOR_MODES = ['hs', 'xy', 'rgb', 'rgbw', 'rgbww'];
|
|
3
|
+
function colorOf(a) {
|
|
4
|
+
const kelvin = num(a.color_temp_kelvin);
|
|
5
|
+
if (a.color_mode === 'color_temp' && kelvin !== undefined) {
|
|
6
|
+
return { mode: 'temperature', kelvin };
|
|
7
|
+
}
|
|
8
|
+
const hs = a.hs_color;
|
|
9
|
+
if (Array.isArray(hs) && typeof hs[0] === 'number' && typeof hs[1] === 'number') {
|
|
10
|
+
const rgb = a.rgb_color;
|
|
11
|
+
return {
|
|
12
|
+
mode: 'color',
|
|
13
|
+
hue: hs[0],
|
|
14
|
+
saturation: hs[1],
|
|
15
|
+
...(Array.isArray(rgb) && rgb.length >= 3 && rgb.every((n) => typeof n === 'number')
|
|
16
|
+
? { rgb: [rgb[0], rgb[1], rgb[2]] }
|
|
17
|
+
: {}),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
return kelvin !== undefined ? { mode: 'temperature', kelvin } : undefined;
|
|
21
|
+
}
|
|
22
|
+
export function mapLight(entity) {
|
|
23
|
+
const a = entity.attributes;
|
|
24
|
+
const modes = strings(a.supported_color_modes);
|
|
25
|
+
const brightness = num(a.brightness);
|
|
26
|
+
const dimmable = modes.some((mode) => mode !== 'onoff' && mode !== 'unknown');
|
|
27
|
+
const colorTemperature = modes.includes('color_temp');
|
|
28
|
+
const color = colorOf(a);
|
|
29
|
+
return {
|
|
30
|
+
kind: 'light',
|
|
31
|
+
...baseOf(entity),
|
|
32
|
+
on: entity.state === 'on',
|
|
33
|
+
...(brightness !== undefined ? { brightness: brightness / 255 } : {}),
|
|
34
|
+
...(color ? { color } : {}),
|
|
35
|
+
capabilities: {
|
|
36
|
+
brightness: dimmable,
|
|
37
|
+
colorTemperature,
|
|
38
|
+
color: modes.some((mode) => COLOR_MODES.includes(mode)),
|
|
39
|
+
...(colorTemperature
|
|
40
|
+
? {
|
|
41
|
+
kelvinRange: {
|
|
42
|
+
min: num(a.min_color_temp_kelvin) ?? 2000,
|
|
43
|
+
max: num(a.max_color_temp_kelvin) ?? 6500,
|
|
44
|
+
},
|
|
45
|
+
}
|
|
46
|
+
: {}),
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { baseOf, fileUrl, num, str } from './common.js';
|
|
2
|
+
/** `MediaPlayerEntityFeature` bit flags from Home Assistant. */
|
|
3
|
+
const FEATURE = {
|
|
4
|
+
seek: 2,
|
|
5
|
+
volumeSet: 4,
|
|
6
|
+
volumeMute: 8,
|
|
7
|
+
previous: 16,
|
|
8
|
+
next: 32,
|
|
9
|
+
shuffle: 32768,
|
|
10
|
+
browse: 131072,
|
|
11
|
+
};
|
|
12
|
+
const PLAYBACK = {
|
|
13
|
+
playing: 'playing',
|
|
14
|
+
paused: 'paused',
|
|
15
|
+
idle: 'idle',
|
|
16
|
+
standby: 'idle',
|
|
17
|
+
on: 'idle',
|
|
18
|
+
off: 'off',
|
|
19
|
+
buffering: 'buffering',
|
|
20
|
+
};
|
|
21
|
+
export function mapMediaPlayer(entity, toAssetUrl = (path) => path) {
|
|
22
|
+
const a = entity.attributes;
|
|
23
|
+
const features = num(a.supported_features);
|
|
24
|
+
const has = (flag, fallback) => features === undefined ? fallback : (features & flag) !== 0;
|
|
25
|
+
const volume = num(a.volume_level);
|
|
26
|
+
const title = str(a.media_title);
|
|
27
|
+
const artist = str(a.media_artist);
|
|
28
|
+
const album = str(a.media_album_name);
|
|
29
|
+
const picture = str(a.entity_picture_local) ?? str(a.entity_picture);
|
|
30
|
+
const artworkUrl = picture ? fileUrl(picture, toAssetUrl) : undefined;
|
|
31
|
+
const position = num(a.media_position);
|
|
32
|
+
const duration = num(a.media_duration);
|
|
33
|
+
const positionUpdatedAt = str(a.media_position_updated_at);
|
|
34
|
+
const shuffle = typeof a.shuffle === 'boolean' ? a.shuffle : undefined;
|
|
35
|
+
return {
|
|
36
|
+
kind: 'mediaPlayer',
|
|
37
|
+
...baseOf(entity),
|
|
38
|
+
playback: PLAYBACK[entity.state] ?? 'idle',
|
|
39
|
+
muted: a.is_volume_muted === true,
|
|
40
|
+
...(volume !== undefined ? { volume } : {}),
|
|
41
|
+
...(title || artist || album || artworkUrl
|
|
42
|
+
? {
|
|
43
|
+
media: {
|
|
44
|
+
...(title ? { title } : {}),
|
|
45
|
+
...(artist ? { artist } : {}),
|
|
46
|
+
...(album ? { album } : {}),
|
|
47
|
+
...(artworkUrl ? { artworkUrl } : {}),
|
|
48
|
+
},
|
|
49
|
+
}
|
|
50
|
+
: {}),
|
|
51
|
+
...(shuffle !== undefined ? { shuffle } : {}),
|
|
52
|
+
...(position !== undefined ? { position } : {}),
|
|
53
|
+
...(duration !== undefined ? { duration } : {}),
|
|
54
|
+
...(position !== undefined && positionUpdatedAt ? { positionUpdatedAt } : {}),
|
|
55
|
+
capabilities: {
|
|
56
|
+
volume: volume !== undefined || has(FEATURE.volumeSet, false),
|
|
57
|
+
mute: a.is_volume_muted !== undefined || has(FEATURE.volumeMute, false),
|
|
58
|
+
next: has(FEATURE.next, true),
|
|
59
|
+
previous: has(FEATURE.previous, true),
|
|
60
|
+
browse: has(FEATURE.browse, false),
|
|
61
|
+
// Home Assistant's browse contract has no search.
|
|
62
|
+
search: false,
|
|
63
|
+
seek: has(FEATURE.seek, false),
|
|
64
|
+
shuffle: has(FEATURE.shuffle, false),
|
|
65
|
+
transfer: false,
|
|
66
|
+
group: false,
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { EntityInput } from '@hashsome/core';
|
|
2
|
+
import type { HassEntity } from 'home-assistant-js-websocket';
|
|
3
|
+
export declare function mapSensor(entity: HassEntity): EntityInput;
|
|
4
|
+
/** `on` for an on/off thing; for a lock, `locked`. */
|
|
5
|
+
export declare function mapSwitch(entity: HassEntity): EntityInput;
|
|
6
|
+
export declare function mapAction(entity: HassEntity): EntityInput;
|
|
7
|
+
export declare function mapPerson(entity: HassEntity, toAssetUrl?: (path: string) => string): EntityInput;
|
|
8
|
+
export declare function mapGeneric(entity: HassEntity): EntityInput;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { baseOf, fileUrl, num, str } from './common.js';
|
|
2
|
+
export function mapSensor(entity) {
|
|
3
|
+
const unit = str(entity.attributes.unit_of_measurement);
|
|
4
|
+
const measurement = str(entity.attributes.device_class);
|
|
5
|
+
const numeric = entity.state.trim() === '' ? undefined : num(Number(entity.state));
|
|
6
|
+
return {
|
|
7
|
+
kind: 'sensor',
|
|
8
|
+
...baseOf(entity),
|
|
9
|
+
value: entity.state,
|
|
10
|
+
...(numeric !== undefined ? { numeric } : {}),
|
|
11
|
+
...(unit ? { unit } : {}),
|
|
12
|
+
...(measurement ? { measurement } : {}),
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
/** `on` for an on/off thing; for a lock, `locked`. */
|
|
16
|
+
export function mapSwitch(entity) {
|
|
17
|
+
const on = entity.state === 'on' || entity.state === 'locked';
|
|
18
|
+
return { kind: 'switch', ...baseOf(entity), on };
|
|
19
|
+
}
|
|
20
|
+
export function mapAction(entity) {
|
|
21
|
+
const last = str(entity.attributes.last_triggered);
|
|
22
|
+
return { kind: 'action', ...baseOf(entity), ...(last ? { lastTriggered: last } : {}) };
|
|
23
|
+
}
|
|
24
|
+
export function mapPerson(entity, toAssetUrl = (path) => path) {
|
|
25
|
+
const raw = str(entity.attributes.entity_picture);
|
|
26
|
+
const picture = raw ? fileUrl(raw, toAssetUrl) : undefined;
|
|
27
|
+
return {
|
|
28
|
+
kind: 'person',
|
|
29
|
+
...baseOf(entity),
|
|
30
|
+
location: entity.state,
|
|
31
|
+
home: entity.state === 'home',
|
|
32
|
+
...(picture ? { pictureUrl: picture } : {}),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
export function mapGeneric(entity) {
|
|
36
|
+
const unit = str(entity.attributes.unit_of_measurement);
|
|
37
|
+
return { kind: 'generic', ...baseOf(entity), value: entity.state, ...(unit ? { unit } : {}) };
|
|
38
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type EntityInput } from '@hashsome/core';
|
|
2
|
+
import type { HassEntity } from 'home-assistant-js-websocket';
|
|
3
|
+
/** A `weather.*` entity: its state is the current condition, its attributes the current readings
|
|
4
|
+
* (the forecast is separate, not part of the entity's state: see `forecast()`). */
|
|
5
|
+
export declare function mapWeather(entity: HassEntity): EntityInput;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { WEATHER_CONDITIONS } from '@hashsome/core';
|
|
2
|
+
import { forecastsOf } from '../forecast.js';
|
|
3
|
+
import { baseOf, bearing, num, str } from './common.js';
|
|
4
|
+
const isCondition = (state) => WEATHER_CONDITIONS.includes(state);
|
|
5
|
+
/** A `weather.*` entity: its state is the current condition, its attributes the current readings
|
|
6
|
+
* (the forecast is separate, not part of the entity's state: see `forecast()`). */
|
|
7
|
+
export function mapWeather(entity) {
|
|
8
|
+
const a = entity.attributes;
|
|
9
|
+
const temperature = num(a.temperature);
|
|
10
|
+
const unit = str(a.temperature_unit);
|
|
11
|
+
const humidity = num(a.humidity);
|
|
12
|
+
const windSpeed = num(a.wind_speed);
|
|
13
|
+
const windBearing = bearing(a.wind_bearing);
|
|
14
|
+
const windUnit = str(a.wind_speed_unit);
|
|
15
|
+
const uvIndex = num(a.uv_index);
|
|
16
|
+
const cloudCoverage = num(a.cloud_coverage);
|
|
17
|
+
const apparentTemperature = num(a.apparent_temperature);
|
|
18
|
+
const pressure = num(a.pressure);
|
|
19
|
+
const pressureUnit = str(a.pressure_unit);
|
|
20
|
+
const precipitationUnit = str(a.precipitation_unit);
|
|
21
|
+
const forecasts = forecastsOf(num(a.supported_features));
|
|
22
|
+
return {
|
|
23
|
+
kind: 'weather',
|
|
24
|
+
...baseOf(entity),
|
|
25
|
+
condition: isCondition(entity.state) ? entity.state : 'unknown',
|
|
26
|
+
...(temperature !== undefined ? { temperature } : {}),
|
|
27
|
+
...(unit ? { unit } : {}),
|
|
28
|
+
...(humidity !== undefined ? { humidity } : {}),
|
|
29
|
+
...(windSpeed !== undefined ? { windSpeed } : {}),
|
|
30
|
+
...(windBearing !== undefined ? { windBearing } : {}),
|
|
31
|
+
...(windUnit ? { windUnit } : {}),
|
|
32
|
+
...(uvIndex !== undefined ? { uvIndex } : {}),
|
|
33
|
+
...(cloudCoverage !== undefined ? { cloudCoverage } : {}),
|
|
34
|
+
...(apparentTemperature !== undefined ? { apparentTemperature } : {}),
|
|
35
|
+
...(pressure !== undefined ? { pressure } : {}),
|
|
36
|
+
...(pressureUnit ? { pressureUnit } : {}),
|
|
37
|
+
...(precipitationUnit ? { precipitationUnit } : {}),
|
|
38
|
+
...(forecasts.length > 0 ? { forecasts } : {}),
|
|
39
|
+
};
|
|
40
|
+
}
|
package/dist/mock.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { MockIntegration, type EntityInput } from '@hashsome/core';
|
|
2
|
+
export interface HomeAssistantMockOptions {
|
|
3
|
+
/** Integration id. Defaults to `ha`. */
|
|
4
|
+
id?: string;
|
|
5
|
+
/** Extra entities, by local id; they replace a default with the same id. */
|
|
6
|
+
entities?: Record<string, EntityInput>;
|
|
7
|
+
}
|
|
8
|
+
/** A Home Assistant integration that needs no Home Assistant: representative devices, mapped the
|
|
9
|
+
* same way real ones are, and commands that change their state. For tests, the gallery and
|
|
10
|
+
* dashboards under development. */
|
|
11
|
+
export declare function createMock(options?: HomeAssistantMockOptions): MockIntegration;
|
package/dist/mock.js
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { MockIntegration, mockLibrary } from '@hashsome/core';
|
|
2
|
+
import { mapEntity } from './mappers/index.js';
|
|
3
|
+
const state = (id, value, attributes = {}) => ({
|
|
4
|
+
entity_id: id,
|
|
5
|
+
state: value,
|
|
6
|
+
attributes,
|
|
7
|
+
last_changed: '2026-01-01T00:00:00.000Z',
|
|
8
|
+
last_updated: '2026-01-01T00:00:00.000Z',
|
|
9
|
+
context: { id: 'mock', parent_id: null, user_id: null },
|
|
10
|
+
});
|
|
11
|
+
/** What Home Assistant reports for a representative home: every domain the mappers understand,
|
|
12
|
+
* and the states a dashboard has to handle (off, unavailable, unknown). Raw states, so the mock
|
|
13
|
+
* is built by the same mapping the real integration uses. */
|
|
14
|
+
const states = () => [
|
|
15
|
+
state('light.plain', 'on', { friendly_name: 'Lamp', supported_color_modes: ['onoff'] }),
|
|
16
|
+
state('light.dimmable', 'on', {
|
|
17
|
+
friendly_name: 'Dimmable light',
|
|
18
|
+
supported_color_modes: ['brightness'],
|
|
19
|
+
brightness: 153,
|
|
20
|
+
}),
|
|
21
|
+
state('light.tunable', 'on', {
|
|
22
|
+
friendly_name: 'Tunable white',
|
|
23
|
+
supported_color_modes: ['color_temp'],
|
|
24
|
+
color_mode: 'color_temp',
|
|
25
|
+
brightness: 255,
|
|
26
|
+
color_temp_kelvin: 2700,
|
|
27
|
+
min_color_temp_kelvin: 2000,
|
|
28
|
+
max_color_temp_kelvin: 6500,
|
|
29
|
+
}),
|
|
30
|
+
state('light.color', 'off', {
|
|
31
|
+
friendly_name: 'Color light',
|
|
32
|
+
supported_color_modes: ['hs'],
|
|
33
|
+
brightness: 255,
|
|
34
|
+
hs_color: [280, 80],
|
|
35
|
+
}),
|
|
36
|
+
state('light.unavailable', 'unavailable', { friendly_name: 'Unplugged light' }),
|
|
37
|
+
state('climate.heater', 'heat', {
|
|
38
|
+
friendly_name: 'Heater',
|
|
39
|
+
hvac_modes: ['off', 'heat'],
|
|
40
|
+
temperature: 21,
|
|
41
|
+
current_temperature: 19.4,
|
|
42
|
+
min_temp: 5,
|
|
43
|
+
max_temp: 30,
|
|
44
|
+
target_temp_step: 0.5,
|
|
45
|
+
}),
|
|
46
|
+
state('climate.thermostat', 'off', {
|
|
47
|
+
friendly_name: 'Thermostat',
|
|
48
|
+
hvac_modes: ['off', 'heat', 'cool', 'auto'],
|
|
49
|
+
temperature: 21.5,
|
|
50
|
+
current_temperature: 18.8,
|
|
51
|
+
preset_modes: ['home', 'away'],
|
|
52
|
+
preset_mode: 'home',
|
|
53
|
+
}),
|
|
54
|
+
state('sensor.temperature', '18.4', {
|
|
55
|
+
friendly_name: 'Temperature',
|
|
56
|
+
device_class: 'temperature',
|
|
57
|
+
unit_of_measurement: '°C',
|
|
58
|
+
}),
|
|
59
|
+
state('sensor.humidity', '52', {
|
|
60
|
+
friendly_name: 'Humidity',
|
|
61
|
+
device_class: 'humidity',
|
|
62
|
+
unit_of_measurement: '%',
|
|
63
|
+
}),
|
|
64
|
+
state('sensor.power', '9', {
|
|
65
|
+
friendly_name: 'Power',
|
|
66
|
+
device_class: 'power',
|
|
67
|
+
unit_of_measurement: 'W',
|
|
68
|
+
}),
|
|
69
|
+
state('sensor.energy', '84.3', {
|
|
70
|
+
friendly_name: 'Energy',
|
|
71
|
+
device_class: 'energy',
|
|
72
|
+
unit_of_measurement: 'kWh',
|
|
73
|
+
}),
|
|
74
|
+
state('sensor.not_reporting', 'unknown', {
|
|
75
|
+
friendly_name: 'Silent sensor',
|
|
76
|
+
device_class: 'temperature',
|
|
77
|
+
}),
|
|
78
|
+
state('binary_sensor.door', 'off', { friendly_name: 'Door', device_class: 'door' }),
|
|
79
|
+
state('person.dan', 'home', { friendly_name: 'Dan' }),
|
|
80
|
+
state('person.alex', 'not_home', { friendly_name: 'Alex' }),
|
|
81
|
+
state('scene.movie_night', 'scening', { friendly_name: 'Movie night' }),
|
|
82
|
+
state('script.shower', 'off', { friendly_name: 'Shower' }),
|
|
83
|
+
state('vacuum.robot', 'docked', { friendly_name: 'Robot vacuum' }),
|
|
84
|
+
state('switch.coffee_maker', 'off', { friendly_name: 'Coffee maker' }),
|
|
85
|
+
state('fan.exhaust', 'off', { friendly_name: 'Exhaust fan' }),
|
|
86
|
+
state('lock.front_door', 'locked', { friendly_name: 'Front door' }),
|
|
87
|
+
state('media_player.living_room', 'playing', {
|
|
88
|
+
friendly_name: 'Living room',
|
|
89
|
+
media_title: 'Blank Space',
|
|
90
|
+
media_artist: 'More More',
|
|
91
|
+
volume_level: 0.4,
|
|
92
|
+
media_position: 64,
|
|
93
|
+
media_duration: 231,
|
|
94
|
+
media_position_updated_at: new Date().toISOString(),
|
|
95
|
+
shuffle: false,
|
|
96
|
+
// play, pause, seek, volume, mute, previous, next, shuffle and browse.
|
|
97
|
+
supported_features: 163967,
|
|
98
|
+
}),
|
|
99
|
+
state('input_boolean.guest_mode', 'off', { friendly_name: 'Guest mode' }),
|
|
100
|
+
];
|
|
101
|
+
/** A Home Assistant integration that needs no Home Assistant: representative devices, mapped the
|
|
102
|
+
* same way real ones are, and commands that change their state. For tests, the gallery and
|
|
103
|
+
* dashboards under development. */
|
|
104
|
+
export function createMock(options = {}) {
|
|
105
|
+
const entities = {};
|
|
106
|
+
for (const hass of states()) {
|
|
107
|
+
entities[hass.entity_id] = mapEntity(hass, { temperatureUnit: '°C' });
|
|
108
|
+
}
|
|
109
|
+
return new MockIntegration({
|
|
110
|
+
id: options.id ?? 'ha',
|
|
111
|
+
library: mockLibrary(),
|
|
112
|
+
entities: { ...entities, ...options.entities },
|
|
113
|
+
});
|
|
114
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,41 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hashsome/integration.home-assistant",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Home Assistant integration: entities, services, area/entity registry",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"dependencies": {
|
|
7
|
+
"@hashsome/core": "0.2.0",
|
|
8
|
+
"home-assistant-js-websocket": "9.7.0"
|
|
9
|
+
},
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"./mock": {
|
|
16
|
+
"types": "./dist/mock.d.ts",
|
|
17
|
+
"default": "./dist/mock.js"
|
|
18
|
+
},
|
|
19
|
+
"./package.json": "./package.json"
|
|
20
|
+
},
|
|
21
|
+
"license": "GPL-3.0-only",
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "git+https://github.com/dstoyanoff/hashsome.git",
|
|
25
|
+
"directory": "packages/integrations/home-assistant"
|
|
26
|
+
},
|
|
27
|
+
"homepage": "https://github.com/dstoyanoff/hashsome#readme",
|
|
28
|
+
"bugs": "https://github.com/dstoyanoff/hashsome/issues",
|
|
29
|
+
"files": [
|
|
30
|
+
"dist"
|
|
31
|
+
],
|
|
32
|
+
"publishConfig": {
|
|
33
|
+
"access": "public",
|
|
34
|
+
"provenance": true
|
|
35
|
+
},
|
|
36
|
+
"scripts": {
|
|
37
|
+
"typecheck": "tsc --noEmit",
|
|
38
|
+
"states": "node scripts/print-states.ts",
|
|
39
|
+
"build": "tsc -b tsconfig.build.json"
|
|
40
|
+
}
|
|
6
41
|
}
|
package/README.md
DELETED