@programinglive/dev-workflow-mcp-server 1.5.0 → 1.5.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.
Files changed (99) hide show
  1. package/.agent/workflows/dev-workflow-bugfix.md +250 -250
  2. package/.agent/workflows/dev-workflow-feature.md +201 -201
  3. package/.agent/workflows/dev-workflow-full-cycle.md +400 -400
  4. package/.agent/workflows/dev-workflow-refactor.md +238 -238
  5. package/.github/ISSUE_TEMPLATE/bug_report.md +34 -34
  6. package/.github/ISSUE_TEMPLATE/feature_request.md +25 -25
  7. package/.github/pull_request_template.md +22 -22
  8. package/.github/workflows/npm-publish.yml +50 -47
  9. package/.husky/commit-msg +0 -0
  10. package/CHANGELOG.md +750 -491
  11. package/CODE_OF_CONDUCT.md +79 -79
  12. package/CONTRIBUTING.md +60 -60
  13. package/LICENSE +21 -21
  14. package/README.md +632 -609
  15. package/SECURITY.md +20 -20
  16. package/db/index.js +204 -204
  17. package/docs/NETLIFY_DEPLOYMENT.md +125 -0
  18. package/docs/PLESK_DEPLOYMENT.md +170 -0
  19. package/docs/WORKFLOW-BEST-PRACTICES.md +58 -0
  20. package/docs/antigravity/EXAMPLES.md +535 -535
  21. package/docs/antigravity/GETTING-STARTED.md +292 -292
  22. package/docs/antigravity/TROUBLESHOOTING.md +742 -742
  23. package/docs/antigravity/WALKTHROUGH.md +25 -25
  24. package/docs/antigravity/WORKFLOW-TEMPLATES.md +670 -670
  25. package/docs/product/PRD.md +67 -64
  26. package/docs/release-notes/RELEASE_NOTES.md +226 -132
  27. package/docs/web-dashboard.md +175 -163
  28. package/exec.js +49 -49
  29. package/index.js +279 -279
  30. package/install.js +73 -73
  31. package/package.json +11 -9
  32. package/release-wrapper.js +192 -157
  33. package/scripts/build-docs.js +63 -63
  34. package/scripts/hello-world.js +1 -1
  35. package/scripts/release.cjs +252 -252
  36. package/scripts/update-release-notes.cjs +178 -182
  37. package/server.json +23 -0
  38. package/tailwind.config.js +8 -10
  39. package/tests/build.test.js +83 -48
  40. package/tests/exec.test.js +35 -35
  41. package/tests/handlers.test.js +1058 -980
  42. package/tests/hello-world.test.js +13 -13
  43. package/tests/mcpRegistry.test.js +65 -0
  44. package/tests/release-wrapper.test.js +107 -105
  45. package/tests/workflowState.test.js +505 -502
  46. package/tools/definitions.js +332 -332
  47. package/tools/handlers.js +1019 -1019
  48. package/vite.config.js +43 -43
  49. package/vite.web.config.js +23 -0
  50. package/web/README.md +36 -0
  51. package/web/app/docs/page.tsx +202 -0
  52. package/web/app/favicon.ico +0 -0
  53. package/web/app/globals.css +91 -0
  54. package/web/app/layout.tsx +36 -0
  55. package/web/app/page.tsx +15 -0
  56. package/web/components/ApiReference.tsx +20 -0
  57. package/web/components/Features.tsx +61 -0
  58. package/web/components/Footer.tsx +9 -0
  59. package/web/components/Hero.tsx +96 -0
  60. package/web/components/History.tsx +143 -0
  61. package/web/components/Navbar.tsx +64 -0
  62. package/web/components/Terminal.tsx +114 -0
  63. package/web/eslint.config.mjs +18 -0
  64. package/web/lib/db.js +131 -0
  65. package/web/lib/docs.js +73 -0
  66. package/web/netlify.toml +3 -0
  67. package/web/next.config.ts +12 -0
  68. package/web/package-lock.json +6560 -0
  69. package/web/package.json +28 -0
  70. package/web/postcss.config.mjs +9 -0
  71. package/web/public/assets/android-chrome-192x192.png +0 -0
  72. package/web/public/assets/android-chrome-512x512.png +0 -0
  73. package/web/public/assets/apple-touch-icon.png +0 -0
  74. package/web/public/assets/favicon-16x16.png +0 -0
  75. package/web/public/assets/favicon-32x32.png +0 -0
  76. package/web/public/assets/favicon.ico +0 -0
  77. package/web/public/assets/favicon.png +0 -0
  78. package/web/public/assets/logo.png +0 -0
  79. package/web/public/assets/og-preview.png +0 -0
  80. package/web/public/assets/site.webmanifest +1 -0
  81. package/web/public/file.svg +1 -0
  82. package/web/public/globe.svg +1 -0
  83. package/web/public/next.svg +1 -0
  84. package/web/public/vercel.svg +1 -0
  85. package/web/public/window.svg +1 -0
  86. package/web/server.js +31 -159
  87. package/web/tailwind.config.ts +19 -0
  88. package/web/tsconfig.json +42 -0
  89. package/workflow-state.js +612 -626
  90. package/dist/docs/EXAMPLES.html +0 -411
  91. package/dist/docs/GETTING-STARTED.html +0 -233
  92. package/dist/docs/README.html +0 -535
  93. package/dist/docs/RELEASE_NOTES.html +0 -167
  94. package/dist/docs/TROUBLESHOOTING.html +0 -542
  95. package/dist/docs/WORKFLOW-TEMPLATES.html +0 -591
  96. package/dist/docs/web-dashboard.html +0 -158
  97. package/web/public/index.html +0 -1046
  98. package/web/public/input.css +0 -3
  99. package/web/public/output.css +0 -1
