@paths.design/caws-cli 3.3.0 → 3.4.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/dist/commands/diagnose.d.ts.map +1 -1
- package/dist/commands/diagnose.js +39 -4
- package/dist/commands/evaluate.d.ts +8 -0
- package/dist/commands/evaluate.d.ts.map +1 -0
- package/dist/commands/evaluate.js +288 -0
- package/dist/commands/iterate.d.ts +8 -0
- package/dist/commands/iterate.d.ts.map +1 -0
- package/dist/commands/iterate.js +341 -0
- package/dist/commands/quality-monitor.d.ts +17 -0
- package/dist/commands/quality-monitor.d.ts.map +1 -0
- package/dist/commands/quality-monitor.js +265 -0
- package/dist/commands/status.d.ts +6 -1
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +120 -20
- package/dist/commands/troubleshoot.d.ts +8 -0
- package/dist/commands/troubleshoot.d.ts.map +1 -0
- package/dist/commands/troubleshoot.js +104 -0
- package/dist/commands/waivers.d.ts +8 -0
- package/dist/commands/waivers.d.ts.map +1 -0
- package/dist/commands/waivers.js +293 -0
- package/dist/commands/workflow.d.ts +85 -0
- package/dist/commands/workflow.d.ts.map +1 -0
- package/dist/commands/workflow.js +243 -0
- package/dist/error-handler.d.ts +91 -2
- package/dist/error-handler.d.ts.map +1 -1
- package/dist/error-handler.js +362 -16
- package/dist/index.js +95 -0
- package/dist/scaffold/index.d.ts.map +1 -1
- package/dist/scaffold/index.js +13 -0
- package/dist/utils/typescript-detector.d.ts +31 -0
- package/dist/utils/typescript-detector.d.ts.map +1 -1
- package/dist/utils/typescript-detector.js +245 -7
- package/package.json +5 -4
- package/templates/OIDC_SETUP.md +300 -0
- package/templates/agents.md +912 -686
- package/templates/apps/tools/caws/gates.ts +34 -0
- package/templates/apps/tools/caws/shared/gate-checker.ts +265 -13
- package/templates/apps/tools/caws/templates/working-spec.template.yml +14 -0
|
@@ -100,24 +100,257 @@ function detectTestFramework(projectDir = process.cwd(), packageJson = null) {
|
|
|
100
100
|
};
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
+
/**
|
|
104
|
+
* Get workspace directories from package.json
|
|
105
|
+
* @param {string} projectDir - Project directory path
|
|
106
|
+
* @returns {string[]} Array of workspace directories
|
|
107
|
+
*/
|
|
108
|
+
/**
|
|
109
|
+
* Get workspace directories from npm/yarn package.json workspaces
|
|
110
|
+
* @param {string} projectDir - Project directory path
|
|
111
|
+
* @returns {string[]} Array of workspace directories
|
|
112
|
+
*/
|
|
113
|
+
function getNpmWorkspaces(projectDir) {
|
|
114
|
+
const packageJsonPath = path.join(projectDir, 'package.json');
|
|
115
|
+
|
|
116
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
117
|
+
return [];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
try {
|
|
121
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
122
|
+
const workspaces = packageJson.workspaces || [];
|
|
123
|
+
|
|
124
|
+
// Convert glob patterns to actual directories (simple implementation)
|
|
125
|
+
const workspaceDirs = [];
|
|
126
|
+
for (const ws of workspaces) {
|
|
127
|
+
// Handle simple patterns like "packages/*" or "iterations/*"
|
|
128
|
+
if (ws.includes('*')) {
|
|
129
|
+
const baseDir = ws.split('*')[0];
|
|
130
|
+
const fullBaseDir = path.join(projectDir, baseDir);
|
|
131
|
+
|
|
132
|
+
if (fs.existsSync(fullBaseDir)) {
|
|
133
|
+
const entries = fs.readdirSync(fullBaseDir, { withFileTypes: true });
|
|
134
|
+
for (const entry of entries) {
|
|
135
|
+
if (entry.isDirectory()) {
|
|
136
|
+
const wsPath = path.join(fullBaseDir, entry.name);
|
|
137
|
+
if (fs.existsSync(path.join(wsPath, 'package.json'))) {
|
|
138
|
+
workspaceDirs.push(wsPath);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
} else {
|
|
144
|
+
// Direct path
|
|
145
|
+
const wsPath = path.join(projectDir, ws);
|
|
146
|
+
if (fs.existsSync(path.join(wsPath, 'package.json'))) {
|
|
147
|
+
workspaceDirs.push(wsPath);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return workspaceDirs;
|
|
153
|
+
} catch (error) {
|
|
154
|
+
return [];
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Get workspace directories from pnpm-workspace.yaml
|
|
160
|
+
* @param {string} projectDir - Project directory path
|
|
161
|
+
* @returns {string[]} Array of workspace directories
|
|
162
|
+
*/
|
|
163
|
+
function getPnpmWorkspaces(projectDir) {
|
|
164
|
+
const pnpmFile = path.join(projectDir, 'pnpm-workspace.yaml');
|
|
165
|
+
|
|
166
|
+
if (!fs.existsSync(pnpmFile)) {
|
|
167
|
+
return [];
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
try {
|
|
171
|
+
const yaml = require('js-yaml');
|
|
172
|
+
const config = yaml.load(fs.readFileSync(pnpmFile, 'utf8'));
|
|
173
|
+
const workspacePatterns = config.packages || [];
|
|
174
|
+
|
|
175
|
+
// Convert glob patterns to actual directories
|
|
176
|
+
const workspaceDirs = [];
|
|
177
|
+
for (const pattern of workspacePatterns) {
|
|
178
|
+
if (pattern.includes('*')) {
|
|
179
|
+
const baseDir = pattern.split('*')[0];
|
|
180
|
+
const fullBaseDir = path.join(projectDir, baseDir);
|
|
181
|
+
|
|
182
|
+
if (fs.existsSync(fullBaseDir)) {
|
|
183
|
+
const entries = fs.readdirSync(fullBaseDir, { withFileTypes: true });
|
|
184
|
+
for (const entry of entries) {
|
|
185
|
+
if (entry.isDirectory()) {
|
|
186
|
+
const wsPath = path.join(fullBaseDir, entry.name);
|
|
187
|
+
if (fs.existsSync(path.join(wsPath, 'package.json'))) {
|
|
188
|
+
workspaceDirs.push(wsPath);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
} else {
|
|
194
|
+
// Direct path
|
|
195
|
+
const wsPath = path.join(projectDir, pattern);
|
|
196
|
+
if (fs.existsSync(path.join(wsPath, 'package.json'))) {
|
|
197
|
+
workspaceDirs.push(wsPath);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return workspaceDirs;
|
|
203
|
+
} catch (error) {
|
|
204
|
+
return [];
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Get workspace directories from lerna.json
|
|
210
|
+
* @param {string} projectDir - Project directory path
|
|
211
|
+
* @returns {string[]} Array of workspace directories
|
|
212
|
+
*/
|
|
213
|
+
function getLernaWorkspaces(projectDir) {
|
|
214
|
+
const lernaFile = path.join(projectDir, 'lerna.json');
|
|
215
|
+
|
|
216
|
+
if (!fs.existsSync(lernaFile)) {
|
|
217
|
+
return [];
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
try {
|
|
221
|
+
const config = JSON.parse(fs.readFileSync(lernaFile, 'utf8'));
|
|
222
|
+
const workspacePatterns = config.packages || ['packages/*'];
|
|
223
|
+
|
|
224
|
+
// Convert glob patterns to actual directories
|
|
225
|
+
const workspaceDirs = [];
|
|
226
|
+
for (const pattern of workspacePatterns) {
|
|
227
|
+
if (pattern.includes('*')) {
|
|
228
|
+
const baseDir = pattern.split('*')[0];
|
|
229
|
+
const fullBaseDir = path.join(projectDir, baseDir);
|
|
230
|
+
|
|
231
|
+
if (fs.existsSync(fullBaseDir)) {
|
|
232
|
+
const entries = fs.readdirSync(fullBaseDir, { withFileTypes: true });
|
|
233
|
+
for (const entry of entries) {
|
|
234
|
+
if (entry.isDirectory()) {
|
|
235
|
+
const wsPath = path.join(fullBaseDir, entry.name);
|
|
236
|
+
if (fs.existsSync(path.join(wsPath, 'package.json'))) {
|
|
237
|
+
workspaceDirs.push(wsPath);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
} else {
|
|
243
|
+
// Direct path
|
|
244
|
+
const wsPath = path.join(projectDir, pattern);
|
|
245
|
+
if (fs.existsSync(path.join(wsPath, 'package.json'))) {
|
|
246
|
+
workspaceDirs.push(wsPath);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return workspaceDirs;
|
|
252
|
+
} catch (error) {
|
|
253
|
+
return [];
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Check if a dependency exists in hoisted node_modules
|
|
259
|
+
* @param {string} depName - Dependency name to check
|
|
260
|
+
* @param {string} projectDir - Project directory path
|
|
261
|
+
* @returns {boolean} True if dependency found in hoisted node_modules
|
|
262
|
+
*/
|
|
263
|
+
function checkHoistedDependency(depName, projectDir) {
|
|
264
|
+
const hoistedPath = path.join(projectDir, 'node_modules', depName, 'package.json');
|
|
265
|
+
return fs.existsSync(hoistedPath);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function getWorkspaceDirectories(projectDir = process.cwd()) {
|
|
269
|
+
const workspaceDirs = [
|
|
270
|
+
...getNpmWorkspaces(projectDir),
|
|
271
|
+
...getPnpmWorkspaces(projectDir),
|
|
272
|
+
...getLernaWorkspaces(projectDir),
|
|
273
|
+
];
|
|
274
|
+
|
|
275
|
+
// Remove duplicates
|
|
276
|
+
return [...new Set(workspaceDirs)];
|
|
277
|
+
}
|
|
278
|
+
|
|
103
279
|
/**
|
|
104
280
|
* Check if TypeScript project needs test configuration
|
|
105
281
|
* @param {string} projectDir - Project directory path
|
|
106
282
|
* @returns {Object} Configuration status
|
|
107
283
|
*/
|
|
108
284
|
function checkTypeScriptTestConfig(projectDir = process.cwd()) {
|
|
109
|
-
|
|
110
|
-
const
|
|
285
|
+
// First check root directory
|
|
286
|
+
const rootTsDetection = detectTypeScript(projectDir);
|
|
287
|
+
const rootTestDetection = detectTestFramework(projectDir, rootTsDetection.packageJson);
|
|
288
|
+
|
|
289
|
+
// Get workspace directories and check them too
|
|
290
|
+
const workspaceDirs = getWorkspaceDirectories(projectDir);
|
|
291
|
+
const workspaceResults = [];
|
|
292
|
+
|
|
293
|
+
for (const wsDir of workspaceDirs) {
|
|
294
|
+
const wsTsDetection = detectTypeScript(wsDir);
|
|
295
|
+
const wsTestDetection = detectTestFramework(wsDir, wsTsDetection.packageJson);
|
|
296
|
+
|
|
297
|
+
workspaceResults.push({
|
|
298
|
+
directory: path.relative(projectDir, wsDir),
|
|
299
|
+
tsDetection: wsTsDetection,
|
|
300
|
+
testDetection: wsTestDetection,
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Determine overall status - prefer workspace results if they exist
|
|
305
|
+
let primaryTsDetection = rootTsDetection;
|
|
306
|
+
let primaryTestDetection = rootTestDetection;
|
|
307
|
+
let primaryWorkspace = null;
|
|
308
|
+
|
|
309
|
+
// Find the workspace with the most complete TypeScript setup
|
|
310
|
+
for (const wsResult of workspaceResults) {
|
|
311
|
+
if (wsResult.tsDetection.isTypeScript) {
|
|
312
|
+
if (
|
|
313
|
+
!primaryTsDetection.isTypeScript ||
|
|
314
|
+
(wsResult.tsDetection.hasTsConfig && !primaryTsDetection.hasTsConfig) ||
|
|
315
|
+
(wsResult.testDetection.framework !== 'none' && primaryTestDetection.framework === 'none')
|
|
316
|
+
) {
|
|
317
|
+
primaryTsDetection = wsResult.tsDetection;
|
|
318
|
+
primaryTestDetection = wsResult.testDetection;
|
|
319
|
+
primaryWorkspace = wsResult.directory;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Check for ts-jest in workspaces and hoisted node_modules
|
|
325
|
+
let hasTsJestAnywhere = primaryTestDetection.hasTsJest;
|
|
326
|
+
|
|
327
|
+
// If not found in primary workspace, check all workspaces
|
|
328
|
+
if (!hasTsJestAnywhere) {
|
|
329
|
+
hasTsJestAnywhere = workspaceResults.some((ws) => ws.testDetection.hasTsJest);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// If still not found, check hoisted node_modules
|
|
333
|
+
if (!hasTsJestAnywhere) {
|
|
334
|
+
hasTsJestAnywhere = checkHoistedDependency('ts-jest', projectDir);
|
|
335
|
+
}
|
|
111
336
|
|
|
112
337
|
const needsConfig =
|
|
113
|
-
|
|
338
|
+
primaryTsDetection.isTypeScript &&
|
|
339
|
+
primaryTestDetection.framework === 'jest' &&
|
|
340
|
+
!hasTsJestAnywhere;
|
|
114
341
|
|
|
115
342
|
return {
|
|
116
|
-
...
|
|
117
|
-
testFramework:
|
|
118
|
-
needsJestConfig:
|
|
343
|
+
...primaryTsDetection,
|
|
344
|
+
testFramework: primaryTestDetection,
|
|
345
|
+
needsJestConfig: primaryTsDetection.isTypeScript && !primaryTestDetection.isConfigured,
|
|
119
346
|
needsTsJest: needsConfig,
|
|
120
|
-
recommendations: generateRecommendations(
|
|
347
|
+
recommendations: generateRecommendations(primaryTsDetection, primaryTestDetection),
|
|
348
|
+
workspaceInfo: {
|
|
349
|
+
hasWorkspaces: workspaceDirs.length > 0,
|
|
350
|
+
workspaceCount: workspaceDirs.length,
|
|
351
|
+
primaryWorkspace,
|
|
352
|
+
allWorkspaces: workspaceResults.map((ws) => ws.directory),
|
|
353
|
+
},
|
|
121
354
|
};
|
|
122
355
|
}
|
|
123
356
|
|
|
@@ -179,6 +412,11 @@ function displayTypeScriptDetection(detection) {
|
|
|
179
412
|
module.exports = {
|
|
180
413
|
detectTypeScript,
|
|
181
414
|
detectTestFramework,
|
|
415
|
+
getWorkspaceDirectories,
|
|
416
|
+
getNpmWorkspaces,
|
|
417
|
+
getPnpmWorkspaces,
|
|
418
|
+
getLernaWorkspaces,
|
|
419
|
+
checkHoistedDependency,
|
|
182
420
|
checkTypeScriptTestConfig,
|
|
183
421
|
generateRecommendations,
|
|
184
422
|
displayTypeScriptDetection,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@paths.design/caws-cli",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.4.0",
|
|
4
4
|
"description": "CAWS CLI - Coding Agent Workflow System command line tools",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -52,9 +52,11 @@
|
|
|
52
52
|
},
|
|
53
53
|
"dependencies": {
|
|
54
54
|
"ajv": "^8.12.0",
|
|
55
|
+
"chalk": "4.1.2",
|
|
55
56
|
"commander": "^11.0.0",
|
|
56
57
|
"fs-extra": "^11.0.0",
|
|
57
|
-
"inquirer": "8.2.7"
|
|
58
|
+
"inquirer": "8.2.7",
|
|
59
|
+
"js-yaml": "4.1.0"
|
|
58
60
|
},
|
|
59
61
|
"devDependencies": {
|
|
60
62
|
"@eslint/js": "^9.0.0",
|
|
@@ -66,10 +68,9 @@
|
|
|
66
68
|
"@types/inquirer": "^8.2.6",
|
|
67
69
|
"@types/js-yaml": "^4.0.0",
|
|
68
70
|
"@types/node": "^20.0.0",
|
|
69
|
-
"
|
|
71
|
+
"esbuild": "0.25.10",
|
|
70
72
|
"eslint": "^9.0.0",
|
|
71
73
|
"jest": "30.1.3",
|
|
72
|
-
"js-yaml": "4.1.0",
|
|
73
74
|
"lint-staged": "15.5.2",
|
|
74
75
|
"prettier": "^3.0.0",
|
|
75
76
|
"semantic-release": "25.0.0-beta.6",
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
# OIDC Trusted Publisher Setup
|
|
2
|
+
|
|
3
|
+
This guide helps you set up OIDC (OpenID Connect) trusted publisher for automated publishing to package registries.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
OIDC trusted publisher allows you to publish packages without storing long-lived tokens or passwords in your CI/CD environment. Instead, it uses short-lived tokens issued by the OIDC provider.
|
|
8
|
+
|
|
9
|
+
## Supported Registries
|
|
10
|
+
|
|
11
|
+
- **npm**: npm Registry
|
|
12
|
+
- **PyPI**: Python Package Index
|
|
13
|
+
- **Maven Central**: Java packages
|
|
14
|
+
- **NuGet**: .NET packages
|
|
15
|
+
|
|
16
|
+
## Setup Process
|
|
17
|
+
|
|
18
|
+
### 1. Configure OIDC Provider
|
|
19
|
+
|
|
20
|
+
Most CI/CD platforms (GitHub Actions, GitLab CI, etc.) provide built-in OIDC support.
|
|
21
|
+
|
|
22
|
+
**GitHub Actions Example:**
|
|
23
|
+
|
|
24
|
+
```yaml
|
|
25
|
+
# .github/workflows/publish.yml
|
|
26
|
+
name: Publish Package
|
|
27
|
+
|
|
28
|
+
on:
|
|
29
|
+
release:
|
|
30
|
+
types: [published]
|
|
31
|
+
|
|
32
|
+
jobs:
|
|
33
|
+
publish:
|
|
34
|
+
runs-on: ubuntu-latest
|
|
35
|
+
permissions:
|
|
36
|
+
contents: read
|
|
37
|
+
id-token: write
|
|
38
|
+
steps:
|
|
39
|
+
- uses: actions/checkout@v4
|
|
40
|
+
- name: Setup Node.js
|
|
41
|
+
uses: actions/setup-node@v4
|
|
42
|
+
with:
|
|
43
|
+
node-version: '20'
|
|
44
|
+
registry-url: 'https://registry.npmjs.org'
|
|
45
|
+
- name: Install dependencies
|
|
46
|
+
run: npm ci
|
|
47
|
+
- name: Build package
|
|
48
|
+
run: npm run build
|
|
49
|
+
- name: Publish to npm
|
|
50
|
+
run: npm publish
|
|
51
|
+
env:
|
|
52
|
+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### 2. Registry Configuration
|
|
56
|
+
|
|
57
|
+
#### npm Registry
|
|
58
|
+
|
|
59
|
+
1. **Create OIDC Integration**:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
# Using npm CLI
|
|
63
|
+
npm profile enable-2fa auth-and-writes
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
2. **Configure Trusted Publisher**:
|
|
67
|
+
- Go to npmjs.com → Account Settings → Access Tokens
|
|
68
|
+
- Create "Automation" token
|
|
69
|
+
- Configure OIDC integration
|
|
70
|
+
|
|
71
|
+
3. **Repository Settings**:
|
|
72
|
+
```json
|
|
73
|
+
// package.json
|
|
74
|
+
{
|
|
75
|
+
"publishConfig": {
|
|
76
|
+
"registry": "https://registry.npmjs.org/"
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
#### PyPI (Python)
|
|
82
|
+
|
|
83
|
+
1. **Create API Token**:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
# Using twine
|
|
87
|
+
twine upload --config-file ~/.pypirc dist/*
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
2. **OIDC Configuration**:
|
|
91
|
+
```yaml
|
|
92
|
+
# .github/workflows/publish.yml
|
|
93
|
+
- name: Publish to PyPI
|
|
94
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
95
|
+
with:
|
|
96
|
+
password: ${{ secrets.PYPI_API_TOKEN }}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### 3. Security Best Practices
|
|
100
|
+
|
|
101
|
+
#### Token Management
|
|
102
|
+
|
|
103
|
+
- ✅ **Use short-lived tokens** (1-6 hours)
|
|
104
|
+
- ✅ **Scope tokens to specific repositories**
|
|
105
|
+
- ✅ **Rotate tokens regularly**
|
|
106
|
+
- ❌ **Never store long-lived tokens in code**
|
|
107
|
+
- ❌ **Never commit tokens to version control**
|
|
108
|
+
|
|
109
|
+
#### Environment Variables
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
# Good: Short-lived, scoped token
|
|
113
|
+
NODE_AUTH_TOKEN=gho_shortlivedtoken123
|
|
114
|
+
|
|
115
|
+
# Bad: Long-lived, broad token
|
|
116
|
+
NPM_TOKEN=longlivedbroadtoken456
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
#### Repository Secrets
|
|
120
|
+
|
|
121
|
+
Store sensitive tokens in repository secrets:
|
|
122
|
+
|
|
123
|
+
**GitHub**: Settings → Secrets and variables → Actions
|
|
124
|
+
**GitLab**: Settings → CI/CD → Variables
|
|
125
|
+
**Azure DevOps**: Pipelines → Library → Variable groups
|
|
126
|
+
|
|
127
|
+
### 4. Testing the Setup
|
|
128
|
+
|
|
129
|
+
#### Local Testing
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
# Test with dry run
|
|
133
|
+
npm publish --dry-run
|
|
134
|
+
|
|
135
|
+
# Test with local registry
|
|
136
|
+
npm publish --registry http://localhost:4873
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
#### CI/CD Testing
|
|
140
|
+
|
|
141
|
+
```yaml
|
|
142
|
+
# Add to your workflow for testing
|
|
143
|
+
- name: Test publish (dry run)
|
|
144
|
+
run: npm publish --dry-run
|
|
145
|
+
env:
|
|
146
|
+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### 5. Troubleshooting
|
|
150
|
+
|
|
151
|
+
#### Common Issues
|
|
152
|
+
|
|
153
|
+
**Token Expired**:
|
|
154
|
+
|
|
155
|
+
```
|
|
156
|
+
npm ERR! code E401
|
|
157
|
+
npm ERR! Unable to authenticate, need: Basic
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
**Solution**: Check token expiration and refresh if needed.
|
|
161
|
+
|
|
162
|
+
**Insufficient Permissions**:
|
|
163
|
+
|
|
164
|
+
```
|
|
165
|
+
npm ERR! code E403
|
|
166
|
+
npm ERR! Forbidden
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
**Solution**: Verify token has publish permissions for the package.
|
|
170
|
+
|
|
171
|
+
**OIDC Provider Issues**:
|
|
172
|
+
|
|
173
|
+
```
|
|
174
|
+
Error: Failed to get OIDC token
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
**Solution**: Check OIDC provider configuration and permissions.
|
|
178
|
+
|
|
179
|
+
#### Debug Mode
|
|
180
|
+
|
|
181
|
+
Enable debug logging:
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
# npm
|
|
185
|
+
npm config set loglevel verbose
|
|
186
|
+
|
|
187
|
+
# Python
|
|
188
|
+
export TWINE_VERBOSE=1
|
|
189
|
+
|
|
190
|
+
# Maven
|
|
191
|
+
mvn deploy -X
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### 6. Migration from Legacy Tokens
|
|
195
|
+
|
|
196
|
+
If you're migrating from username/password or long-lived tokens:
|
|
197
|
+
|
|
198
|
+
1. **Audit existing tokens**:
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
# npm
|
|
202
|
+
npm profile get
|
|
203
|
+
|
|
204
|
+
# List all tokens
|
|
205
|
+
npm token list
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
2. **Revoke old tokens**:
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
npm token delete <token-id>
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
3. **Update CI/CD workflows**:
|
|
215
|
+
- Replace `NPM_TOKEN` with `NODE_AUTH_TOKEN`
|
|
216
|
+
- Add OIDC permissions
|
|
217
|
+
- Test in staging environment
|
|
218
|
+
|
|
219
|
+
### 7. Monitoring and Alerts
|
|
220
|
+
|
|
221
|
+
Set up monitoring for:
|
|
222
|
+
|
|
223
|
+
- **Publish failures**: Alert on failed deployments
|
|
224
|
+
- **Token expiration**: Proactive token renewal
|
|
225
|
+
- **Security events**: Unusual publish patterns
|
|
226
|
+
- **Registry status**: External service health
|
|
227
|
+
|
|
228
|
+
#### Example Monitoring
|
|
229
|
+
|
|
230
|
+
```yaml
|
|
231
|
+
# .github/workflows/monitor.yml
|
|
232
|
+
name: Monitor Publishing
|
|
233
|
+
|
|
234
|
+
on:
|
|
235
|
+
workflow_run:
|
|
236
|
+
workflows: ['Publish Package']
|
|
237
|
+
types: [completed]
|
|
238
|
+
|
|
239
|
+
jobs:
|
|
240
|
+
monitor:
|
|
241
|
+
runs-on: ubuntu-latest
|
|
242
|
+
steps:
|
|
243
|
+
- name: Check publish status
|
|
244
|
+
if: ${{ github.event.workflow_run.conclusion == 'failure' }}
|
|
245
|
+
run: |
|
|
246
|
+
echo "Publish failed! Check logs."
|
|
247
|
+
# Send alert to Slack/Teams/etc.
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
## CAWS Integration
|
|
251
|
+
|
|
252
|
+
For CAWS projects, OIDC setup integrates with:
|
|
253
|
+
|
|
254
|
+
- **Provenance tracking**: Automatic attestation of published packages
|
|
255
|
+
- **Security scanning**: Validation of published artifacts
|
|
256
|
+
- **Quality gates**: Ensure packages meet standards before publish
|
|
257
|
+
|
|
258
|
+
### CAWS-Specific Configuration
|
|
259
|
+
|
|
260
|
+
```yaml
|
|
261
|
+
# .caws/working-spec.yaml
|
|
262
|
+
non_functional:
|
|
263
|
+
security:
|
|
264
|
+
- 'oidc-authentication'
|
|
265
|
+
- 'token-rotation'
|
|
266
|
+
- 'publish-attestation'
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### Automated Provenance
|
|
270
|
+
|
|
271
|
+
CAWS automatically generates provenance information:
|
|
272
|
+
|
|
273
|
+
```bash
|
|
274
|
+
# Generate SBOM and attestation
|
|
275
|
+
caws attest --format=slsa
|
|
276
|
+
|
|
277
|
+
# Validate before publish
|
|
278
|
+
caws validate --security-scan
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
## Resources
|
|
282
|
+
|
|
283
|
+
- [npm OIDC Documentation](https://docs.npmjs.com/about-access-tokens)
|
|
284
|
+
- [GitHub Actions OIDC](https://docs.github.com/en/actions/deployment/security/hardening-your-deployments/about-security-hardening-with-openid-connect)
|
|
285
|
+
- [PyPI Trusted Publishing](https://docs.pypi.org/trusted-publishing/)
|
|
286
|
+
- [OIDC Specification](https://openid.net/connect/)
|
|
287
|
+
|
|
288
|
+
## Support
|
|
289
|
+
|
|
290
|
+
For issues with OIDC setup:
|
|
291
|
+
|
|
292
|
+
1. Check the troubleshooting section above
|
|
293
|
+
2. Review registry-specific documentation
|
|
294
|
+
3. Open an issue in the CAWS repository
|
|
295
|
+
4. Contact your organization's security team
|
|
296
|
+
|
|
297
|
+
---
|
|
298
|
+
|
|
299
|
+
**Note**: This guide provides general OIDC setup instructions. Always follow your organization's specific security policies and procedures.
|
|
300
|
+
|