specweave 0.23.2 → 0.23.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +268 -0
- package/dist/plugins/specweave/lib/utils/fs-native.d.ts +133 -0
- package/dist/plugins/specweave/lib/utils/fs-native.d.ts.map +1 -0
- package/dist/plugins/specweave/lib/utils/fs-native.js +224 -0
- package/dist/plugins/specweave/lib/utils/fs-native.js.map +1 -0
- package/dist/plugins/specweave-github/lib/github-client-v2.js +1 -1
- package/dist/plugins/specweave-github/lib/github-client-v2.js.map +1 -1
- package/dist/plugins/specweave-github/lib/github-feature-sync.d.ts.map +1 -1
- package/dist/plugins/specweave-github/lib/github-feature-sync.js +52 -20
- package/dist/plugins/specweave-github/lib/github-feature-sync.js.map +1 -1
- package/dist/src/cli/helpers/init/initial-increment-generator.d.ts.map +1 -1
- package/dist/src/cli/helpers/init/initial-increment-generator.js +2 -1
- package/dist/src/cli/helpers/init/initial-increment-generator.js.map +1 -1
- package/dist/src/core/ac-test-validator-cli.d.ts +16 -0
- package/dist/src/core/ac-test-validator-cli.d.ts.map +1 -0
- package/dist/src/core/ac-test-validator-cli.js +118 -0
- package/dist/src/core/ac-test-validator-cli.js.map +1 -0
- package/dist/src/core/ac-test-validator.d.ts +111 -0
- package/dist/src/core/ac-test-validator.d.ts.map +1 -0
- package/dist/src/core/ac-test-validator.js +292 -0
- package/dist/src/core/ac-test-validator.js.map +1 -0
- package/dist/src/core/increment/desync-detector.d.ts +142 -0
- package/dist/src/core/increment/desync-detector.d.ts.map +1 -0
- package/dist/src/core/increment/desync-detector.js +270 -0
- package/dist/src/core/increment/desync-detector.js.map +1 -0
- package/dist/src/core/increment/metadata-manager.d.ts +8 -4
- package/dist/src/core/increment/metadata-manager.d.ts.map +1 -1
- package/dist/src/core/increment/metadata-manager.js +45 -21
- package/dist/src/core/increment/metadata-manager.js.map +1 -1
- package/dist/src/core/qa/qa-runner.js +9 -2
- package/dist/src/core/qa/qa-runner.js.map +1 -1
- package/dist/src/sync/sync-coordinator.d.ts +1 -1
- package/dist/src/sync/sync-coordinator.d.ts.map +1 -1
- package/dist/src/sync/sync-coordinator.js +40 -2
- package/dist/src/sync/sync-coordinator.js.map +1 -1
- package/dist/src/utils/fs-native.d.ts +133 -0
- package/dist/src/utils/fs-native.d.ts.map +1 -0
- package/dist/src/utils/fs-native.js +224 -0
- package/dist/src/utils/fs-native.js.map +1 -0
- package/package.json +1 -1
- package/plugins/specweave/.claude-plugin/plugin.json +12 -0
- package/plugins/specweave/agents/AGENTS-INDEX.md +216 -0
- package/plugins/specweave/agents/architect/AGENT.md +17 -0
- package/plugins/specweave/agents/code-standards-detective/AGENT.md +16 -0
- package/plugins/specweave/agents/docs-writer/AGENT.md +16 -0
- package/plugins/specweave/agents/increment-quality-judge-v2/AGENT.md +704 -0
- package/plugins/specweave/agents/infrastructure/AGENT.md +16 -0
- package/plugins/specweave/agents/performance/AGENT.md +16 -0
- package/plugins/specweave/agents/pm/AGENT.md +17 -0
- package/plugins/specweave/agents/qa-lead/AGENT.md +15 -0
- package/plugins/specweave/agents/reflective-reviewer/AGENT.md +16 -0
- package/plugins/specweave/agents/security/AGENT.md +16 -0
- package/plugins/specweave/agents/tdd-orchestrator/AGENT.md +16 -0
- package/plugins/specweave/agents/tech-lead/AGENT.md +16 -0
- package/plugins/specweave/agents/test-aware-planner/AGENT.md +16 -0
- package/plugins/specweave/agents/translator/AGENT.md +13 -0
- package/plugins/specweave/commands/specweave-done.md +14 -0
- package/plugins/specweave/commands/specweave-qa.md +11 -1
- package/plugins/specweave/commands/specweave-sync-status.md +356 -0
- package/plugins/specweave/commands/specweave-validate.md +10 -1
- package/plugins/specweave/hooks/pre-task-completion.sh +196 -0
- package/plugins/specweave/lib/hooks/git-diff-analyzer.js +3 -3
- package/plugins/specweave/lib/hooks/git-diff-analyzer.ts +3 -3
- package/plugins/specweave/lib/hooks/invoke-translator-skill.js +3 -2
- package/plugins/specweave/lib/hooks/invoke-translator-skill.ts +3 -2
- package/plugins/specweave/lib/hooks/prepare-reflection-context.js +3 -3
- package/plugins/specweave/lib/hooks/prepare-reflection-context.ts +3 -3
- package/plugins/specweave/lib/hooks/reflection-config-loader.js +4 -4
- package/plugins/specweave/lib/hooks/reflection-config-loader.ts +4 -4
- package/plugins/specweave/lib/hooks/reflection-storage.js +9 -9
- package/plugins/specweave/lib/hooks/reflection-storage.ts +9 -9
- package/plugins/specweave/lib/hooks/sync-cache.js +9 -8
- package/plugins/specweave/lib/hooks/sync-living-docs.js +57 -6
- package/plugins/specweave/lib/hooks/sync-us-tasks.js +6 -6
- package/plugins/specweave/lib/hooks/translate-file.js +3 -2
- package/plugins/specweave/lib/hooks/translate-file.ts +3 -2
- package/plugins/specweave/lib/hooks/translate-living-docs.js +4 -3
- package/plugins/specweave/lib/hooks/translate-living-docs.ts +4 -3
- package/plugins/specweave/lib/utils/fs-native.js +182 -0
- package/plugins/specweave/lib/utils/fs-native.ts +283 -0
- package/plugins/specweave/lib/vendor/core/increment/metadata-manager.d.ts +8 -4
- package/plugins/specweave/lib/vendor/core/increment/metadata-manager.js +45 -21
- package/plugins/specweave/lib/vendor/core/increment/metadata-manager.js.map +1 -1
- package/plugins/specweave/skills/SKILLS-INDEX.md +26 -2
- package/plugins/specweave/skills/increment-planner/SKILL.md +2 -2
- package/plugins/specweave-ado/commands/specweave-ado-close-workitem.md +1 -1
- package/plugins/specweave-ado/commands/specweave-ado-create-workitem.md +1 -1
- package/plugins/specweave-ado/commands/specweave-ado-status.md +1 -1
- package/plugins/specweave-ado/commands/specweave-ado-sync.md +1 -1
- package/plugins/specweave-diagrams/agents/diagrams-architect/AGENT.md +1 -1
- package/plugins/specweave-diagrams/skills/diagrams-generator/SKILL.md +4 -4
- package/plugins/specweave-github/lib/github-client-v2.js +2 -1
- package/plugins/specweave-github/lib/github-client-v2.ts +1 -1
- package/plugins/specweave-github/lib/github-feature-sync.js +30 -17
- package/plugins/specweave-github/lib/github-feature-sync.ts +54 -24
- package/plugins/specweave-mobile/README.md +1 -1
- package/plugins/specweave-release/hooks/.specweave/logs/dora-tracking.log +72 -0
- package/plugins/specweave/skills/task-builder/README.md +0 -84
|
@@ -1,15 +1,31 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import fs from "fs
|
|
2
|
+
import { promises as fs, existsSync } from "fs";
|
|
3
3
|
import path from "path";
|
|
4
4
|
import { execSync } from "child_process";
|
|
5
|
+
import { mkdirpSync } from "../utils/fs-native.js";
|
|
5
6
|
async function syncLivingDocs(incrementId) {
|
|
6
7
|
try {
|
|
7
8
|
console.log(`
|
|
8
9
|
\u{1F4DA} Checking living docs sync for increment: ${incrementId}`);
|
|
10
|
+
// Load and validate config with error handling
|
|
9
11
|
const configPath = path.join(process.cwd(), ".specweave", "config.json");
|
|
10
12
|
let config = {};
|
|
11
|
-
|
|
12
|
-
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
if (existsSync(configPath)) {
|
|
16
|
+
const rawContent = await fs.readFile(configPath, "utf-8");
|
|
17
|
+
config = JSON.parse(rawContent);
|
|
18
|
+
} else {
|
|
19
|
+
console.log("\u26A0\uFE0F No config.json found, using safe defaults (all permissions disabled)");
|
|
20
|
+
console.log(" To configure: Run 'specweave init' or create .specweave/config.json");
|
|
21
|
+
config = { sync: { settings: {} } };
|
|
22
|
+
}
|
|
23
|
+
} catch (error) {
|
|
24
|
+
console.error("\u274C Failed to load config.json:", error.message);
|
|
25
|
+
console.error(" File may be corrupted or contain invalid JSON");
|
|
26
|
+
console.error(" Using safe defaults (all permissions denied)");
|
|
27
|
+
console.error(" To fix: Check .specweave/config.json for syntax errors");
|
|
28
|
+
config = { sync: { settings: {} } };
|
|
13
29
|
}
|
|
14
30
|
const syncEnabled = config.hooks?.post_task_completion?.sync_living_docs ?? false;
|
|
15
31
|
if (!syncEnabled) {
|
|
@@ -18,6 +34,22 @@ async function syncLivingDocs(incrementId) {
|
|
|
18
34
|
return;
|
|
19
35
|
}
|
|
20
36
|
console.log("\u2705 Living docs sync enabled");
|
|
37
|
+
|
|
38
|
+
// ========================================================================
|
|
39
|
+
// GATE 1: canUpsertInternalItems (v0.24.0+ - Internal Docs Permission)
|
|
40
|
+
// ========================================================================
|
|
41
|
+
// This permission controls whether SpecWeave can CREATE/UPDATE internal docs.
|
|
42
|
+
// If false, ALL living docs sync is blocked (both local and external).
|
|
43
|
+
const canUpsertInternal = config.sync?.settings?.canUpsertInternalItems ?? false;
|
|
44
|
+
|
|
45
|
+
if (!canUpsertInternal) {
|
|
46
|
+
console.log("\u26D4 Living docs sync BLOCKED (canUpsertInternalItems = false)");
|
|
47
|
+
console.log(" To enable: Set sync.settings.canUpsertInternalItems = true in config.json");
|
|
48
|
+
console.log(" No internal docs or external tools will be updated");
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
console.log("\u2705 Internal docs sync permitted (canUpsertInternalItems = true)");
|
|
21
53
|
const intelligentEnabled = config.livingDocs?.intelligent?.enabled ?? false;
|
|
22
54
|
let specCopied = false;
|
|
23
55
|
let changedDocs = [];
|
|
@@ -54,6 +86,25 @@ async function syncLivingDocs(incrementId) {
|
|
|
54
86
|
return;
|
|
55
87
|
}
|
|
56
88
|
|
|
89
|
+
// ========================================================================
|
|
90
|
+
// GATE 3: autoSyncOnCompletion (v0.24.0+ - Automatic vs Manual Sync)
|
|
91
|
+
// ========================================================================
|
|
92
|
+
// This setting controls whether sync to external tools happens automatically
|
|
93
|
+
// on increment completion or requires manual /specweave:sync-* commands.
|
|
94
|
+
// DEFAULT: true (automatic sync enabled for better UX)
|
|
95
|
+
const autoSync = config.sync?.settings?.autoSyncOnCompletion ?? true;
|
|
96
|
+
|
|
97
|
+
if (!autoSync) {
|
|
98
|
+
console.log("\u26A0\uFE0F Automatic external sync DISABLED (autoSyncOnCompletion = false)");
|
|
99
|
+
console.log(" Living docs updated locally, but external tools NOT synced");
|
|
100
|
+
console.log(" To sync manually: Run /specweave-github:sync or /specweave-jira:sync");
|
|
101
|
+
console.log(" To enable auto-sync: Set sync.settings.autoSyncOnCompletion = true");
|
|
102
|
+
console.log("\u2705 Living docs sync complete (manual external sync required)\n");
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
console.log("\u2705 Automatic external sync permitted (autoSyncOnCompletion = true)");
|
|
107
|
+
|
|
57
108
|
// T-034E: Use FormatPreservationSyncService for origin-aware sync
|
|
58
109
|
await syncWithFormatPreservation(incrementId);
|
|
59
110
|
|
|
@@ -184,7 +235,7 @@ async function extractAndMergeLivingDocs(incrementId) {
|
|
|
184
235
|
} = await import("../../../../dist/src/utils/spec-parser.js");
|
|
185
236
|
const projectRoot = process.cwd();
|
|
186
237
|
const incrementSpecPath = path.join(projectRoot, ".specweave", "increments", incrementId, "spec.md");
|
|
187
|
-
if (!
|
|
238
|
+
if (!existsSync(incrementSpecPath)) {
|
|
188
239
|
console.log(`\u26A0\uFE0F Increment spec not found: ${incrementSpecPath}`);
|
|
189
240
|
return false;
|
|
190
241
|
}
|
|
@@ -198,7 +249,7 @@ async function extractAndMergeLivingDocs(incrementId) {
|
|
|
198
249
|
const specId = incrementSpec.implementsSpec || extractSpecId(incrementId);
|
|
199
250
|
const livingDocsDir = path.join(projectRoot, ".specweave", "docs", "internal", "specs", "default");
|
|
200
251
|
const livingDocsPath = path.join(livingDocsDir, `${specId}-${incrementId.replace(/^\d+-/, "")}.md`);
|
|
201
|
-
const livingDocsExists =
|
|
252
|
+
const livingDocsExists = existsSync(livingDocsPath);
|
|
202
253
|
if (livingDocsExists) {
|
|
203
254
|
console.log(` \u{1F4DA} Living docs spec exists, merging user stories...`);
|
|
204
255
|
const livingSpec = await parseLivingDocsSpec(livingDocsPath);
|
|
@@ -253,7 +304,7 @@ async function extractAndMergeLivingDocs(incrementId) {
|
|
|
253
304
|
created: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
|
|
254
305
|
lastUpdated: (/* @__PURE__ */ new Date()).toISOString().split("T")[0]
|
|
255
306
|
};
|
|
256
|
-
|
|
307
|
+
mkdirpSync(livingDocsDir);
|
|
257
308
|
await writeLivingDocsSpec(livingDocsPath, livingSpec);
|
|
258
309
|
console.log(` \u2705 Created new living docs spec: ${specId}`);
|
|
259
310
|
console.log(` \u2705 Added ${incrementSpec.userStories.length} user stories`);
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* Part of increment 0047-us-task-linkage implementation.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import fs from 'fs
|
|
13
|
+
import { readFileSync, existsSync, promises as fs } from 'fs';
|
|
14
14
|
import path from 'path';
|
|
15
15
|
import { parseTasksWithUSLinks, getAllTasks } from '../vendor/generators/spec/task-parser.js';
|
|
16
16
|
import { glob } from 'glob';
|
|
@@ -32,7 +32,7 @@ export async function syncUSTasksToLivingDocs(incrementId, projectRoot, featureI
|
|
|
32
32
|
const tasksPath = path.join(projectRoot, '.specweave', 'increments', incrementId, 'tasks.md');
|
|
33
33
|
|
|
34
34
|
// Check if tasks.md exists
|
|
35
|
-
if (!
|
|
35
|
+
if (!existsSync(tasksPath)) {
|
|
36
36
|
console.log(` ⚠️ tasks.md not found, skipping task sync`);
|
|
37
37
|
return { success: true, updatedFiles: [], errors: [] };
|
|
38
38
|
}
|
|
@@ -451,8 +451,8 @@ function getProjectId(projectRoot, incrementId) {
|
|
|
451
451
|
try {
|
|
452
452
|
// Try to get from metadata.json
|
|
453
453
|
const metadataPath = path.join(projectRoot, '.specweave', 'increments', incrementId, 'metadata.json');
|
|
454
|
-
if (
|
|
455
|
-
const metadata = JSON.parse(
|
|
454
|
+
if (existsSync(metadataPath)) {
|
|
455
|
+
const metadata = JSON.parse(readFileSync(metadataPath, 'utf-8'));
|
|
456
456
|
if (metadata.project) {
|
|
457
457
|
return metadata.project;
|
|
458
458
|
}
|
|
@@ -460,8 +460,8 @@ function getProjectId(projectRoot, incrementId) {
|
|
|
460
460
|
|
|
461
461
|
// Try to get from config.json
|
|
462
462
|
const configPath = path.join(projectRoot, '.specweave', 'config.json');
|
|
463
|
-
if (
|
|
464
|
-
const config = JSON.parse(
|
|
463
|
+
if (existsSync(configPath)) {
|
|
464
|
+
const config = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
465
465
|
if (config.defaultProject) {
|
|
466
466
|
return config.defaultProject;
|
|
467
467
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { existsSync } from "fs";
|
|
2
|
+
import { promises as fs } from "fs";
|
|
2
3
|
import {
|
|
3
4
|
detectLanguage,
|
|
4
5
|
prepareTranslation,
|
|
@@ -9,7 +10,7 @@ import {
|
|
|
9
10
|
} from "../vendor/utils/translation.js";
|
|
10
11
|
async function translateFile(options) {
|
|
11
12
|
const { filePath, targetLang, preview, verbose } = options;
|
|
12
|
-
if (!
|
|
13
|
+
if (!existsSync(filePath)) {
|
|
13
14
|
throw new Error(`File not found: ${filePath}`);
|
|
14
15
|
}
|
|
15
16
|
if (verbose) {
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
* @see .specweave/increments/0006-llm-native-i18n/reports/DESIGN-POST-GENERATION-TRANSLATION.md
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
import
|
|
19
|
+
import { existsSync } from 'fs';
|
|
20
|
+
import { promises as fs } from 'fs';
|
|
20
21
|
import path from 'path';
|
|
21
22
|
import {
|
|
22
23
|
detectLanguage,
|
|
@@ -62,7 +63,7 @@ export async function translateFile(options: CLIOptions): Promise<FileTranslatio
|
|
|
62
63
|
const { filePath, targetLang, preview, verbose } = options;
|
|
63
64
|
|
|
64
65
|
// 1. Validate file exists
|
|
65
|
-
if (!
|
|
66
|
+
if (!existsSync(filePath)) {
|
|
66
67
|
throw new Error(`File not found: ${filePath}`);
|
|
67
68
|
}
|
|
68
69
|
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { execSync } from "child_process";
|
|
2
|
-
import
|
|
2
|
+
import { existsSync } from "fs";
|
|
3
|
+
import { promises as fs } from "fs";
|
|
3
4
|
async function translateLivingDocs(incrementId) {
|
|
4
5
|
try {
|
|
5
6
|
const configPath = ".specweave/config.json";
|
|
6
|
-
if (!
|
|
7
|
+
if (!existsSync(configPath)) {
|
|
7
8
|
console.log("[translate-living-docs] No config found, skipping translation");
|
|
8
9
|
return;
|
|
9
10
|
}
|
|
10
|
-
const config = await fs.
|
|
11
|
+
const config = JSON.parse(await fs.readFile(configPath, "utf-8"));
|
|
11
12
|
if (!config.language || config.language === "en") {
|
|
12
13
|
console.log("[translate-living-docs] Project language is English, skipping translation");
|
|
13
14
|
return;
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { execSync } from 'child_process';
|
|
11
|
-
import
|
|
11
|
+
import { existsSync } from 'fs';
|
|
12
|
+
import { promises as fs } from 'fs';
|
|
12
13
|
import path from 'path';
|
|
13
14
|
|
|
14
15
|
interface Config {
|
|
@@ -27,12 +28,12 @@ export async function translateLivingDocs(incrementId: string): Promise<void> {
|
|
|
27
28
|
try {
|
|
28
29
|
// 1. Load config
|
|
29
30
|
const configPath = '.specweave/config.json';
|
|
30
|
-
if (!
|
|
31
|
+
if (!existsSync(configPath)) {
|
|
31
32
|
console.log('[translate-living-docs] No config found, skipping translation');
|
|
32
33
|
return;
|
|
33
34
|
}
|
|
34
35
|
|
|
35
|
-
const config: Config = await fs.
|
|
36
|
+
const config: Config = JSON.parse(await fs.readFile(configPath, 'utf-8'));
|
|
36
37
|
|
|
37
38
|
// 2. Check if translation is enabled
|
|
38
39
|
if (!config.language || config.language === 'en') {
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { promises as fsPromises, existsSync, mkdirSync, readFileSync, writeFileSync, statSync, readdirSync, rmSync, unlinkSync, copyFileSync } from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
async function ensureDir(dirPath) {
|
|
4
|
+
if (!existsSync(dirPath)) {
|
|
5
|
+
await fsPromises.mkdir(dirPath, { recursive: true });
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
function ensureDirSync(dirPath) {
|
|
9
|
+
if (!existsSync(dirPath)) {
|
|
10
|
+
mkdirSync(dirPath, { recursive: true });
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function mkdirpSync(dirPath) {
|
|
14
|
+
ensureDirSync(dirPath);
|
|
15
|
+
}
|
|
16
|
+
async function pathExists(filePath) {
|
|
17
|
+
return existsSync(filePath);
|
|
18
|
+
}
|
|
19
|
+
async function readJson(filePath) {
|
|
20
|
+
const content = await fsPromises.readFile(filePath, "utf-8");
|
|
21
|
+
return JSON.parse(content);
|
|
22
|
+
}
|
|
23
|
+
function readJsonSync(filePath) {
|
|
24
|
+
const content = readFileSync(filePath, "utf-8");
|
|
25
|
+
return JSON.parse(content);
|
|
26
|
+
}
|
|
27
|
+
async function writeJson(filePath, data, options) {
|
|
28
|
+
const spaces = options?.spaces ?? 2;
|
|
29
|
+
const content = JSON.stringify(data, null, spaces);
|
|
30
|
+
await fsPromises.writeFile(filePath, content, "utf-8");
|
|
31
|
+
}
|
|
32
|
+
function writeJsonSync(filePath, data, options) {
|
|
33
|
+
const spaces = options?.spaces ?? 2;
|
|
34
|
+
const content = JSON.stringify(data, null, spaces);
|
|
35
|
+
writeFileSync(filePath, content, "utf-8");
|
|
36
|
+
}
|
|
37
|
+
async function remove(targetPath) {
|
|
38
|
+
if (existsSync(targetPath)) {
|
|
39
|
+
await fsPromises.rm(targetPath, { recursive: true, force: true });
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function removeSync(targetPath) {
|
|
43
|
+
if (existsSync(targetPath)) {
|
|
44
|
+
rmSync(targetPath, { recursive: true, force: true });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
async function copy(src, dest, options) {
|
|
48
|
+
const srcStat = await fsPromises.stat(src);
|
|
49
|
+
if (srcStat.isFile()) {
|
|
50
|
+
await fsPromises.mkdir(path.dirname(dest), { recursive: true });
|
|
51
|
+
await fsPromises.copyFile(src, dest);
|
|
52
|
+
} else if (srcStat.isDirectory()) {
|
|
53
|
+
await fsPromises.mkdir(dest, { recursive: true });
|
|
54
|
+
const entries = await fsPromises.readdir(src, { withFileTypes: true });
|
|
55
|
+
for (const entry of entries) {
|
|
56
|
+
const srcPath = path.join(src, entry.name);
|
|
57
|
+
const destPath = path.join(dest, entry.name);
|
|
58
|
+
if (options?.filter && !options.filter(srcPath)) {
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (entry.isDirectory()) {
|
|
62
|
+
await copy(srcPath, destPath, options);
|
|
63
|
+
} else {
|
|
64
|
+
await fsPromises.copyFile(srcPath, destPath);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function copySync(src, dest, options) {
|
|
70
|
+
const srcStat = statSync(src);
|
|
71
|
+
if (srcStat.isFile()) {
|
|
72
|
+
mkdirSync(path.dirname(dest), { recursive: true });
|
|
73
|
+
copyFileSync(src, dest);
|
|
74
|
+
} else if (srcStat.isDirectory()) {
|
|
75
|
+
mkdirSync(dest, { recursive: true });
|
|
76
|
+
const entries = readdirSync(src, { withFileTypes: true });
|
|
77
|
+
for (const entry of entries) {
|
|
78
|
+
const srcPath = path.join(src, entry.name);
|
|
79
|
+
const destPath = path.join(dest, entry.name);
|
|
80
|
+
if (options?.filter && !options.filter(srcPath)) {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if (entry.isDirectory()) {
|
|
84
|
+
copySync(srcPath, destPath, options);
|
|
85
|
+
} else {
|
|
86
|
+
copyFileSync(srcPath, destPath);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
async function ensureFile(filePath) {
|
|
92
|
+
if (!existsSync(filePath)) {
|
|
93
|
+
await fsPromises.mkdir(path.dirname(filePath), { recursive: true });
|
|
94
|
+
await fsPromises.writeFile(filePath, "", "utf-8");
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function ensureFileSync(filePath) {
|
|
98
|
+
if (!existsSync(filePath)) {
|
|
99
|
+
mkdirSync(path.dirname(filePath), { recursive: true });
|
|
100
|
+
writeFileSync(filePath, "", "utf-8");
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
const {
|
|
104
|
+
readFile,
|
|
105
|
+
writeFile,
|
|
106
|
+
appendFile,
|
|
107
|
+
stat,
|
|
108
|
+
readdir,
|
|
109
|
+
access,
|
|
110
|
+
unlink,
|
|
111
|
+
rmdir,
|
|
112
|
+
rename,
|
|
113
|
+
chmod
|
|
114
|
+
} = fsPromises;
|
|
115
|
+
var fs_native_default = {
|
|
116
|
+
// Async methods
|
|
117
|
+
ensureDir,
|
|
118
|
+
pathExists,
|
|
119
|
+
readJson,
|
|
120
|
+
writeJson,
|
|
121
|
+
remove,
|
|
122
|
+
copy,
|
|
123
|
+
ensureFile,
|
|
124
|
+
readFile,
|
|
125
|
+
writeFile,
|
|
126
|
+
appendFile,
|
|
127
|
+
stat,
|
|
128
|
+
readdir,
|
|
129
|
+
access,
|
|
130
|
+
unlink,
|
|
131
|
+
// Sync methods
|
|
132
|
+
ensureDirSync,
|
|
133
|
+
mkdirpSync,
|
|
134
|
+
existsSync,
|
|
135
|
+
readJsonSync,
|
|
136
|
+
writeJsonSync,
|
|
137
|
+
removeSync,
|
|
138
|
+
copySync,
|
|
139
|
+
ensureFileSync,
|
|
140
|
+
readFileSync,
|
|
141
|
+
writeFileSync,
|
|
142
|
+
statSync,
|
|
143
|
+
readdirSync,
|
|
144
|
+
unlinkSync,
|
|
145
|
+
mkdirSync,
|
|
146
|
+
rmSync
|
|
147
|
+
};
|
|
148
|
+
export {
|
|
149
|
+
access,
|
|
150
|
+
appendFile,
|
|
151
|
+
chmod,
|
|
152
|
+
copy,
|
|
153
|
+
copySync,
|
|
154
|
+
fs_native_default as default,
|
|
155
|
+
ensureDir,
|
|
156
|
+
ensureDirSync,
|
|
157
|
+
ensureFile,
|
|
158
|
+
ensureFileSync,
|
|
159
|
+
existsSync,
|
|
160
|
+
mkdirSync,
|
|
161
|
+
mkdirpSync,
|
|
162
|
+
pathExists,
|
|
163
|
+
readFile,
|
|
164
|
+
readFileSync,
|
|
165
|
+
readJson,
|
|
166
|
+
readJsonSync,
|
|
167
|
+
readdir,
|
|
168
|
+
readdirSync,
|
|
169
|
+
remove,
|
|
170
|
+
removeSync,
|
|
171
|
+
rename,
|
|
172
|
+
rmSync,
|
|
173
|
+
rmdir,
|
|
174
|
+
stat,
|
|
175
|
+
statSync,
|
|
176
|
+
unlink,
|
|
177
|
+
unlinkSync,
|
|
178
|
+
writeFile,
|
|
179
|
+
writeFileSync,
|
|
180
|
+
writeJson,
|
|
181
|
+
writeJsonSync
|
|
182
|
+
};
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Native Node.js fs API Helpers
|
|
3
|
+
*
|
|
4
|
+
* Drop-in replacements for fs-extra methods using only Node.js stdlib.
|
|
5
|
+
* All methods use native Node.js 20+ APIs with no external dependencies.
|
|
6
|
+
*
|
|
7
|
+
* Migration from fs-extra:
|
|
8
|
+
* - import fs from 'fs-extra' → import * as fs from './utils/fs-native.js'
|
|
9
|
+
* - All fs-extra methods work as drop-in replacements
|
|
10
|
+
*
|
|
11
|
+
* Benefits:
|
|
12
|
+
* - Zero bundle overhead (no npm packages)
|
|
13
|
+
* - Works in marketplace (no node_modules needed)
|
|
14
|
+
* - Faster startup (native APIs)
|
|
15
|
+
* - Better debugging (native stack traces)
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { promises as fsPromises, existsSync, mkdirSync, readFileSync, writeFileSync, statSync, readdirSync, rmSync, unlinkSync, copyFileSync } from 'fs';
|
|
19
|
+
import { execSync } from 'child_process';
|
|
20
|
+
import path from 'path';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Ensures that a directory exists. If the directory does not exist, it is created.
|
|
24
|
+
* @param dirPath - The directory path to ensure
|
|
25
|
+
*/
|
|
26
|
+
export async function ensureDir(dirPath: string): Promise<void> {
|
|
27
|
+
if (!existsSync(dirPath)) {
|
|
28
|
+
await fsPromises.mkdir(dirPath, { recursive: true });
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Synchronous version of ensureDir
|
|
34
|
+
*/
|
|
35
|
+
export function ensureDirSync(dirPath: string): void {
|
|
36
|
+
if (!existsSync(dirPath)) {
|
|
37
|
+
mkdirSync(dirPath, { recursive: true });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Alias for ensureDirSync (fs-extra compatibility)
|
|
43
|
+
*/
|
|
44
|
+
export function mkdirpSync(dirPath: string): void {
|
|
45
|
+
ensureDirSync(dirPath);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Check if a path exists
|
|
50
|
+
* @param filePath - The path to check
|
|
51
|
+
*/
|
|
52
|
+
export async function pathExists(filePath: string): Promise<boolean> {
|
|
53
|
+
return existsSync(filePath);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Synchronous version of pathExists
|
|
58
|
+
*/
|
|
59
|
+
export { existsSync };
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Read a JSON file and parse it
|
|
63
|
+
* @param filePath - The JSON file path
|
|
64
|
+
*/
|
|
65
|
+
export async function readJson(filePath: string): Promise<any> {
|
|
66
|
+
const content = await fsPromises.readFile(filePath, 'utf-8');
|
|
67
|
+
return JSON.parse(content);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Synchronous version of readJson
|
|
72
|
+
*/
|
|
73
|
+
export function readJsonSync(filePath: string): any {
|
|
74
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
75
|
+
return JSON.parse(content);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Write a JSON file with formatting
|
|
80
|
+
* @param filePath - The JSON file path
|
|
81
|
+
* @param data - The data to write
|
|
82
|
+
* @param options - Options (spaces for indentation)
|
|
83
|
+
*/
|
|
84
|
+
export async function writeJson(
|
|
85
|
+
filePath: string,
|
|
86
|
+
data: any,
|
|
87
|
+
options?: { spaces?: number }
|
|
88
|
+
): Promise<void> {
|
|
89
|
+
const spaces = options?.spaces ?? 2;
|
|
90
|
+
const content = JSON.stringify(data, null, spaces);
|
|
91
|
+
await fsPromises.writeFile(filePath, content, 'utf-8');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Synchronous version of writeJson
|
|
96
|
+
*/
|
|
97
|
+
export function writeJsonSync(
|
|
98
|
+
filePath: string,
|
|
99
|
+
data: any,
|
|
100
|
+
options?: { spaces?: number }
|
|
101
|
+
): void {
|
|
102
|
+
const spaces = options?.spaces ?? 2;
|
|
103
|
+
const content = JSON.stringify(data, null, spaces);
|
|
104
|
+
writeFileSync(filePath, content, 'utf-8');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Remove a file or directory (recursively)
|
|
109
|
+
* @param targetPath - The path to remove
|
|
110
|
+
*/
|
|
111
|
+
export async function remove(targetPath: string): Promise<void> {
|
|
112
|
+
if (existsSync(targetPath)) {
|
|
113
|
+
await fsPromises.rm(targetPath, { recursive: true, force: true });
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Synchronous version of remove
|
|
119
|
+
*/
|
|
120
|
+
export function removeSync(targetPath: string): void {
|
|
121
|
+
if (existsSync(targetPath)) {
|
|
122
|
+
rmSync(targetPath, { recursive: true, force: true });
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Copy a file or directory
|
|
128
|
+
* @param src - Source path
|
|
129
|
+
* @param dest - Destination path
|
|
130
|
+
* @param options - Copy options
|
|
131
|
+
*/
|
|
132
|
+
export async function copy(
|
|
133
|
+
src: string,
|
|
134
|
+
dest: string,
|
|
135
|
+
options?: { overwrite?: boolean; filter?: (src: string) => boolean }
|
|
136
|
+
): Promise<void> {
|
|
137
|
+
const srcStat = await fsPromises.stat(src);
|
|
138
|
+
|
|
139
|
+
if (srcStat.isFile()) {
|
|
140
|
+
// Copy single file
|
|
141
|
+
await fsPromises.mkdir(path.dirname(dest), { recursive: true });
|
|
142
|
+
await fsPromises.copyFile(src, dest);
|
|
143
|
+
} else if (srcStat.isDirectory()) {
|
|
144
|
+
// Copy directory recursively
|
|
145
|
+
await fsPromises.mkdir(dest, { recursive: true });
|
|
146
|
+
const entries = await fsPromises.readdir(src, { withFileTypes: true });
|
|
147
|
+
|
|
148
|
+
for (const entry of entries) {
|
|
149
|
+
const srcPath = path.join(src, entry.name);
|
|
150
|
+
const destPath = path.join(dest, entry.name);
|
|
151
|
+
|
|
152
|
+
// Apply filter if provided
|
|
153
|
+
if (options?.filter && !options.filter(srcPath)) {
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (entry.isDirectory()) {
|
|
158
|
+
await copy(srcPath, destPath, options);
|
|
159
|
+
} else {
|
|
160
|
+
await fsPromises.copyFile(srcPath, destPath);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Synchronous version of copy
|
|
168
|
+
*/
|
|
169
|
+
export function copySync(
|
|
170
|
+
src: string,
|
|
171
|
+
dest: string,
|
|
172
|
+
options?: { overwrite?: boolean; filter?: (src: string) => boolean }
|
|
173
|
+
): void {
|
|
174
|
+
const srcStat = statSync(src);
|
|
175
|
+
|
|
176
|
+
if (srcStat.isFile()) {
|
|
177
|
+
// Copy single file
|
|
178
|
+
mkdirSync(path.dirname(dest), { recursive: true });
|
|
179
|
+
copyFileSync(src, dest);
|
|
180
|
+
} else if (srcStat.isDirectory()) {
|
|
181
|
+
// Copy directory recursively
|
|
182
|
+
mkdirSync(dest, { recursive: true });
|
|
183
|
+
const entries = readdirSync(src, { withFileTypes: true });
|
|
184
|
+
|
|
185
|
+
for (const entry of entries) {
|
|
186
|
+
const srcPath = path.join(src, entry.name);
|
|
187
|
+
const destPath = path.join(dest, entry.name);
|
|
188
|
+
|
|
189
|
+
// Apply filter if provided
|
|
190
|
+
if (options?.filter && !options.filter(srcPath)) {
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (entry.isDirectory()) {
|
|
195
|
+
copySync(srcPath, destPath, options);
|
|
196
|
+
} else {
|
|
197
|
+
copyFileSync(srcPath, destPath);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Ensure a file exists (create if it doesn't)
|
|
205
|
+
* @param filePath - The file path
|
|
206
|
+
*/
|
|
207
|
+
export async function ensureFile(filePath: string): Promise<void> {
|
|
208
|
+
if (!existsSync(filePath)) {
|
|
209
|
+
await fsPromises.mkdir(path.dirname(filePath), { recursive: true });
|
|
210
|
+
await fsPromises.writeFile(filePath, '', 'utf-8');
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Synchronous version of ensureFile
|
|
216
|
+
*/
|
|
217
|
+
export function ensureFileSync(filePath: string): void {
|
|
218
|
+
if (!existsSync(filePath)) {
|
|
219
|
+
mkdirSync(path.dirname(filePath), { recursive: true });
|
|
220
|
+
writeFileSync(filePath, '', 'utf-8');
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Re-export common fs/promises methods for convenience
|
|
225
|
+
export const {
|
|
226
|
+
readFile,
|
|
227
|
+
writeFile,
|
|
228
|
+
appendFile,
|
|
229
|
+
stat,
|
|
230
|
+
readdir,
|
|
231
|
+
access,
|
|
232
|
+
unlink,
|
|
233
|
+
rmdir,
|
|
234
|
+
rename,
|
|
235
|
+
chmod,
|
|
236
|
+
} = fsPromises;
|
|
237
|
+
|
|
238
|
+
// Re-export common synchronous methods
|
|
239
|
+
export {
|
|
240
|
+
readFileSync,
|
|
241
|
+
writeFileSync,
|
|
242
|
+
statSync,
|
|
243
|
+
readdirSync,
|
|
244
|
+
unlinkSync,
|
|
245
|
+
mkdirSync,
|
|
246
|
+
rmSync,
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
// Default export for convenience
|
|
250
|
+
export default {
|
|
251
|
+
// Async methods
|
|
252
|
+
ensureDir,
|
|
253
|
+
pathExists,
|
|
254
|
+
readJson,
|
|
255
|
+
writeJson,
|
|
256
|
+
remove,
|
|
257
|
+
copy,
|
|
258
|
+
ensureFile,
|
|
259
|
+
readFile,
|
|
260
|
+
writeFile,
|
|
261
|
+
appendFile,
|
|
262
|
+
stat,
|
|
263
|
+
readdir,
|
|
264
|
+
access,
|
|
265
|
+
unlink,
|
|
266
|
+
|
|
267
|
+
// Sync methods
|
|
268
|
+
ensureDirSync,
|
|
269
|
+
mkdirpSync,
|
|
270
|
+
existsSync,
|
|
271
|
+
readJsonSync,
|
|
272
|
+
writeJsonSync,
|
|
273
|
+
removeSync,
|
|
274
|
+
copySync,
|
|
275
|
+
ensureFileSync,
|
|
276
|
+
readFileSync,
|
|
277
|
+
writeFileSync,
|
|
278
|
+
statSync,
|
|
279
|
+
readdirSync,
|
|
280
|
+
unlinkSync,
|
|
281
|
+
mkdirSync,
|
|
282
|
+
rmSync,
|
|
283
|
+
};
|