@stonepandastudio/cairn 0.4.0 → 0.4.1

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 CHANGED
@@ -79,7 +79,9 @@ Credentials come from the repo's `.env` — `JIRA_BASE_URL` / `JIRA_USER` /
79
79
  `JIRA_PASSWORD` for `jira-server` (Basic auth: Jira Server 8.5.1 predates PATs), or
80
80
  `YOUTRACK_URL` / `YOUTRACK_TOKEN` / `YOUTRACK_PROJECT` for `youtrack` (Bearer token,
81
81
  admin-read scope for the project and stage-bundle lookups). Anything already in the
82
- environment wins over the file.
82
+ environment wins over the file. If the repo's own app reads those same names, set
83
+ `tracker.envPrefix` (`cairn init --env-prefix CAIRN_`) so cairn reads
84
+ `CAIRN_YOUTRACK_TOKEN` instead — or point `tracker.env` at a separate file.
83
85
 
84
86
  ### Legacy command names
85
87
 
@@ -168,4 +170,4 @@ schema.json JSON Schema for cairn.config.json
168
170
  test/run.js dependency-free test runner
169
171
  ```
170
172
 
171
- No runtime dependencies. Node >= 18. `npm test` runs 51 tests.
173
+ No runtime dependencies. Node >= 18. `npm test` runs 54 tests.
package/lib/config.js CHANGED
@@ -84,6 +84,10 @@ function normalizeTracker(tracker) {
84
84
  t.issueTypes = { story: 'Story', subtask: 'Sub-task', ...(t.issueTypes || {}) };
85
85
  t.statuses = { todo: 'To Do', inProgress: 'In Progress', done: 'Done', ...(t.statuses || {}) };
86
86
  t.env = t.env || '.env';
87
+ // Prepended to every credential var name the provider reads — set it when the
88
+ // repo's app talks to the same tracker with its own credentials and `JIRA_*` /
89
+ // `YOUTRACK_*` would collide. Empty by default.
90
+ t.envPrefix = t.envPrefix || '';
87
91
  return t;
88
92
  }
89
93
 
package/lib/init.js CHANGED
@@ -49,8 +49,10 @@ Usage: cairn init [options]
49
49
  --stack <names> comma-separated, framework first: nestjs,typeorm | angular | cli
50
50
  --tracker <provider> ${listProviders().join(' | ')} (default: none)
51
51
  --project-key <KEY> tracker project key, e.g. PROOF
52
- --story-type <name> parent issue type (default: Story)
53
- --subtask-type <name> child issue type (default: Sub-task)
52
+ --story-type <name> parent issue type (default: Story, jira-server only)
53
+ --subtask-type <name> child issue type (default: Sub-task, jira-server only)
54
+ --env-prefix <PFX> prefix for the tracker credential vars, e.g. CAIRN_
55
+ (only when the repo's app reads JIRA_*/YOUTRACK_* itself)
54
56
  --no-shim do not vendor or replace the ai/scripts/*.js client shims
55
57
  --force overwrite an existing cairn.config.json
56
58
  --dry-run print what would be written, write nothing
@@ -64,6 +66,7 @@ function parseArgs(argv) {
64
66
  projectKey: null,
65
67
  storyType: 'Story',
66
68
  subtaskType: 'Sub-task',
69
+ envPrefix: '',
67
70
  shim: true,
68
71
  force: false,
69
72
  dryRun: false,
@@ -77,6 +80,7 @@ function parseArgs(argv) {
77
80
  else if (a === '--project-key') args.projectKey = argv[++i];
78
81
  else if (a === '--story-type') args.storyType = argv[++i];
79
82
  else if (a === '--subtask-type') args.subtaskType = argv[++i];
83
+ else if (a === '--env-prefix') args.envPrefix = argv[++i];
80
84
  else if (a === '--no-shim') args.shim = false;
81
85
  else if (a === '--force') args.force = true;
82
86
  else if (a === '--dry-run') args.dryRun = true;
@@ -100,6 +104,7 @@ function buildConfig(args) {
100
104
  if (args.tracker !== 'none') {
101
105
  cfg.tracker.projectKey = args.projectKey;
102
106
  cfg.tracker.env = '.env';
107
+ if (args.envPrefix) cfg.tracker.envPrefix = args.envPrefix;
103
108
  // issueTypes is a Jira concept — YouTrack has no Task/Sub-task split.
104
109
  if (args.tracker === 'jira-server') {
105
110
  cfg.tracker.issueTypes = { story: args.storyType, subtask: args.subtaskType };
@@ -190,9 +195,8 @@ function main(argv = process.argv.slice(2)) {
190
195
  );
191
196
  }
192
197
  if (args.tracker !== 'none') {
193
- console.log(
194
- paint.dim(`\n Next: ensure .env has JIRA_BASE_URL, JIRA_USER, JIRA_PASSWORD, then run`),
195
- );
198
+ const envVars = require('./tracker').providerEnv(args.tracker, args.envPrefix);
199
+ console.log(paint.dim(`\n Next: ensure .env has ${envVars.join(', ')}, then run`));
196
200
  console.log(paint.dim(` npx cairn tracker list-statuses ${args.projectKey}`));
197
201
  }
198
202
  return 0;
@@ -1,8 +1,10 @@
1
1
  'use strict';
2
2
 
3
3
  const { loadEnv, loadRepoConfig, findRepoRoot, ConfigError } = require('../config');
4
- const { createJiraServer, TrackerError, NoTransitionError } = require('./jira-server');
5
- const { createYouTrack } = require('./youtrack');
4
+ const jiraServer = require('./jira-server');
5
+ const youtrack = require('./youtrack');
6
+ const { createJiraServer, TrackerError, NoTransitionError } = jiraServer;
7
+ const { createYouTrack } = youtrack;
6
8
  const { createNone } = require('./none');
7
9
 
8
10
  // Provider registry. Adding Linear or GitHub Issues later means adding one entry
@@ -18,6 +20,18 @@ function listProviders() {
18
20
  return Object.keys(PROVIDERS);
19
21
  }
20
22
 
23
+ // The .env variables a provider reads. `cairn init` prints these as the next step,
24
+ // so a youtrack repo is not told to set JIRA_* and vice versa.
25
+ const CREDENTIAL_ENV = {
26
+ 'jira-server': jiraServer.CREDENTIAL_ENV,
27
+ youtrack: youtrack.CREDENTIAL_ENV,
28
+ none: [],
29
+ };
30
+
31
+ function providerEnv(name, prefix = '') {
32
+ return (CREDENTIAL_ENV[name] || []).map((v) => prefix + v);
33
+ }
34
+
21
35
  // Build a tracker straight from a tracker config block plus an env bag.
22
36
  function createTracker(trackerConfig = {}, env = process.env) {
23
37
  const name = trackerConfig.provider || 'none';
@@ -48,6 +62,7 @@ module.exports = {
48
62
  createTracker,
49
63
  trackerForRepo,
50
64
  listProviders,
65
+ providerEnv,
51
66
  TrackerError,
52
67
  NoTransitionError,
53
68
  };
@@ -38,14 +38,15 @@ class NoTransitionError extends TrackerError {
38
38
  const REQUIRED_ENV = ['JIRA_BASE_URL', 'JIRA_USER', 'JIRA_PASSWORD'];
39
39
 
40
40
  function createJiraServer(config = {}, env = process.env) {
41
- const baseUrl = (config.baseUrl || env.JIRA_BASE_URL || '').replace(/\/+$/, '');
42
- const user = config.user || env.JIRA_USER;
43
- const password = config.password || env.JIRA_PASSWORD;
41
+ const pfx = config.envPrefix || '';
42
+ const baseUrl = (config.baseUrl || env[pfx + 'JIRA_BASE_URL'] || '').replace(/\/+$/, '');
43
+ const user = config.user || env[pfx + 'JIRA_USER'];
44
+ const password = config.password || env[pfx + 'JIRA_PASSWORD'];
44
45
  const projectKey = config.projectKey;
45
46
  const issueTypes = { story: 'Story', subtask: 'Sub-task', ...(config.issueTypes || {}) };
46
47
 
47
48
  function assertConfig() {
48
- const missing = REQUIRED_ENV.filter((k, i) => ![baseUrl, user, password][i]);
49
+ const missing = REQUIRED_ENV.filter((k, i) => ![baseUrl, user, password][i]).map((k) => pfx + k);
49
50
  if (missing.length) {
50
51
  throw new TrackerError(
51
52
  `Missing Jira credentials: ${missing.join(', ')}. Set them in the repo's .env or the environment.`,
