@acme-skunkworks/eslint-config 1.0.2 → 1.0.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/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -3
- package/dist/infrastructure/scripts/add-links-changelog.d.ts +12 -0
- package/dist/infrastructure/scripts/add-links-changelog.d.ts.map +1 -0
- package/dist/infrastructure/scripts/add-links-changelog.js +56 -0
- package/dist/infrastructure/scripts/enrich-changelog.d.ts +26 -0
- package/dist/infrastructure/scripts/enrich-changelog.d.ts.map +1 -0
- package/dist/infrastructure/scripts/enrich-changelog.js +60 -0
- package/dist/infrastructure/scripts/finalise-changelog.d.ts +26 -0
- package/dist/infrastructure/scripts/finalise-changelog.d.ts.map +1 -0
- package/dist/infrastructure/scripts/finalise-changelog.js +152 -0
- package/dist/infrastructure/scripts/stamp-changelog-version.d.ts +10 -0
- package/dist/infrastructure/scripts/stamp-changelog-version.d.ts.map +1 -0
- package/dist/infrastructure/scripts/stamp-changelog-version.js +35 -0
- package/dist/infrastructure/scripts/validate-changelog.d.ts +7 -0
- package/dist/infrastructure/scripts/validate-changelog.d.ts.map +1 -0
- package/dist/infrastructure/scripts/validate-changelog.js +216 -0
- package/dist/rules/astro.d.ts.map +1 -1
- package/dist/rules/astro.js +9 -6
- package/dist/rules/commonjs.d.ts +2 -1
- package/dist/rules/commonjs.d.ts.map +1 -1
- package/dist/rules/commonjs.js +2 -1
- package/dist/rules/complexity.d.ts.map +1 -1
- package/dist/rules/complexity.js +3 -0
- package/dist/rules/e2e.d.ts.map +1 -1
- package/dist/rules/e2e.js +3 -0
- package/dist/rules/frameworkRouting.d.ts.map +1 -1
- package/dist/rules/frameworkRouting.js +4 -3
- package/dist/rules/ignoredFileAndFolders.d.ts +1 -0
- package/dist/rules/ignoredFileAndFolders.d.ts.map +1 -1
- package/dist/rules/ignoredFileAndFolders.js +1 -0
- package/dist/rules/packageJson.d.ts.map +1 -1
- package/dist/rules/packageJson.js +3 -0
- package/dist/rules/preferences.d.ts.map +1 -1
- package/dist/rules/preferences.js +67 -29
- package/dist/rules/reactRouterExceptions.d.ts.map +1 -1
- package/dist/rules/reactRouterExceptions.js +3 -14
- package/dist/rules/sanity.d.ts +1 -1
- package/dist/rules/sanity.d.ts.map +1 -1
- package/dist/rules/sanity.js +18 -7
- package/dist/rules/storybook.d.ts.map +1 -1
- package/dist/rules/storybook.js +6 -2
- package/dist/rules/tableComponents.d.ts.map +1 -1
- package/dist/rules/tableComponents.js +3 -0
- package/dist/rules/testFiles.d.ts.map +1 -1
- package/dist/rules/testFiles.js +12 -10
- package/dist/rules/typescriptOverrides.d.ts.map +1 -1
- package/dist/rules/typescriptOverrides.js +6 -0
- package/package.json +12 -10
- package/dist/infrastructure/scripts/retitle-release-pr.d.ts +0 -11
- package/dist/infrastructure/scripts/retitle-release-pr.d.ts.map +0 -1
- package/dist/infrastructure/scripts/retitle-release-pr.js +0 -50
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAerC;;;;;GAKG;AACH,eAAO,MAAM,IAAI,EAAE,MAAM,CAAC,MAAM,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAerC;;;;;GAKG;AACH,eAAO,MAAM,IAAI,EAAE,MAAM,CAAC,MAAM,EAS/B,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,UAAU,EAAE,MAAM,CAAC,MAA4B,CAAC;AAE7D;;;;;;;;GAQG;AACH,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAG3C,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,OAAO,EAAE,MAAM,CAAC,MAAkB,CAAC;AAEhD;;GAEG;AACH,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACzC;;GAEG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACnD;;GAEG;AACH,OAAO,EAAE,GAAG,EAAE,MAAM,gBAAgB,CAAC;AACrC;;GAEG;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAC3C;;GAEG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD;;GAEG;AACH,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAE7D;;;;;;;;GAQG;AACH,QAAA,MAAM,aAAa,EAAE,MAAM,CAAC,MAAM,EASjC,CAAC;AAEF,eAAe,aAAa,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -13,10 +13,10 @@ import { testFiles } from "./rules/testFiles.js";
|
|
|
13
13
|
import { typescriptOverrides } from "./rules/typescriptOverrides.js";
|
|
14
14
|
import eslintConfigCanonicalAuto from "eslint-config-canonical/auto";
|
|
15
15
|
import pluginImportX from "eslint-plugin-import-x";
|
|
16
|
-
// Plugin aliasing
|
|
17
|
-
// Canonical references
|
|
18
|
-
// Register under both names so canonical's rules resolve and modern code works.
|
|
16
|
+
// Plugin aliasing — registers eslint-plugin-import-x under both `import` and `import-x`.
|
|
17
|
+
// Canonical's config references the `import` plugin name; we ship import-x. Both names must resolve.
|
|
19
18
|
// See: https://github.com/RobEasthope/protomolecule/issues/259
|
|
19
|
+
// https://github.com/un-ts/eslint-plugin-import-x
|
|
20
20
|
const importXAlias = {
|
|
21
21
|
plugins: {
|
|
22
22
|
import: pluginImportX,
|
|
@@ -32,6 +32,8 @@ const importXAlias = {
|
|
|
32
32
|
export const base = [
|
|
33
33
|
importXAlias,
|
|
34
34
|
ignoredFileAndFolders,
|
|
35
|
+
// eslint-config-canonical/auto — upstream baseline (most rules live here, not in rules/*.ts).
|
|
36
|
+
// Per-rule inventory: https://github.com/gajus/eslint-config-canonical
|
|
35
37
|
...eslintConfigCanonicalAuto,
|
|
36
38
|
packageJson,
|
|
37
39
|
commonjs,
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rewrite bare Linear IDs in a markdown body to links, masking code/links.
|
|
3
|
+
*/
|
|
4
|
+
export declare function rewriteBody(body: string): string;
|
|
5
|
+
/**
|
|
6
|
+
* Split leading YAML frontmatter from the body, preserving the fence bytes.
|
|
7
|
+
*/
|
|
8
|
+
export declare function splitFrontmatter(raw: string): {
|
|
9
|
+
body: string;
|
|
10
|
+
fm: string;
|
|
11
|
+
};
|
|
12
|
+
//# sourceMappingURL=add-links-changelog.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"add-links-changelog.d.ts","sourceRoot":"","sources":["../../../infrastructure/scripts/add-links-changelog.ts"],"names":[],"mappings":"AAwBA;;GAEG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAgBhD;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAe1E"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// Pure helper: rewrite bare Linear issue IDs (e.g. ASW-123) in changelog entry
|
|
2
|
+
// bodies into markdown links, masking code fences / inline code / already-linked
|
|
3
|
+
// IDs first so they're left untouched. Ported from octavo's add-links.mjs,
|
|
4
|
+
// adapted to this workspace (acme-skunkworks).
|
|
5
|
+
//
|
|
6
|
+
// Library module (no CLI): the release-time orchestrator finalise-changelog.ts
|
|
7
|
+
// applies it. Kept pure so it's trivially unit-testable.
|
|
8
|
+
const WORKSPACE = "acme-skunkworks";
|
|
9
|
+
const TEAM_KEYS = ["ASW", "AKW"];
|
|
10
|
+
const ISSUE_RE = new RegExp(`\\b(?:${TEAM_KEYS.join("|")})-\\d+\\b`, "g");
|
|
11
|
+
const FENCE_RE = /```[\s\S]*?```/g;
|
|
12
|
+
const INLINE_CODE_RE = /`[^`]*`/g;
|
|
13
|
+
const ALREADY_LINKED_RE = /\[[^\]]*\]\([^)]*\)/g;
|
|
14
|
+
// Private-Use-Area sentinel wrapping each masked span, so a placeholder can
|
|
15
|
+
// never collide with literal body text (e.g. a code sample containing the
|
|
16
|
+
// string "FENCE0"). U+E000 cannot appear in a markdown source file.
|
|
17
|
+
const SENTINEL = "\u{E000}";
|
|
18
|
+
const PLACEHOLDER_RE = /\u{E000}(?:FENCE|INLINE|LINK)(\d+)\u{E000}/gu;
|
|
19
|
+
function buildUrl(id) {
|
|
20
|
+
return `https://linear.app/${WORKSPACE}/issue/${id}`;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Rewrite bare Linear IDs in a markdown body to links, masking code/links.
|
|
24
|
+
*/
|
|
25
|
+
export function rewriteBody(body) {
|
|
26
|
+
const masks = [];
|
|
27
|
+
function mask(label) {
|
|
28
|
+
return (matched) => {
|
|
29
|
+
masks.push(matched);
|
|
30
|
+
return `${SENTINEL}${label}${masks.length - 1}${SENTINEL}`;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
const masked = body
|
|
34
|
+
.replaceAll(FENCE_RE, mask("FENCE"))
|
|
35
|
+
.replaceAll(INLINE_CODE_RE, mask("INLINE"))
|
|
36
|
+
.replaceAll(ALREADY_LINKED_RE, mask("LINK"))
|
|
37
|
+
.replaceAll(ISSUE_RE, (id) => `[${id}](${buildUrl(id)})`);
|
|
38
|
+
return masked.replaceAll(PLACEHOLDER_RE, (_, index) => masks[Number(index)]);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Split leading YAML frontmatter from the body, preserving the fence bytes.
|
|
42
|
+
*/
|
|
43
|
+
export function splitFrontmatter(raw) {
|
|
44
|
+
if (!raw.startsWith("---\n")) {
|
|
45
|
+
return { body: raw, fm: "" };
|
|
46
|
+
}
|
|
47
|
+
// Search from index 3: the opening "---\n" is exactly 4 bytes, so the
|
|
48
|
+
// earliest a closing "\n---\n" can start is index 3 (the newline ending the
|
|
49
|
+
// opening fence). Starting at 4 would miss the close of an empty frontmatter
|
|
50
|
+
// ("---\n---\n").
|
|
51
|
+
const end = raw.indexOf("\n---\n", 3);
|
|
52
|
+
if (end === -1) {
|
|
53
|
+
return { body: raw, fm: "" };
|
|
54
|
+
}
|
|
55
|
+
return { body: raw.slice(end + 5), fm: raw.slice(0, end + 5) };
|
|
56
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export type EnrichInput = {
|
|
2
|
+
additions?: null | string;
|
|
3
|
+
/**
|
|
4
|
+
* Feature branch name — the stable lookup key.
|
|
5
|
+
*/
|
|
6
|
+
branch: string;
|
|
7
|
+
changedFiles?: null | string;
|
|
8
|
+
deletions?: null | string;
|
|
9
|
+
/**
|
|
10
|
+
* PR merged_at timestamp (ISO 8601 UTC).
|
|
11
|
+
*/
|
|
12
|
+
mergedAt: string;
|
|
13
|
+
/**
|
|
14
|
+
* Merge commit SHA (full or short); only the first 7 chars are stored.
|
|
15
|
+
*/
|
|
16
|
+
mergeSha: string;
|
|
17
|
+
mergeStrategy?: null | string;
|
|
18
|
+
prNumber?: null | string;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Apply enrichment to a single entry's raw markdown and return the rewritten
|
|
22
|
+
* markdown. Fill-once for merged_at/commit/merge_strategy/pr; authoritative
|
|
23
|
+
* overwrite for stats. created_at is never touched.
|
|
24
|
+
*/
|
|
25
|
+
export declare function enrichFrontmatter(raw: string, input: EnrichInput): string;
|
|
26
|
+
//# sourceMappingURL=enrich-changelog.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"enrich-changelog.d.ts","sourceRoot":"","sources":["../../../infrastructure/scripts/enrich-changelog.ts"],"names":[],"mappings":"AAYA,MAAM,MAAM,WAAW,GAAG;IACxB,SAAS,CAAC,EAAE,IAAI,GAAG,MAAM,CAAC;IAC1B;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,IAAI,GAAG,MAAM,CAAC;IAC7B,SAAS,CAAC,EAAE,IAAI,GAAG,MAAM,CAAC;IAC1B;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,IAAI,GAAG,MAAM,CAAC;IAC9B,QAAQ,CAAC,EAAE,IAAI,GAAG,MAAM,CAAC;CAC1B,CAAC;AASF;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,GAAG,MAAM,CAkDzE"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// Pure enrichment of a changelog entry's frontmatter — fills the fields that
|
|
2
|
+
// are only knowable once the PR has merged (merged_at / commit / merge_strategy
|
|
3
|
+
// / pr) plus authoritative stats. `version` is filled separately by
|
|
4
|
+
// stamp-changelog-version. created_at is never touched.
|
|
5
|
+
//
|
|
6
|
+
// This is a library module (no CLI): the release-time orchestrator
|
|
7
|
+
// finalise-changelog.ts composes it with the PR data it resolves from `gh`.
|
|
8
|
+
// Ported from octavo's enrich-changelog.mjs, minus affected_packages (single
|
|
9
|
+
// package). Kept pure so it's trivially unit-testable.
|
|
10
|
+
import matter from "gray-matter";
|
|
11
|
+
/**
|
|
12
|
+
* True when a value is unset (null/undefined/"").
|
|
13
|
+
*/
|
|
14
|
+
function blank(value) {
|
|
15
|
+
return value === null || value === undefined || value === "";
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Apply enrichment to a single entry's raw markdown and return the rewritten
|
|
19
|
+
* markdown. Fill-once for merged_at/commit/merge_strategy/pr; authoritative
|
|
20
|
+
* overwrite for stats. created_at is never touched.
|
|
21
|
+
*/
|
|
22
|
+
export function enrichFrontmatter(raw, input) {
|
|
23
|
+
const parsed = matter(raw);
|
|
24
|
+
const fm = { ...parsed.data };
|
|
25
|
+
if (!fm.created_at) {
|
|
26
|
+
throw new Error("entry has no created_at; refusing to enrich");
|
|
27
|
+
}
|
|
28
|
+
const shortSha = input.mergeSha.slice(0, 7);
|
|
29
|
+
if (blank(fm.merged_at)) {
|
|
30
|
+
fm.merged_at = input.mergedAt;
|
|
31
|
+
}
|
|
32
|
+
if (blank(fm.commit)) {
|
|
33
|
+
fm.commit = shortSha;
|
|
34
|
+
}
|
|
35
|
+
if (blank(fm.merge_strategy) && input.mergeStrategy) {
|
|
36
|
+
fm.merge_strategy = input.mergeStrategy;
|
|
37
|
+
}
|
|
38
|
+
if (blank(fm.pr) && input.prNumber) {
|
|
39
|
+
fm.pr = Number.parseInt(input.prNumber, 10);
|
|
40
|
+
}
|
|
41
|
+
// Authoritative overwrites from the GH API, always under stats: { ... }.
|
|
42
|
+
const stats = typeof fm.stats === "object" &&
|
|
43
|
+
fm.stats !== null &&
|
|
44
|
+
!Array.isArray(fm.stats)
|
|
45
|
+
? { ...fm.stats }
|
|
46
|
+
: {};
|
|
47
|
+
// Guard with blank() (not just null/undefined): an empty string would slip
|
|
48
|
+
// through and Number.parseInt("", 10) is NaN, which the validator rejects.
|
|
49
|
+
if (!blank(input.additions)) {
|
|
50
|
+
stats.loc_added = Number.parseInt(input.additions, 10);
|
|
51
|
+
}
|
|
52
|
+
if (!blank(input.deletions)) {
|
|
53
|
+
stats.loc_removed = Number.parseInt(input.deletions, 10);
|
|
54
|
+
}
|
|
55
|
+
if (!blank(input.changedFiles)) {
|
|
56
|
+
stats.files_changed = Number.parseInt(input.changedFiles, 10);
|
|
57
|
+
}
|
|
58
|
+
fm.stats = stats;
|
|
59
|
+
return matter.stringify(parsed.content, fm);
|
|
60
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/usr/bin/env -S npx tsx
|
|
2
|
+
export declare const CHANGELOG_DIR = "changelog";
|
|
3
|
+
export type ResolvedPr = {
|
|
4
|
+
additions: null | string;
|
|
5
|
+
changedFiles: null | string;
|
|
6
|
+
deletions: null | string;
|
|
7
|
+
mergedAt: string;
|
|
8
|
+
mergeSha: string;
|
|
9
|
+
mergeStrategy: null | string;
|
|
10
|
+
prNumber: string;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Resolve the merged PR for a branch, or null when none is found.
|
|
14
|
+
*/
|
|
15
|
+
export type PrResolver = (branch: string) => null | ResolvedPr;
|
|
16
|
+
export type Runner = (cmd: string, args: readonly string[]) => string;
|
|
17
|
+
/**
|
|
18
|
+
* Finalise one entry's raw markdown for release. Returns the rewritten markdown,
|
|
19
|
+
* or null when nothing changed (already finalised).
|
|
20
|
+
*/
|
|
21
|
+
export declare function finaliseEntry(raw: string, version: string, resolvePr: PrResolver): null | string;
|
|
22
|
+
/**
|
|
23
|
+
* Build a PR resolver backed by `gh` + `git` (injectable runner for tests).
|
|
24
|
+
*/
|
|
25
|
+
export declare function makeResolver(run: Runner): PrResolver;
|
|
26
|
+
//# sourceMappingURL=finalise-changelog.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"finalise-changelog.d.ts","sourceRoot":"","sources":["../../../infrastructure/scripts/finalise-changelog.ts"],"names":[],"mappings":";AAuBA,eAAO,MAAM,aAAa,cAAc,CAAC;AAEzC,MAAM,MAAM,UAAU,GAAG;IACvB,SAAS,EAAE,IAAI,GAAG,MAAM,CAAC;IACzB,YAAY,EAAE,IAAI,GAAG,MAAM,CAAC;IAC5B,SAAS,EAAE,IAAI,GAAG,MAAM,CAAC;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,IAAI,GAAG,MAAM,CAAC;IAC7B,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,GAAG,UAAU,CAAC;AAE/D,MAAM,MAAM,MAAM,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,KAAK,MAAM,CAAC;AAMtE;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,UAAU,GACpB,IAAI,GAAG,MAAM,CAgCf;AAaD;;GAEG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,CA4EpD"}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
#!/usr/bin/env -S npx tsx
|
|
2
|
+
// Release-time finalisation of changelog entries — run by changesets/action's
|
|
3
|
+
// `version:` command, right after `changeset version`, so the result is
|
|
4
|
+
// committed into the "release: version packages" PR (no separate workflow, no
|
|
5
|
+
// bot push to main).
|
|
6
|
+
//
|
|
7
|
+
// For every entry that isn't finalised yet (empty `version`):
|
|
8
|
+
// 1. resolve its merged PR from the `branch` field via `gh` and enrich
|
|
9
|
+
// (merged_at / commit / pr / merge_strategy / stats);
|
|
10
|
+
// 2. stamp `version` with the just-bumped package.json version;
|
|
11
|
+
// 3. rewrite bare Linear IDs to links.
|
|
12
|
+
//
|
|
13
|
+
// The pure `finaliseEntry(raw, version, resolvePr)` is unit-testable with a fake
|
|
14
|
+
// resolver; main() wires the real `gh`/`git` resolver and walks the directory.
|
|
15
|
+
import { rewriteBody, splitFrontmatter } from "./add-links-changelog.js";
|
|
16
|
+
import { enrichFrontmatter } from "./enrich-changelog.js";
|
|
17
|
+
import { readPackageVersion, stampVersion } from "./stamp-changelog-version.js";
|
|
18
|
+
import matter from "gray-matter";
|
|
19
|
+
import { execFileSync } from "node:child_process";
|
|
20
|
+
import { readdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
21
|
+
import { join } from "node:path";
|
|
22
|
+
export const CHANGELOG_DIR = "changelog";
|
|
23
|
+
function blank(value) {
|
|
24
|
+
return value === null || value === undefined || value === "";
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Finalise one entry's raw markdown for release. Returns the rewritten markdown,
|
|
28
|
+
* or null when nothing changed (already finalised).
|
|
29
|
+
*/
|
|
30
|
+
export function finaliseEntry(raw, version, resolvePr) {
|
|
31
|
+
const fm = matter(raw).data;
|
|
32
|
+
if (!blank(fm.version)) {
|
|
33
|
+
return null; // already shipped in a release
|
|
34
|
+
}
|
|
35
|
+
let next = raw;
|
|
36
|
+
const branch = typeof fm.branch === "string" ? fm.branch : "";
|
|
37
|
+
const needsEnrich = blank(fm.merged_at) || blank(fm.commit) || blank(fm.pr);
|
|
38
|
+
if (branch && needsEnrich) {
|
|
39
|
+
const pr = resolvePr(branch);
|
|
40
|
+
if (pr) {
|
|
41
|
+
next = enrichFrontmatter(next, {
|
|
42
|
+
additions: pr.additions,
|
|
43
|
+
branch,
|
|
44
|
+
changedFiles: pr.changedFiles,
|
|
45
|
+
deletions: pr.deletions,
|
|
46
|
+
mergedAt: pr.mergedAt,
|
|
47
|
+
mergeSha: pr.mergeSha,
|
|
48
|
+
mergeStrategy: pr.mergeStrategy,
|
|
49
|
+
prNumber: pr.prNumber,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
next = stampVersion(next, version) ?? next;
|
|
54
|
+
const { body, fm: fmText } = splitFrontmatter(next);
|
|
55
|
+
next = fmText + rewriteBody(body);
|
|
56
|
+
return next === raw ? null : next;
|
|
57
|
+
}
|
|
58
|
+
function realRunner(cmd, args) {
|
|
59
|
+
return execFileSync(cmd, args, {
|
|
60
|
+
encoding: "utf8",
|
|
61
|
+
stdio: ["ignore", "pipe", "inherit"],
|
|
62
|
+
// Fail fast if gh/git stalls (network/auth). Enrichment is best-effort, so
|
|
63
|
+
// a timeout throws → makeResolver's try/catch falls back to null rather
|
|
64
|
+
// than hanging the release until the whole job times out.
|
|
65
|
+
timeout: 30_000,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Build a PR resolver backed by `gh` + `git` (injectable runner for tests).
|
|
70
|
+
*/
|
|
71
|
+
export function makeResolver(run) {
|
|
72
|
+
function resolve(branch) {
|
|
73
|
+
const json = run("gh", [
|
|
74
|
+
"pr",
|
|
75
|
+
"list",
|
|
76
|
+
"--head",
|
|
77
|
+
branch,
|
|
78
|
+
"--state",
|
|
79
|
+
"merged",
|
|
80
|
+
"--limit",
|
|
81
|
+
"1",
|
|
82
|
+
"--json",
|
|
83
|
+
"number,mergedAt,additions,deletions,changedFiles,mergeCommit,headRefOid",
|
|
84
|
+
]);
|
|
85
|
+
const list = JSON.parse(json);
|
|
86
|
+
if (list.length === 0) {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
const pr = list[0];
|
|
90
|
+
const mergeSha = pr.mergeCommit?.oid ?? "";
|
|
91
|
+
// Infer merge strategy from the merge commit shape (GitHub doesn't expose
|
|
92
|
+
// it directly): 2+ parents -> merge; otherwise squash.
|
|
93
|
+
// NOTE: rebase merges are also reported as "squash" — GitHub replays them
|
|
94
|
+
// with fresh SHAs, so mergeCommit.oid never equals headRefOid and the
|
|
95
|
+
// "rebase" branch below is effectively unreachable. This repo squash-merges
|
|
96
|
+
// anyway, and merge_strategy is only record-keeping metadata, so the
|
|
97
|
+
// imprecision is harmless.
|
|
98
|
+
let mergeStrategy = null;
|
|
99
|
+
if (mergeSha) {
|
|
100
|
+
const parents = (run("git", ["cat-file", "-p", mergeSha]).match(/^parent /gm) ?? []).length;
|
|
101
|
+
if (parents >= 2) {
|
|
102
|
+
mergeStrategy = "merge";
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
mergeStrategy = mergeSha === pr.headRefOid ? "rebase" : "squash";
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// Absent numeric fields stay null (not ""), so the enrich guard skips them
|
|
109
|
+
// rather than parsing "" into NaN.
|
|
110
|
+
return {
|
|
111
|
+
additions: pr.additions === undefined ? null : String(pr.additions),
|
|
112
|
+
changedFiles: pr.changedFiles === undefined ? null : String(pr.changedFiles),
|
|
113
|
+
deletions: pr.deletions === undefined ? null : String(pr.deletions),
|
|
114
|
+
mergedAt: pr.mergedAt ?? "",
|
|
115
|
+
mergeSha,
|
|
116
|
+
mergeStrategy,
|
|
117
|
+
prNumber: String(pr.number ?? ""),
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
return (branch) => {
|
|
121
|
+
// Enrichment is best-effort metadata: a gh/git failure here must NOT abort
|
|
122
|
+
// `changeset version` and block the release. On any error, warn and return
|
|
123
|
+
// null — the entry still gets version-stamped, just without PR metadata.
|
|
124
|
+
try {
|
|
125
|
+
return resolve(branch);
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
console.warn(`⚠️ Could not resolve PR for branch ${branch}: ${error.message}`);
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
function main() {
|
|
134
|
+
const version = readPackageVersion(readFileSync("package.json", "utf8"));
|
|
135
|
+
const resolvePr = makeResolver(realRunner);
|
|
136
|
+
const files = readdirSync(CHANGELOG_DIR)
|
|
137
|
+
.filter((name) => name.endsWith(".md") && name !== "README.md")
|
|
138
|
+
.map((name) => join(CHANGELOG_DIR, name));
|
|
139
|
+
let finalised = 0;
|
|
140
|
+
for (const file of files) {
|
|
141
|
+
const next = finaliseEntry(readFileSync(file, "utf8"), version, resolvePr);
|
|
142
|
+
if (next !== null) {
|
|
143
|
+
writeFileSync(file, next);
|
|
144
|
+
finalised++;
|
|
145
|
+
console.log(`finalised ${version}: ${file}`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
console.log(`Changelog finalisation complete. ${finalised} entr${finalised === 1 ? "y" : "ies"} finalised with ${version}.`);
|
|
149
|
+
}
|
|
150
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
151
|
+
main();
|
|
152
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stamp `version` onto an entry if it has none. Returns the rewritten markdown,
|
|
3
|
+
* or null when the entry already has a version (no write needed).
|
|
4
|
+
*/
|
|
5
|
+
export declare function stampVersion(raw: string, version: string): null | string;
|
|
6
|
+
/**
|
|
7
|
+
* Read the `version` field from a package.json string.
|
|
8
|
+
*/
|
|
9
|
+
export declare function readPackageVersion(packageJsonRaw: string): string;
|
|
10
|
+
//# sourceMappingURL=stamp-changelog-version.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stamp-changelog-version.d.ts","sourceRoot":"","sources":["../../../infrastructure/scripts/stamp-changelog-version.ts"],"names":[],"mappings":"AAeA;;;GAGG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CASxE;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,cAAc,EAAE,MAAM,GAAG,MAAM,CAOjE"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// Pure helpers for release-time version stamping: set `version` on an entry
|
|
2
|
+
// that doesn't have one, and read the version from package.json.
|
|
3
|
+
//
|
|
4
|
+
// Library module (no CLI): the release-time orchestrator finalise-changelog.ts
|
|
5
|
+
// composes these. Kept pure so they're trivially unit-testable.
|
|
6
|
+
import matter from "gray-matter";
|
|
7
|
+
/**
|
|
8
|
+
* True when a value is unset (null/undefined/"").
|
|
9
|
+
*/
|
|
10
|
+
function blank(value) {
|
|
11
|
+
return value === null || value === undefined || value === "";
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Stamp `version` onto an entry if it has none. Returns the rewritten markdown,
|
|
15
|
+
* or null when the entry already has a version (no write needed).
|
|
16
|
+
*/
|
|
17
|
+
export function stampVersion(raw, version) {
|
|
18
|
+
const parsed = matter(raw);
|
|
19
|
+
const fm = { ...parsed.data };
|
|
20
|
+
if (!blank(fm.version)) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
fm.version = version;
|
|
24
|
+
return matter.stringify(parsed.content, fm);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Read the `version` field from a package.json string.
|
|
28
|
+
*/
|
|
29
|
+
export function readPackageVersion(packageJsonRaw) {
|
|
30
|
+
const pkg = JSON.parse(packageJsonRaw);
|
|
31
|
+
if (typeof pkg.version !== "string" || pkg.version.length === 0) {
|
|
32
|
+
throw new Error("package.json is missing a string `version`");
|
|
33
|
+
}
|
|
34
|
+
return pkg.version;
|
|
35
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
#!/usr/bin/env -S npx tsx
|
|
2
|
+
export declare const CHANGELOG_DIR = "changelog";
|
|
3
|
+
/**
|
|
4
|
+
* Validate one entry. Returns an array of human-readable error strings.
|
|
5
|
+
*/
|
|
6
|
+
export declare function validateEntry(name: string, raw: string): string[];
|
|
7
|
+
//# sourceMappingURL=validate-changelog.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate-changelog.d.ts","sourceRoot":"","sources":["../../../infrastructure/scripts/validate-changelog.ts"],"names":[],"mappings":";AAoBA,eAAO,MAAM,aAAa,cAAc,CAAC;AA0DzC;;GAEG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CA0JjE"}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
#!/usr/bin/env -S npx tsx
|
|
2
|
+
// Validates the individual dated changelog entries under `changelog/`.
|
|
3
|
+
//
|
|
4
|
+
// Ported from octavo's scripts/validate-changelog.mjs and adapted for this
|
|
5
|
+
// repo (single, semver'd npm package):
|
|
6
|
+
// - `version` is accepted (typed-when-present semver string); octavo has none.
|
|
7
|
+
// - `affected_packages` is dropped (one package, not a monorepo).
|
|
8
|
+
// - the REQUIRED set is relaxed to title/created_at/category/breaking so that
|
|
9
|
+
// both backfilled historical entries (no branch/author/stats) and in-flight
|
|
10
|
+
// entries (no version/merged_at/pr/commit/stats until enriched) validate.
|
|
11
|
+
// /send-it is the guarantee that new entries get branch/author/co_authors;
|
|
12
|
+
// validation is the safety net, not the sole guard.
|
|
13
|
+
//
|
|
14
|
+
// The pure `validateEntry(name, raw)` returns an array of error strings (empty
|
|
15
|
+
// means valid), so it's trivially unit-testable; main() walks the directory.
|
|
16
|
+
import matter from "gray-matter";
|
|
17
|
+
import { readdirSync, readFileSync, statSync } from "node:fs";
|
|
18
|
+
import { basename, join } from "node:path";
|
|
19
|
+
export const CHANGELOG_DIR = "changelog";
|
|
20
|
+
const FILENAME_RE = /^(\d{8})-(\d{6})-([a-z0-9-]+)\.md$/;
|
|
21
|
+
const ISO_UTC_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/;
|
|
22
|
+
// SemVer 2.0.0: prerelease and build identifiers are dot-separated and may
|
|
23
|
+
// contain ASCII alphanumerics and hyphens (e.g. 1.2.3-rc-1, 1.2.3+build-45).
|
|
24
|
+
const SEMVER_RE = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
|
|
25
|
+
const SHA7_RE = /^[0-9a-f]{7}$/;
|
|
26
|
+
const ISSUE_RE = /^[A-Z]{2,}-\d+$/;
|
|
27
|
+
const CATEGORIES = new Set([
|
|
28
|
+
"chore",
|
|
29
|
+
"docs",
|
|
30
|
+
"feature",
|
|
31
|
+
"fix",
|
|
32
|
+
"perf",
|
|
33
|
+
"refactor",
|
|
34
|
+
]);
|
|
35
|
+
const MERGE_STRATEGIES = new Set(["merge", "rebase", "squash"]);
|
|
36
|
+
const SECTION_RE = /^##\s+(Breaking|Added|Changed|Fixed)\b/m;
|
|
37
|
+
const REQUIRED = ["title", "created_at", "category", "breaking"];
|
|
38
|
+
/**
|
|
39
|
+
* True when a value is set to something meaningful (not null/undefined/"").
|
|
40
|
+
*/
|
|
41
|
+
function present(value) {
|
|
42
|
+
return value !== null && value !== undefined && value !== "";
|
|
43
|
+
}
|
|
44
|
+
function isInt(value) {
|
|
45
|
+
return typeof value === "number" && Number.isInteger(value);
|
|
46
|
+
}
|
|
47
|
+
function isNonNegInt(value) {
|
|
48
|
+
return isInt(value) && value >= 0;
|
|
49
|
+
}
|
|
50
|
+
function isStringArray(value) {
|
|
51
|
+
return (Array.isArray(value) && value.every((item) => typeof item === "string"));
|
|
52
|
+
}
|
|
53
|
+
function asIso(value) {
|
|
54
|
+
if (typeof value === "string") {
|
|
55
|
+
return value;
|
|
56
|
+
}
|
|
57
|
+
if (value instanceof Date) {
|
|
58
|
+
return value.toISOString();
|
|
59
|
+
}
|
|
60
|
+
return "";
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Validate one entry. Returns an array of human-readable error strings.
|
|
64
|
+
*/
|
|
65
|
+
export function validateEntry(name, raw) {
|
|
66
|
+
const errors = [];
|
|
67
|
+
function fail(message) {
|
|
68
|
+
errors.push(`${name}: ${message}`);
|
|
69
|
+
}
|
|
70
|
+
if (!FILENAME_RE.test(name)) {
|
|
71
|
+
fail("filename must match YYYYMMDD-HHMMSS-<slug>.md (slug: [a-z0-9-]+)");
|
|
72
|
+
return errors;
|
|
73
|
+
}
|
|
74
|
+
let parsed;
|
|
75
|
+
try {
|
|
76
|
+
parsed = matter(raw);
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
fail(`frontmatter unparseable: ${error.message}`);
|
|
80
|
+
return errors;
|
|
81
|
+
}
|
|
82
|
+
const fm = (parsed.data ?? {});
|
|
83
|
+
const body = parsed.content ?? "";
|
|
84
|
+
for (const key of REQUIRED) {
|
|
85
|
+
if (!(key in fm)) {
|
|
86
|
+
fail(`missing required field: ${key}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if ("title" in fm &&
|
|
90
|
+
(typeof fm.title !== "string" || fm.title.trim() === "")) {
|
|
91
|
+
fail("title must be a non-empty string");
|
|
92
|
+
}
|
|
93
|
+
if ("release_note" in fm &&
|
|
94
|
+
fm.release_note !== null &&
|
|
95
|
+
typeof fm.release_note !== "string") {
|
|
96
|
+
fail("release_note must be a string or null when present");
|
|
97
|
+
}
|
|
98
|
+
if (present(fm.version) &&
|
|
99
|
+
(typeof fm.version !== "string" || !SEMVER_RE.test(fm.version))) {
|
|
100
|
+
fail(`version must be a semver string when set (got ${JSON.stringify(fm.version)})`);
|
|
101
|
+
}
|
|
102
|
+
if ("created_at" in fm && !ISO_UTC_RE.test(asIso(fm.created_at))) {
|
|
103
|
+
fail(`created_at must be ISO 8601 UTC with Z suffix (got ${JSON.stringify(fm.created_at)})`);
|
|
104
|
+
}
|
|
105
|
+
if (present(fm.merged_at) && !ISO_UTC_RE.test(asIso(fm.merged_at))) {
|
|
106
|
+
fail("merged_at must be ISO 8601 UTC with Z suffix when set");
|
|
107
|
+
}
|
|
108
|
+
if ("branch" in fm &&
|
|
109
|
+
(typeof fm.branch !== "string" || fm.branch.trim() === "")) {
|
|
110
|
+
fail("branch must be a non-empty string when present");
|
|
111
|
+
}
|
|
112
|
+
if (present(fm.pr) && !isInt(fm.pr)) {
|
|
113
|
+
fail("pr must be an integer when set");
|
|
114
|
+
}
|
|
115
|
+
if (present(fm.commit) && !SHA7_RE.test(String(fm.commit))) {
|
|
116
|
+
fail("commit must be a 7-char hex SHA when set");
|
|
117
|
+
}
|
|
118
|
+
if (present(fm.merge_strategy) &&
|
|
119
|
+
!MERGE_STRATEGIES.has(String(fm.merge_strategy))) {
|
|
120
|
+
fail(`merge_strategy must be one of: ${[...MERGE_STRATEGIES].join(", ")}`);
|
|
121
|
+
}
|
|
122
|
+
if ("author" in fm &&
|
|
123
|
+
(typeof fm.author !== "string" || fm.author.trim() === "")) {
|
|
124
|
+
fail("author must be a non-empty string when present");
|
|
125
|
+
}
|
|
126
|
+
if ("co_authors" in fm && !isStringArray(fm.co_authors)) {
|
|
127
|
+
fail("co_authors must be an array of strings (use [] when none)");
|
|
128
|
+
}
|
|
129
|
+
if ("category" in fm && !CATEGORIES.has(String(fm.category))) {
|
|
130
|
+
fail(`category must be one of: ${[...CATEGORIES].join(", ")}`);
|
|
131
|
+
}
|
|
132
|
+
if ("breaking" in fm && typeof fm.breaking !== "boolean") {
|
|
133
|
+
fail("breaking must be a boolean");
|
|
134
|
+
}
|
|
135
|
+
if ("issues" in fm) {
|
|
136
|
+
if (isStringArray(fm.issues)) {
|
|
137
|
+
for (const id of fm.issues) {
|
|
138
|
+
if (!ISSUE_RE.test(id)) {
|
|
139
|
+
fail(`issues entry ${JSON.stringify(id)} must match [A-Z]{2,}-\\d+`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
fail("issues must be an array of strings when present");
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
// PR stats live under stats: { files_changed, loc_added, loc_removed }.
|
|
148
|
+
const statKeys = ["files_changed", "loc_added", "loc_removed"];
|
|
149
|
+
for (const key of statKeys) {
|
|
150
|
+
if (key in fm) {
|
|
151
|
+
fail(`${key} must be under stats, not top-level`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
// stats is optional (filled by enrichment), but must be a well-formed object
|
|
155
|
+
// with non-negative integer values when present.
|
|
156
|
+
if (present(fm.stats)) {
|
|
157
|
+
if (typeof fm.stats !== "object" || Array.isArray(fm.stats)) {
|
|
158
|
+
fail("stats must be an object");
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
const stats = fm.stats;
|
|
162
|
+
for (const key of statKeys) {
|
|
163
|
+
if (key in stats && present(stats[key]) && !isNonNegInt(stats[key])) {
|
|
164
|
+
fail(`stats.${key} must be a non-negative integer when set`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
// The schema (changelog/README.md) requires "## Breaking" to be the FIRST
|
|
170
|
+
// body section when breaking: true — not merely present somewhere.
|
|
171
|
+
if (fm.breaking === true) {
|
|
172
|
+
const firstSection = body.match(/^##\s+([A-Za-z]+)\b/m)?.[1];
|
|
173
|
+
if (firstSection !== "Breaking") {
|
|
174
|
+
fail('breaking: true requires "## Breaking" as the first body section');
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
if (!SECTION_RE.test(body)) {
|
|
178
|
+
fail("body must contain at least one of: ## Breaking | ## Added | ## Changed | ## Fixed");
|
|
179
|
+
}
|
|
180
|
+
return errors;
|
|
181
|
+
}
|
|
182
|
+
function listEntries(directory) {
|
|
183
|
+
let stat;
|
|
184
|
+
try {
|
|
185
|
+
stat = statSync(directory);
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
console.error(`changelog directory not found: ${directory}`);
|
|
189
|
+
process.exit(2);
|
|
190
|
+
}
|
|
191
|
+
if (!stat.isDirectory()) {
|
|
192
|
+
console.error(`${directory} is not a directory`);
|
|
193
|
+
process.exit(2);
|
|
194
|
+
}
|
|
195
|
+
return readdirSync(directory)
|
|
196
|
+
.filter((name) => name.endsWith(".md") && name !== "README.md")
|
|
197
|
+
.map((name) => join(directory, name));
|
|
198
|
+
}
|
|
199
|
+
function main() {
|
|
200
|
+
const files = listEntries(CHANGELOG_DIR);
|
|
201
|
+
const errors = [];
|
|
202
|
+
for (const file of files) {
|
|
203
|
+
errors.push(...validateEntry(basename(file), readFileSync(file, "utf8")));
|
|
204
|
+
}
|
|
205
|
+
if (errors.length > 0) {
|
|
206
|
+
console.error(`Changelog validation failed with ${errors.length} error(s):\n`);
|
|
207
|
+
for (const message of errors) {
|
|
208
|
+
console.error(` - ${message}`);
|
|
209
|
+
}
|
|
210
|
+
process.exit(1);
|
|
211
|
+
}
|
|
212
|
+
console.log(`Changelog validation passed (${files.length} entr${files.length === 1 ? "y" : "ies"} checked).`);
|
|
213
|
+
}
|
|
214
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
215
|
+
main();
|
|
216
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"astro.d.ts","sourceRoot":"","sources":["../../rules/astro.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAGrC;;;;;;;GAOG;AACH,eAAO,MAAM,KAAK,EAAE,MAAM,CAAC,MAAM,
|
|
1
|
+
{"version":3,"file":"astro.d.ts","sourceRoot":"","sources":["../../rules/astro.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAGrC;;;;;;;GAOG;AACH,eAAO,MAAM,KAAK,EAAE,MAAM,CAAC,MAAM,EAsBhC,CAAC"}
|