@@ -1,63 +1,63 @@
1
- import { readFile, writeFile, mkdir } from 'node:fs/promises';
2
- import path from 'node:path';
3
- import { fileURLToPath } from 'node:url';
4
- import { marked } from 'marked';
5
-
6
- const __filename = fileURLToPath(import.meta.url);
7
- const __dirname = path.dirname(__filename);
8
- const projectRoot = path.join(__dirname, '..');
9
-
10
- const docs = [
11
- {
12
- key: 'README.md',
13
- source: path.join(projectRoot, 'README.md'),
14
- output: 'README.html',
15
- },
16
- {
17
- key: 'web-dashboard.md',
18
- source: path.join(projectRoot, 'docs', 'web-dashboard.md'),
19
- output: 'web-dashboard.html',
20
- },
21
- {
22
- key: 'GETTING-STARTED.md',
23
- source: path.join(projectRoot, 'docs', 'antigravity', 'GETTING-STARTED.md'),
24
- output: 'GETTING-STARTED.html',
25
- },
26
- {
27
- key: 'EXAMPLES.md',
28
- source: path.join(projectRoot, 'docs', 'antigravity', 'EXAMPLES.md'),
29
- output: 'EXAMPLES.html',
30
- },
31
- {
32
- key: 'WORKFLOW-TEMPLATES.md',
33
- source: path.join(projectRoot, 'docs', 'antigravity', 'WORKFLOW-TEMPLATES.md'),
34
- output: 'WORKFLOW-TEMPLATES.html',
35
- },
36
- {
37
- key: 'TROUBLESHOOTING.md',
38
- source: path.join(projectRoot, 'docs', 'antigravity', 'TROUBLESHOOTING.md'),
39
- output: 'TROUBLESHOOTING.html',
40
- },
41
- {
42
- key: 'RELEASE_NOTES.md',
43
- source: path.join(projectRoot, 'docs', 'release-notes', 'RELEASE_NOTES.md'),
44
- output: 'RELEASE_NOTES.html',
45
- },
46
- ];
47
-
48
- const distDocDir = path.join(projectRoot, 'dist', 'docs');
49
-
50
- async function buildDocs() {
51
- await mkdir(distDocDir, { recursive: true });
52
-
53
- for (const doc of docs) {
54
- const markdown = await readFile(doc.source, 'utf-8');
55
- const html = marked.parse(markdown);
56
- await writeFile(path.join(distDocDir, doc.output), html, 'utf-8');
57
- }
58
- }
59
-
60
- buildDocs().catch((error) => {
61
- console.error('Failed to build documentation assets:', error);
62
- process.exitCode = 1;
63
- });
1
+ import { readFile, writeFile, mkdir } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { marked } from 'marked';
5
+
6
+ const __filename = fileURLToPath(import.meta.url);
7
+ const __dirname = path.dirname(__filename);
8
+ const projectRoot = path.join(__dirname, '..');
9
+
10
+ const docs = [
11
+ {
12
+ key: 'README.md',
13
+ source: path.join(projectRoot, 'README.md'),
14
+ output: 'README.html',
15
+ },
16
+ {
17
+ key: 'web-dashboard.md',
18
+ source: path.join(projectRoot, 'docs', 'web-dashboard.md'),
19
+ output: 'web-dashboard.html',
20
+ },
21
+ {
22
+ key: 'GETTING-STARTED.md',
23
+ source: path.join(projectRoot, 'docs', 'antigravity', 'GETTING-STARTED.md'),
24
+ output: 'GETTING-STARTED.html',
25
+ },
26
+ {
27
+ key: 'EXAMPLES.md',
28
+ source: path.join(projectRoot, 'docs', 'antigravity', 'EXAMPLES.md'),
29
+ output: 'EXAMPLES.html',
30
+ },
31
+ {
32
+ key: 'WORKFLOW-TEMPLATES.md',
33
+ source: path.join(projectRoot, 'docs', 'antigravity', 'WORKFLOW-TEMPLATES.md'),
34
+ output: 'WORKFLOW-TEMPLATES.html',
35
+ },
36
+ {
37
+ key: 'TROUBLESHOOTING.md',
38
+ source: path.join(projectRoot, 'docs', 'antigravity', 'TROUBLESHOOTING.md'),
39
+ output: 'TROUBLESHOOTING.html',
40
+ },
41
+ {
42
+ key: 'RELEASE_NOTES.md',
43
+ source: path.join(projectRoot, 'docs', 'release-notes', 'RELEASE_NOTES.md'),
44
+ output: 'RELEASE_NOTES.html',
45
+ },
46
+ ];
47
+
48
+ const distDocDir = path.join(projectRoot, 'dist', 'docs');
49
+
50
+ async function buildDocs() {
51
+ await mkdir(distDocDir, { recursive: true });
52
+
53
+ for (const doc of docs) {
54
+ const markdown = await readFile(doc.source, 'utf-8');
55
+ const html = marked.parse(markdown);
56
+ await writeFile(path.join(distDocDir, doc.output), html, 'utf-8');
57
+ }
58
+ }
59
+
60
+ buildDocs().catch((error) => {
61
+ console.error('Failed to build documentation assets:', error);
62
+ process.exitCode = 1;
63
+ });
@@ -1 +1 @@
1
- console.log('Hello from Antigravity!');
1
+ console.log('Hello from Antigravity!');
@@ -1,252 +1,252 @@
1
- #!/usr/bin/env node
2
-
3
- const { spawnSync } = require('child_process');
4
- const fs = require('fs');
5
- const path = require('path');
6
-
7
- const { updateReleaseNotes } = require('./update-release-notes.cjs');
8
-
9
- const fsFokPreloadPath = path.join(__dirname, 'preload', 'fs-f-ok.cjs');
10
- const FS_FOK_PRELOAD_FLAG = buildPreloadFlag(fsFokPreloadPath);
11
-
12
- require('./preload/fs-f-ok.cjs');
13
-
14
- const VALID_RELEASE_TYPES = new Set([
15
- 'major',
16
- 'minor',
17
- 'patch',
18
- 'premajor',
19
- 'preminor',
20
- 'prepatch',
21
- 'prerelease'
22
- ]);
23
-
24
- const SEMVER_REGEX = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
25
-
26
- const LOCKFILE_PREFERENCES = [
27
- { manager: 'pnpm', file: 'pnpm-lock.yaml' },
28
- { manager: 'yarn', file: 'yarn.lock' },
29
- { manager: 'bun', file: 'bun.lockb' },
30
- { manager: 'npm', file: 'package-lock.json' }
31
- ];
32
-
33
- function getCliArguments(argv = process.argv) {
34
- const rawArgs = argv.slice(2);
35
- if (rawArgs.length === 0) {
36
- return { releaseType: undefined, extraArgs: [] };
37
- }
38
-
39
- if (rawArgs[0].startsWith('-')) {
40
- return { releaseType: undefined, extraArgs: rawArgs };
41
- }
42
-
43
- const [firstArg, ...rest] = rawArgs;
44
- return { releaseType: firstArg, extraArgs: rest };
45
- }
46
-
47
- function getNpmRunArgument(env = process.env) {
48
- try {
49
- const npmArgs = JSON.parse(env.npm_config_argv || '{}');
50
- const original = npmArgs.original || [];
51
- const releaseIndex = original.lastIndexOf('release');
52
- if (releaseIndex !== -1) {
53
- return original[releaseIndex + 1];
54
- }
55
- } catch (error) {
56
- // Ignore JSON parsing issues and fall back to defaults
57
- }
58
- return undefined;
59
- }
60
-
61
- function buildStandardVersionArgs({ releaseType, extraArgs }) {
62
- const args = [];
63
- if (releaseType) {
64
- const normalized = releaseType.trim();
65
- const isValid = VALID_RELEASE_TYPES.has(normalized) || SEMVER_REGEX.test(normalized);
66
- if (!isValid) {
67
- const allowed = Array.from(VALID_RELEASE_TYPES).join(', ');
68
- throw new Error(`Unknown release type "${normalized}". Use one of ${allowed} or a valid semver version.`);
69
- }
70
- args.push('--release-as', normalized);
71
- }
72
-
73
- if (Array.isArray(extraArgs) && extraArgs.length > 0) {
74
- args.push(...extraArgs);
75
- }
76
-
77
- return args;
78
- }
79
-
80
- function loadPackageJson(cwd = process.cwd()) {
81
- try {
82
- const packageJsonPath = path.join(cwd, 'package.json');
83
- const contents = fs.readFileSync(packageJsonPath, 'utf8');
84
- return JSON.parse(contents);
85
- } catch (error) {
86
- return null;
87
- }
88
- }
89
-
90
- function detectPackageManager({ env = process.env, cwd = process.cwd() } = {}) {
91
- const userAgent = env.npm_config_user_agent || '';
92
- if (userAgent.startsWith('pnpm/')) return 'pnpm';
93
- if (userAgent.startsWith('yarn/')) return 'yarn';
94
- if (userAgent.startsWith('bun/')) return 'bun';
95
-
96
- for (const { manager, file } of LOCKFILE_PREFERENCES) {
97
- if (fs.existsSync(path.join(cwd, file))) {
98
- return manager;
99
- }
100
- }
101
-
102
- return 'npm';
103
- }
104
-
105
- function buildTestCommand(packageManager) {
106
- switch (packageManager) {
107
- case 'pnpm':
108
- return { command: 'pnpm', args: ['test'] };
109
- case 'yarn':
110
- return { command: 'yarn', args: ['test'] };
111
- case 'bun':
112
- return { command: 'bun', args: ['test'] };
113
- default:
114
- return { command: 'npm', args: ['test'] };
115
- }
116
- }
117
-
118
- function runProjectTests({ spawn = spawnSync, env = process.env, cwd = process.cwd() } = {}) {
119
- const packageJson = loadPackageJson(cwd);
120
- const scripts = packageJson && packageJson.scripts ? packageJson.scripts : {};
121
-
122
- if (!scripts.test) {
123
- console.log('ℹ️ Skipping tests: no "test" script detected in package.json');
124
- return { status: 0 };
125
- }
126
-
127
- const packageManager = detectPackageManager({ env, cwd });
128
- const { command, args } = buildTestCommand(packageManager);
129
-
130
- console.log(`🧪 Running tests with ${packageManager} ${args.join(' ')}`.trim());
131
- return spawn(command, args, {
132
- stdio: 'inherit',
133
- env
134
- });
135
- }
136
-
137
- function runRelease({
138
- argv = process.argv,
139
- env = process.env,
140
- spawn = spawnSync,
141
- cwd = process.cwd(),
142
- dependencies = {}
143
- } = {}) {
144
- const { releaseType: cliReleaseType, extraArgs } = getCliArguments(argv);
145
- const inferredReleaseType = cliReleaseType || getNpmRunArgument(env);
146
- const standardVersionArgs = buildStandardVersionArgs({
147
- releaseType: inferredReleaseType,
148
- extraArgs
149
- });
150
-
151
- const checkWorkingTreeClean = dependencies.isWorkingTreeClean || (() => isWorkingTreeClean({ cwd }));
152
- const workingTreeClean = checkWorkingTreeClean();
153
- if (!workingTreeClean) {
154
- throw new Error('Working tree has uncommitted changes. Commit or stash before running release.');
155
- }
156
-
157
- const testResult = runProjectTests({ spawn, env });
158
- if (testResult && typeof testResult.status === 'number' && testResult.status !== 0) {
159
- return testResult;
160
- }
161
-
162
- const standardVersionBin = require.resolve('standard-version/bin/cli.js');
163
- const childEnv = appendPreloadToNodeOptions(env, FS_FOK_PRELOAD_FLAG);
164
- const releaseResult = spawn(process.execPath, [standardVersionBin, ...standardVersionArgs], {
165
- stdio: 'inherit',
166
- env: childEnv,
167
- cwd
168
- });
169
-
170
- if (releaseResult && typeof releaseResult.status === 'number' && releaseResult.status !== 0) {
171
- return releaseResult;
172
- }
173
-
174
- const releaseNotesPath = dependencies.releaseNotesPath || path.join('docs', 'release-notes', 'RELEASE_NOTES.md');
175
- const updateNotes = dependencies.updateReleaseNotes || ((options = {}) => updateReleaseNotes({ rootDir: cwd, ...options }));
176
-
177
- let notesUpdated = false;
178
- try {
179
- notesUpdated = updateNotes();
180
- } catch (error) {
181
- console.warn(`⚠️ Skipping release notes update: ${error.message}`);
182
- }
183
-
184
- if (notesUpdated) {
185
- const gitAddResult = spawnSync('git', ['add', releaseNotesPath], { stdio: 'inherit', cwd });
186
- if (!gitAddResult || typeof gitAddResult.status !== 'number' || gitAddResult.status !== 0) {
187
- console.warn('⚠️ Release notes updated but failed to stage. Please add manually: git add ' + releaseNotesPath);
188
- }
189
- }
190
-
191
- return releaseResult;
192
- }
193
-
194
- if (require.main === module) {
195
- try {
196
- const result = runRelease();
197
- if (result.status !== 0) {
198
- process.exit(result.status ?? 1);
199
- }
200
- } catch (error) {
201
- console.error(`❌ ${error.message}`);
202
- process.exit(1);
203
- }
204
- }
205
-
206
- module.exports = {
207
- VALID_RELEASE_TYPES,
208
- SEMVER_REGEX,
209
- getCliArguments,
210
- getNpmRunArgument,
211
- buildStandardVersionArgs,
212
- loadPackageJson,
213
- detectPackageManager,
214
- runProjectTests,
215
- runRelease,
216
- isWorkingTreeClean
217
- };
218
-
219
- function buildPreloadFlag(filePath) {
220
- const resolved = path.resolve(filePath);
221
- const escaped = resolved.includes(' ')
222
- ? `"${resolved.replace(/"/g, '\"')}"`
223
- : resolved;
224
- return `--require ${escaped}`;
225
- }
226
-
227
- function appendPreloadToNodeOptions(env, preloadFlag) {
228
- const nextEnv = { ...env };
229
- const existing = typeof nextEnv.NODE_OPTIONS === 'string' ? nextEnv.NODE_OPTIONS.trim() : '';
230
- nextEnv.NODE_OPTIONS = existing ? `${existing} ${preloadFlag}` : preloadFlag;
231
- return nextEnv;
232
- }
233
-
234
- function isWorkingTreeClean({ cwd = process.cwd() } = {}) {
235
- const result = spawnSync('git', ['status', '--porcelain'], {
236
- cwd,
237
- encoding: 'utf8'
238
- });
239
-
240
- if (result.error) {
241
- throw result.error;
242
- }
243
-
244
- if (typeof result.status === 'number' && result.status !== 0) {
245
- const stderr = typeof result.stderr === 'string' ? result.stderr.trim() : '';
246
- const message = stderr || 'Failed to determine working tree status.';
247
- throw new Error(message);
248
- }
249
-
250
- const output = typeof result.stdout === 'string' ? result.stdout : '';
251
- return output.trim().length === 0;
252
- }
1
+ #!/usr/bin/env node
2
+
3
+ const { spawnSync } = require('child_process');
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+
7
+ const { updateReleaseNotes } = require('./update-release-notes.cjs');
8
+
9
+ const fsFokPreloadPath = path.join(__dirname, 'preload', 'fs-f-ok.cjs');
10
+ const FS_FOK_PRELOAD_FLAG = buildPreloadFlag(fsFokPreloadPath);
11
+
12
+ require('./preload/fs-f-ok.cjs');
13
+
14
+ const VALID_RELEASE_TYPES = new Set([
15
+ 'major',
16
+ 'minor',
17
+ 'patch',
18
+ 'premajor',
19
+ 'preminor',
20
+ 'prepatch',
21
+ 'prerelease'
22
+ ]);
23
+
24
+ const SEMVER_REGEX = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
25
+
26
+ const LOCKFILE_PREFERENCES = [
27
+ { manager: 'pnpm', file: 'pnpm-lock.yaml' },
28
+ { manager: 'yarn', file: 'yarn.lock' },
29
+ { manager: 'bun', file: 'bun.lockb' },
30
+ { manager: 'npm', file: 'package-lock.json' }
31
+ ];
32
+
33
+ function getCliArguments(argv = process.argv) {
34
+ const rawArgs = argv.slice(2);
35
+ if (rawArgs.length === 0) {
36
+ return { releaseType: undefined, extraArgs: [] };
37
+ }
38
+
39
+ if (rawArgs[0].startsWith('-')) {
40
+ return { releaseType: undefined, extraArgs: rawArgs };
41
+ }
42
+
43
+ const [firstArg, ...rest] = rawArgs;
44
+ return { releaseType: firstArg, extraArgs: rest };
45
+ }
46
+
47
+ function getNpmRunArgument(env = process.env) {
48
+ try {
49
+ const npmArgs = JSON.parse(env.npm_config_argv || '{}');
50
+ const original = npmArgs.original || [];
51
+ const releaseIndex = original.lastIndexOf('release');
52
+ if (releaseIndex !== -1) {
53
+ return original[releaseIndex + 1];
54
+ }
55
+ } catch (error) {
56
+ // Ignore JSON parsing issues and fall back to defaults
57
+ }
58
+ return undefined;
59
+ }
60
+
61
+ function buildStandardVersionArgs({ releaseType, extraArgs }) {
62
+ const args = [];
63
+ if (releaseType) {
64
+ const normalized = releaseType.trim();
65
+ const isValid = VALID_RELEASE_TYPES.has(normalized) || SEMVER_REGEX.test(normalized);
66
+ if (!isValid) {
67
+ const allowed = Array.from(VALID_RELEASE_TYPES).join(', ');
68
+ throw new Error(`Unknown release type "${normalized}". Use one of ${allowed} or a valid semver version.`);
69
+ }
70
+ args.push('--release-as', normalized);
71
+ }
72
+
73
+ if (Array.isArray(extraArgs) && extraArgs.length > 0) {
74
+ args.push(...extraArgs);
75
+ }
76
+
77
+ return args;
78
+ }
79
+
80
+ function loadPackageJson(cwd = process.cwd()) {
81
+ try {
82
+ const packageJsonPath = path.join(cwd, 'package.json');
83
+ const contents = fs.readFileSync(packageJsonPath, 'utf8');
84
+ return JSON.parse(contents);
85
+ } catch (error) {
86
+ return null;
87
+ }
88
+ }
89
+
90
+ function detectPackageManager({ env = process.env, cwd = process.cwd() } = {}) {
91
+ const userAgent = env.npm_config_user_agent || '';
92
+ if (userAgent.startsWith('pnpm/')) return 'pnpm';
93
+ if (userAgent.startsWith('yarn/')) return 'yarn';
94
+ if (userAgent.startsWith('bun/')) return 'bun';
95
+
96
+ for (const { manager, file } of LOCKFILE_PREFERENCES) {
97
+ if (fs.existsSync(path.join(cwd, file))) {
98
+ return manager;
99
+ }
100
+ }
101
+
102
+ return 'npm';
103
+ }
104
+
105
+ function buildTestCommand(packageManager) {
106
+ switch (packageManager) {
107
+ case 'pnpm':
108
+ return { command: 'pnpm', args: ['test'] };
109
+ case 'yarn':
110
+ return { command: 'yarn', args: ['test'] };
111
+ case 'bun':
112
+ return { command: 'bun', args: ['test'] };
113
+ default:
114
+ return { command: 'npm', args: ['test'] };
115
+ }
116
+ }
117
+
118
+ function runProjectTests({ spawn = spawnSync, env = process.env, cwd = process.cwd() } = {}) {
119
+ const packageJson = loadPackageJson(cwd);
120
+ const scripts = packageJson && packageJson.scripts ? packageJson.scripts : {};
121
+
122
+ if (!scripts.test) {
123
+ console.log('ℹ️ Skipping tests: no "test" script detected in package.json');
124
+ return { status: 0 };
125
+ }
126
+
127
+ const packageManager = detectPackageManager({ env, cwd });
128
+ const { command, args } = buildTestCommand(packageManager);
129
+
130
+ console.log(`🧪 Running tests with ${packageManager} ${args.join(' ')}`.trim());
131
+ return spawn(command, args, {
132
+ stdio: 'inherit',
133
+ env
134
+ });
135
+ }
136
+
137
+ function runRelease({
138
+ argv = process.argv,
139
+ env = process.env,
140
+ spawn = spawnSync,
141
+ cwd = process.cwd(),
142
+ dependencies = {}
143
+ } = {}) {
144
+ const { releaseType: cliReleaseType, extraArgs } = getCliArguments(argv);
145
+ const inferredReleaseType = cliReleaseType || getNpmRunArgument(env);
146
+ const standardVersionArgs = buildStandardVersionArgs({
147
+ releaseType: inferredReleaseType,
148
+ extraArgs
149
+ });
150
+
151
+ const checkWorkingTreeClean = dependencies.isWorkingTreeClean || (() => isWorkingTreeClean({ cwd }));
152
+ const workingTreeClean = checkWorkingTreeClean();
153
+ if (!workingTreeClean) {
154
+ throw new Error('Working tree has uncommitted changes. Commit or stash before running release.');
155
+ }
156
+
157
+ const testResult = runProjectTests({ spawn, env });
158
+ if (testResult && typeof testResult.status === 'number' && testResult.status !== 0) {
159
+ return testResult;
160
+ }
161
+
162
+ const standardVersionBin = require.resolve('standard-version/bin/cli.js');
163
+ const childEnv = appendPreloadToNodeOptions(env, FS_FOK_PRELOAD_FLAG);
164
+ const releaseResult = spawn(process.execPath, [standardVersionBin, ...standardVersionArgs], {
165
+ stdio: 'inherit',
166
+ env: childEnv,
167
+ cwd
168
+ });
169
+
170
+ if (releaseResult && typeof releaseResult.status === 'number' && releaseResult.status !== 0) {
171
+ return releaseResult;
172
+ }
173
+
174
+ const releaseNotesPath = dependencies.releaseNotesPath || path.join('docs', 'release-notes', 'RELEASE_NOTES.md');
175
+ const updateNotes = dependencies.updateReleaseNotes || ((options = {}) => updateReleaseNotes({ rootDir: cwd, ...options }));
176
+
177
+ let notesUpdated = false;
178
+ try {
179
+ notesUpdated = updateNotes();
180
+ } catch (error) {
181
+ console.warn(`⚠️ Skipping release notes update: ${error.message}`);
182
+ }
183
+
184
+ if (notesUpdated) {
185
+ const gitAddResult = spawnSync('git', ['add', releaseNotesPath], { stdio: 'inherit', cwd });
186
+ if (!gitAddResult || typeof gitAddResult.status !== 'number' || gitAddResult.status !== 0) {
187
+ console.warn('⚠️ Release notes updated but failed to stage. Please add manually: git add ' + releaseNotesPath);
188
+ }
189
+ }
190
+
191
+ return releaseResult;
192
+ }
193
+
194
+ if (require.main === module) {
195
+ try {
196
+ const result = runRelease();
197
+ if (result.status !== 0) {
198
+ process.exit(result.status ?? 1);
199
+ }
200
+ } catch (error) {
201
+ console.error(`❌ ${error.message}`);
202
+ process.exit(1);
203
+ }
204
+ }
205
+
206
+ module.exports = {
207
+ VALID_RELEASE_TYPES,
208
+ SEMVER_REGEX,
209
+ getCliArguments,
210
+ getNpmRunArgument,
211
+ buildStandardVersionArgs,
212
+ loadPackageJson,
213
+ detectPackageManager,
214
+ runProjectTests,
215
+ runRelease,
216
+ isWorkingTreeClean
217
+ };
218
+
219
+ function buildPreloadFlag(filePath) {
220
+ const resolved = path.resolve(filePath);
221
+ const escaped = resolved.includes(' ')
222
+ ? `"${resolved.replace(/"/g, '\"')}"`
223
+ : resolved;
224
+ return `--require ${escaped}`;
225
+ }
226
+
227
+ function appendPreloadToNodeOptions(env, preloadFlag) {
228
+ const nextEnv = { ...env };
229
+ const existing = typeof nextEnv.NODE_OPTIONS === 'string' ? nextEnv.NODE_OPTIONS.trim() : '';
230
+ nextEnv.NODE_OPTIONS = existing ? `${existing} ${preloadFlag}` : preloadFlag;
231
+ return nextEnv;
232
+ }
233
+
234
+ function isWorkingTreeClean({ cwd = process.cwd() } = {}) {
235
+ const result = spawnSync('git', ['status', '--porcelain'], {
236
+ cwd,
237
+ encoding: 'utf8'
238
+ });
239
+
240
+ if (result.error) {
241
+ throw result.error;
242
+ }
243
+
244
+ if (typeof result.status === 'number' && result.status !== 0) {
245
+ const stderr = typeof result.stderr === 'string' ? result.stderr.trim() : '';
246
+ const message = stderr || 'Failed to determine working tree status.';
247
+ throw new Error(message);
248
+ }
249
+
250
+ const output = typeof result.stdout === 'string' ? result.stdout : '';
251
+ return output.trim().length === 0;
252
+ }