@@ -322,4 +323,4 @@ function createJiraServer(config = {}, env = process.env) {
322
323
  };
323
324
  }
324
325
 
325
- module.exports = { createJiraServer, TrackerError, NoTransitionError };
326
+ module.exports = { createJiraServer, TrackerError, NoTransitionError, CREDENTIAL_ENV: REQUIRED_ENV };
@@ -21,16 +21,20 @@
21
21
  const { TrackerError, NoTransitionError } = require('./jira-server');
22
22
 
23
23
  const REQUIRED_ENV = ['YOUTRACK_URL', 'YOUTRACK_TOKEN'];
24
+ // What `cairn init` tells the user to put in .env — includes the project, which
25
+ // the guard treats as optional only because it can also come from cairn.config.json.
26
+ const CREDENTIAL_ENV = ['YOUTRACK_URL', 'YOUTRACK_TOKEN', 'YOUTRACK_PROJECT'];
24
27
  const STAGE_FIELD = 'Stage';
25
28
  const ASSIGNEE_FIELD = 'Assignee';
26
29
 
27
30
  function createYouTrack(config = {}, env = process.env) {
28
- const baseUrl = (config.baseUrl || env.YOUTRACK_URL || '').replace(/\/+$/, '');
29
- const token = config.token || env.YOUTRACK_TOKEN;
30
- const projectKey = config.projectKey || env.YOUTRACK_PROJECT;
31
+ const pfx = config.envPrefix || '';
32
+ const baseUrl = (config.baseUrl || env[pfx + 'YOUTRACK_URL'] || '').replace(/\/+$/, '');
33
+ const token = config.token || env[pfx + 'YOUTRACK_TOKEN'];
34
+ const projectKey = config.projectKey || env[pfx + 'YOUTRACK_PROJECT'];
31
35
 
32
36
  function assertConfig() {
33
- const missing = REQUIRED_ENV.filter((k, i) => ![baseUrl, token][i]);
37
+ const missing = REQUIRED_ENV.filter((k, i) => ![baseUrl, token][i]).map((k) => pfx + k);
34
38
  if (missing.length) {
35
39
  throw new TrackerError(
36
40
  `Missing YouTrack credentials: ${missing.join(', ')}. Set them in the repo's .env or the environment.`,
@@ -314,4 +318,4 @@ function createYouTrack(config = {}, env = process.env) {
314
318
  };
315
319
  }
316
320
 
317
- module.exports = { createYouTrack, TrackerError, NoTransitionError };
321
+ module.exports = { createYouTrack, TrackerError, NoTransitionError, CREDENTIAL_ENV };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stonepandastudio/cairn",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "Shared AI workflow scaffolding for Stone Panda repos — issue tracker client and drift doctor.",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {
package/schema.json CHANGED
@@ -51,6 +51,12 @@
51
51
  "type": "string",
52
52
  "default": ".env",
53
53
  "description": "Path, relative to the repo root, of the file holding tracker credentials."
54
+ },
55
+ "envPrefix": {
56
+ "type": "string",
57
+ "default": "",
58
+ "description": "Prepended to every credential var the provider reads (e.g. \"CAIRN_\" → CAIRN_YOUTRACK_TOKEN). Set it only when the repo's own app reads JIRA_*/YOUTRACK_* and the names would collide.",
59
+ "examples": ["CAIRN_"]
54
60
  }
55
61
  }
56
62
  },