@savvy-web/changesets 0.1.0 → 0.1.1

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.
@@ -37,7 +37,12 @@ const checkCommand = Command.make("check", {
37
37
  }, ({ dir })=>runCheck(dir)).pipe(Command.withDescription("Full changeset validation with summary"));
38
38
  const CUSTOM_RULES_ENTRY = "@savvy-web/changesets/markdownlint";
39
39
  const CHANGELOG_ENTRY = "@savvy-web/changesets/changelog";
40
- const BASE_CONFIG_PATH = "lib/configs/.markdownlint-cli2.jsonc";
40
+ const MARKDOWNLINT_CONFIG_PATHS = [
41
+ "lib/configs/.markdownlint-cli2.jsonc",
42
+ "lib/configs/.markdownlint-cli2.json",
43
+ ".markdownlint-cli2.jsonc",
44
+ ".markdownlint-cli2.json"
45
+ ];
41
46
  const RULE_NAMES = [
42
47
  "changeset-heading-hierarchy",
43
48
  "changeset-required-sections",
@@ -70,6 +75,7 @@ class InitError extends InitErrorBase {
70
75
  const forceOption = Options.boolean("force").pipe(Options.withAlias("f"), Options.withDescription("Overwrite existing config files"), Options.withDefault(false));
71
76
  const quietOption = Options.boolean("quiet").pipe(Options.withAlias("q"), Options.withDescription("Silence warnings, always exit 0"), Options.withDefault(false));
72
77
  const markdownlintOption = Options.boolean("markdownlint").pipe(Options.withDescription("Register rules in base markdownlint config"), Options.withDefault(true));
78
+ const checkOption = Options.boolean("check").pipe(Options.withDescription("Check configuration without writing (for postinstall scripts)"), Options.withDefault(false));
73
79
  function detectGitHubRepo(cwd) {
74
80
  try {
75
81
  const url = execSync("git remote get-url origin", {
@@ -89,6 +95,10 @@ function stripJsoncComments(text) {
89
95
  function resolveWorkspaceRoot(cwd) {
90
96
  return findProjectRoot(cwd) ?? cwd;
91
97
  }
98
+ function findMarkdownlintConfig(root) {
99
+ for (const configPath of MARKDOWNLINT_CONFIG_PATHS)if (existsSync(join(root, configPath))) return configPath;
100
+ return null;
101
+ }
92
102
  function ensureChangesetDir(root) {
93
103
  return Effect["try"]({
94
104
  try: ()=>{
@@ -140,19 +150,20 @@ function handleConfig(changesetDir, repoSlug, force) {
140
150
  function handleBaseMarkdownlint(root) {
141
151
  return Effect["try"]({
142
152
  try: ()=>{
143
- const baseConfigPath = join(root, BASE_CONFIG_PATH);
144
- if (!existsSync(baseConfigPath)) return null;
145
- const raw = readFileSync(baseConfigPath, "utf-8");
153
+ const foundPath = findMarkdownlintConfig(root);
154
+ if (!foundPath) return `Warning: no markdownlint config found (checked ${MARKDOWNLINT_CONFIG_PATHS.join(", ")})`;
155
+ const fullPath = join(root, foundPath);
156
+ const raw = readFileSync(fullPath, "utf-8");
146
157
  const parsed = JSON.parse(stripJsoncComments(raw));
147
158
  if (!Array.isArray(parsed.customRules)) parsed.customRules = [];
148
159
  if (!parsed.customRules.includes(CUSTOM_RULES_ENTRY)) parsed.customRules.push(CUSTOM_RULES_ENTRY);
149
160
  if ("object" != typeof parsed.config || null === parsed.config) parsed.config = {};
150
161
  for (const rule of RULE_NAMES)if (!(rule in parsed.config)) parsed.config[rule] = false;
151
- writeFileSync(baseConfigPath, `${JSON.stringify(parsed, null, "\t")}\n`);
152
- return "Updated lib/configs/.markdownlint-cli2.jsonc";
162
+ writeFileSync(fullPath, `${JSON.stringify(parsed, null, "\t")}\n`);
163
+ return `Updated ${foundPath}`;
153
164
  },
154
165
  catch: (error)=>new InitError({
155
- step: "lib/configs/.markdownlint-cli2.jsonc",
166
+ step: "markdownlint config",
156
167
  reason: error instanceof Error ? error.message : String(error)
157
168
  })
158
169
  });
@@ -161,10 +172,10 @@ function handleChangesetMarkdownlint(changesetDir, root, force) {
161
172
  return Effect["try"]({
162
173
  try: ()=>{
163
174
  const mdlintPath = join(changesetDir, ".markdownlint.json");
164
- const hasBaseConfig = existsSync(join(root, BASE_CONFIG_PATH));
175
+ const baseConfig = findMarkdownlintConfig(root);
165
176
  if (force || !existsSync(mdlintPath)) {
166
177
  const mdlintConfig = {};
167
- if (hasBaseConfig) mdlintConfig.extends = `../${BASE_CONFIG_PATH}`;
178
+ if (baseConfig) mdlintConfig.extends = `../${baseConfig}`;
168
179
  mdlintConfig.default = false;
169
180
  mdlintConfig.MD041 = false;
170
181
  for (const rule of RULE_NAMES)mdlintConfig[rule] = true;
@@ -182,15 +193,131 @@ function handleChangesetMarkdownlint(changesetDir, root, force) {
182
193
  })
183
194
  });
184
195
  }
196
+ function checkChangesetDir(root) {
197
+ const dir = join(root, ".changeset");
198
+ if (!existsSync(dir)) return [
199
+ {
200
+ file: ".changeset/",
201
+ message: "directory does not exist"
202
+ }
203
+ ];
204
+ return [];
205
+ }
206
+ function checkConfig(changesetDir, repoSlug) {
207
+ const configPath = join(changesetDir, "config.json");
208
+ if (!existsSync(configPath)) return [
209
+ {
210
+ file: ".changeset/config.json",
211
+ message: "file does not exist"
212
+ }
213
+ ];
214
+ try {
215
+ const config = JSON.parse(readFileSync(configPath, "utf-8"));
216
+ const issues = [];
217
+ const changelog = config.changelog;
218
+ const entry = Array.isArray(changelog) ? changelog[0] : changelog;
219
+ const repo = Array.isArray(changelog) ? changelog[1]?.repo : void 0;
220
+ if (entry !== CHANGELOG_ENTRY) issues.push({
221
+ file: ".changeset/config.json",
222
+ message: `changelog formatter is "${entry}", expected "${CHANGELOG_ENTRY}"`
223
+ });
224
+ else if (repo !== repoSlug) issues.push({
225
+ file: ".changeset/config.json",
226
+ message: `changelog repo is "${repo ?? "(not set)"}", expected "${repoSlug}"`
227
+ });
228
+ return issues;
229
+ } catch {
230
+ return [
231
+ {
232
+ file: ".changeset/config.json",
233
+ message: "could not parse file"
234
+ }
235
+ ];
236
+ }
237
+ }
238
+ function checkBaseMarkdownlint(root) {
239
+ const foundPath = findMarkdownlintConfig(root);
240
+ if (!foundPath) return [
241
+ {
242
+ file: "markdownlint config",
243
+ message: `not found (checked ${MARKDOWNLINT_CONFIG_PATHS.join(", ")})`
244
+ }
245
+ ];
246
+ try {
247
+ const raw = readFileSync(join(root, foundPath), "utf-8");
248
+ const parsed = JSON.parse(stripJsoncComments(raw));
249
+ const issues = [];
250
+ if (!Array.isArray(parsed.customRules) || !parsed.customRules.includes(CUSTOM_RULES_ENTRY)) issues.push({
251
+ file: foundPath,
252
+ message: `customRules does not include ${CUSTOM_RULES_ENTRY}`
253
+ });
254
+ const config = parsed.config;
255
+ if ("object" != typeof config || null === config) issues.push({
256
+ file: foundPath,
257
+ message: "config section is missing"
258
+ });
259
+ else for (const rule of RULE_NAMES)if (!(rule in config)) issues.push({
260
+ file: foundPath,
261
+ message: `rule "${rule}" is not configured`
262
+ });
263
+ return issues;
264
+ } catch {
265
+ return [
266
+ {
267
+ file: foundPath,
268
+ message: "could not parse file"
269
+ }
270
+ ];
271
+ }
272
+ }
273
+ function checkChangesetMarkdownlint(changesetDir) {
274
+ const mdlintPath = join(changesetDir, ".markdownlint.json");
275
+ if (!existsSync(mdlintPath)) return [
276
+ {
277
+ file: ".changeset/.markdownlint.json",
278
+ message: "file does not exist"
279
+ }
280
+ ];
281
+ try {
282
+ const existing = JSON.parse(readFileSync(mdlintPath, "utf-8"));
283
+ const issues = [];
284
+ for (const rule of RULE_NAMES)if (true !== existing[rule]) issues.push({
285
+ file: ".changeset/.markdownlint.json",
286
+ message: `rule "${rule}" is not enabled`
287
+ });
288
+ return issues;
289
+ } catch {
290
+ return [
291
+ {
292
+ file: ".changeset/.markdownlint.json",
293
+ message: "could not parse file"
294
+ }
295
+ ];
296
+ }
297
+ }
185
298
  const initCommand = Command.make("init", {
186
299
  force: forceOption,
187
300
  quiet: quietOption,
188
- markdownlint: markdownlintOption
189
- }, ({ force, quiet, markdownlint })=>Effect.gen(function*() {
301
+ markdownlint: markdownlintOption,
302
+ check: checkOption
303
+ }, ({ force, quiet, markdownlint, check })=>Effect.gen(function*() {
190
304
  const root = resolveWorkspaceRoot(process.cwd());
191
305
  const repo = detectGitHubRepo(root);
192
306
  if (!repo && !quiet) yield* Effect.log("Warning: could not detect GitHub repo from git remote, using placeholder");
193
307
  const repoSlug = repo ?? "owner/repo";
308
+ if (check) {
309
+ const changesetDir = join(root, ".changeset");
310
+ const issues = [
311
+ ...checkChangesetDir(root),
312
+ ...checkConfig(changesetDir, repoSlug),
313
+ ...markdownlint ? checkBaseMarkdownlint(root) : [],
314
+ ...checkChangesetMarkdownlint(changesetDir)
315
+ ];
316
+ if (0 === issues.length) return void (yield* Effect.log("All @savvy-web/changesets config files are up to date."));
317
+ for (const issue of issues)yield* Effect.logWarning(`${issue.file}: ${issue.message}`);
318
+ yield* Effect.logWarning('Run "savvy-changesets init --force" to fix.');
319
+ return;
320
+ }
194
321
  const changesetDir = yield* ensureChangesetDir(root);
195
322
  yield* Effect.log("Ensured .changeset/ directory");
196
323
  const errors = [];
@@ -199,9 +326,8 @@ const initCommand = Command.make("init", {
199
326
  else errors.push(configResult.left);
200
327
  if (markdownlint) {
201
328
  const baseResult = yield* handleBaseMarkdownlint(root).pipe(Effect.either);
202
- if ("Right" === baseResult._tag) {
203
- if (baseResult.right) yield* Effect.log(baseResult.right);
204
- } else errors.push(baseResult.left);
329
+ if ("Right" === baseResult._tag) yield* Effect.log(baseResult.right);
330
+ else errors.push(baseResult.left);
205
331
  }
206
332
  const mdlintResult = yield* handleChangesetMarkdownlint(changesetDir, root, force).pipe(Effect.either);
207
333
  if ("Right" === mdlintResult._tag) yield* Effect.log(mdlintResult.right);
@@ -239,7 +365,7 @@ const fileArg = Args.file({
239
365
  name: "file"
240
366
  }).pipe(Args.withDefault("CHANGELOG.md"));
241
367
  const dryRunOption = Options.boolean("dry-run").pipe(Options.withAlias("n"), Options.withDescription("Print transformed output instead of writing"), Options.withDefault(false));
242
- const checkOption = Options.boolean("check").pipe(Options.withAlias("c"), Options.withDescription("Exit 1 if file would change (for CI)"), Options.withDefault(false));
368
+ const transform_checkOption = Options.boolean("check").pipe(Options.withAlias("c"), Options.withDescription("Exit 1 if file would change (for CI)"), Options.withDefault(false));
243
369
  function runTransform(file, dryRun, check) {
244
370
  return Effect.gen(function*() {
245
371
  const resolved = resolve(file);
@@ -260,7 +386,7 @@ function runTransform(file, dryRun, check) {
260
386
  const transformCommand = Command.make("transform", {
261
387
  file: fileArg,
262
388
  dryRun: dryRunOption,
263
- check: checkOption
389
+ check: transform_checkOption
264
390
  }, ({ file, dryRun, check })=>runTransform(file, dryRun, check)).pipe(Command.withDescription("Post-process CHANGELOG.md"));
265
391
  class Workspace {
266
392
  static detectPackageManager(cwd = process.cwd()) {
@@ -366,7 +492,7 @@ const rootCommand = Command.make("savvy-changesets").pipe(Command.withSubcommand
366
492
  ]));
367
493
  const cli = Command.run(rootCommand, {
368
494
  name: "savvy-changesets",
369
- version: "0.1.0"
495
+ version: "0.1.1"
370
496
  });
371
497
  function runCli() {
372
498
  const main = Effect.suspend(()=>cli(process.argv)).pipe(Effect.provide(NodeContext.layer));
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Changesets API changelog formatter.
3
+ *
4
+ * This module exports the `ChangelogFunctions` required by the Changesets API.
5
+ * Configure in `.changeset/config.json`:
6
+ *
7
+ * ```json
8
+ * {
9
+ * "changelog": ["\@savvy-web/changesets/changelog", { "repo": "savvy-web/package-name" }]
10
+ * }
11
+ * ```
12
+ *
13
+ * @packageDocumentation
14
+ */
15
+
16
+ import { ChangelogFunctions } from '@changesets/types';
17
+
18
+ declare const changelogFunctions: ChangelogFunctions;
19
+ export default changelogFunctions;
20
+
21
+ export { }