spec-first-copilot 0.5.0-beta.11 → 0.5.0-beta.13
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/lib/init.js +7 -17
- package/lib/update.js +39 -1
- package/package.json +1 -1
- package/templates/_gitignore +35 -0
package/lib/init.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
-
const { execSync } = require('child_process');
|
|
4
3
|
|
|
5
4
|
const PLACEHOLDER = '{{PROJECT_NAME}}';
|
|
6
5
|
|
|
@@ -8,7 +7,9 @@ function copyDir(src, dest, replacements) {
|
|
|
8
7
|
fs.mkdirSync(dest, { recursive: true });
|
|
9
8
|
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
10
9
|
const srcPath = path.join(src, entry.name);
|
|
11
|
-
|
|
10
|
+
// Rename _gitignore → .gitignore (npm strips .gitignore from packages)
|
|
11
|
+
const destName = entry.name === '_gitignore' ? '.gitignore' : entry.name;
|
|
12
|
+
const destPath = path.join(dest, destName);
|
|
12
13
|
if (entry.isDirectory()) {
|
|
13
14
|
copyDir(srcPath, destPath, replacements);
|
|
14
15
|
} else {
|
|
@@ -24,7 +25,7 @@ function copyFile(src, dest, replacements) {
|
|
|
24
25
|
const textExtensions = ['.md', '.yaml', '.yml', '.json', '.js', '.ts', '.gitignore', '.gitkeep', ''];
|
|
25
26
|
const ext = path.extname(src).toLowerCase();
|
|
26
27
|
const basename = path.basename(src);
|
|
27
|
-
const isText = textExtensions.includes(ext) || basename.startsWith('.');
|
|
28
|
+
const isText = textExtensions.includes(ext) || basename.startsWith('.') || basename === '_gitignore';
|
|
28
29
|
|
|
29
30
|
if (isText) {
|
|
30
31
|
let content = fs.readFileSync(src, 'utf-8');
|
|
@@ -47,23 +48,12 @@ function init({ name, templatesDir, targetDir }) {
|
|
|
47
48
|
copyDir(templatesDir, dest, replacements);
|
|
48
49
|
patchProjectName(dest, name);
|
|
49
50
|
|
|
50
|
-
|
|
51
|
-
execSync('git init', { cwd: dest, stdio: 'pipe' });
|
|
52
|
-
execSync('git add -A', { cwd: dest, stdio: 'pipe' });
|
|
53
|
-
execSync(`git commit -m "chore: init project ${name} via spec-first-workflow"`, {
|
|
54
|
-
cwd: dest,
|
|
55
|
-
stdio: 'pipe',
|
|
56
|
-
});
|
|
57
|
-
console.log('Git initialized with initial commit.');
|
|
58
|
-
} catch {
|
|
59
|
-
console.log('Git init skipped (git not available or error).');
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
console.log(`\nDone! Project "${name}" is ready.`);
|
|
51
|
+
console.log(`Done! Project "${name}" is ready.`);
|
|
63
52
|
console.log('\nNext steps:');
|
|
64
53
|
console.log(` 1. cd ${name}`);
|
|
65
54
|
console.log(' 2. Create a folder in workspace/Input/ with your project files (e.g. workspace/Input/my_app/)');
|
|
66
|
-
console.log(' 3.
|
|
55
|
+
console.log(' 3. Copy sfw.config.yml.example to sfw.config.yml and configure your backend');
|
|
56
|
+
console.log(' 4. Run /sf-new-project <folder-name> to start the pipeline');
|
|
67
57
|
console.log('');
|
|
68
58
|
}
|
|
69
59
|
|
package/lib/update.js
CHANGED
|
@@ -37,6 +37,9 @@ function update({ templatesDir, targetDir, force }) {
|
|
|
37
37
|
const stats = { added: [], updated: [], skipped: 0 };
|
|
38
38
|
syncDir(templatesDir, dest, force, stats, dest);
|
|
39
39
|
|
|
40
|
+
// Auto-configure .gitignore based on sfw.config.yml (before printing results)
|
|
41
|
+
adjustGitignore(dest, stats);
|
|
42
|
+
|
|
40
43
|
console.log('');
|
|
41
44
|
if (stats.added.length > 0) {
|
|
42
45
|
console.log(`Added (${stats.added.length}):`);
|
|
@@ -51,6 +54,7 @@ function update({ templatesDir, targetDir, force }) {
|
|
|
51
54
|
} else {
|
|
52
55
|
console.log(`\n${stats.added.length} added, ${stats.updated.length} updated, ${stats.skipped} unchanged`);
|
|
53
56
|
}
|
|
57
|
+
|
|
54
58
|
console.log('');
|
|
55
59
|
}
|
|
56
60
|
|
|
@@ -58,7 +62,8 @@ function syncDir(src, dest, force, stats, root) {
|
|
|
58
62
|
fs.mkdirSync(dest, { recursive: true });
|
|
59
63
|
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
60
64
|
const srcPath = path.join(src, entry.name);
|
|
61
|
-
const
|
|
65
|
+
const destName = entry.name === '_gitignore' ? '.gitignore' : entry.name;
|
|
66
|
+
const destPath = path.join(dest, destName);
|
|
62
67
|
if (entry.isDirectory()) {
|
|
63
68
|
syncDir(srcPath, destPath, force, stats, root);
|
|
64
69
|
} else {
|
|
@@ -127,4 +132,37 @@ function isFrameworkPath(rel) {
|
|
|
127
132
|
return false;
|
|
128
133
|
}
|
|
129
134
|
|
|
135
|
+
const WORKSPACE_IGNORE_MARKER = '# SFW: workspace ignored (external backend detected)';
|
|
136
|
+
const WORKSPACE_IGNORE_BLOCK = `
|
|
137
|
+
${WORKSPACE_IGNORE_MARKER}
|
|
138
|
+
# Content lives in the external backend (Confluence, etc.) — local is just cache
|
|
139
|
+
workspace/Output/**
|
|
140
|
+
!workspace/Output/.gitkeep
|
|
141
|
+
`;
|
|
142
|
+
|
|
143
|
+
function adjustGitignore(dest, stats) {
|
|
144
|
+
const configPath = path.join(dest, 'sfw.config.yml');
|
|
145
|
+
const gitignorePath = path.join(dest, '.gitignore');
|
|
146
|
+
|
|
147
|
+
if (!fs.existsSync(configPath) || !fs.existsSync(gitignorePath)) return;
|
|
148
|
+
|
|
149
|
+
const config = fs.readFileSync(configPath, 'utf-8');
|
|
150
|
+
const gitignore = fs.readFileSync(gitignorePath, 'utf-8');
|
|
151
|
+
|
|
152
|
+
const inputMatch = config.match(/^\s*adapter:\s*(\S+)/m);
|
|
153
|
+
const inputAdapter = inputMatch ? inputMatch[1] : 'filesystem';
|
|
154
|
+
const isExternal = inputAdapter !== 'filesystem';
|
|
155
|
+
|
|
156
|
+
const alreadyHasBlock = gitignore.includes(WORKSPACE_IGNORE_MARKER);
|
|
157
|
+
|
|
158
|
+
if (isExternal && !alreadyHasBlock) {
|
|
159
|
+
fs.appendFileSync(gitignorePath, WORKSPACE_IGNORE_BLOCK);
|
|
160
|
+
stats.updated.push('.gitignore (workspace ignored — external backend)');
|
|
161
|
+
} else if (!isExternal && alreadyHasBlock) {
|
|
162
|
+
const cleaned = gitignore.split(WORKSPACE_IGNORE_MARKER)[0].trimEnd() + '\n';
|
|
163
|
+
fs.writeFileSync(gitignorePath, cleaned, 'utf-8');
|
|
164
|
+
stats.updated.push('.gitignore (workspace restored — local mode)');
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
130
168
|
module.exports = { update };
|
package/package.json
CHANGED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Insumos brutos do usuário — conteúdo nunca vai pro repositório
|
|
2
|
+
# A pasta workspace/Input/ existe no repo (via .gitkeep), mas seu conteúdo é ignorado
|
|
3
|
+
workspace/Input/**
|
|
4
|
+
!workspace/Input/.gitkeep
|
|
5
|
+
!workspace/Input/**/.gitkeep
|
|
6
|
+
|
|
7
|
+
# Memory pessoal (opcional — commit napkin.md se quiser compartilhar com o time)
|
|
8
|
+
# .ai/memory/napkin.md
|
|
9
|
+
|
|
10
|
+
# Repositórios de serviços (clonados/criados pelo workflow — cada um tem seu próprio .git)
|
|
11
|
+
projetos/
|
|
12
|
+
|
|
13
|
+
# MCP config — contém credenciais hardcoded (Confluence token, etc.)
|
|
14
|
+
.mcp.json
|
|
15
|
+
|
|
16
|
+
# Logs internos do workflow (append-only, gerados pelos commands)
|
|
17
|
+
.ai/load-log.md
|
|
18
|
+
.ai/publish-log.md
|
|
19
|
+
|
|
20
|
+
# IDEs
|
|
21
|
+
.vscode/
|
|
22
|
+
.idea/
|
|
23
|
+
|
|
24
|
+
# OS
|
|
25
|
+
.DS_Store
|
|
26
|
+
Thumbs.db
|
|
27
|
+
|
|
28
|
+
# Node
|
|
29
|
+
node_modules/
|
|
30
|
+
dist/
|
|
31
|
+
|
|
32
|
+
# Env
|
|
33
|
+
.env
|
|
34
|
+
.env.*
|
|
35
|
+
!.env.example
|