nonotify 0.1.7 → 0.1.12

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/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ # nonotify
2
+
3
+ ## 0.1.10
4
+
5
+ ### Patch Changes
6
+
7
+ - Migrate the repository to npm workspaces and switch releases to Changesets-based automation with GitHub tags and GitHub Releases.
package/README.md CHANGED
@@ -53,7 +53,7 @@ nnt "Very long task finished. All tests passed, check out result"
53
53
  You can install agent skills for your agents:
54
54
 
55
55
  ```bash
56
- nnt skills add # install skills globally
56
+ nnt skills add # install skills globally
57
57
  cp ~/.agents/skills/nnt-* ./.agents/skills/ # install in current project
58
58
  ```
59
59
 
@@ -102,6 +102,10 @@ nnt profile list --format json
102
102
  nnt profile default --format=md
103
103
  ```
104
104
 
105
+ ## OpenCode plugin
106
+
107
+ There is also the [nonotify-opencode](https://www.npmjs.com/package/nonotify-opencode) plugin that automatically sends you important notifications via `nnt` when you use [OpenCode](https://github.com/anomalyco/opencode). Learn more [here](https://github.com/noartem/nnt/tree/main/nonotify-opencode).
108
+
105
109
  ## Config location
106
110
 
107
111
  Config is stored as JSON at `<config-dir>/nnt.json`.
@@ -164,3 +168,18 @@ await notifier.send({
164
168
 
165
169
  console.log(notifier.profiles);
166
170
  ```
171
+
172
+ ## Monorepo release flow
173
+
174
+ This repository is an npm workspaces monorepo with automated releases via Changesets.
175
+
176
+ - Every user-facing package change should include a changeset:
177
+
178
+ ```bash
179
+ npx changeset
180
+ ```
181
+
182
+ - Release automation behavior:
183
+ - on `main`, GitHub Actions creates/updates a release PR with version bumps and changelogs;
184
+ - after merging that PR, Actions publishes changed packages to npm;
185
+ - Git tags and GitHub Releases are created automatically.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nonotify",
3
- "version": "0.1.7",
3
+ "version": "0.1.12",
4
4
  "description": "nnt CLI for Telegram notifications",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -16,13 +16,14 @@
16
16
  "nnt": "./dist/cli.js"
17
17
  },
18
18
  "scripts": {
19
- "prettier:check": "npx prettier --check .",
20
- "prettier:fix": "npx prettier --write .",
19
+ "prettier:check": "npx prettier --check \"src/**/*.ts\"",
20
+ "prettier:fix": "npx prettier --write \"src/**/*.ts\"",
21
+ "copy:readme": "node ./scripts/copy-readme.mjs",
21
22
  "check": "npm run prettier:check",
22
23
  "fix": "npm run prettier:fix",
23
24
  "test": "npm run check",
24
25
  "build": "tsc -p tsconfig.json",
25
- "prepublishOnly": "npm run build",
26
+ "prepublishOnly": "npm run copy:readme && npm run build",
26
27
  "dev": "tsx src/cli.ts",
27
28
  "start": "node dist/cli.js"
28
29
  },
