@bzenky/spoti 0.2.0 → 0.4.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/README.md +122 -4
- package/dist/app.d.ts +23 -1
- package/dist/app.js +397 -36
- package/dist/app.js.map +1 -1
- package/dist/auth/config.d.ts +1 -1
- package/dist/auth/config.js +3 -3
- package/dist/auth/config.js.map +1 -1
- package/dist/cli.js +54 -0
- package/dist/cli.js.map +1 -1
- package/dist/services/auth.service.d.ts +1 -1
- package/dist/services/auth.service.js +9 -3
- package/dist/services/auth.service.js.map +1 -1
- package/dist/services/catalog.service.d.ts +9 -0
- package/dist/services/catalog.service.js +22 -0
- package/dist/services/catalog.service.js.map +1 -0
- package/dist/services/device.service.js +22 -2
- package/dist/services/device.service.js.map +1 -1
- package/dist/services/library.service.d.ts +13 -0
- package/dist/services/library.service.js +46 -0
- package/dist/services/library.service.js.map +1 -0
- package/dist/services/mappers.d.ts +10 -0
- package/dist/services/mappers.js +104 -0
- package/dist/services/mappers.js.map +1 -0
- package/dist/services/models.d.ts +45 -0
- package/dist/services/player.service.d.ts +4 -1
- package/dist/services/player.service.js +21 -14
- package/dist/services/player.service.js.map +1 -1
- package/dist/services/playlist.service.d.ts +9 -0
- package/dist/services/playlist.service.js +33 -0
- package/dist/services/playlist.service.js.map +1 -0
- package/dist/services/recent.service.d.ts +7 -0
- package/dist/services/recent.service.js +24 -0
- package/dist/services/recent.service.js.map +1 -0
- package/dist/services/search.service.d.ts +5 -1
- package/dist/services/search.service.js +32 -17
- package/dist/services/search.service.js.map +1 -1
- package/dist/services/update.service.d.ts +57 -0
- package/dist/services/update.service.js +313 -0
- package/dist/services/update.service.js.map +1 -0
- package/dist/spotify/client.js +14 -1
- package/dist/spotify/client.js.map +1 -1
- package/dist/spotify/scopes.d.ts +1 -1
- package/dist/spotify/scopes.js +4 -0
- package/dist/spotify/scopes.js.map +1 -1
- package/dist/spotify/types.d.ts +110 -8
- package/dist/storage/config.d.ts +5 -1
- package/dist/storage/config.js +21 -1
- package/dist/storage/config.js.map +1 -1
- package/dist/storage/update-cache.d.ts +29 -0
- package/dist/storage/update-cache.js +135 -0
- package/dist/storage/update-cache.js.map +1 -0
- package/dist/ui/completions.d.ts +3 -0
- package/dist/ui/completions.js +237 -0
- package/dist/ui/completions.js.map +1 -0
- package/dist/ui/interactive-search.d.ts +33 -0
- package/dist/ui/interactive-search.js +174 -0
- package/dist/ui/interactive-search.js.map +1 -0
- package/dist/ui/output.d.ts +10 -1
- package/dist/ui/output.js +60 -0
- package/dist/ui/output.js.map +1 -1
- package/dist/ui/progress.d.ts +6 -0
- package/dist/ui/progress.js +25 -0
- package/dist/ui/progress.js.map +1 -0
- package/dist/ui/prompts.d.ts +12 -1
- package/dist/ui/prompts.js +73 -6
- package/dist/ui/prompts.js.map +1 -1
- package/dist/update-worker.d.ts +9 -0
- package/dist/update-worker.js +91 -0
- package/dist/update-worker.js.map +1 -0
- package/package.json +1 -1
package/dist/spotify/scopes.js
CHANGED
|
@@ -5,5 +5,9 @@ export const LEGACY_SPOTIFY_SCOPES = [
|
|
|
5
5
|
export const SPOTIFY_SCOPES = [
|
|
6
6
|
...LEGACY_SPOTIFY_SCOPES,
|
|
7
7
|
'user-read-currently-playing',
|
|
8
|
+
'playlist-read-private',
|
|
9
|
+
'user-library-read',
|
|
10
|
+
'user-library-modify',
|
|
11
|
+
'user-read-recently-played',
|
|
8
12
|
];
|
|
9
13
|
//# sourceMappingURL=scopes.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scopes.js","sourceRoot":"","sources":["../../src/spotify/scopes.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,qBAAqB,GAAG;IACnC,0BAA0B;IAC1B,4BAA4B;CACpB,CAAC;AAEX,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,GAAG,qBAAqB;IACxB,6BAA6B;
|
|
1
|
+
{"version":3,"file":"scopes.js","sourceRoot":"","sources":["../../src/spotify/scopes.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,qBAAqB,GAAG;IACnC,0BAA0B;IAC1B,4BAA4B;CACpB,CAAC;AAEX,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,GAAG,qBAAqB;IACxB,6BAA6B;IAC7B,uBAAuB;IACvB,mBAAmB;IACnB,qBAAqB;IACrB,2BAA2B;CACnB,CAAC"}
|
package/dist/spotify/types.d.ts
CHANGED
|
@@ -3,6 +3,9 @@ export interface SpotifyImage {
|
|
|
3
3
|
height: number | null;
|
|
4
4
|
width: number | null;
|
|
5
5
|
}
|
|
6
|
+
export interface SpotifyExternalUrls {
|
|
7
|
+
spotify?: string;
|
|
8
|
+
}
|
|
6
9
|
export interface SpotifyUserProfile {
|
|
7
10
|
id: string;
|
|
8
11
|
display_name: string | null;
|
|
@@ -10,26 +13,117 @@ export interface SpotifyUserProfile {
|
|
|
10
13
|
}
|
|
11
14
|
export interface SpotifyArtist {
|
|
12
15
|
id: string;
|
|
16
|
+
uri?: string;
|
|
17
|
+
name: string;
|
|
18
|
+
external_urls?: SpotifyExternalUrls;
|
|
19
|
+
}
|
|
20
|
+
export interface SpotifyFullArtist extends SpotifyArtist {
|
|
21
|
+
uri: string;
|
|
22
|
+
images: SpotifyImage[];
|
|
23
|
+
}
|
|
24
|
+
export interface SpotifySimplifiedAlbum {
|
|
25
|
+
id?: string;
|
|
26
|
+
uri?: string;
|
|
13
27
|
name: string;
|
|
28
|
+
album_type?: string;
|
|
29
|
+
artists?: SpotifyArtist[];
|
|
30
|
+
images: SpotifyImage[];
|
|
31
|
+
release_date?: string;
|
|
32
|
+
total_tracks?: number;
|
|
33
|
+
external_urls?: SpotifyExternalUrls;
|
|
14
34
|
}
|
|
15
35
|
export interface SpotifyTrack {
|
|
16
|
-
|
|
36
|
+
type?: 'track';
|
|
37
|
+
id: string | null;
|
|
17
38
|
uri: string;
|
|
18
39
|
name: string;
|
|
19
40
|
duration_ms: number;
|
|
20
41
|
artists: SpotifyArtist[];
|
|
21
|
-
album:
|
|
22
|
-
|
|
23
|
-
|
|
42
|
+
album: SpotifySimplifiedAlbum;
|
|
43
|
+
is_local?: boolean;
|
|
44
|
+
is_playable?: boolean;
|
|
45
|
+
external_urls?: SpotifyExternalUrls;
|
|
46
|
+
}
|
|
47
|
+
export interface SpotifySimplifiedTrack {
|
|
48
|
+
type?: 'track';
|
|
49
|
+
id: string | null;
|
|
50
|
+
uri: string;
|
|
51
|
+
name: string;
|
|
52
|
+
duration_ms: number;
|
|
53
|
+
artists: SpotifyArtist[];
|
|
54
|
+
is_local?: boolean;
|
|
55
|
+
is_playable?: boolean;
|
|
56
|
+
external_urls?: SpotifyExternalUrls;
|
|
57
|
+
}
|
|
58
|
+
export interface SpotifyAlbum extends SpotifySimplifiedAlbum {
|
|
59
|
+
id: string;
|
|
60
|
+
uri: string;
|
|
61
|
+
artists: SpotifyArtist[];
|
|
62
|
+
tracks: SpotifyPaging<SpotifySimplifiedTrack>;
|
|
63
|
+
}
|
|
64
|
+
export interface SpotifyPlaylistOwner {
|
|
65
|
+
id: string;
|
|
66
|
+
display_name: string | null;
|
|
67
|
+
}
|
|
68
|
+
export interface SpotifySimplifiedPlaylist {
|
|
69
|
+
id: string;
|
|
70
|
+
uri: string;
|
|
71
|
+
name: string;
|
|
72
|
+
description: string | null;
|
|
73
|
+
public: boolean | null;
|
|
74
|
+
collaborative: boolean;
|
|
75
|
+
owner: SpotifyPlaylistOwner;
|
|
76
|
+
images: SpotifyImage[] | null;
|
|
77
|
+
external_urls?: SpotifyExternalUrls;
|
|
78
|
+
items: {
|
|
79
|
+
total: number;
|
|
24
80
|
};
|
|
25
|
-
|
|
26
|
-
|
|
81
|
+
}
|
|
82
|
+
export interface SpotifyPlaylist extends SpotifySimplifiedPlaylist {
|
|
83
|
+
followers?: {
|
|
84
|
+
total: number;
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
export interface SpotifyEpisode {
|
|
88
|
+
type: 'episode';
|
|
89
|
+
id?: string | null;
|
|
90
|
+
uri?: string;
|
|
91
|
+
name?: string;
|
|
92
|
+
}
|
|
93
|
+
export type SpotifyPlaybackItem = SpotifyTrack | SpotifyEpisode | Record<string, unknown>;
|
|
94
|
+
export interface SpotifyPlaylistItem {
|
|
95
|
+
added_at: string | null;
|
|
96
|
+
is_local: boolean;
|
|
97
|
+
item: SpotifyPlaybackItem | null;
|
|
98
|
+
}
|
|
99
|
+
export interface SpotifyRecentlyPlayedItem {
|
|
100
|
+
track: SpotifyPlaybackItem | null;
|
|
101
|
+
played_at: string;
|
|
102
|
+
context: {
|
|
103
|
+
uri: string;
|
|
104
|
+
} | null;
|
|
105
|
+
}
|
|
106
|
+
export interface SpotifyPaging<T> {
|
|
107
|
+
items: T[];
|
|
108
|
+
limit: number;
|
|
109
|
+
offset: number;
|
|
110
|
+
total: number;
|
|
111
|
+
next: string | null;
|
|
112
|
+
previous: string | null;
|
|
113
|
+
}
|
|
114
|
+
export interface SpotifyCursorPaging<T> {
|
|
115
|
+
items: T[];
|
|
116
|
+
limit: number;
|
|
117
|
+
next: string | null;
|
|
118
|
+
cursors?: {
|
|
119
|
+
after?: string;
|
|
120
|
+
before?: string;
|
|
27
121
|
};
|
|
28
122
|
}
|
|
29
123
|
export interface SpotifyPlaybackState {
|
|
30
124
|
is_playing: boolean;
|
|
31
125
|
progress_ms: number | null;
|
|
32
|
-
item:
|
|
126
|
+
item: SpotifyPlaybackItem | null;
|
|
33
127
|
device: {
|
|
34
128
|
id: string | null;
|
|
35
129
|
name: string;
|
|
@@ -39,10 +133,18 @@ export interface SpotifyPlaybackState {
|
|
|
39
133
|
};
|
|
40
134
|
}
|
|
41
135
|
export interface SpotifySearchResponse {
|
|
42
|
-
tracks
|
|
136
|
+
tracks?: SpotifyPaging<SpotifyTrack> | {
|
|
43
137
|
items: SpotifyTrack[];
|
|
44
138
|
};
|
|
139
|
+
albums?: SpotifyPaging<SpotifySimplifiedAlbum>;
|
|
140
|
+
artists?: SpotifyPaging<SpotifyFullArtist>;
|
|
141
|
+
playlists?: SpotifyPaging<SpotifySimplifiedPlaylist | null>;
|
|
142
|
+
}
|
|
143
|
+
export interface SpotifySavedTrack {
|
|
144
|
+
added_at: string;
|
|
145
|
+
track: SpotifyTrack;
|
|
45
146
|
}
|
|
147
|
+
export type SpotifyRecentlyPlayedResponse = SpotifyCursorPaging<SpotifyRecentlyPlayedItem>;
|
|
46
148
|
export interface SpotifyErrorBody {
|
|
47
149
|
error?: {
|
|
48
150
|
status?: number;
|
package/dist/storage/config.d.ts
CHANGED
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
declare const configSchema: z.ZodObject<{
|
|
3
|
+
spotifyClientId: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
|
3
4
|
watchAfterPlay: z.ZodDefault<z.ZodBoolean>;
|
|
4
5
|
refreshIntervalMs: z.ZodDefault<z.ZodNumber>;
|
|
5
6
|
}, z.core.$strict>;
|
|
6
7
|
export type AppConfig = z.infer<typeof configSchema>;
|
|
7
|
-
export declare const CONFIG_KEYS: readonly ["watchAfterPlay", "refreshIntervalMs"];
|
|
8
|
+
export declare const CONFIG_KEYS: readonly ["spotifyClientId", "watchAfterPlay", "refreshIntervalMs"];
|
|
8
9
|
export type ConfigKey = (typeof CONFIG_KEYS)[number];
|
|
9
10
|
export declare const DEFAULT_CONFIG: Readonly<AppConfig>;
|
|
10
11
|
export interface ConfigStore {
|
|
12
|
+
readonly path?: string;
|
|
11
13
|
read(): Promise<AppConfig>;
|
|
12
14
|
write(config: AppConfig): Promise<void>;
|
|
13
15
|
set<Key extends ConfigKey>(key: Key, value: AppConfig[Key]): Promise<void>;
|
|
16
|
+
resetKey<Key extends ConfigKey>(key: Key): Promise<void>;
|
|
14
17
|
reset(): Promise<void>;
|
|
15
18
|
}
|
|
16
19
|
export interface FileConfigStoreOptions {
|
|
@@ -23,6 +26,7 @@ export declare class FileConfigStore implements ConfigStore {
|
|
|
23
26
|
read(): Promise<AppConfig>;
|
|
24
27
|
write(config: AppConfig): Promise<void>;
|
|
25
28
|
set<Key extends ConfigKey>(key: Key, value: AppConfig[Key]): Promise<void>;
|
|
29
|
+
resetKey<Key extends ConfigKey>(key: Key): Promise<void>;
|
|
26
30
|
reset(): Promise<void>;
|
|
27
31
|
}
|
|
28
32
|
export declare function parseConfigValue<Key extends ConfigKey>(key: Key, value: string): AppConfig[Key];
|
package/dist/storage/config.js
CHANGED
|
@@ -4,11 +4,22 @@ import { join } from 'node:path';
|
|
|
4
4
|
import { z } from 'zod';
|
|
5
5
|
import { ConfigurationError, toError } from '../utils/errors.js';
|
|
6
6
|
import { getConfigDirectory } from './credentials.js';
|
|
7
|
+
const spotifyClientIdSchema = z
|
|
8
|
+
.string()
|
|
9
|
+
.trim()
|
|
10
|
+
.min(1)
|
|
11
|
+
.max(256)
|
|
12
|
+
.regex(/^[A-Za-z0-9]+$/);
|
|
7
13
|
const configSchema = z.strictObject({
|
|
14
|
+
spotifyClientId: spotifyClientIdSchema.nullable().default(null),
|
|
8
15
|
watchAfterPlay: z.boolean().default(false),
|
|
9
16
|
refreshIntervalMs: z.number().int().min(1_000).max(30_000).default(1_000),
|
|
10
17
|
});
|
|
11
|
-
export const CONFIG_KEYS = [
|
|
18
|
+
export const CONFIG_KEYS = [
|
|
19
|
+
'spotifyClientId',
|
|
20
|
+
'watchAfterPlay',
|
|
21
|
+
'refreshIntervalMs',
|
|
22
|
+
];
|
|
12
23
|
export const DEFAULT_CONFIG = Object.freeze(configSchema.parse({}));
|
|
13
24
|
export class FileConfigStore {
|
|
14
25
|
path;
|
|
@@ -72,6 +83,9 @@ export class FileConfigStore {
|
|
|
72
83
|
const current = await this.read();
|
|
73
84
|
await this.write({ ...current, [key]: value });
|
|
74
85
|
}
|
|
86
|
+
async resetKey(key) {
|
|
87
|
+
await this.set(key, DEFAULT_CONFIG[key]);
|
|
88
|
+
}
|
|
75
89
|
async reset() {
|
|
76
90
|
try {
|
|
77
91
|
await unlink(this.path);
|
|
@@ -84,6 +98,12 @@ export class FileConfigStore {
|
|
|
84
98
|
}
|
|
85
99
|
}
|
|
86
100
|
export function parseConfigValue(key, value) {
|
|
101
|
+
if (key === 'spotifyClientId') {
|
|
102
|
+
const clientId = spotifyClientIdSchema.safeParse(value);
|
|
103
|
+
if (clientId.success)
|
|
104
|
+
return clientId.data;
|
|
105
|
+
throw new ConfigurationError('Invalid Spotify client ID: expected a non-empty alphanumeric value.');
|
|
106
|
+
}
|
|
87
107
|
if (key === 'watchAfterPlay') {
|
|
88
108
|
if (value === 'true')
|
|
89
109
|
return true;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/storage/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACrF,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,kBAAkB,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AACjE,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAEtD,MAAM,YAAY,GAAG,CAAC,CAAC,YAAY,CAAC;IAClC,cAAc,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;IAC1C,iBAAiB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC;CAC1E,CAAC,CAAC;AAIH,MAAM,CAAC,MAAM,WAAW,GAAG,
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/storage/config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export interface SuccessfulUpdateCheckCache {
|
|
2
|
+
checkedAt: number;
|
|
3
|
+
currentVersion: string;
|
|
4
|
+
latestVersion: string;
|
|
5
|
+
}
|
|
6
|
+
export interface UpdateCache {
|
|
7
|
+
version: 1;
|
|
8
|
+
lastCheckedAt: number;
|
|
9
|
+
lastSuccessfulCheck?: SuccessfulUpdateCheckCache;
|
|
10
|
+
lastFailedCheckAt?: number;
|
|
11
|
+
}
|
|
12
|
+
export interface UpdateCacheStore {
|
|
13
|
+
read(): Promise<UpdateCache | null>;
|
|
14
|
+
write(cache: UpdateCache): Promise<void>;
|
|
15
|
+
}
|
|
16
|
+
export interface FileUpdateCacheStoreOptions {
|
|
17
|
+
directory?: string;
|
|
18
|
+
}
|
|
19
|
+
export declare class UpdateCacheError extends Error {
|
|
20
|
+
constructor(message: string, options?: ErrorOptions);
|
|
21
|
+
}
|
|
22
|
+
export declare class FileUpdateCacheStore implements UpdateCacheStore {
|
|
23
|
+
readonly path: string;
|
|
24
|
+
private readonly directory;
|
|
25
|
+
constructor(options?: FileUpdateCacheStoreOptions);
|
|
26
|
+
read(): Promise<UpdateCache | null>;
|
|
27
|
+
write(cache: UpdateCache): Promise<void>;
|
|
28
|
+
}
|
|
29
|
+
export declare function parseUpdateCache(value: unknown): UpdateCache;
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { chmod, mkdir, readFile, rename, unlink, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { getConfigDirectory } from './credentials.js';
|
|
5
|
+
export class UpdateCacheError extends Error {
|
|
6
|
+
constructor(message, options) {
|
|
7
|
+
super(message, options);
|
|
8
|
+
this.name = 'UpdateCacheError';
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export class FileUpdateCacheStore {
|
|
12
|
+
path;
|
|
13
|
+
directory;
|
|
14
|
+
constructor(options = {}) {
|
|
15
|
+
this.directory = options.directory ?? getConfigDirectory();
|
|
16
|
+
this.path = join(this.directory, 'update-cache.json');
|
|
17
|
+
}
|
|
18
|
+
async read() {
|
|
19
|
+
let contents;
|
|
20
|
+
try {
|
|
21
|
+
contents = await readFile(this.path, 'utf8');
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
if (isFileNotFound(error))
|
|
25
|
+
return null;
|
|
26
|
+
throw new UpdateCacheError(`Unable to read the update cache: ${errorMessage(error)}`, {
|
|
27
|
+
cause: error,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
return parseUpdateCache(JSON.parse(contents));
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
// Cache data is disposable. Treat malformed or partially written data as a cache miss.
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
async write(cache) {
|
|
39
|
+
const validated = parseUpdateCache(cache);
|
|
40
|
+
try {
|
|
41
|
+
await mkdir(this.directory, { recursive: true, mode: 0o700 });
|
|
42
|
+
await chmod(this.directory, 0o700);
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
throw new UpdateCacheError(`Unable to prepare the update cache directory: ${errorMessage(error)}`, {
|
|
46
|
+
cause: error,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
const temporaryPath = join(this.directory, `.update-cache-${randomUUID()}.tmp`);
|
|
50
|
+
try {
|
|
51
|
+
await writeFile(temporaryPath, `${JSON.stringify(validated, null, 2)}\n`, {
|
|
52
|
+
encoding: 'utf8',
|
|
53
|
+
flag: 'wx',
|
|
54
|
+
mode: 0o600,
|
|
55
|
+
});
|
|
56
|
+
await chmod(temporaryPath, 0o600);
|
|
57
|
+
await rename(temporaryPath, this.path);
|
|
58
|
+
await chmod(this.path, 0o600);
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
await unlink(temporaryPath).catch(() => undefined);
|
|
62
|
+
throw new UpdateCacheError(`Unable to store the update cache: ${errorMessage(error)}`, {
|
|
63
|
+
cause: error,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
export function parseUpdateCache(value) {
|
|
69
|
+
if (!isRecord(value) || !hasOnlyKeys(value, CACHE_KEYS)) {
|
|
70
|
+
throw new UpdateCacheError('The update cache has an invalid format.');
|
|
71
|
+
}
|
|
72
|
+
if (value.version !== 1 || !isTimestamp(value.lastCheckedAt)) {
|
|
73
|
+
throw new UpdateCacheError('The update cache has an invalid format.');
|
|
74
|
+
}
|
|
75
|
+
const successful = parseSuccessfulCheck(value.lastSuccessfulCheck);
|
|
76
|
+
const failedAt = value.lastFailedCheckAt;
|
|
77
|
+
if (failedAt !== undefined && !isTimestamp(failedAt)) {
|
|
78
|
+
throw new UpdateCacheError('The update cache has an invalid format.');
|
|
79
|
+
}
|
|
80
|
+
if (!successful && failedAt === undefined) {
|
|
81
|
+
throw new UpdateCacheError('The update cache has an invalid format.');
|
|
82
|
+
}
|
|
83
|
+
const newestOutcomeAt = Math.max(successful?.checkedAt ?? -1, failedAt ?? -1);
|
|
84
|
+
if (value.lastCheckedAt !== newestOutcomeAt) {
|
|
85
|
+
throw new UpdateCacheError('The update cache has inconsistent timestamps.');
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
version: 1,
|
|
89
|
+
lastCheckedAt: value.lastCheckedAt,
|
|
90
|
+
...(successful ? { lastSuccessfulCheck: successful } : {}),
|
|
91
|
+
...(failedAt === undefined ? {} : { lastFailedCheckAt: failedAt }),
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
const CACHE_KEYS = new Set([
|
|
95
|
+
'version',
|
|
96
|
+
'lastCheckedAt',
|
|
97
|
+
'lastSuccessfulCheck',
|
|
98
|
+
'lastFailedCheckAt',
|
|
99
|
+
]);
|
|
100
|
+
const SUCCESS_KEYS = new Set(['checkedAt', 'currentVersion', 'latestVersion']);
|
|
101
|
+
function parseSuccessfulCheck(value) {
|
|
102
|
+
if (value === undefined)
|
|
103
|
+
return undefined;
|
|
104
|
+
if (!isRecord(value) ||
|
|
105
|
+
!hasOnlyKeys(value, SUCCESS_KEYS) ||
|
|
106
|
+
!isTimestamp(value.checkedAt) ||
|
|
107
|
+
!isNonemptyString(value.currentVersion) ||
|
|
108
|
+
!isNonemptyString(value.latestVersion)) {
|
|
109
|
+
throw new UpdateCacheError('The update cache has an invalid successful check.');
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
checkedAt: value.checkedAt,
|
|
113
|
+
currentVersion: value.currentVersion,
|
|
114
|
+
latestVersion: value.latestVersion,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
function isRecord(value) {
|
|
118
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
119
|
+
}
|
|
120
|
+
function hasOnlyKeys(value, allowed) {
|
|
121
|
+
return Object.keys(value).every((key) => allowed.has(key));
|
|
122
|
+
}
|
|
123
|
+
function isTimestamp(value) {
|
|
124
|
+
return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0;
|
|
125
|
+
}
|
|
126
|
+
function isNonemptyString(value) {
|
|
127
|
+
return typeof value === 'string' && value.length > 0;
|
|
128
|
+
}
|
|
129
|
+
function isFileNotFound(error) {
|
|
130
|
+
return isRecord(error) && error.code === 'ENOENT';
|
|
131
|
+
}
|
|
132
|
+
function errorMessage(error) {
|
|
133
|
+
return error instanceof Error ? error.message : String(error);
|
|
134
|
+
}
|
|
135
|
+
//# sourceMappingURL=update-cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"update-cache.js","sourceRoot":"","sources":["../../src/storage/update-cache.ts"],"names":[],"mappings":"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"}
|