safeword 0.8.7 → 0.8.10
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/{check-PPVIEF3Q.js → check-4HX4SNVV.js} +27 -27
- package/dist/check-4HX4SNVV.js.map +1 -0
- package/dist/{chunk-W66Z3C5H.js → chunk-FJYRWU2V.js} +5 -5
- package/dist/chunk-FJYRWU2V.js.map +1 -0
- package/dist/chunk-POPS3ZRQ.js +1219 -0
- package/dist/chunk-POPS3ZRQ.js.map +1 -0
- package/dist/cli.js +5 -9
- package/dist/cli.js.map +1 -1
- package/dist/{diff-S3ICSYQY.js → diff-7QIV6Z5B.js} +14 -16
- package/dist/diff-7QIV6Z5B.js.map +1 -0
- package/dist/index.d.ts +2 -7
- package/dist/reset-RP7AGR2Y.js +73 -0
- package/dist/reset-RP7AGR2Y.js.map +1 -0
- package/dist/setup-ZYRPDTQI.js +91 -0
- package/dist/setup-ZYRPDTQI.js.map +1 -0
- package/dist/upgrade-K2FFESUH.js +76 -0
- package/dist/upgrade-K2FFESUH.js.map +1 -0
- package/package.json +2 -2
- package/templates/SAFEWORD.md +5 -2
- package/templates/commands/cleanup-zombies.md +48 -0
- package/templates/guides/cli-reference.md +9 -11
- package/templates/guides/zombie-process-cleanup.md +40 -24
- package/templates/scripts/cleanup-zombies.sh +222 -0
- package/dist/check-PPVIEF3Q.js.map +0 -1
- package/dist/chunk-34PU3QZI.js +0 -1047
- package/dist/chunk-34PU3QZI.js.map +0 -1
- package/dist/chunk-3OK3NQEW.js +0 -476
- package/dist/chunk-3OK3NQEW.js.map +0 -1
- package/dist/chunk-BFBUEJDH.js +0 -88
- package/dist/chunk-BFBUEJDH.js.map +0 -1
- package/dist/chunk-W66Z3C5H.js.map +0 -1
- package/dist/diff-S3ICSYQY.js.map +0 -1
- package/dist/reset-ZST2SGZ2.js +0 -74
- package/dist/reset-ZST2SGZ2.js.map +0 -1
- package/dist/setup-ANAIEP3D.js +0 -100
- package/dist/setup-ANAIEP3D.js.map +0 -1
- package/dist/sync-V6D7QTMO.js +0 -9
- package/dist/sync-V6D7QTMO.js.map +0 -1
- package/dist/upgrade-QFIGWZ5I.js +0 -76
- package/dist/upgrade-QFIGWZ5I.js.map +0 -1
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import {
|
|
2
|
+
compareVersions
|
|
3
|
+
} from "./chunk-FJYRWU2V.js";
|
|
4
|
+
import {
|
|
5
|
+
SAFEWORD_SCHEMA,
|
|
6
|
+
createProjectContext,
|
|
7
|
+
error,
|
|
8
|
+
exists,
|
|
9
|
+
header,
|
|
10
|
+
info,
|
|
11
|
+
listItem,
|
|
12
|
+
readFileSafe,
|
|
13
|
+
reconcile,
|
|
14
|
+
success,
|
|
15
|
+
warn
|
|
16
|
+
} from "./chunk-POPS3ZRQ.js";
|
|
17
|
+
import {
|
|
18
|
+
VERSION
|
|
19
|
+
} from "./chunk-ORQHKDT2.js";
|
|
20
|
+
|
|
21
|
+
// src/commands/upgrade.ts
|
|
22
|
+
import nodePath from "path";
|
|
23
|
+
function getProjectVersion(safewordDirectory) {
|
|
24
|
+
const versionPath = nodePath.join(safewordDirectory, "version");
|
|
25
|
+
return readFileSafe(versionPath)?.trim() ?? "0.0.0";
|
|
26
|
+
}
|
|
27
|
+
function printUpgradeSummary(result, projectVersion) {
|
|
28
|
+
header("Upgrade Complete");
|
|
29
|
+
info(`
|
|
30
|
+
Version: v${projectVersion} \u2192 v${VERSION}`);
|
|
31
|
+
if (result.created.length > 0) {
|
|
32
|
+
info("\nCreated:");
|
|
33
|
+
for (const file of result.created) listItem(file);
|
|
34
|
+
}
|
|
35
|
+
if (result.updated.length > 0) {
|
|
36
|
+
info("\nUpdated:");
|
|
37
|
+
for (const file of result.updated) listItem(file);
|
|
38
|
+
}
|
|
39
|
+
if (result.packagesToRemove.length > 0) {
|
|
40
|
+
warn(`
|
|
41
|
+
${result.packagesToRemove.length} package(s) are now bundled in eslint-plugin-safeword:`);
|
|
42
|
+
for (const pkg of result.packagesToRemove) listItem(pkg);
|
|
43
|
+
info("\nIf you don't use these elsewhere, you can remove them:");
|
|
44
|
+
listItem(`npm uninstall ${result.packagesToRemove.join(" ")}`);
|
|
45
|
+
}
|
|
46
|
+
success(`
|
|
47
|
+
Safeword upgraded to v${VERSION}`);
|
|
48
|
+
}
|
|
49
|
+
async function upgrade() {
|
|
50
|
+
const cwd = process.cwd();
|
|
51
|
+
const safewordDirectory = nodePath.join(cwd, ".safeword");
|
|
52
|
+
if (!exists(safewordDirectory)) {
|
|
53
|
+
error("Not configured. Run `safeword setup` first.");
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
const projectVersion = getProjectVersion(safewordDirectory);
|
|
57
|
+
if (compareVersions(VERSION, projectVersion) < 0) {
|
|
58
|
+
error(`CLI v${VERSION} is older than project v${projectVersion}.`);
|
|
59
|
+
error("Update the CLI first: npm install -g safeword");
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
header("Safeword Upgrade");
|
|
63
|
+
info(`Upgrading from v${projectVersion} to v${VERSION}`);
|
|
64
|
+
try {
|
|
65
|
+
const ctx = createProjectContext(cwd);
|
|
66
|
+
const result = await reconcile(SAFEWORD_SCHEMA, "upgrade", ctx);
|
|
67
|
+
printUpgradeSummary(result, projectVersion);
|
|
68
|
+
} catch (error_) {
|
|
69
|
+
error(`Upgrade failed: ${error_ instanceof Error ? error_.message : "Unknown error"}`);
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
export {
|
|
74
|
+
upgrade
|
|
75
|
+
};
|
|
76
|
+
//# sourceMappingURL=upgrade-K2FFESUH.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/upgrade.ts"],"sourcesContent":["/**\n * Upgrade command - Update safeword configuration to latest version\n *\n * Uses reconcile() with mode='upgrade' to update all managed files.\n */\n\nimport nodePath from 'node:path';\n\nimport { reconcile, type ReconcileResult } from '../reconcile.js';\nimport { SAFEWORD_SCHEMA } from '../schema.js';\nimport { createProjectContext } from '../utils/context.js';\nimport { exists, readFileSafe } from '../utils/fs.js';\nimport { error, header, info, listItem, success, warn } from '../utils/output.js';\nimport { compareVersions } from '../utils/version.js';\nimport { VERSION } from '../version.js';\n\nfunction getProjectVersion(safewordDirectory: string): string {\n const versionPath = nodePath.join(safewordDirectory, 'version');\n return readFileSafe(versionPath)?.trim() ?? '0.0.0';\n}\n\nfunction printUpgradeSummary(result: ReconcileResult, projectVersion: string): void {\n header('Upgrade Complete');\n info(`\\nVersion: v${projectVersion} → v${VERSION}`);\n\n if (result.created.length > 0) {\n info('\\nCreated:');\n for (const file of result.created) listItem(file);\n }\n\n if (result.updated.length > 0) {\n info('\\nUpdated:');\n for (const file of result.updated) listItem(file);\n }\n\n if (result.packagesToRemove.length > 0) {\n warn(`\\n${result.packagesToRemove.length} package(s) are now bundled in eslint-plugin-safeword:`);\n for (const pkg of result.packagesToRemove) listItem(pkg);\n info(\"\\nIf you don't use these elsewhere, you can remove them:\");\n listItem(`npm uninstall ${result.packagesToRemove.join(' ')}`);\n }\n\n success(`\\nSafeword upgraded to v${VERSION}`);\n}\n\nexport async function upgrade(): Promise<void> {\n const cwd = process.cwd();\n const safewordDirectory = nodePath.join(cwd, '.safeword');\n\n if (!exists(safewordDirectory)) {\n error('Not configured. Run `safeword setup` first.');\n process.exit(1);\n }\n\n const projectVersion = getProjectVersion(safewordDirectory);\n\n if (compareVersions(VERSION, projectVersion) < 0) {\n error(`CLI v${VERSION} is older than project v${projectVersion}.`);\n error('Update the CLI first: npm install -g safeword');\n process.exit(1);\n }\n\n header('Safeword Upgrade');\n info(`Upgrading from v${projectVersion} to v${VERSION}`);\n\n try {\n const ctx = createProjectContext(cwd);\n const result = await reconcile(SAFEWORD_SCHEMA, 'upgrade', ctx);\n printUpgradeSummary(result, projectVersion);\n } catch (error_) {\n error(`Upgrade failed: ${error_ instanceof Error ? error_.message : 'Unknown error'}`);\n process.exit(1);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAMA,OAAO,cAAc;AAUrB,SAAS,kBAAkB,mBAAmC;AAC5D,QAAM,cAAc,SAAS,KAAK,mBAAmB,SAAS;AAC9D,SAAO,aAAa,WAAW,GAAG,KAAK,KAAK;AAC9C;AAEA,SAAS,oBAAoB,QAAyB,gBAA8B;AAClF,SAAO,kBAAkB;AACzB,OAAK;AAAA,YAAe,cAAc,YAAO,OAAO,EAAE;AAElD,MAAI,OAAO,QAAQ,SAAS,GAAG;AAC7B,SAAK,YAAY;AACjB,eAAW,QAAQ,OAAO,QAAS,UAAS,IAAI;AAAA,EAClD;AAEA,MAAI,OAAO,QAAQ,SAAS,GAAG;AAC7B,SAAK,YAAY;AACjB,eAAW,QAAQ,OAAO,QAAS,UAAS,IAAI;AAAA,EAClD;AAEA,MAAI,OAAO,iBAAiB,SAAS,GAAG;AACtC,SAAK;AAAA,EAAK,OAAO,iBAAiB,MAAM,wDAAwD;AAChG,eAAW,OAAO,OAAO,iBAAkB,UAAS,GAAG;AACvD,SAAK,0DAA0D;AAC/D,aAAS,iBAAiB,OAAO,iBAAiB,KAAK,GAAG,CAAC,EAAE;AAAA,EAC/D;AAEA,UAAQ;AAAA,wBAA2B,OAAO,EAAE;AAC9C;AAEA,eAAsB,UAAyB;AAC7C,QAAM,MAAM,QAAQ,IAAI;AACxB,QAAM,oBAAoB,SAAS,KAAK,KAAK,WAAW;AAExD,MAAI,CAAC,OAAO,iBAAiB,GAAG;AAC9B,UAAM,6CAA6C;AACnD,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,iBAAiB,kBAAkB,iBAAiB;AAE1D,MAAI,gBAAgB,SAAS,cAAc,IAAI,GAAG;AAChD,UAAM,QAAQ,OAAO,2BAA2B,cAAc,GAAG;AACjE,UAAM,+CAA+C;AACrD,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,SAAO,kBAAkB;AACzB,OAAK,mBAAmB,cAAc,QAAQ,OAAO,EAAE;AAEvD,MAAI;AACF,UAAM,MAAM,qBAAqB,GAAG;AACpC,UAAM,SAAS,MAAM,UAAU,iBAAiB,WAAW,GAAG;AAC9D,wBAAoB,QAAQ,cAAc;AAAA,EAC5C,SAAS,QAAQ;AACf,UAAM,mBAAmB,kBAAkB,QAAQ,OAAO,UAAU,eAAe,EAAE;AACrF,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "safeword",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.10",
|
|
4
4
|
"description": "CLI for setting up and managing safeword development environments",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"build": "tsup",
|
|
24
24
|
"dev": "tsup --watch",
|
|
25
25
|
"test": "vitest run",
|
|
26
|
-
"test:
|
|
26
|
+
"test:integration": "vitest run tests/integration/",
|
|
27
27
|
"test:watch": "vitest",
|
|
28
28
|
"test:coverage": "vitest run --coverage",
|
|
29
29
|
"typecheck": "tsc --noEmit",
|
package/templates/SAFEWORD.md
CHANGED
|
@@ -86,6 +86,7 @@ Training data is stale. Follow this sequence:
|
|
|
86
86
|
| Test definitions | `.safeword/planning/test-definitions/` | `feature-*.md` (L2 features only) |
|
|
87
87
|
| Design docs | `.safeword/planning/design/` | Complex features (3+ components) |
|
|
88
88
|
| Issues | `.safeword/planning/issues/` | Issue tracking |
|
|
89
|
+
| Execution plans | `.safeword/planning/plans/` | LLM-ready task breakdowns |
|
|
89
90
|
|
|
90
91
|
**Artifact Levels:**
|
|
91
92
|
|
|
@@ -311,7 +312,9 @@ When markdown lint reports MD040 (missing language), choose:
|
|
|
311
312
|
|
|
312
313
|
1. **Clarity → Simplicity → Correctness** (in that order)
|
|
313
314
|
2. **Test what you can test**—never ask user to verify
|
|
314
|
-
3. **RED → GREEN → REFACTOR**—never skip steps
|
|
315
|
+
3. **ALWAYS USE STRICT TDD: RED → GREEN → REFACTOR**—never skip steps
|
|
315
316
|
4. **Commit after each GREEN phase**
|
|
316
317
|
5. **Read the matching guide** when a trigger fires
|
|
317
|
-
6. **
|
|
318
|
+
6. **Always read the latest documentation for the relevant tool**
|
|
319
|
+
7. **AVOID BLOAT**
|
|
320
|
+
8. **End every response** with: `{"proposedChanges": bool, "madeChanges": bool, "askedQuestion": bool}`
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Kill zombie dev servers and test processes for this project
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Cleanup Zombies
|
|
6
|
+
|
|
7
|
+
Kill zombie processes (dev servers, Playwright browsers, test runners) for the current project only. Safe to use in multi-project environments.
|
|
8
|
+
|
|
9
|
+
## Instructions
|
|
10
|
+
|
|
11
|
+
Run the cleanup script with `--dry-run` first to preview what will be killed:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
./.safeword/scripts/cleanup-zombies.sh --dry-run
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
If the output looks correct, run without `--dry-run`:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
./.safeword/scripts/cleanup-zombies.sh
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## What It Does
|
|
24
|
+
|
|
25
|
+
1. **Auto-detects framework** - Finds port from vite.config.ts, next.config.js, etc. (checks root, `packages/*/`, `apps/*/` for monorepos)
|
|
26
|
+
2. **Kills by port** - Dev server port AND test port (port + 1000)
|
|
27
|
+
3. **Kills test processes** - Playwright, Chromium, Electron (scoped to this project)
|
|
28
|
+
4. **Multi-project safe** - Only kills processes matching this project's directory
|
|
29
|
+
|
|
30
|
+
## Manual Override
|
|
31
|
+
|
|
32
|
+
If auto-detection fails or you need a specific port:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
# Explicit port
|
|
36
|
+
./.safeword/scripts/cleanup-zombies.sh 5173
|
|
37
|
+
|
|
38
|
+
# Port + additional pattern
|
|
39
|
+
./.safeword/scripts/cleanup-zombies.sh 5173 "electron"
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## When to Use
|
|
43
|
+
|
|
44
|
+
- Port already in use when starting dev server
|
|
45
|
+
- Tests hanging or failing due to zombie processes
|
|
46
|
+
- Switching between projects
|
|
47
|
+
- Before running E2E tests
|
|
48
|
+
- After interrupted test runs
|
|
@@ -4,14 +4,13 @@ Commands for managing safeword in projects.
|
|
|
4
4
|
|
|
5
5
|
## Commands
|
|
6
6
|
|
|
7
|
-
| Command | Purpose
|
|
8
|
-
| ----------------------------- |
|
|
9
|
-
| `npx safeword@latest setup` | Install safeword in current project
|
|
10
|
-
| `npx safeword@latest check` | Check project health and versions
|
|
11
|
-
| `npx safeword@latest upgrade` | Upgrade to latest version
|
|
12
|
-
| `npx safeword@latest diff` | Preview changes before upgrading
|
|
13
|
-
| `npx safeword
|
|
14
|
-
| `npx safeword reset` | Remove safeword from project |
|
|
7
|
+
| Command | Purpose |
|
|
8
|
+
| ----------------------------- | --------------------------------- |
|
|
9
|
+
| `npx safeword@latest setup` | Install safeword in current project |
|
|
10
|
+
| `npx safeword@latest check` | Check project health and versions |
|
|
11
|
+
| `npx safeword@latest upgrade` | Upgrade to latest version |
|
|
12
|
+
| `npx safeword@latest diff` | Preview changes before upgrading |
|
|
13
|
+
| `npx safeword reset` | Remove safeword from project |
|
|
15
14
|
|
|
16
15
|
## When to Use
|
|
17
16
|
|
|
@@ -21,7 +20,6 @@ Commands for managing safeword in projects.
|
|
|
21
20
|
| Check if update available | `npx safeword@latest check` |
|
|
22
21
|
| Update after CLI release | `npx safeword@latest upgrade` |
|
|
23
22
|
| See what upgrade changes | `npx safeword@latest diff` |
|
|
24
|
-
| Added/removed framework | `npx safeword sync` |
|
|
25
23
|
| Remove safeword completely | `npx safeword reset --full` |
|
|
26
24
|
|
|
27
25
|
## Options
|
|
@@ -32,12 +30,12 @@ Common flags:
|
|
|
32
30
|
|
|
33
31
|
- `-y, --yes` - Skip confirmations (setup, reset)
|
|
34
32
|
- `-v, --verbose` - Show detailed output (diff)
|
|
35
|
-
-
|
|
33
|
+
- `--offline` - Skip remote version check (check)
|
|
34
|
+
- `--full` - Also remove linting config + packages (reset)
|
|
36
35
|
|
|
37
36
|
---
|
|
38
37
|
|
|
39
38
|
## Key Takeaways
|
|
40
39
|
|
|
41
40
|
- Always use `@latest` for setup/check/upgrade/diff to get current CLI
|
|
42
|
-
- Run `sync` after adding/removing frameworks to update linting plugins
|
|
43
41
|
- Use `diff` before `upgrade` to preview changes
|
|
@@ -4,6 +4,24 @@
|
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
+
## Quick Start
|
|
8
|
+
|
|
9
|
+
Use the built-in cleanup script:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# Preview what would be killed (safe)
|
|
13
|
+
./.safeword/scripts/cleanup-zombies.sh --dry-run
|
|
14
|
+
|
|
15
|
+
# Kill zombie processes
|
|
16
|
+
./.safeword/scripts/cleanup-zombies.sh
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
The script auto-detects your framework (Vite, Next.js, etc.) and kills only processes belonging to this project.
|
|
20
|
+
|
|
21
|
+
For manual control or debugging, see the detailed sections below.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
7
25
|
## The Problem
|
|
8
26
|
|
|
9
27
|
When running dev servers and E2E tests across multiple projects, zombie processes accumulate:
|
|
@@ -52,35 +70,32 @@ sleep 2
|
|
|
52
70
|
|
|
53
71
|
---
|
|
54
72
|
|
|
55
|
-
##
|
|
73
|
+
## Built-in Cleanup Script
|
|
56
74
|
|
|
57
|
-
|
|
75
|
+
Safeword includes a cleanup script at `.safeword/scripts/cleanup-zombies.sh`:
|
|
58
76
|
|
|
59
77
|
```bash
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
DEV_PORT=3000 # Dev server port (change per project)
|
|
64
|
-
TEST_PORT=$((DEV_PORT + 1000)) # Test server port (Playwright managed)
|
|
65
|
-
PROJECT_DIR="$(pwd)"
|
|
78
|
+
# Auto-detect framework and clean up
|
|
79
|
+
./.safeword/scripts/cleanup-zombies.sh
|
|
66
80
|
|
|
67
|
-
|
|
81
|
+
# Preview first (recommended)
|
|
82
|
+
./.safeword/scripts/cleanup-zombies.sh --dry-run
|
|
68
83
|
|
|
69
|
-
#
|
|
70
|
-
|
|
84
|
+
# Explicit port override
|
|
85
|
+
./.safeword/scripts/cleanup-zombies.sh 5173
|
|
71
86
|
|
|
72
|
-
#
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
# Kill test runners
|
|
76
|
-
pgrep -f "playwright test.*$(basename $PROJECT_DIR)" | xargs kill -9 2>/dev/null
|
|
77
|
-
|
|
78
|
-
echo "Cleanup complete!"
|
|
87
|
+
# Port + additional pattern
|
|
88
|
+
./.safeword/scripts/cleanup-zombies.sh 5173 "electron"
|
|
79
89
|
```
|
|
80
90
|
|
|
81
|
-
**
|
|
91
|
+
**Features:**
|
|
92
|
+
|
|
93
|
+
- Auto-detects port from config files (vite.config.ts, next.config.js, etc.)
|
|
94
|
+
- Kills dev port AND test port (port + 1000)
|
|
95
|
+
- Scopes all pattern matching to current project directory
|
|
96
|
+
- `--dry-run` shows what would be killed without killing
|
|
82
97
|
|
|
83
|
-
**
|
|
98
|
+
**Supported frameworks:** Vite, Next.js, Nuxt, SvelteKit, Astro, Angular
|
|
84
99
|
|
|
85
100
|
---
|
|
86
101
|
|
|
@@ -197,9 +212,10 @@ ps aux | grep "/Users/alex/projects/my-project"
|
|
|
197
212
|
|
|
198
213
|
| Situation | Command |
|
|
199
214
|
| ---------------------------------------- | ---------------------------------------------------------------- |
|
|
215
|
+
| Quick cleanup (recommended) | `./.safeword/scripts/cleanup-zombies.sh` |
|
|
216
|
+
| Preview before killing | `./.safeword/scripts/cleanup-zombies.sh --dry-run` |
|
|
200
217
|
| Kill dev + test servers (use your ports) | `lsof -ti:$DEV_PORT -ti:$TEST_PORT \| xargs kill -9 2>/dev/null` |
|
|
201
218
|
| Kill Playwright (this project) | `pkill -f "playwright.*$(pwd)"` |
|
|
202
|
-
| Kill all for this project | `./scripts/cleanup.sh` |
|
|
203
219
|
| Check what's on port | `lsof -i:3000` |
|
|
204
220
|
| Find zombie processes | `ps aux \| grep -E "(node\|playwright\|chromium)"` |
|
|
205
221
|
| Preview what `pkill -f` would kill | `pgrep -f "pattern"` (verify before running pkill) |
|
|
@@ -224,7 +240,7 @@ ps aux | grep "/Users/alex/projects/my-project"
|
|
|
224
240
|
|
|
225
241
|
## Key Takeaways
|
|
226
242
|
|
|
227
|
-
- Use
|
|
228
|
-
-
|
|
243
|
+
- Use `./.safeword/scripts/cleanup-zombies.sh` for quick, safe cleanup
|
|
244
|
+
- Always preview with `--dry-run` first when unsure
|
|
245
|
+
- Never use `killall node` (affects all projects)
|
|
229
246
|
- Clean up before AND after development sessions
|
|
230
|
-
- Create project-specific `scripts/cleanup.sh` for consistent cleanup
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Cleanup zombie processes: Kill dev servers and test processes for THIS project
|
|
3
|
+
#
|
|
4
|
+
# Use when: Port in use after tests, dev server won't start, zombie node/playwright
|
|
5
|
+
# processes, need to clean up before running tests, switching between projects
|
|
6
|
+
#
|
|
7
|
+
# Auto-detection: Checks root, packages/*/, apps/*/ for framework configs (monorepo support)
|
|
8
|
+
#
|
|
9
|
+
# Usage: ./cleanup-zombies.sh [port] [pattern]
|
|
10
|
+
# Example: ./cleanup-zombies.sh # auto-detect from config files
|
|
11
|
+
# Example: ./cleanup-zombies.sh 5173 # explicit port
|
|
12
|
+
# Example: ./cleanup-zombies.sh 5173 "vite" # port + pattern
|
|
13
|
+
# Example: ./cleanup-zombies.sh --dry-run # preview what would be killed
|
|
14
|
+
# Example: ./cleanup-zombies.sh --dry-run 5173 # preview with explicit port
|
|
15
|
+
|
|
16
|
+
set -e
|
|
17
|
+
|
|
18
|
+
# Colors for output
|
|
19
|
+
GREEN='\033[0;32m'
|
|
20
|
+
YELLOW='\033[0;33m'
|
|
21
|
+
NC='\033[0m' # No Color
|
|
22
|
+
|
|
23
|
+
# Parse arguments
|
|
24
|
+
DRY_RUN=false
|
|
25
|
+
PORT=""
|
|
26
|
+
PATTERN=""
|
|
27
|
+
|
|
28
|
+
for arg in "$@"; do
|
|
29
|
+
case "$arg" in
|
|
30
|
+
--dry-run)
|
|
31
|
+
DRY_RUN=true
|
|
32
|
+
;;
|
|
33
|
+
--help|-h)
|
|
34
|
+
echo "Usage: $0 [--dry-run] [port] [pattern]"
|
|
35
|
+
echo ""
|
|
36
|
+
echo "Cleanup zombie processes for the current project."
|
|
37
|
+
echo ""
|
|
38
|
+
echo "Options:"
|
|
39
|
+
echo " --dry-run Show what would be killed without killing"
|
|
40
|
+
echo " --help Show this help message"
|
|
41
|
+
echo ""
|
|
42
|
+
echo "Arguments:"
|
|
43
|
+
echo " port Port number (auto-detected if not provided)"
|
|
44
|
+
echo " pattern Additional process pattern to match"
|
|
45
|
+
echo ""
|
|
46
|
+
echo "Examples:"
|
|
47
|
+
echo " $0 # Auto-detect port from config files"
|
|
48
|
+
echo " $0 5173 # Kill processes on port 5173"
|
|
49
|
+
echo " $0 5173 electron # Port 5173 + electron processes"
|
|
50
|
+
echo " $0 --dry-run # Preview without killing"
|
|
51
|
+
exit 0
|
|
52
|
+
;;
|
|
53
|
+
*)
|
|
54
|
+
if [ -z "$PORT" ] && [[ "$arg" =~ ^[0-9]+$ ]]; then
|
|
55
|
+
PORT="$arg"
|
|
56
|
+
elif [ -z "$PATTERN" ]; then
|
|
57
|
+
PATTERN="$arg"
|
|
58
|
+
fi
|
|
59
|
+
;;
|
|
60
|
+
esac
|
|
61
|
+
done
|
|
62
|
+
|
|
63
|
+
# Check if any file matching pattern exists (supports globs)
|
|
64
|
+
has_config() {
|
|
65
|
+
local pattern=$1
|
|
66
|
+
# Check root first, then common monorepo locations
|
|
67
|
+
compgen -G "$pattern" >/dev/null 2>&1 && return 0
|
|
68
|
+
compgen -G "packages/*/$pattern" >/dev/null 2>&1 && return 0
|
|
69
|
+
compgen -G "apps/*/$pattern" >/dev/null 2>&1 && return 0
|
|
70
|
+
return 1
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
# Auto-detect port from framework config files
|
|
74
|
+
detect_port() {
|
|
75
|
+
if has_config "vite.config.*"; then
|
|
76
|
+
echo "5173"
|
|
77
|
+
elif has_config "next.config.*"; then
|
|
78
|
+
echo "3000"
|
|
79
|
+
elif has_config "nuxt.config.*"; then
|
|
80
|
+
echo "3000"
|
|
81
|
+
elif has_config "svelte.config.js"; then
|
|
82
|
+
echo "5173"
|
|
83
|
+
elif has_config "astro.config.*"; then
|
|
84
|
+
echo "4321"
|
|
85
|
+
elif has_config "angular.json"; then
|
|
86
|
+
echo "4200"
|
|
87
|
+
else
|
|
88
|
+
echo ""
|
|
89
|
+
fi
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
# Auto-detect framework pattern
|
|
93
|
+
detect_pattern() {
|
|
94
|
+
if has_config "vite.config.*"; then
|
|
95
|
+
echo "vite"
|
|
96
|
+
elif has_config "next.config.*"; then
|
|
97
|
+
echo "next"
|
|
98
|
+
elif has_config "nuxt.config.*"; then
|
|
99
|
+
echo "nuxt"
|
|
100
|
+
else
|
|
101
|
+
echo ""
|
|
102
|
+
fi
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
# Use auto-detection if not provided
|
|
106
|
+
if [ -z "$PORT" ]; then
|
|
107
|
+
PORT=$(detect_port)
|
|
108
|
+
fi
|
|
109
|
+
|
|
110
|
+
if [ -z "$PATTERN" ]; then
|
|
111
|
+
PATTERN=$(detect_pattern)
|
|
112
|
+
fi
|
|
113
|
+
|
|
114
|
+
PROJECT_DIR="$(pwd)"
|
|
115
|
+
PROJECT_NAME="$(basename "$PROJECT_DIR")"
|
|
116
|
+
|
|
117
|
+
echo "Cleanup zombies for: $PROJECT_NAME"
|
|
118
|
+
echo " Directory: $PROJECT_DIR"
|
|
119
|
+
[ -n "$PORT" ] && echo " Port: $PORT (+ test port $((PORT + 1000)))"
|
|
120
|
+
[ -n "$PATTERN" ] && echo " Pattern: $PATTERN"
|
|
121
|
+
$DRY_RUN && echo -e " ${YELLOW}DRY RUN - no processes will be killed${NC}"
|
|
122
|
+
echo ""
|
|
123
|
+
|
|
124
|
+
# Track what we find/kill
|
|
125
|
+
FOUND_COUNT=0
|
|
126
|
+
KILLED_COUNT=0
|
|
127
|
+
|
|
128
|
+
# Function to find and optionally kill processes by port
|
|
129
|
+
cleanup_port() {
|
|
130
|
+
local port=$1
|
|
131
|
+
local pids
|
|
132
|
+
pids=$(lsof -ti:"$port" 2>/dev/null || true)
|
|
133
|
+
|
|
134
|
+
if [ -n "$pids" ]; then
|
|
135
|
+
local count
|
|
136
|
+
count=$(echo "$pids" | wc -l | tr -d ' ')
|
|
137
|
+
FOUND_COUNT=$((FOUND_COUNT + count))
|
|
138
|
+
|
|
139
|
+
echo "Port $port: $count process(es)"
|
|
140
|
+
for pid in $pids; do
|
|
141
|
+
local cmd
|
|
142
|
+
cmd=$(ps -p "$pid" -o command= 2>/dev/null | head -c 80 || echo "unknown")
|
|
143
|
+
echo " PID $pid: $cmd"
|
|
144
|
+
done
|
|
145
|
+
|
|
146
|
+
if [ "$DRY_RUN" = false ]; then
|
|
147
|
+
echo "$pids" | xargs kill -9 2>/dev/null || true
|
|
148
|
+
KILLED_COUNT=$((KILLED_COUNT + count))
|
|
149
|
+
fi
|
|
150
|
+
fi
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
# Function to find and optionally kill processes by pattern (scoped to project)
|
|
154
|
+
cleanup_pattern() {
|
|
155
|
+
local pattern=$1
|
|
156
|
+
local pids
|
|
157
|
+
# Match pattern AND project directory for safety
|
|
158
|
+
pids=$(pgrep -f "$pattern.*$PROJECT_DIR" 2>/dev/null || pgrep -f "$PROJECT_DIR.*$pattern" 2>/dev/null || true)
|
|
159
|
+
|
|
160
|
+
if [ -n "$pids" ]; then
|
|
161
|
+
local count
|
|
162
|
+
count=$(echo "$pids" | wc -l | tr -d ' ')
|
|
163
|
+
FOUND_COUNT=$((FOUND_COUNT + count))
|
|
164
|
+
|
|
165
|
+
echo "Pattern '$pattern' (project-scoped): $count process(es)"
|
|
166
|
+
for pid in $pids; do
|
|
167
|
+
local cmd
|
|
168
|
+
cmd=$(ps -p "$pid" -o command= 2>/dev/null | head -c 80 || echo "unknown")
|
|
169
|
+
echo " PID $pid: $cmd"
|
|
170
|
+
done
|
|
171
|
+
|
|
172
|
+
if [ "$DRY_RUN" = false ]; then
|
|
173
|
+
echo "$pids" | xargs kill -9 2>/dev/null || true
|
|
174
|
+
KILLED_COUNT=$((KILLED_COUNT + count))
|
|
175
|
+
fi
|
|
176
|
+
fi
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
# 1. Kill by port (dev server)
|
|
180
|
+
if [ -n "$PORT" ]; then
|
|
181
|
+
cleanup_port "$PORT"
|
|
182
|
+
|
|
183
|
+
# Also kill test port (dev port + 1000, common convention)
|
|
184
|
+
TEST_PORT=$((PORT + 1000))
|
|
185
|
+
cleanup_port "$TEST_PORT"
|
|
186
|
+
fi
|
|
187
|
+
|
|
188
|
+
# 2. Kill Playwright/test processes scoped to this project
|
|
189
|
+
cleanup_pattern "playwright"
|
|
190
|
+
cleanup_pattern "chromium"
|
|
191
|
+
cleanup_pattern "electron"
|
|
192
|
+
|
|
193
|
+
# 3. Kill framework-specific processes scoped to this project
|
|
194
|
+
if [ -n "$PATTERN" ]; then
|
|
195
|
+
cleanup_pattern "$PATTERN"
|
|
196
|
+
fi
|
|
197
|
+
|
|
198
|
+
# 4. Wait for cleanup
|
|
199
|
+
if [ "$DRY_RUN" = false ] && [ "$KILLED_COUNT" -gt 0 ]; then
|
|
200
|
+
sleep 2
|
|
201
|
+
fi
|
|
202
|
+
|
|
203
|
+
# 5. Summary
|
|
204
|
+
echo ""
|
|
205
|
+
if [ "$FOUND_COUNT" -eq 0 ]; then
|
|
206
|
+
echo -e "${GREEN}No zombie processes found - already clean!${NC}"
|
|
207
|
+
elif [ "$DRY_RUN" = true ]; then
|
|
208
|
+
echo -e "${YELLOW}Found $FOUND_COUNT process(es) that would be killed${NC}"
|
|
209
|
+
echo " Run without --dry-run to kill them"
|
|
210
|
+
else
|
|
211
|
+
echo -e "${GREEN}Killed $KILLED_COUNT process(es)${NC}"
|
|
212
|
+
|
|
213
|
+
# Verify port is free
|
|
214
|
+
if [ -n "$PORT" ]; then
|
|
215
|
+
if lsof -i:"$PORT" >/dev/null 2>&1; then
|
|
216
|
+
echo -e "${YELLOW}Warning: Port $PORT still in use${NC}"
|
|
217
|
+
lsof -i:"$PORT"
|
|
218
|
+
else
|
|
219
|
+
echo " Port $PORT is now free"
|
|
220
|
+
fi
|
|
221
|
+
fi
|
|
222
|
+
fi
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/commands/check.ts"],"sourcesContent":["/**\n * Check command - Verify project health and configuration\n *\n * Uses reconcile() with dryRun to detect missing files and configuration issues.\n */\n\nimport { join } from 'node:path';\nimport { VERSION } from '../version.js';\nimport { exists, readFileSafe } from '../utils/fs.js';\nimport { info, success, warn, header, keyValue } from '../utils/output.js';\nimport { isNewerVersion } from '../utils/version.js';\nimport { createProjectContext } from '../utils/context.js';\nimport { reconcile } from '../reconcile.js';\nimport { SAFEWORD_SCHEMA } from '../schema.js';\n\nexport interface CheckOptions {\n offline?: boolean;\n}\n\n/** Check for missing files from write actions */\nfunction findMissingFiles(cwd: string, actions: { type: string; path: string }[]): string[] {\n const issues: string[] = [];\n for (const action of actions) {\n if (action.type === 'write' && !exists(join(cwd, action.path))) {\n issues.push(`Missing: ${action.path}`);\n }\n }\n return issues;\n}\n\n/** Check for missing text patch markers */\nfunction findMissingPatches(\n cwd: string,\n actions: { type: string; path: string; definition?: { marker: string } }[],\n): string[] {\n const issues: string[] = [];\n for (const action of actions) {\n if (action.type !== 'text-patch') continue;\n\n const fullPath = join(cwd, action.path);\n if (!exists(fullPath)) {\n issues.push(`${action.path} file missing`);\n } else {\n const content = readFileSafe(fullPath) ?? '';\n if (action.definition && !content.includes(action.definition.marker)) {\n issues.push(`${action.path} missing safeword link`);\n }\n }\n }\n return issues;\n}\n\ninterface HealthStatus {\n configured: boolean;\n projectVersion: string | null;\n cliVersion: string;\n updateAvailable: boolean;\n latestVersion: string | null;\n issues: string[];\n missingPackages: string[];\n}\n\n/**\n * Check for latest version from npm (with timeout)\n */\nasync function checkLatestVersion(timeout = 3000): Promise<string | null> {\n try {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeout);\n\n const response = await fetch('https://registry.npmjs.org/safeword/latest', {\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n if (!response.ok) return null;\n\n const data = (await response.json()) as { version?: string };\n return data.version ?? null;\n } catch {\n return null;\n }\n}\n\n/**\n * Check project configuration health using reconcile dryRun\n */\nasync function checkHealth(cwd: string): Promise<HealthStatus> {\n const safewordDir = join(cwd, '.safeword');\n\n // Check if configured\n if (!exists(safewordDir)) {\n return {\n configured: false,\n projectVersion: null,\n cliVersion: VERSION,\n updateAvailable: false,\n latestVersion: null,\n issues: [],\n missingPackages: [],\n };\n }\n\n // Read project version\n const versionPath = join(safewordDir, 'version');\n const projectVersion = readFileSafe(versionPath)?.trim() ?? null;\n\n // Use reconcile with dryRun to detect issues\n const ctx = createProjectContext(cwd);\n const result = await reconcile(SAFEWORD_SCHEMA, 'upgrade', ctx, { dryRun: true });\n\n // Collect issues from write actions and text patches\n const issues: string[] = [\n ...findMissingFiles(cwd, result.actions),\n ...findMissingPatches(cwd, result.actions),\n ];\n\n // Check for missing .claude/settings.json\n if (!exists(join(cwd, '.claude', 'settings.json'))) {\n issues.push('Missing: .claude/settings.json');\n }\n\n return {\n configured: true,\n projectVersion,\n cliVersion: VERSION,\n updateAvailable: false,\n latestVersion: null,\n issues,\n missingPackages: result.packagesToInstall,\n };\n}\n\n/** Check for CLI updates and report status */\nasync function reportUpdateStatus(health: HealthStatus): Promise<void> {\n info('\\nChecking for updates...');\n const latestVersion = await checkLatestVersion();\n\n if (!latestVersion) {\n warn(\"Couldn't check for updates (offline?)\");\n return;\n }\n\n health.latestVersion = latestVersion;\n health.updateAvailable = isNewerVersion(health.cliVersion, latestVersion);\n\n if (health.updateAvailable) {\n warn(`Update available: v${latestVersion}`);\n info('Run `npm install -g safeword` to upgrade');\n } else {\n success('CLI is up to date');\n }\n}\n\n/** Compare project version vs CLI version and report */\nfunction reportVersionMismatch(health: HealthStatus): void {\n if (!health.projectVersion) return;\n\n if (isNewerVersion(health.cliVersion, health.projectVersion)) {\n warn(`Project config (v${health.projectVersion}) is newer than CLI (v${health.cliVersion})`);\n info('Consider upgrading the CLI');\n } else if (isNewerVersion(health.projectVersion, health.cliVersion)) {\n info(`\\nUpgrade available for project config`);\n info(\n `Run \\`safeword upgrade\\` to update from v${health.projectVersion} to v${health.cliVersion}`,\n );\n }\n}\n\n/** Report issues or success */\nfunction reportHealthSummary(health: HealthStatus): void {\n if (health.issues.length > 0) {\n header('Issues Found');\n for (const issue of health.issues) {\n warn(issue);\n }\n info('\\nRun `safeword upgrade` to repair configuration');\n return;\n }\n\n if (health.missingPackages.length > 0) {\n header('Missing Packages');\n info(`${health.missingPackages.length} linting packages not installed`);\n info('Run `safeword sync` to install missing packages');\n return;\n }\n\n success('\\nConfiguration is healthy');\n}\n\nexport async function check(options: CheckOptions): Promise<void> {\n const cwd = process.cwd();\n\n header('Safeword Health Check');\n\n const health = await checkHealth(cwd);\n\n // Not configured\n if (!health.configured) {\n info('Not configured. Run `safeword setup` to initialize.');\n return;\n }\n\n // Show versions\n keyValue('Safeword CLI', `v${health.cliVersion}`);\n keyValue('Project config', health.projectVersion ? `v${health.projectVersion}` : 'unknown');\n\n // Check for updates (unless offline)\n if (options.offline) {\n info('\\nSkipped update check (offline mode)');\n } else {\n await reportUpdateStatus(health);\n }\n\n reportVersionMismatch(health);\n reportHealthSummary(health);\n}\n"],"mappings":"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","names":[]}
|