@@ -0,0 +1,11 @@
1
+ import { copyFile, mkdir } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+
5
+ const scriptDir = path.dirname(fileURLToPath(import.meta.url));
6
+ const packageDir = path.resolve(scriptDir, "..");
7
+ const sourceReadme = path.resolve(packageDir, "..", "README.md");
8
+ const targetReadme = path.resolve(packageDir, "README.md");
9
+
10
+ await mkdir(packageDir, { recursive: true });
11
+ await copyFile(sourceReadme, targetReadme);
package/src/cli.ts CHANGED
@@ -46,7 +46,7 @@ profileCli.command("add", {
46
46
  if (shouldRenderPretty(c.agent)) {
47
47
  process.stdout.write("\nSend any message to your bot in Telegram.\n");
48
48
  process.stdout.write(
49
- "Waiting for message to detect chat_id (up to 120s)...\n",
49
+ "Waiting for message to detect chat_id (up to 120s)...\n"
50
50
  );
51
51
  }
52
52
 
@@ -57,7 +57,7 @@ profileCli.command("add", {
57
57
  if (shouldRenderPretty(c.agent)) {
58
58
  if (connection.username) {
59
59
  process.stdout.write(
60
- `Connected Telegram username: @${connection.username}\n`,
60
+ `Connected Telegram username: @${connection.username}\n`
61
61
  );
62
62
  } else {
63
63
  process.stdout.write("Connected Telegram user has no username set.\n");
@@ -85,7 +85,7 @@ profileCli.command("add", {
85
85
  await sendTelegramMessage(
86
86
  botToken,
87
87
  chatId,
88
- `nnt: profile "${profileName}" connected successfully. You can now send notifications from CLI.`,
88
+ `nnt: profile "${profileName}" connected successfully. You can now send notifications from CLI.`
89
89
  );
90
90
  confirmationSent = true;
91
91
  } catch (error) {
@@ -128,7 +128,7 @@ profileCli.command("list", {
128
128
  async run(c) {
129
129
  const config = await loadConfig();
130
130
  const names = Object.keys(config.profiles).sort((a, b) =>
131
- a.localeCompare(b),
131
+ a.localeCompare(b)
132
132
  );
133
133
  const profiles = names.map((name) => ({
134
134
  name,
@@ -208,7 +208,7 @@ profileCli.command("delete", {
208
208
 
209
209
  if (config.defaultProfile === targetName) {
210
210
  const remaining = Object.keys(config.profiles).sort((a, b) =>
211
- a.localeCompare(b),
211
+ a.localeCompare(b)
212
212
  );
213
213
  config.defaultProfile = remaining[0] ?? null;
214
214
  }
@@ -256,7 +256,7 @@ profileCli.command("edit", {
256
256
  async run(c) {
257
257
  const config = await loadConfig();
258
258
  const profileNames = Object.keys(config.profiles).sort((a, b) =>
259
- a.localeCompare(b),
259
+ a.localeCompare(b)
260
260
  );
261
261
 
262
262
  if (profileNames.length === 0) {
@@ -265,15 +265,15 @@ profileCli.command("edit", {
265
265
 
266
266
  const hasDirectEditOptions = Boolean(
267
267
  c.options.newName ||
268
- c.options.botToken ||
269
- c.options.chatId ||
270
- c.options.reconnect,
268
+ c.options.botToken ||
269
+ c.options.chatId ||
270
+ c.options.reconnect
271
271
  );
272
272
 
273
273
  const sourceName = await resolveProfileForEdit(
274
274
  c.args.profile,
275
275
  profileNames,
276
- hasDirectEditOptions,
276
+ hasDirectEditOptions
277
277
  );
278
278
  const sourceProfile = config.profiles[sourceName];
279
279
 
@@ -302,19 +302,19 @@ profileCli.command("edit", {
302
302
 
303
303
  nextBotToken = await askRequiredWithInitial(
304
304
  "Telegram bot token",
305
- sourceProfile.botToken,
305
+ sourceProfile.botToken
306
306
  );
307
307
 
308
308
  const shouldReconnect = await askConfirm(
309
309
  "Reconnect and detect chat_id from a new message?",
310
- false,
310
+ false
311
311
  );
312
312
 
313
313
  if (shouldReconnect) {
314
314
  if (shouldRenderPretty(c.agent)) {
315
315
  process.stdout.write("\nSend any message to your bot in Telegram.\n");
316
316
  process.stdout.write(
317
- "Waiting for message to detect chat_id (up to 120s)...\n",
317
+ "Waiting for message to detect chat_id (up to 120s)...\n"
318
318
  );
319
319
  }
320
320
 
@@ -326,18 +326,18 @@ profileCli.command("edit", {
326
326
  if (shouldRenderPretty(c.agent)) {
327
327
  if (connection.username) {
328
328
  process.stdout.write(
329
- `Connected Telegram username: @${connection.username}\n`,
329
+ `Connected Telegram username: @${connection.username}\n`
330
330
  );
331
331
  } else {
332
332
  process.stdout.write(
333
- "Connected Telegram user has no username set.\n",
333
+ "Connected Telegram user has no username set.\n"
334
334
  );
335
335
  }
336
336
  }
337
337
  } else {
338
338
  chatId = await askRequiredWithInitial(
339
339
  "Telegram chat_id",
340
- sourceProfile.chatId,
340
+ sourceProfile.chatId
341
341
  );
342
342
  }
343
343
  }
@@ -346,7 +346,7 @@ profileCli.command("edit", {
346
346
  if (shouldRenderPretty(c.agent)) {
347
347
  process.stdout.write("\nSend any message to your bot in Telegram.\n");
348
348
  process.stdout.write(
349
- "Waiting for message to detect chat_id (up to 120s)...\n",
349
+ "Waiting for message to detect chat_id (up to 120s)...\n"
350
350
  );
351
351
  }
352
352
 
@@ -358,11 +358,11 @@ profileCli.command("edit", {
358
358
  if (shouldRenderPretty(c.agent)) {
359
359
  if (connection.username) {
360
360
  process.stdout.write(
361
- `Connected Telegram username: @${connection.username}\n`,
361
+ `Connected Telegram username: @${connection.username}\n`
362
362
  );
363
363
  } else {
364
364
  process.stdout.write(
365
- "Connected Telegram user has no username set.\n",
365
+ "Connected Telegram user has no username set.\n"
366
366
  );
367
367
  }
368
368
  }
@@ -534,7 +534,7 @@ function isAgentEnvironment(): boolean {
534
534
 
535
535
  function withAgentDefaultFormat(
536
536
  argv: string[],
537
- strictOutputRequested: boolean,
537
+ strictOutputRequested: boolean
538
538
  ): string[] {
539
539
  if (strictOutputRequested || !isAgentEnvironment()) {
540
540
  return argv;
@@ -550,7 +550,7 @@ function canPromptInteractively(): boolean {
550
550
  async function resolveProfileForEdit(
551
551
  profileFromArgs: string | undefined,
552
552
  profileNames: string[],
553
- hasDirectEditOptions: boolean,
553
+ hasDirectEditOptions: boolean
554
554
  ): Promise<string> {
555
555
  if (profileFromArgs) {
556
556
  return profileFromArgs;
@@ -565,7 +565,7 @@ async function resolveProfileForEdit(
565
565
  profileNames.map((name) => ({
566
566
  value: name,
567
567
  label: name,
568
- })),
568
+ }))
569
569
  );
570
570
  }
571
571
 
@@ -573,7 +573,7 @@ const normalizedArgv = normalizeFormatFlag(process.argv.slice(2));
573
573
  const strictOutputRequested = isStrictOutputRequested(normalizedArgv);
574
574
  const argvWithAgentDefaults = withAgentDefaultFormat(
575
575
  normalizedArgv,
576
- strictOutputRequested,
576
+ strictOutputRequested
577
577
  );
578
578
 
579
579
  function shouldRenderPretty(agent: boolean): boolean {
package/src/display.ts CHANGED
@@ -25,7 +25,7 @@ export function printProfilesTable(rows: ProfileRow[]): void {
25
25
 
26
26
  export function printKeyValueTable(
27
27
  title: string,
28
- rows: Array<{ key: string; value: string }>,
28
+ rows: Array<{ key: string; value: string }>
29
29
  ): void {
30
30
  process.stdout.write(`${title}\n`);
31
31
 
package/src/notifier.ts CHANGED
@@ -75,7 +75,7 @@ type RawRecordProfile = {
75
75
  export class EnvConfigLoader implements NotifierConfigLoader {
76
76
  load(): NotifierConfig {
77
77
  const configPaths = Array.from(
78
- new Set([getConfigPath(), getLegacyConfigPath()]),
78
+ new Set([getConfigPath(), getLegacyConfigPath()])
79
79
  );
80
80
 
81
81
  for (const configPath of configPaths) {
@@ -90,7 +90,7 @@ export class EnvConfigLoader implements NotifierConfigLoader {
90
90
 
91
91
  if (error instanceof SyntaxError) {
92
92
  throw new NotifierError(
93
- `Invalid JSON in config file at ${configPath}: ${error.message}`,
93
+ `Invalid JSON in config file at ${configPath}: ${error.message}`
94
94
  );
95
95
  }
96
96
 
@@ -114,18 +114,18 @@ export class Notifier {
114
114
  >;
115
115
 
116
116
  constructor(
117
- source: NotifierConfig | NotifierConfigLoader = new EnvConfigLoader(),
117
+ source: NotifierConfig | NotifierConfigLoader = new EnvConfigLoader()
118
118
  ) {
119
119
  const config = isConfigLoader(source) ? source.load() : source;
120
120
  const normalized = normalizeNotifierConfig(config);
121
121
  const frozenProfiles = normalized.profiles.map((profile) =>
122
- Object.freeze({ ...profile }),
122
+ Object.freeze({ ...profile })
123
123
  );
124
124
 
125
125
  this.profiles = Object.freeze(frozenProfiles);
126
126
  this.defaultProfile = normalized.defaultProfile;
127
127
  this.profilesByName = new Map(
128
- frozenProfiles.map((profile) => [profile.name, profile]),
128
+ frozenProfiles.map((profile) => [profile.name, profile])
129
129
  );
130
130
  }
131
131
 
@@ -141,7 +141,7 @@ export class Notifier {
141
141
  await sendTelegramMessage(
142
142
  selectedProfile.botToken,
143
143
  selectedProfile.chatId,
144
- message,
144
+ message
145
145
  );
146
146
 
147
147
  return {
@@ -194,22 +194,22 @@ function normalizeProfilesFromUnknown(rawProfiles: unknown): NotifierProfile[] {
194
194
 
195
195
  if (Array.isArray(rawProfiles)) {
196
196
  return rawProfiles.map((profile, index) =>
197
- normalizeSingleProfile(profile, `profiles[${index}]`),
197
+ normalizeSingleProfile(profile, `profiles[${index}]`)
198
198
  );
199
199
  }
200
200
 
201
201
  if (typeof rawProfiles === "object") {
202
202
  const entries = Object.entries(
203
- rawProfiles as Record<string, RawRecordProfile>,
203
+ rawProfiles as Record<string, RawRecordProfile>
204
204
  );
205
205
 
206
206
  return entries.map(([name, profile]) =>
207
- normalizeRecordProfile(name, profile, `profiles.${name}`),
207
+ normalizeRecordProfile(name, profile, `profiles.${name}`)
208
208
  );
209
209
  }
210
210
 
211
211
  throw new NotifierError(
212
- "Invalid config format: `profiles` must be an array or object.",
212
+ "Invalid config format: `profiles` must be an array or object."
213
213
  );
214
214
  }
215
215
 
@@ -219,12 +219,12 @@ function normalizeNotifierConfig(input: NotifierConfig): {
219
219
  } {
220
220
  if (!Array.isArray(input.profiles)) {
221
221
  throw new NotifierError(
222
- "Invalid Notifier config: `profiles` must be an array.",
222
+ "Invalid Notifier config: `profiles` must be an array."
223
223
  );
224
224
  }
225
225
 
226
226
  const profiles = input.profiles.map((profile, index) =>
227
- normalizeSingleProfile(profile, `profiles[${index}]`),
227
+ normalizeSingleProfile(profile, `profiles[${index}]`)
228
228
  );
229
229
 
230
230
  const names = new Set<string>();
@@ -232,7 +232,7 @@ function normalizeNotifierConfig(input: NotifierConfig): {
232
232
  for (const profile of profiles) {
233
233
  if (names.has(profile.name)) {
234
234
  throw new NotifierError(
235
- `Invalid Notifier config: duplicate profile name "${profile.name}".`,
235
+ `Invalid Notifier config: duplicate profile name "${profile.name}".`
236
236
  );
237
237
  }
238
238
 
@@ -249,11 +249,11 @@ function normalizeNotifierConfig(input: NotifierConfig): {
249
249
  function normalizeRecordProfile(
250
250
  profileName: string,
251
251
  rawProfile: RawRecordProfile,
252
- sourceLabel: string,
252
+ sourceLabel: string
253
253
  ): NotifierProfile {
254
254
  if (!rawProfile || typeof rawProfile !== "object") {
255
255
  throw new NotifierError(
256
- `Invalid profile at ${sourceLabel}: expected an object.`,
256
+ `Invalid profile at ${sourceLabel}: expected an object.`
257
257
  );
258
258
  }
259
259
 
@@ -262,7 +262,7 @@ function normalizeRecordProfile(
262
262
  name: profileName,
263
263
  botToken: requireNonEmptyString(
264
264
  rawProfile.botToken,
265
- `${sourceLabel}.botToken`,
265
+ `${sourceLabel}.botToken`
266
266
  ),
267
267
  chatId: requireNonEmptyString(rawProfile.chatId, `${sourceLabel}.chatId`),
268
268
  };
@@ -270,11 +270,11 @@ function normalizeRecordProfile(
270
270
 
271
271
  function normalizeSingleProfile(
272
272
  rawProfile: unknown,
273
- sourceLabel: string,
273
+ sourceLabel: string
274
274
  ): NotifierProfile {
275
275
  if (!rawProfile || typeof rawProfile !== "object") {
276
276
  throw new NotifierError(
277
- `Invalid profile at ${sourceLabel}: expected an object.`,
277
+ `Invalid profile at ${sourceLabel}: expected an object.`
278
278
  );
279
279
  }
280
280
 
@@ -287,7 +287,7 @@ function normalizeSingleProfile(
287
287
 
288
288
  if (profile.type !== undefined && profile.type !== "telegram") {
289
289
  throw new NotifierError(
290
- `Invalid profile at ${sourceLabel}.type: only "telegram" is supported.`,
290
+ `Invalid profile at ${sourceLabel}.type: only "telegram" is supported.`
291
291
  );
292
292
  }
293
293
 
@@ -296,7 +296,7 @@ function normalizeSingleProfile(
296
296
  name: requireNonEmptyString(profile.name, `${sourceLabel}.name`),
297
297
  botToken: requireNonEmptyString(
298
298
  profile.botToken,
299
- `${sourceLabel}.botToken`,
299
+ `${sourceLabel}.botToken`
300
300
  ),
301
301
  chatId: requireNonEmptyString(profile.chatId, `${sourceLabel}.chatId`),
302
302
  };
@@ -305,7 +305,7 @@ function normalizeSingleProfile(
305
305
  function requireNonEmptyString(value: unknown, label: string): string {
306
306
  if (typeof value !== "string" || value.trim() === "") {
307
307
  throw new NotifierError(
308
- `Invalid value for ${label}: expected non-empty string.`,
308
+ `Invalid value for ${label}: expected non-empty string.`
309
309
  );
310
310
  }
311
311
 
package/src/prompt.ts CHANGED
@@ -6,7 +6,7 @@ export async function askRequired(question: string): Promise<string> {
6
6
 
7
7
  export async function askRequiredWithInitial(
8
8
  question: string,
9
- initialValue?: string,
9
+ initialValue?: string
10
10
  ): Promise<string> {
11
11
  const message = normalizeQuestion(question);
12
12
 
@@ -32,7 +32,7 @@ export async function askRequiredWithInitial(
32
32
 
33
33
  export async function askConfirm(
34
34
  question: string,
35
- initialValue = false,
35
+ initialValue = false
36
36
  ): Promise<boolean> {
37
37
  const value = await confirm({
38
38
  message: normalizeQuestion(question),
@@ -56,7 +56,7 @@ type SelectOption = {
56
56
 
57
57
  export async function askSelect(
58
58
  question: string,
59
- options: SelectOption[],
59
+ options: SelectOption[]
60
60
  ): Promise<string> {
61
61
  const value = await select<string>({
62
62
  message: normalizeQuestion(question),
package/src/telegram.ts CHANGED
@@ -31,7 +31,7 @@ export type TelegramConnection = {
31
31
  async function telegramRequest<T>(
32
32
  botToken: string,
33
33
  method: string,
34
- payload: Record<string, unknown>,
34
+ payload: Record<string, unknown>
35
35
  ): Promise<T> {
36
36
  const response = await fetch(
37
37
  `https://api.telegram.org/bot${botToken}/${method}`,
@@ -41,7 +41,7 @@ async function telegramRequest<T>(
41
41
  "content-type": "application/json",
42
42
  },
43
43
  body: JSON.stringify(payload),
44
- },
44
+ }
45
45
  );
46
46
 
47
47
  if (!response.ok) {
@@ -64,7 +64,7 @@ export async function getLatestUpdateOffset(botToken: string): Promise<number> {
64
64
  {
65
65
  timeout: 0,
66
66
  allowed_updates: ["message"],
67
- },
67
+ }
68
68
  );
69
69
 
70
70
  if (updates.length === 0) {
@@ -73,7 +73,7 @@ export async function getLatestUpdateOffset(botToken: string): Promise<number> {
73
73
 
74
74
  const maxUpdateId = updates.reduce(
75
75
  (acc, item) => Math.max(acc, item.update_id),
76
- 0,
76
+ 0
77
77
  );
78
78
  return maxUpdateId + 1;
79
79
  }
@@ -81,7 +81,7 @@ export async function getLatestUpdateOffset(botToken: string): Promise<number> {
81
81
  export async function waitForChatId(
82
82
  botToken: string,
83
83
  offset: number,
84
- timeoutSeconds = 120,
84
+ timeoutSeconds = 120
85
85
  ): Promise<TelegramConnection> {
86
86
  const startedAt = Date.now();
87
87
  let currentOffset = offset;
@@ -98,7 +98,7 @@ export async function waitForChatId(
98
98
  offset: currentOffset,
99
99
  timeout: pollTimeout,
100
100
  allowed_updates: ["message"],
101
- },
101
+ }
102
102
  );
103
103
 
104
104
  for (const update of updates) {
@@ -117,14 +117,14 @@ export async function waitForChatId(
117
117
  }
118
118
 
119
119
  throw new Error(
120
- "Timed out waiting for Telegram message. Send a message to your bot and try again.",
120
+ "Timed out waiting for Telegram message. Send a message to your bot and try again."
121
121
  );
122
122
  }
123
123
 
124
124
  export async function sendTelegramMessage(
125
125
  botToken: string,
126
126
  chatId: string,
127
- text: string,
127
+ text: string
128
128
  ): Promise<void> {
129
129
  await telegramRequest(botToken, "sendMessage", {
130
130
  chat_id: chatId,
@@ -1,113 +0,0 @@
1
- name: Release & Publish
2
-
3
- on:
4
- push:
5
- branches: [main]
6
-
7
- permissions:
8
- contents: write
9
-
10
- jobs:
11
- release:
12
- runs-on: ubuntu-latest
13
-
14
- steps:
15
- - name: Checkout
16
- uses: actions/checkout@v4
17
- with:
18
- fetch-depth: 0
19
-
20
- - name: Setup Node
21
- uses: actions/setup-node@v4
22
- with:
23
- node-version: 20
24
- registry-url: https://registry.npmjs.org/
25
-
26
- - name: Decide whether to release
27
- id: guard
28
- shell: bash
29
- run: |
30
- set -euo pipefail
31
-
32
- VERSION="$(node -p "require('./package.json').version")"
33
- NAME="$(node -p "require('./package.json').name")"
34
-
35
- git fetch --tags --force
36
-
37
- # Latest tag (empty if no tags yet)
38
- LATEST_TAG="$(git tag --sort=-v:refname | head -n 1 || true)"
39
-
40
- # Does tag for this version already exist?
41
- if git rev-parse -q --verify "refs/tags/$VERSION" >/dev/null; then
42
- TAG_EXISTS="true"
43
- else
44
- TAG_EXISTS="false"
45
- fi
46
-
47
- # Is that version already on npm?
48
- if npm view "${NAME}@${VERSION}" version >/dev/null 2>&1; then
49
- NPM_EXISTS="true"
50
- else
51
- NPM_EXISTS="false"
52
- fi
53
-
54
- # Decide
55
- SHOULD_RELEASE="true"
56
- REASON="ok"
57
-
58
- if [[ "$TAG_EXISTS" == "true" ]]; then
59
- SHOULD_RELEASE="false"
60
- REASON="git tag already exists"
61
- elif [[ -n "$LATEST_TAG" && "$VERSION" == "$LATEST_TAG" ]]; then
62
- SHOULD_RELEASE="false"
63
- REASON="package.json version equals latest tag"
64
- elif [[ "$NPM_EXISTS" == "true" ]]; then
65
- SHOULD_RELEASE="false"
66
- REASON="version already published on npm"
67
- fi
68
-
69
- echo "version=$VERSION" >> "$GITHUB_OUTPUT"
70
- echo "latest_tag=$LATEST_TAG" >> "$GITHUB_OUTPUT"
71
- echo "tag_exists=$TAG_EXISTS" >> "$GITHUB_OUTPUT"
72
- echo "npm_exists=$NPM_EXISTS" >> "$GITHUB_OUTPUT"
73
- echo "should_release=$SHOULD_RELEASE" >> "$GITHUB_OUTPUT"
74
- echo "reason=$REASON" >> "$GITHUB_OUTPUT"
75
-
76
- echo "Decision: should_release=$SHOULD_RELEASE ($REASON)"
77
-
78
- - name: Stop summary
79
- if: steps.guard.outputs.should_release != 'true'
80
- run: |
81
- echo "Skipping release: ${{ steps.guard.outputs.reason }}"
82
- echo "Version: ${{ steps.guard.outputs.version }}"
83
- echo "Latest tag: ${{ steps.guard.outputs.latest_tag }}"
84
-
85
- - name: Install dependencies
86
- if: steps.guard.outputs.should_release == 'true'
87
- run: npm ci
88
-
89
- - name: Run tests
90
- if: steps.guard.outputs.should_release == 'true'
91
- run: npm test
92
-
93
- - name: Create git tag
94
- if: steps.guard.outputs.should_release == 'true'
95
- run: |
96
- git config user.name "github-actions"
97
- git config user.email "github-actions@github.com"
98
- git tag "${{ steps.guard.outputs.version }}"
99
- git push origin "${{ steps.guard.outputs.version }}"
100
-
101
- - name: Create GitHub Release
102
- if: steps.guard.outputs.should_release == 'true'
103
- uses: softprops/action-gh-release@v2
104
- with:
105
- tag_name: ${{ steps.guard.outputs.version }}
106
- name: Release ${{ steps.guard.outputs.version }}
107
- generate_release_notes: true
108
-
109
- - name: Publish to npm
110
- if: steps.guard.outputs.should_release == 'true'
111
- run: npm publish
112
- env:
113
- NODE_AUTH_TOKEN: ${{ secrets.npm_token }}