@popoverai/dotrequirements 0.13.0 → 0.14.0
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/README.md +47 -47
- package/dist/cli.js +0 -10
- package/dist/commands/init.js +166 -226
- package/dist/commands/link.d.ts +9 -10
- package/dist/commands/link.js +81 -106
- package/dist/commands/mcp-setup.js +77 -94
- package/dist/commands/pull.js +7 -29
- package/dist/commands/push.js +4 -13
- package/dist/harness/cache.d.ts +0 -5
- package/dist/harness/cache.js +0 -48
- package/dist/harness/convexReporting.js +7 -14
- package/dist/harness/finalize.js +7 -12
- package/dist/harness/prepare.js +7 -9
- package/dist/mcp/convexClient.d.ts +5 -1
- package/dist/mcp/convexClient.js +14 -34
- package/dist/mcp/index.js +12 -73
- package/dist/templates/context-file-section.md +59 -0
- package/dist/utils/context-file.d.ts +38 -0
- package/dist/utils/context-file.js +94 -0
- package/dist/utils/env.d.ts +0 -13
- package/dist/utils/env.js +0 -19
- package/dist/utils/gitignore.d.ts +2 -2
- package/dist/utils/gitignore.js +4 -4
- package/dist/utils/oauth-flow.d.ts +0 -1
- package/dist/utils/oauth-flow.js +0 -9
- package/dist/utils/project-discovery.d.ts +3 -5
- package/dist/utils/project-discovery.js +18 -42
- package/dist/utils/project-selector.d.ts +17 -3
- package/dist/utils/project-selector.js +35 -3
- package/dist/utils/project-settings.d.ts +47 -0
- package/dist/utils/project-settings.js +110 -0
- package/dist/utils/templates.d.ts +0 -24
- package/dist/utils/templates.js +0 -39
- package/package.json +1 -1
- package/dist/harness/localReporting.d.ts +0 -6
- package/dist/harness/localReporting.js +0 -49
- package/dist/templates/antigravity-gemini.md +0 -3
- package/dist/templates/antigravity-overview-rule.md +0 -3
- package/dist/templates/antigravity-test-rule.md +0 -3
- package/dist/templates/behavioral-core.md +0 -25
- package/dist/templates/claude-code-overview-skill.md +0 -6
- package/dist/templates/claude-code-skill.md +0 -6
- package/dist/templates/claude-code-test-skill.md +0 -6
- package/dist/templates/codex-agents.md +0 -3
- package/dist/templates/codex-overview-agents.md +0 -3
- package/dist/templates/codex-test-agents.md +0 -3
- package/dist/templates/cursor-overview-rule.mdc +0 -5
- package/dist/templates/cursor-rule.mdc +0 -5
- package/dist/templates/cursor-test-rule.mdc +0 -5
- package/dist/templates/overview-core.md +0 -27
- package/dist/templates/test-writing-core.md +0 -72
- package/dist/utils/detect-existing-project.d.ts +0 -5
- package/dist/utils/detect-existing-project.js +0 -34
package/dist/commands/init.js
CHANGED
|
@@ -6,52 +6,48 @@ import { generateRequirementsReadme } from '../templates/requirements-readme.js'
|
|
|
6
6
|
import { generateExampleRequirements } from '../templates/example-requirements.js';
|
|
7
7
|
import { promptChoice, promptConfirm } from '../utils/prompts.js';
|
|
8
8
|
import { pullCommand } from './pull.js';
|
|
9
|
-
import { LOCAL_PROJECT_CREDENTIALS } from '../utils/local-project.js';
|
|
10
|
-
import { loadEnvFile } from '../utils/env.js';
|
|
11
|
-
import { hasValidTokens, getTokens } from '../utils/token-storage.js';
|
|
12
9
|
import { executeOAuthFlow } from '../utils/oauth-flow.js';
|
|
13
10
|
import { ConvexHttpClient } from 'convex/browser';
|
|
14
11
|
import { getConvexUrl } from '../config.js';
|
|
15
12
|
import { api } from '../convex.js';
|
|
16
13
|
import { brand } from '../utils/brand.js';
|
|
17
|
-
import {
|
|
14
|
+
import { selectProject, getOrCreateProjectSecret, promptExpiryDays } from '../utils/project-selector.js';
|
|
15
|
+
import { writeProjectSettings, findProjectRoot } from '../utils/project-settings.js';
|
|
18
16
|
export async function initCommand(options = {}) {
|
|
19
17
|
try {
|
|
20
18
|
console.log(`Initializing ${brand} project...\n`);
|
|
21
19
|
const cwd = process.cwd();
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
if (
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
if (!shouldReinit) {
|
|
29
|
-
console.log('Aborted.');
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
20
|
+
// INIT-2: Check if .requirements/ folder already exists
|
|
21
|
+
const existingProject = findProjectRoot(cwd);
|
|
22
|
+
if (existingProject === cwd) {
|
|
23
|
+
console.log('This project is already initialized.');
|
|
24
|
+
console.log('Use "dotrequirements link" to connect or reconnect to cloud.\n');
|
|
25
|
+
return;
|
|
33
26
|
}
|
|
34
|
-
//
|
|
35
|
-
const
|
|
36
|
-
{ title: '
|
|
37
|
-
{ title: '
|
|
27
|
+
// INIT-1: Ask user whether to authenticate
|
|
28
|
+
const authChoice = await promptChoice('Do you want to authenticate before initializing a project? (Recommended)', [
|
|
29
|
+
{ title: 'Yes', value: 'yes' },
|
|
30
|
+
{ title: 'No', value: 'no' },
|
|
38
31
|
]);
|
|
39
|
-
if (!
|
|
40
|
-
|
|
32
|
+
if (!authChoice) {
|
|
33
|
+
// INIT-1.3: User cancelled
|
|
34
|
+
console.log('Initialization cancelled.');
|
|
41
35
|
return;
|
|
42
36
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
await
|
|
37
|
+
if (authChoice === 'no') {
|
|
38
|
+
// INIT-3: Local-only workflow
|
|
39
|
+
await localOnlyFlow(cwd);
|
|
46
40
|
}
|
|
47
41
|
else {
|
|
48
|
-
|
|
42
|
+
// INIT-17+: Authenticated workflow
|
|
43
|
+
await authenticatedFlow(cwd, options);
|
|
49
44
|
}
|
|
50
45
|
console.log('\nDocumentation: https://docs.dotrequirements.io/getting-started');
|
|
51
46
|
console.log('By using this service, you agree to our Terms: https://app.dotrequirements.io/terms');
|
|
52
47
|
}
|
|
53
48
|
catch (error) {
|
|
54
49
|
console.error(`\n✗ Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
50
|
+
// INIT-11: Network error guidance
|
|
55
51
|
if (error instanceof Error && error.message.includes('fetch')) {
|
|
56
52
|
console.error('\nCheck your internet connection and try again.');
|
|
57
53
|
console.error('If the problem persists, visit https://status.convex.dev');
|
|
@@ -60,18 +56,86 @@ export async function initCommand(options = {}) {
|
|
|
60
56
|
}
|
|
61
57
|
}
|
|
62
58
|
/**
|
|
63
|
-
* INIT-
|
|
59
|
+
* INIT-3: Local-only workflow - creates folder structure without credentials
|
|
60
|
+
*/
|
|
61
|
+
async function localOnlyFlow(cwd) {
|
|
62
|
+
console.log('\nInitializing local project...');
|
|
63
|
+
// INIT-3.0: Create .requirements/ directory
|
|
64
|
+
const requirementsDir = path.join(cwd, '.requirements');
|
|
65
|
+
if (!fs.existsSync(requirementsDir)) {
|
|
66
|
+
fs.mkdirSync(requirementsDir, { recursive: true });
|
|
67
|
+
console.log('✓ Created .requirements/ directory');
|
|
68
|
+
}
|
|
69
|
+
// Create README and example files
|
|
70
|
+
const readmePath = path.join(requirementsDir, 'README.md');
|
|
71
|
+
fs.writeFileSync(readmePath, generateRequirementsReadme());
|
|
72
|
+
console.log('✓ Created .requirements/README.md');
|
|
73
|
+
const examplePath = path.join(requirementsDir, 'example.requirements.md');
|
|
74
|
+
if (!fs.existsSync(examplePath)) {
|
|
75
|
+
fs.writeFileSync(examplePath, generateExampleRequirements('local'));
|
|
76
|
+
console.log('✓ Created .requirements/example.requirements.md');
|
|
77
|
+
}
|
|
78
|
+
// INIT-5.2: Update .gitignore
|
|
79
|
+
ensureGitignore(cwd);
|
|
80
|
+
console.log('✓ Updated .gitignore\n');
|
|
81
|
+
// INIT-3.1: No credentials written (local-only)
|
|
82
|
+
// INIT-3.2, INIT-3.3: Show guidance
|
|
83
|
+
console.log('Next steps:');
|
|
84
|
+
console.log(' 1. Create requirements in .requirements/ directory');
|
|
85
|
+
console.log(' 2. Add the test harness to your test setup (see README)');
|
|
86
|
+
console.log(' 3. Run "dotrequirements link" to connect to cloud features');
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* INIT-17, INIT-18, INIT-7, INIT-4, INIT-6: Authenticated workflow
|
|
90
|
+
*/
|
|
91
|
+
async function authenticatedFlow(cwd, options) {
|
|
92
|
+
// INIT-17: Inline OAuth (no persistent tokens)
|
|
93
|
+
let oauthResult;
|
|
94
|
+
try {
|
|
95
|
+
console.log();
|
|
96
|
+
oauthResult = await executeOAuthFlow();
|
|
97
|
+
console.log();
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
// INIT-17.3: OAuth failed
|
|
101
|
+
console.error('\nAuthentication failed:', error instanceof Error ? error.message : String(error));
|
|
102
|
+
console.log('Please try again later.\n');
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
// Set up authenticated client using the session token
|
|
106
|
+
const client = new ConvexHttpClient(getConvexUrl());
|
|
107
|
+
client.setAuth(oauthResult.accessToken);
|
|
108
|
+
// INIT-18: Select team
|
|
109
|
+
const selectedTeam = await selectTeam(client);
|
|
110
|
+
if (selectedTeam === undefined) {
|
|
111
|
+
// User cancelled
|
|
112
|
+
console.log('Initialization cancelled.');
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
// INIT-7: Choose between create and connect
|
|
116
|
+
const projectChoice = await chooseProjectAction(client, selectedTeam.teamId);
|
|
117
|
+
if (!projectChoice) {
|
|
118
|
+
console.log('Initialization cancelled.');
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (projectChoice === 'create') {
|
|
122
|
+
await createNewProjectFlow(cwd, client, selectedTeam, options);
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
await connectExistingProjectFlow(cwd, client, selectedTeam);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* INIT-18: Prompt user to select a team after OAuth
|
|
64
130
|
* Returns:
|
|
65
|
-
* - teamId
|
|
66
|
-
* - null if no teams exist (auto-create flow)
|
|
131
|
+
* - { teamId, tier } if user selected a team
|
|
67
132
|
* - undefined if user cancelled
|
|
68
133
|
*/
|
|
69
134
|
async function selectTeam(client) {
|
|
70
135
|
console.log('Fetching your teams...\n');
|
|
71
|
-
//
|
|
72
|
-
// This handles new users who haven't joined via invite
|
|
136
|
+
// INIT-18.0: Ensure user has a default team if they don't have any
|
|
73
137
|
await client.mutation(api.teams.mutations.ensureDefaultTeam, {});
|
|
74
|
-
//
|
|
138
|
+
// Query all teams for the authenticated user
|
|
75
139
|
const teams = await client.query(api.teams.queries.listForUserWithUsage);
|
|
76
140
|
if (teams.length === 0) {
|
|
77
141
|
console.log('\nYou are not a member of any teams.');
|
|
@@ -79,20 +143,21 @@ async function selectTeam(client) {
|
|
|
79
143
|
console.log('After creating your team, run "dotrequirements init" again.\n');
|
|
80
144
|
process.exit(0);
|
|
81
145
|
}
|
|
82
|
-
// INIT-
|
|
146
|
+
// INIT-18.1, INIT-18.2, INIT-18.3: Build team selection options with usage display
|
|
83
147
|
const teamChoices = teams.map((team) => {
|
|
84
148
|
const limitDisplay = team.projectLimit === -1
|
|
85
149
|
? 'unlimited'
|
|
86
150
|
: `${team.projectCount}/${team.projectLimit}`;
|
|
87
|
-
// INIT-
|
|
151
|
+
// INIT-18.3: Mark teams at capacity
|
|
88
152
|
const capacityMarker = team.isAtCapacity ? ' [at capacity]' : '';
|
|
89
153
|
return {
|
|
90
154
|
title: `${team.name} (${team.tierName} - ${limitDisplay} projects)${capacityMarker}`,
|
|
91
155
|
value: team.teamId,
|
|
92
|
-
disabled: team.isAtCapacity,
|
|
156
|
+
disabled: team.isAtCapacity,
|
|
93
157
|
};
|
|
94
158
|
});
|
|
95
|
-
// INIT-
|
|
159
|
+
// INIT-18.4: Add "Create new team" option if user can create one
|
|
160
|
+
// (Check if user is already admin of a free team - simplified to always show for now)
|
|
96
161
|
teamChoices.push({
|
|
97
162
|
title: 'Create new team',
|
|
98
163
|
value: 'CREATE_NEW_TEAM',
|
|
@@ -100,14 +165,12 @@ async function selectTeam(client) {
|
|
|
100
165
|
});
|
|
101
166
|
const selectedTeamId = await promptChoice('Select a team for this project:', teamChoices);
|
|
102
167
|
if (!selectedTeamId) {
|
|
103
|
-
// User cancelled
|
|
104
168
|
return undefined;
|
|
105
169
|
}
|
|
106
|
-
// INIT-14: Handle selection of at-capacity team (
|
|
170
|
+
// INIT-14: Handle selection of at-capacity team (defensive check)
|
|
107
171
|
const selectedTeam = teams.find((t) => t.teamId === selectedTeamId);
|
|
108
172
|
if (selectedTeam && selectedTeam.isAtCapacity) {
|
|
109
173
|
console.error('\n✗ This team is at its project limit. Please select a different team or create a new one.\n');
|
|
110
|
-
// Retry selection
|
|
111
174
|
return await selectTeam(client);
|
|
112
175
|
}
|
|
113
176
|
// INIT-15: Handle "Create new team" selection
|
|
@@ -116,122 +179,78 @@ async function selectTeam(client) {
|
|
|
116
179
|
console.log('After creating your team, run "dotrequirements init" again.\n');
|
|
117
180
|
process.exit(0);
|
|
118
181
|
}
|
|
119
|
-
|
|
120
|
-
|
|
182
|
+
return {
|
|
183
|
+
teamId: selectedTeamId,
|
|
184
|
+
tier: selectedTeam.tier,
|
|
185
|
+
};
|
|
121
186
|
}
|
|
122
187
|
/**
|
|
123
|
-
*
|
|
188
|
+
* INIT-7: Choose between creating new project or connecting to existing
|
|
124
189
|
*/
|
|
125
|
-
async function
|
|
126
|
-
//
|
|
190
|
+
async function chooseProjectAction(client, teamId) {
|
|
191
|
+
// INIT-7.3: Check if team has existing projects the user can access
|
|
192
|
+
const projectsWithSecrets = await client.query(api.projectSecrets.queries.listOwnSecretsForTeam, { target: { type: 'team', id: teamId } });
|
|
193
|
+
if (projectsWithSecrets.length === 0) {
|
|
194
|
+
// No existing projects, only offer create
|
|
195
|
+
const confirmed = await promptConfirm('This team has no projects. Create a new one?', true);
|
|
196
|
+
return confirmed ? 'create' : undefined;
|
|
197
|
+
}
|
|
198
|
+
// INIT-7.0: Offer both options
|
|
199
|
+
return await promptChoice('What would you like to do?', [
|
|
200
|
+
{ title: 'Create a new project', value: 'create' },
|
|
201
|
+
{ title: 'Connect to an existing project', value: 'connect' },
|
|
202
|
+
]);
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* INIT-4: Create a new project in DotRequirements Cloud
|
|
206
|
+
*/
|
|
207
|
+
async function createNewProjectFlow(cwd, client, team, options) {
|
|
208
|
+
// INIT-4.0: Detect/prompt for project name
|
|
127
209
|
const projectName = await detectProjectName({ name: options.name });
|
|
128
210
|
console.log(`Project name: ${projectName}\n`);
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
console.log('Creating cloud project...');
|
|
149
|
-
const result = await client.mutation(api.projects.mutations.create, {
|
|
150
|
-
name: projectName,
|
|
151
|
-
teamId: selectedTeamId ?? undefined, // null becomes undefined for auto-create
|
|
152
|
-
});
|
|
153
|
-
// Use slug as the user-facing project identifier
|
|
154
|
-
projectId = result.slug;
|
|
155
|
-
secret = result.secret;
|
|
156
|
-
console.log(`✓ Cloud project created: ${projectId}`);
|
|
157
|
-
}
|
|
158
|
-
else {
|
|
159
|
-
// INIT-3: Unauthenticated user - create local-only project with placeholder credentials
|
|
160
|
-
console.log('Initializing local project...');
|
|
161
|
-
projectId = LOCAL_PROJECT_CREDENTIALS.projectId;
|
|
162
|
-
secret = LOCAL_PROJECT_CREDENTIALS.secret;
|
|
163
|
-
console.log(`✓ Project initialized locally`);
|
|
164
|
-
}
|
|
165
|
-
// 3. Write .env.local
|
|
166
|
-
const shouldContinue = await writeEnvFile(envPath, projectId, secret);
|
|
167
|
-
if (!shouldContinue) {
|
|
168
|
-
// User declined to continue - abort init
|
|
169
|
-
return;
|
|
170
|
-
}
|
|
171
|
-
// Reload env file so newly written credentials are available to subsequent commands
|
|
172
|
-
// Use force=true to overwrite any previously cached values
|
|
173
|
-
loadEnvFile(cwd, true);
|
|
174
|
-
// 4. Create .requirements/ directory
|
|
211
|
+
console.log('Creating cloud project...');
|
|
212
|
+
// INIT-4.1: Create project in cloud (no longer auto-creates secret)
|
|
213
|
+
const result = await client.mutation(api.projects.mutations.create, {
|
|
214
|
+
name: projectName,
|
|
215
|
+
teamId: team.teamId,
|
|
216
|
+
});
|
|
217
|
+
const projectId = result.slug;
|
|
218
|
+
console.log(`✓ Cloud project created: ${projectId}`);
|
|
219
|
+
// AUTHZ-3: Prompt for secret expiry (paid tier only)
|
|
220
|
+
const expiryDays = await promptExpiryDays(team.tier);
|
|
221
|
+
// Get secret for the newly created project
|
|
222
|
+
console.log('Getting project credentials...');
|
|
223
|
+
const secretResult = await getOrCreateProjectSecret(client, result.projectId, expiryDays);
|
|
224
|
+
const secret = secretResult.secret;
|
|
225
|
+
console.log('✓ Created project secret');
|
|
226
|
+
// INIT-4.2: Write credentials to project-settings.json
|
|
227
|
+
writeProjectSettings(cwd, { projectId, projectSecret: secret });
|
|
228
|
+
console.log('✓ Wrote credentials to .requirements/project-settings.json');
|
|
229
|
+
// INIT-5.0, INIT-5.1: Create directory structure (if not already created by writeProjectSettings)
|
|
175
230
|
await createRequirementsDirectory(cwd, projectId);
|
|
176
|
-
// 5. Update .gitignore
|
|
231
|
+
// INIT-5.2: Update .gitignore
|
|
177
232
|
ensureGitignore(cwd);
|
|
178
233
|
console.log('✓ Updated .gitignore\n');
|
|
179
|
-
//
|
|
234
|
+
// INIT-4.3: Show guidance
|
|
180
235
|
console.log('Next steps:');
|
|
181
236
|
console.log(' 1. Create requirements in .requirements/ directory');
|
|
182
237
|
console.log(' 2. Add the test harness to your test setup (see README)');
|
|
183
|
-
if (!isAuthenticated) {
|
|
184
|
-
// INIT-3.1: Local-only guidance
|
|
185
|
-
console.log(' 3. Run "dotrequirements login" for cloud features (push/pull, coverage, AI)');
|
|
186
|
-
}
|
|
187
238
|
}
|
|
188
239
|
/**
|
|
189
|
-
* Connect to existing project
|
|
240
|
+
* INIT-6, INIT-16: Connect to an existing project
|
|
190
241
|
*/
|
|
191
|
-
async function connectExistingProjectFlow(cwd,
|
|
192
|
-
// INIT-6.0
|
|
193
|
-
|
|
194
|
-
if (!isAuthenticated) {
|
|
195
|
-
const shouldLogin = await promptConfirm('You need to authenticate to connect to an existing project. Log in now?', true);
|
|
196
|
-
if (!shouldLogin) {
|
|
197
|
-
console.log('Aborted.');
|
|
198
|
-
return;
|
|
199
|
-
}
|
|
200
|
-
// INIT-6.1: Initiate OAuth login flow inline
|
|
201
|
-
try {
|
|
202
|
-
console.log();
|
|
203
|
-
await executeOAuthFlow();
|
|
204
|
-
console.log();
|
|
205
|
-
isAuthenticated = true;
|
|
206
|
-
}
|
|
207
|
-
catch (error) {
|
|
208
|
-
console.error('\nAuthentication failed:', error instanceof Error ? error.message : String(error));
|
|
209
|
-
console.log('Please try running "dotrequirements login" separately.\n');
|
|
210
|
-
return;
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
// Set up authenticated client
|
|
214
|
-
const tokens = await getTokens();
|
|
215
|
-
if (!tokens) {
|
|
216
|
-
throw new Error('Authentication tokens not found');
|
|
217
|
-
}
|
|
218
|
-
const client = new ConvexHttpClient(getConvexUrl());
|
|
219
|
-
client.setAuth(tokens.accessToken);
|
|
220
|
-
// INIT-6.2: Select team
|
|
221
|
-
const selectedTeamId = await selectTeamFromList(client);
|
|
222
|
-
if (!selectedTeamId) {
|
|
223
|
-
console.log('Aborted.');
|
|
224
|
-
return;
|
|
225
|
-
}
|
|
226
|
-
// INIT-6.3: Select project
|
|
227
|
-
const selectedProject = await selectProject(client, selectedTeamId);
|
|
242
|
+
async function connectExistingProjectFlow(cwd, client, team) {
|
|
243
|
+
// INIT-6.0, INIT-16.0: Select project from team
|
|
244
|
+
const selectedProject = await selectProject(client, team.teamId);
|
|
228
245
|
if (!selectedProject) {
|
|
229
|
-
console.log('
|
|
246
|
+
console.log('Initialization cancelled.');
|
|
230
247
|
return;
|
|
231
248
|
}
|
|
232
|
-
//
|
|
249
|
+
// AUTHZ-3: Prompt for secret expiry (paid tier only, if creating new secret)
|
|
250
|
+
const expiryDays = await promptExpiryDays(team.tier);
|
|
251
|
+
// INIT-16.1: Get or create secret for this project
|
|
233
252
|
console.log('\nGetting project credentials...');
|
|
234
|
-
const secretResult = await getOrCreateProjectSecret(client, selectedProject.projectId);
|
|
253
|
+
const secretResult = await getOrCreateProjectSecret(client, selectedProject.projectId, expiryDays);
|
|
235
254
|
const projectId = selectedProject.projectSlug || selectedProject.projectId;
|
|
236
255
|
const secret = secretResult.secret;
|
|
237
256
|
if (secretResult.created) {
|
|
@@ -240,118 +259,39 @@ async function connectExistingProjectFlow(cwd, envPath) {
|
|
|
240
259
|
else {
|
|
241
260
|
console.log('✓ Retrieved existing project secret');
|
|
242
261
|
}
|
|
243
|
-
// Write .
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
return;
|
|
248
|
-
}
|
|
249
|
-
// Reload env file so newly written credentials are available to pullCommand
|
|
250
|
-
// Use force=true to overwrite any previously cached values
|
|
251
|
-
loadEnvFile(cwd, true);
|
|
252
|
-
// Create .requirements/ directory
|
|
262
|
+
// INIT-6.1: Write credentials to project-settings.json
|
|
263
|
+
writeProjectSettings(cwd, { projectId, projectSecret: secret });
|
|
264
|
+
console.log('✓ Wrote credentials to .requirements/project-settings.json');
|
|
265
|
+
// Create directory structure
|
|
253
266
|
await createRequirementsDirectory(cwd, projectId);
|
|
254
267
|
// Update .gitignore
|
|
255
268
|
ensureGitignore(cwd);
|
|
256
269
|
console.log('✓ Updated .gitignore\n');
|
|
257
|
-
// Pull existing requirements from cloud
|
|
270
|
+
// INIT-6.2: Pull existing requirements from cloud
|
|
258
271
|
console.log('Pulling requirements from cloud...');
|
|
259
272
|
await pullCommand({ project: projectId });
|
|
260
273
|
console.log(`\n✓ Successfully connected to project: ${selectedProject.projectName}`);
|
|
261
274
|
console.log(` Project ID: ${projectId}`);
|
|
275
|
+
// INIT-6.3: Show guidance
|
|
262
276
|
console.log('\nNext steps:');
|
|
263
277
|
console.log(' 1. Review the requirements in .requirements/');
|
|
264
278
|
console.log(' 2. Add the test harness to your test setup (see README)');
|
|
265
279
|
}
|
|
266
280
|
/**
|
|
267
|
-
*
|
|
268
|
-
* Returns true if credentials were written or user chose to continue with existing
|
|
269
|
-
* Returns false if user aborted
|
|
270
|
-
*/
|
|
271
|
-
async function writeEnvFile(envPath, projectId, secret) {
|
|
272
|
-
const newEnvLines = [
|
|
273
|
-
'# dot•requirements configuration',
|
|
274
|
-
`DOTREQUIREMENTS_PROJECT_ID=${projectId}`,
|
|
275
|
-
`DOTREQUIREMENTS_PROJECT_SECRET=${secret}`,
|
|
276
|
-
];
|
|
277
|
-
if (fs.existsSync(envPath)) {
|
|
278
|
-
// Read existing content
|
|
279
|
-
const existingContent = fs.readFileSync(envPath, 'utf-8');
|
|
280
|
-
// Check if dotrequirements variables already exist
|
|
281
|
-
const hasProjectId = existingContent.includes('DOTREQUIREMENTS_PROJECT_ID');
|
|
282
|
-
const hasProjectSecret = existingContent.includes('DOTREQUIREMENTS_PROJECT_SECRET');
|
|
283
|
-
if (hasProjectId || hasProjectSecret) {
|
|
284
|
-
// INIT-2.0: Ask if user wants to update existing values
|
|
285
|
-
const shouldUpdate = await promptConfirm(`.env.local already contains ${brand} configuration. Update it?`, false);
|
|
286
|
-
if (!shouldUpdate) {
|
|
287
|
-
// INIT-2.1: Ask if user wants to continue with existing credentials
|
|
288
|
-
const shouldContinue = await promptConfirm('Continue with existing credentials?', true);
|
|
289
|
-
if (!shouldContinue) {
|
|
290
|
-
// INIT-2.3: User declined to continue - abort
|
|
291
|
-
console.log('Aborted.');
|
|
292
|
-
return false;
|
|
293
|
-
}
|
|
294
|
-
// INIT-2.2: User wants to continue with existing credentials
|
|
295
|
-
console.log('Continuing with existing credentials...');
|
|
296
|
-
return true;
|
|
297
|
-
}
|
|
298
|
-
// Remove old dotrequirements lines
|
|
299
|
-
const lines = existingContent.split('\n');
|
|
300
|
-
const filteredLines = lines.filter(line => !line.includes('DOTREQUIREMENTS_PROJECT_ID') &&
|
|
301
|
-
!line.includes('DOTREQUIREMENTS_PROJECT_SECRET') &&
|
|
302
|
-
!(line.trim() === '# dotrequirements configuration') &&
|
|
303
|
-
!(line.trim() === '# dot•requirements configuration'));
|
|
304
|
-
// Append new configuration
|
|
305
|
-
const updatedContent = filteredLines.join('\n').trimEnd() + '\n\n' + newEnvLines.join('\n') + '\n';
|
|
306
|
-
fs.writeFileSync(envPath, updatedContent);
|
|
307
|
-
console.log(`✓ Updated ${brand} configuration in .env.local`);
|
|
308
|
-
return true;
|
|
309
|
-
}
|
|
310
|
-
else {
|
|
311
|
-
// File exists but doesn't have dotrequirements config - just append
|
|
312
|
-
const shouldAppend = await promptConfirm(`.env.local already exists. Add ${brand} configuration to it?`, true);
|
|
313
|
-
if (!shouldAppend) {
|
|
314
|
-
console.log('\nSkipped writing to .env.local. Please add these values manually:');
|
|
315
|
-
console.log(` DOTREQUIREMENTS_PROJECT_ID=${projectId}`);
|
|
316
|
-
console.log(` DOTREQUIREMENTS_PROJECT_SECRET=${secret}\n`);
|
|
317
|
-
return true; // Continue with init even though we didn't write the file
|
|
318
|
-
}
|
|
319
|
-
// Append to existing file
|
|
320
|
-
const appendContent = '\n' + newEnvLines.join('\n') + '\n';
|
|
321
|
-
fs.appendFileSync(envPath, appendContent);
|
|
322
|
-
console.log(`✓ Added ${brand} configuration to .env.local`);
|
|
323
|
-
return true;
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
else {
|
|
327
|
-
// File doesn't exist - create it
|
|
328
|
-
const envContent = newEnvLines.join('\n') + '\n';
|
|
329
|
-
fs.writeFileSync(envPath, envContent);
|
|
330
|
-
console.log('✓ Wrote configuration to .env.local');
|
|
331
|
-
return true;
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
/**
|
|
335
|
-
* Create .requirements/ directory with confirmation if it already exists
|
|
281
|
+
* INIT-5: Create .requirements/ directory with README and example
|
|
336
282
|
*/
|
|
337
283
|
async function createRequirementsDirectory(cwd, projectId) {
|
|
338
284
|
const requirementsDir = path.join(cwd, '.requirements');
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
if (!shouldOverwrite) {
|
|
342
|
-
console.log('Skipped creating .requirements/ directory');
|
|
343
|
-
return;
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
else {
|
|
285
|
+
// Directory may already exist from writeProjectSettings
|
|
286
|
+
if (!fs.existsSync(requirementsDir)) {
|
|
347
287
|
fs.mkdirSync(requirementsDir, { recursive: true });
|
|
348
288
|
console.log('✓ Created .requirements/ directory');
|
|
349
289
|
}
|
|
350
|
-
// Always create/update README
|
|
290
|
+
// INIT-5.0: Always create/update README
|
|
351
291
|
const readmePath = path.join(requirementsDir, 'README.md');
|
|
352
292
|
fs.writeFileSync(readmePath, generateRequirementsReadme());
|
|
353
293
|
console.log('✓ Created .requirements/README.md');
|
|
354
|
-
// Create example.requirements.md
|
|
294
|
+
// INIT-5.1: Create example.requirements.md
|
|
355
295
|
const examplePath = path.join(requirementsDir, 'example.requirements.md');
|
|
356
296
|
if (!fs.existsSync(examplePath)) {
|
|
357
297
|
fs.writeFileSync(examplePath, generateExampleRequirements(projectId));
|
package/dist/commands/link.d.ts
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Link command - connect local
|
|
2
|
+
* Link command - connect or reconnect local project to cloud.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* 1
|
|
6
|
-
* 2
|
|
7
|
-
* 3
|
|
8
|
-
* 4
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* 8. Optionally pull requirements
|
|
4
|
+
* Requirements:
|
|
5
|
+
* - LINK-1: Requires existing .requirements/ folder
|
|
6
|
+
* - LINK-2: Offers refresh when credentials exist
|
|
7
|
+
* - LINK-3: Uses inline OAuth (no persistent tokens)
|
|
8
|
+
* - LINK-4, LINK-5: Team and project selection
|
|
9
|
+
* - LINK-6: Saves credentials to project-settings.json
|
|
10
|
+
* - LINK-7: Optionally pulls requirements
|
|
11
|
+
* - LINK-8: Handles network errors gracefully
|
|
13
12
|
*/
|
|
14
13
|
export declare function linkCommand(): Promise<void>;
|
|
15
14
|
//# sourceMappingURL=link.d.ts.map
|