@bscotch/gamemaker-releases 0.4.0 → 0.4.2

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.
@@ -1 +1 @@
1
- {"version":3,"file":"feeds.types.d.ts","sourceRoot":"","sources":["../src/feeds.types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,MAAM,OAAO,GAAG,OAAO,QAAQ,CAAC,MAAM,CAAC,CAAC;AAC9C,eAAO,MAAM,QAAQ,gDAAiD,CAAC;AACvE,eAAO,MAAM,aAAa,kDAAmB,CAAC;AAG9C,MAAM,MAAM,YAAY,GAAG,OAAO,aAAa,CAAC,MAAM,CAAC,CAAC;AACxD,eAAO,MAAM,aAAa,6BAA8B,CAAC;AAGzD,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAyBxB,CAAC;AAEH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AACxE,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;;EASlC,CAAC;AAEH,MAAM,MAAM,0BAA0B,GAAG,CAAC,CAAC,KAAK,CAC9C,OAAO,gCAAgC,CACxC,CAAC;AACF,eAAO,MAAM,gCAAgC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAU3C,CAAC;AAUH,MAAM,MAAM,yBAAyB,GAAG,CAAC,CAAC,KAAK,CAC7C,OAAO,+BAA+B,CACvC,CAAC;AACF,eAAO,MAAM,+BAA+B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAIxC,CAAC;AAEL,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AACtE,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAGjC,CAAC;AAEH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAClE,eAAO,MAAM,oBAAoB;;;;;;;;;;;;EAMtB,CAAC;AAEZ,MAAM,MAAM,oBAAoB,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;AAClE,eAAO,MAAM,0BAA0B;;;;;;;;;;;;GAAiC,CAAC"}
1
+ {"version":3,"file":"feeds.types.d.ts","sourceRoot":"","sources":["../src/feeds.types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,MAAM,OAAO,GAAG,OAAO,QAAQ,CAAC,MAAM,CAAC,CAAC;AAC9C,eAAO,MAAM,QAAQ,gDAAiD,CAAC;AACvE,eAAO,MAAM,aAAa,kDAAmB,CAAC;AAG9C,MAAM,MAAM,YAAY,GAAG,OAAO,aAAa,CAAC,MAAM,CAAC,CAAC;AACxD,eAAO,MAAM,aAAa,6BAA8B,CAAC;AAGzD,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAyBxB,CAAC;AAEH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AACxE,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;;EASlC,CAAC;AAEH,MAAM,MAAM,0BAA0B,GAAG,CAAC,CAAC,KAAK,CAC9C,OAAO,gCAAgC,CACxC,CAAC;AACF,eAAO,MAAM,gCAAgC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAU3C,CAAC;AAUH,MAAM,MAAM,yBAAyB,GAAG,CAAC,CAAC,KAAK,CAC7C,OAAO,+BAA+B,CACvC,CAAC;AACF,eAAO,MAAM,+BAA+B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAIxC,CAAC;AAEL,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AACtE,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAGjC,CAAC;AAEH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAClE,eAAO,MAAM,oBAAoB;;;;;;;;;;;;EAMtB,CAAC;AAEZ,MAAM,MAAM,oBAAoB,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;AAClE,eAAO,MAAM,0BAA0B;;;;;;;;;;;;GAAiC,CAAC"}
@@ -1,69 +1,69 @@
1
- import { z } from 'zod';
2
- import { htmlString } from './utils.js';
3
- export const channels = ['lts', 'stable', 'beta', 'unstable'];
4
- export const channelSchema = z.enum(channels);
5
- Object.freeze(channels);
6
- export const artifactTypes = ['ide', 'runtime'];
7
- Object.freeze(artifactTypes);
8
- export const rssFeedSchema = z.object({
9
- rss: z.object({
10
- channel: z.object({
11
- title: htmlString(),
12
- description: htmlString(),
13
- link: z.string(),
14
- item: z.preprocess((arg) => {
15
- if (!Array.isArray(arg)) {
16
- return [arg];
17
- }
18
- return arg;
19
- }, z.array(z.object({
20
- title: z.string().regex(/^Version \d+\.\d+\.\d+\.\d+$/),
21
- pubDate: z.string(),
22
- link: z.string().optional(),
23
- comments: z.string(),
24
- description: htmlString().optional(),
25
- }))),
26
- }),
27
- }),
28
- });
29
- export const gameMakerArtifactSchema = z.object({
30
- type: z.enum(artifactTypes),
31
- version: z.string().regex(/^\d+\.\d+\.\d+\.\d+$/),
32
- channel: channelSchema,
33
- summary: z.string().optional(),
34
- feedUrl: z.string(),
35
- publishedAt: z.string(),
36
- link: z.string().optional(),
37
- notesUrl: z.string(),
38
- });
39
- export const gameMakerArtifactWithNotesSchema = gameMakerArtifactSchema.extend({
40
- notes: z.object({
41
- since: z.string().nullable(),
42
- groups: z.array(z.object({
43
- title: htmlString(),
44
- changes: z.array(htmlString()),
45
- })),
46
- }),
47
- });
48
- const gameMakerReleaseBaseSchema = z.object({
49
- channel: channelSchema,
50
- publishedAt: z.string().describe('Date of release for the IDE in this pair'),
51
- summary: htmlString().describe('Summary of the release, from the RSS feed for the IDE'),
52
- });
53
- export const gameMakerReleaseWithNotesSchema = gameMakerReleaseBaseSchema.extend({
54
- ide: gameMakerArtifactWithNotesSchema.omit({ summary: true }),
55
- runtime: gameMakerArtifactWithNotesSchema.omit({ summary: true }),
56
- });
57
- export const gameMakerReleaseSchema = gameMakerReleaseBaseSchema.extend({
58
- ide: gameMakerArtifactSchema.omit({ summary: true }),
59
- runtime: gameMakerArtifactSchema.omit({ summary: true }),
60
- });
61
- export const rawReleaseNoteSchema = z
62
- .object({
63
- type: z.enum(artifactTypes).optional(),
64
- version: z.string(),
65
- release_notes: z.array(z.string()),
66
- })
67
- .strict();
68
- export const rawReleaseNotesCacheSchema = z.record(rawReleaseNoteSchema);
1
+ import { z } from 'zod';
2
+ import { htmlString } from './utils.js';
3
+ export const channels = ['lts', 'stable', 'beta', 'unstable'];
4
+ export const channelSchema = z.enum(channels);
5
+ Object.freeze(channels);
6
+ export const artifactTypes = ['ide', 'runtime'];
7
+ Object.freeze(artifactTypes);
8
+ export const rssFeedSchema = z.object({
9
+ rss: z.object({
10
+ channel: z.object({
11
+ title: htmlString(),
12
+ description: htmlString(),
13
+ link: z.string(),
14
+ item: z.preprocess((arg) => {
15
+ if (!Array.isArray(arg)) {
16
+ return [arg];
17
+ }
18
+ return arg;
19
+ }, z.array(z.object({
20
+ title: z.string().regex(/^Version \d+\.\d+\.\d+\.\d+$/),
21
+ pubDate: z.string(),
22
+ link: z.string().optional(),
23
+ comments: z.string(),
24
+ description: htmlString().optional(),
25
+ }))),
26
+ }),
27
+ }),
28
+ });
29
+ export const gameMakerArtifactSchema = z.object({
30
+ type: z.enum(artifactTypes),
31
+ version: z.string().regex(/^\d+\.\d+\.\d+\.\d+$/),
32
+ channel: channelSchema,
33
+ summary: z.string().optional(),
34
+ feedUrl: z.string(),
35
+ publishedAt: z.string(),
36
+ link: z.string().optional(),
37
+ notesUrl: z.string(),
38
+ });
39
+ export const gameMakerArtifactWithNotesSchema = gameMakerArtifactSchema.extend({
40
+ notes: z.object({
41
+ since: z.string().nullable(),
42
+ groups: z.array(z.object({
43
+ title: htmlString(),
44
+ changes: z.array(htmlString()),
45
+ })),
46
+ }),
47
+ });
48
+ const gameMakerReleaseBaseSchema = z.object({
49
+ channel: channelSchema,
50
+ publishedAt: z.string().describe('Date of release for the IDE in this pair'),
51
+ summary: htmlString().describe('Summary of the release, from the RSS feed for the IDE'),
52
+ });
53
+ export const gameMakerReleaseWithNotesSchema = gameMakerReleaseBaseSchema.extend({
54
+ ide: gameMakerArtifactWithNotesSchema.omit({ summary: true }),
55
+ runtime: gameMakerArtifactWithNotesSchema.omit({ summary: true }),
56
+ });
57
+ export const gameMakerReleaseSchema = gameMakerReleaseBaseSchema.extend({
58
+ ide: gameMakerArtifactSchema.omit({ summary: true }),
59
+ runtime: gameMakerArtifactSchema.omit({ summary: true }),
60
+ });
61
+ export const rawReleaseNoteSchema = z
62
+ .object({
63
+ type: z.enum(artifactTypes).optional(),
64
+ version: z.string(),
65
+ release_notes: z.array(z.string()),
66
+ })
67
+ .strict();
68
+ export const rawReleaseNotesCacheSchema = z.record(rawReleaseNoteSchema);
69
69
  //# sourceMappingURL=feeds.types.js.map
package/dist/fetch.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- export type Validator<T> = {
2
- parse: (data: any) => T;
3
- };
4
- export declare function fetchXml<T = unknown>(url: string, validator?: Validator<T>): Promise<T>;
5
- export declare function fetchJson<T = unknown>(url: string, validator?: Validator<T>): Promise<T>;
1
+ export type Validator<T> = {
2
+ parse: (data: any) => T;
3
+ };
4
+ export declare function fetchXml<T = unknown>(url: string, validator?: Validator<T>): Promise<T>;
5
+ export declare function fetchJson<T = unknown>(url: string, validator?: Validator<T>): Promise<T>;
6
6
  //# sourceMappingURL=fetch.d.ts.map
package/dist/fetch.js CHANGED
@@ -1,37 +1,37 @@
1
- import fetch from 'node-fetch';
2
- import { XMLParser } from 'fast-xml-parser';
3
- import { isError } from './utils.js';
4
- export async function fetchXml(url, validator) {
5
- const res = await fetchUrl(url);
6
- try {
7
- const data = await res.text();
8
- const asJson = new XMLParser().parse(data);
9
- return validator ? validator.parse(asJson) : asJson;
10
- }
11
- catch (err) {
12
- throw new Error(`Error parsing XML from ${url}: ${isError(err) ? err.message : 'UNKNOWN'}`);
13
- }
14
- }
15
- export async function fetchJson(url, validator) {
16
- const res = await fetchUrl(url);
17
- try {
18
- const parsed = await res.json();
19
- return validator ? validator.parse(parsed) : parsed;
20
- }
21
- catch (err) {
22
- throw new Error(`Error parsing JSON from ${url}: ${isError(err) ? err.message : 'UNKNOWN'}`);
23
- }
24
- }
25
- async function fetchUrl(url) {
26
- try {
27
- const res = await fetch(url);
28
- if (res.status >= 300) {
29
- throw new Error(`Error fetching "${url}": ${res.status} ${res.statusText}`);
30
- }
31
- return res;
32
- }
33
- catch (err) {
34
- throw new Error(`Error fetching "${url}": ${isError(err) ? err.message : 'UNKNOWN'}`);
35
- }
36
- }
1
+ import fetch from 'node-fetch';
2
+ import { XMLParser } from 'fast-xml-parser';
3
+ import { isError } from './utils.js';
4
+ export async function fetchXml(url, validator) {
5
+ const res = await fetchUrl(url);
6
+ try {
7
+ const data = await res.text();
8
+ const asJson = new XMLParser().parse(data);
9
+ return validator ? validator.parse(asJson) : asJson;
10
+ }
11
+ catch (err) {
12
+ throw new Error(`Error parsing XML from ${url}: ${isError(err) ? err.message : 'UNKNOWN'}`);
13
+ }
14
+ }
15
+ export async function fetchJson(url, validator) {
16
+ const res = await fetchUrl(url);
17
+ try {
18
+ const parsed = await res.json();
19
+ return validator ? validator.parse(parsed) : parsed;
20
+ }
21
+ catch (err) {
22
+ throw new Error(`Error parsing JSON from ${url}: ${isError(err) ? err.message : 'UNKNOWN'}`);
23
+ }
24
+ }
25
+ async function fetchUrl(url) {
26
+ try {
27
+ const res = await fetch(url);
28
+ if (res.status >= 300) {
29
+ throw new Error(`Error fetching "${url}": ${res.status} ${res.statusText}`);
30
+ }
31
+ return res;
32
+ }
33
+ catch (err) {
34
+ throw new Error(`Error fetching "${url}": ${isError(err) ? err.message : 'UNKNOWN'}`);
35
+ }
36
+ }
37
37
  //# sourceMappingURL=fetch.js.map
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
- export * from './browser.js';
2
- export { listReleases, listReleasesWithNotes } from './feeds.js';
1
+ export * from './browser.js';
2
+ export { computeReleasesSummary, computeReleasesSummaryWithNotes, } from './feeds.js';
3
+ export { fetchReleasesSummaryWithNotes } from './releases.js';
3
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,OAAO,EAAE,YAAY,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,OAAO,EACL,sBAAsB,EACtB,+BAA+B,GAChC,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,6BAA6B,EAAE,MAAM,eAAe,CAAC"}
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
- export * from './browser.js';
2
- export { listReleases, listReleasesWithNotes } from './feeds.js';
1
+ export * from './browser.js';
2
+ export { computeReleasesSummary, computeReleasesSummaryWithNotes, } from './feeds.js';
3
+ export { fetchReleasesSummaryWithNotes } from './releases.js';
3
4
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,OAAO,EAAE,YAAY,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,OAAO,EACL,sBAAsB,EACtB,+BAA+B,GAChC,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,6BAA6B,EAAE,MAAM,eAAe,CAAC"}
package/dist/notes.d.ts CHANGED
@@ -1,34 +1,34 @@
1
- import { Pathy } from '@bscotch/pathy';
2
- import { ArtifactType, GameMakerRelease } from './feeds.types.js';
3
- export declare function listReleaseNotes(releases: GameMakerRelease[], cache?: Pathy | string): Promise<Record<string, {
4
- version: string;
5
- url: string;
6
- title: string | null;
7
- type: "ide" | "runtime";
8
- changes: {
9
- since: string | null;
10
- groups: {
11
- title: string;
12
- changes: string[];
13
- }[];
14
- };
15
- }>>;
16
- export declare function cleanNote(note: {
17
- type?: ArtifactType;
18
- version: string;
19
- url: string;
20
- release_notes: string[];
21
- }): {
22
- version: string;
23
- url: string;
24
- title: string | null;
25
- type: "ide" | "runtime";
26
- changes: {
27
- since: string | null;
28
- groups: {
29
- title: string;
30
- changes: string[];
31
- }[];
32
- };
33
- };
1
+ import { Pathy } from '@bscotch/pathy';
2
+ import { ArtifactType, GameMakerRelease } from './feeds.types.js';
3
+ export declare function listReleaseNotes(releases: GameMakerRelease[], cache?: Pathy | string): Promise<Record<string, {
4
+ version: string;
5
+ url: string;
6
+ title: string | null;
7
+ type: "ide" | "runtime";
8
+ changes: {
9
+ since: string | null;
10
+ groups: {
11
+ title: string;
12
+ changes: string[];
13
+ }[];
14
+ };
15
+ }>>;
16
+ export declare function cleanNote(note: {
17
+ type?: ArtifactType;
18
+ version: string;
19
+ url: string;
20
+ release_notes: string[];
21
+ }): {
22
+ version: string;
23
+ url: string;
24
+ title: string | null;
25
+ type: "ide" | "runtime";
26
+ changes: {
27
+ since: string | null;
28
+ groups: {
29
+ title: string;
30
+ changes: string[];
31
+ }[];
32
+ };
33
+ };
34
34
  //# sourceMappingURL=notes.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"notes.d.ts","sourceRoot":"","sources":["../src/notes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAGvC,OAAO,EACL,YAAY,EACZ,gBAAgB,EAIjB,MAAM,kBAAkB,CAAC;AAI1B,wBAAsB,gBAAgB,CACpC,QAAQ,EAAE,gBAAgB,EAAE,EAC5B,KAAK,GAAE,KAAK,GAAG,MAA8B;;;;;;;;;;;;IA8B9C;AAqDD,wBAAgB,SAAS,CAAC,IAAI,EAAE;IAC9B,IAAI,CAAC,EAAE,YAAY,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,aAAa,EAAE,MAAM,EAAE,CAAC;CACzB;;;;;;;;;;;;EAiBA"}
1
+ {"version":3,"file":"notes.d.ts","sourceRoot":"","sources":["../src/notes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAGvC,OAAO,EACN,YAAY,EACZ,gBAAgB,EAIhB,MAAM,kBAAkB,CAAC;AAI1B,wBAAsB,gBAAgB,CACrC,QAAQ,EAAE,gBAAgB,EAAE,EAC5B,KAAK,GAAE,KAAK,GAAG,MAA8B;;;;;;;;;;;;IAgC7C;AAqDD,wBAAgB,SAAS,CAAC,IAAI,EAAE;IAC/B,IAAI,CAAC,EAAE,YAAY,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,aAAa,EAAE,MAAM,EAAE,CAAC;CACxB;;;;;;;;;;;;EAiBA"}
package/dist/notes.js CHANGED
@@ -1,141 +1,143 @@
1
- import { Pathy } from '@bscotch/pathy';
2
- import { assert } from '@bscotch/utility/browser';
3
- import { defaultNotesCachePath } from './constants.js';
4
- import { rawReleaseNotesCacheSchema, rawReleaseNoteSchema, } from './feeds.types.js';
5
- import { fetchJson } from './fetch.js';
6
- import { countNonUnique, findMax } from './utils.js';
7
- export async function listReleaseNotes(releases, cache = defaultNotesCachePath) {
8
- const cachePath = Pathy.asInstance(cache).withValidator(rawReleaseNotesCacheSchema);
9
- assert(cachePath.hasExtension('json'), `Cache path must have a .json extension`);
10
- const cacheData = await cachePath.read({ fallback: {} });
11
- for (const release of releases) {
12
- for (const type of ['ide', 'runtime']) {
13
- const url = release[type].notesUrl;
14
- if (cacheData[url]) {
15
- if (!cacheData[url].type) {
16
- cacheData[url].type = type;
17
- await cachePath.write(cacheData);
18
- }
19
- continue;
20
- }
21
- console.info('Notes cache miss:', url);
22
- const note = await fetchJson(url, rawReleaseNoteSchema);
23
- cacheData[url] = {
24
- type,
25
- ...rawReleaseNoteSchema.parse(note),
26
- };
27
- await cachePath.write(cacheData);
28
- }
29
- }
30
- return cleanNotes(cacheData);
31
- }
32
- function cleanNotes(cachedNotes) {
33
- const rawNotes = Object.entries(cachedNotes).map(([url, note]) => ({
34
- ...note,
35
- url,
36
- }));
37
- // As of writing, all versions from downloaded notes are unique.
38
- // We'll assume that moving forward, but also throw if that assumption is broken.
39
- const versions = rawNotes.map((note) => `${note.version} (${note.type})`);
40
- assert(countNonUnique(versions) === 0, `Duplicate versions found in release notes`);
41
- // Convert each note into a well-defined structured document.
42
- const cleanedNotes = rawNotes.map(cleanNote);
43
- // Try to normalize "since" versions, since they're often only the *last* digits
44
- for (const note of cleanedNotes) {
45
- if (!note.changes.since?.match(/^\d+$/)) {
46
- continue;
47
- }
48
- const possibleMatches = versions.filter((v) => v.endsWith(`.${note.changes.since}`));
49
- if (possibleMatches.length === 1) {
50
- note.changes.since = possibleMatches[0];
51
- continue;
52
- }
53
- if (!possibleMatches.length) {
54
- // Then just replace the current version with those digits
55
- note.changes.since = note.version.replace(/\d+$/, note.changes.since);
56
- continue;
57
- }
58
- // Score matches by digits in common
59
- const versionParts = note.version.split('.');
60
- note.changes.since = findMax(possibleMatches, (possibleMatch) => {
61
- const matchParts = possibleMatch.split('.');
62
- let score = 0;
63
- for (let j = 0; j < matchParts.length; j++) {
64
- score += matchParts[j] === versionParts[j] ? 1 : 0;
65
- }
66
- return score;
67
- });
68
- }
69
- // Re-organize into a map for indexing by notes-URL, so that these can be merged
70
- // into the release feed data.
71
- const notesByUrl = cleanedNotes.reduce((acc, note) => {
72
- acc[note.url] = note;
73
- return acc;
74
- }, {});
75
- return notesByUrl;
76
- }
77
- export function cleanNote(note) {
78
- assert(note.type, 'Note type must be set');
79
- const { body, title } = parseHeader(note.release_notes.join(''));
80
- const groups = parseBody(body);
81
- const sinceVersion = relativeToVersion(title);
82
- // Normalize the HTML
83
- return {
84
- version: note.version,
85
- url: note.url,
86
- title,
87
- type: note.type,
88
- changes: {
89
- since: sinceVersion,
90
- groups,
91
- },
92
- };
93
- }
94
- function relativeToVersion(title) {
95
- if (!title) {
96
- return null;
97
- }
98
- const sinceMatch = title.match(/([0-9.]+)/i);
99
- return sinceMatch?.[1] || null;
100
- }
101
- function parseBody(html) {
102
- // Split on h3 elements, and then pull out the lists within each
103
- const parts = html.split(/<h3>\s*(.+?)\s*<\/h3>\s*<ul>\s*(.+?)\s*<\/ul>/gs);
104
- const changes = [];
105
- for (let groupIdx = 0; groupIdx < parts.length - 2; groupIdx += 3) {
106
- const [title, list] = [parts[groupIdx + 1], parts[groupIdx + 2]];
107
- changes.push({ title, changes: parseList(list) });
108
- }
109
- return changes;
110
- }
111
- function parseList(htmlList) {
112
- const parts = htmlList.split(/<li>\s*(.+?)\s*<\/li>/gs);
113
- const changes = [];
114
- for (let groupIdx = 0; groupIdx < parts.length - 1; groupIdx += 2) {
115
- const change = parts[groupIdx + 1];
116
- changes.push(change);
117
- }
118
- return changes;
119
- }
120
- function parseHeader(html) {
121
- // Remove changelogs from other versions (if there are additional h2 headings, remove
122
- // those and all below them)
123
- html = html.trim();
124
- // If there is an h2 with the word "since" in it (otherwise the first h2), treat that
125
- // one as the one describing this version and remove everything above and all headers
126
- // below it.
127
- const h2s = html.match(/<h2>\s*(.+?)\s*<\/h2>/gs);
128
- if (!h2s?.length) {
129
- return { title: null, body: html };
130
- }
131
- const thisVersionH2 = h2s.find((h2) => h2.match(/\bsince\b/i)) || h2s[0];
132
- let [, after] = html.split(thisVersionH2);
133
- const firstH2Index = after.indexOf('<h2>');
134
- if (firstH2Index > 0) {
135
- after = after.slice(0, firstH2Index);
136
- }
137
- assert(!after.includes('<h2>'), `Somehow still has an h2 after parsing`);
138
- const [, title] = thisVersionH2.match(/^<h2>\s*(?<title>.+?)\s*<\/h2>/ms);
139
- return { title, body: after };
140
- }
1
+ import { Pathy } from '@bscotch/pathy';
2
+ import { assert } from '@bscotch/utility/browser';
3
+ import { defaultNotesCachePath } from './constants.js';
4
+ import { rawReleaseNotesCacheSchema, rawReleaseNoteSchema, } from './feeds.types.js';
5
+ import { fetchJson } from './fetch.js';
6
+ import { countNonUnique, findMax } from './utils.js';
7
+ export async function listReleaseNotes(releases, cache = defaultNotesCachePath) {
8
+ const cachePath = Pathy.asInstance(cache).withValidator(rawReleaseNotesCacheSchema);
9
+ assert(cachePath.hasExtension('json'), `Cache path must have a .json extension`);
10
+ const cacheData = await cachePath.read({ fallback: {} });
11
+ for (const release of releases) {
12
+ for (const type of ['ide', 'runtime']) {
13
+ let url = release[type].notesUrl;
14
+ // URL can be malformed
15
+ url = url.replace(/^(.+\.cloudfront\.net)(release.*)$/, '$1/$2');
16
+ if (cacheData[url]) {
17
+ if (!cacheData[url].type) {
18
+ cacheData[url].type = type;
19
+ await cachePath.write(cacheData);
20
+ }
21
+ continue;
22
+ }
23
+ console.info('Notes cache miss:', url);
24
+ const note = await fetchJson(url, rawReleaseNoteSchema);
25
+ cacheData[url] = {
26
+ type,
27
+ ...rawReleaseNoteSchema.parse(note),
28
+ };
29
+ await cachePath.write(cacheData);
30
+ }
31
+ }
32
+ return cleanNotes(cacheData);
33
+ }
34
+ function cleanNotes(cachedNotes) {
35
+ const rawNotes = Object.entries(cachedNotes).map(([url, note]) => ({
36
+ ...note,
37
+ url,
38
+ }));
39
+ // As of writing, all versions from downloaded notes are unique.
40
+ // We'll assume that moving forward, but also throw if that assumption is broken.
41
+ const versions = rawNotes.map((note) => `${note.version} (${note.type})`);
42
+ assert(countNonUnique(versions) === 0, `Duplicate versions found in release notes`);
43
+ // Convert each note into a well-defined structured document.
44
+ const cleanedNotes = rawNotes.map(cleanNote);
45
+ // Try to normalize "since" versions, since they're often only the *last* digits
46
+ for (const note of cleanedNotes) {
47
+ if (!note.changes.since?.match(/^\d+$/)) {
48
+ continue;
49
+ }
50
+ const possibleMatches = versions.filter((v) => v.endsWith(`.${note.changes.since}`));
51
+ if (possibleMatches.length === 1) {
52
+ note.changes.since = possibleMatches[0];
53
+ continue;
54
+ }
55
+ if (!possibleMatches.length) {
56
+ // Then just replace the current version with those digits
57
+ note.changes.since = note.version.replace(/\d+$/, note.changes.since);
58
+ continue;
59
+ }
60
+ // Score matches by digits in common
61
+ const versionParts = note.version.split('.');
62
+ note.changes.since = findMax(possibleMatches, (possibleMatch) => {
63
+ const matchParts = possibleMatch.split('.');
64
+ let score = 0;
65
+ for (let j = 0; j < matchParts.length; j++) {
66
+ score += matchParts[j] === versionParts[j] ? 1 : 0;
67
+ }
68
+ return score;
69
+ });
70
+ }
71
+ // Re-organize into a map for indexing by notes-URL, so that these can be merged
72
+ // into the release feed data.
73
+ const notesByUrl = cleanedNotes.reduce((acc, note) => {
74
+ acc[note.url] = note;
75
+ return acc;
76
+ }, {});
77
+ return notesByUrl;
78
+ }
79
+ export function cleanNote(note) {
80
+ assert(note.type, 'Note type must be set');
81
+ const { body, title } = parseHeader(note.release_notes.join(''));
82
+ const groups = parseBody(body);
83
+ const sinceVersion = relativeToVersion(title);
84
+ // Normalize the HTML
85
+ return {
86
+ version: note.version,
87
+ url: note.url,
88
+ title,
89
+ type: note.type,
90
+ changes: {
91
+ since: sinceVersion,
92
+ groups,
93
+ },
94
+ };
95
+ }
96
+ function relativeToVersion(title) {
97
+ if (!title) {
98
+ return null;
99
+ }
100
+ const sinceMatch = title.match(/([0-9.]+)/i);
101
+ return sinceMatch?.[1] || null;
102
+ }
103
+ function parseBody(html) {
104
+ // Split on h3 elements, and then pull out the lists within each
105
+ const parts = html.split(/<h3>\s*(.+?)\s*<\/h3>\s*<ul>\s*(.+?)\s*<\/ul>/gs);
106
+ const changes = [];
107
+ for (let groupIdx = 0; groupIdx < parts.length - 2; groupIdx += 3) {
108
+ const [title, list] = [parts[groupIdx + 1], parts[groupIdx + 2]];
109
+ changes.push({ title, changes: parseList(list) });
110
+ }
111
+ return changes;
112
+ }
113
+ function parseList(htmlList) {
114
+ const parts = htmlList.split(/<li>\s*(.+?)\s*<\/li>/gs);
115
+ const changes = [];
116
+ for (let groupIdx = 0; groupIdx < parts.length - 1; groupIdx += 2) {
117
+ const change = parts[groupIdx + 1];
118
+ changes.push(change);
119
+ }
120
+ return changes;
121
+ }
122
+ function parseHeader(html) {
123
+ // Remove changelogs from other versions (if there are additional h2 headings, remove
124
+ // those and all below them)
125
+ html = html.trim();
126
+ // If there is an h2 with the word "since" in it (otherwise the first h2), treat that
127
+ // one as the one describing this version and remove everything above and all headers
128
+ // below it.
129
+ const h2s = html.match(/<h2>\s*(.+?)\s*<\/h2>/gs);
130
+ if (!h2s?.length) {
131
+ return { title: null, body: html };
132
+ }
133
+ const thisVersionH2 = h2s.find((h2) => h2.match(/\bsince\b/i)) || h2s[0];
134
+ let [, after] = html.split(thisVersionH2);
135
+ const firstH2Index = after.indexOf('<h2>');
136
+ if (firstH2Index > 0) {
137
+ after = after.slice(0, firstH2Index);
138
+ }
139
+ assert(!after.includes('<h2>'), `Somehow still has an h2 after parsing`);
140
+ const [, title] = thisVersionH2.match(/^<h2>\s*(?<title>.+?)\s*<\/h2>/ms);
141
+ return { title, body: after };
142
+ }
141
143
  //# sourceMappingURL=notes.js.map