@arjunkhera/atlas 0.3.4 → 0.3.5

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "atlas",
3
- "version": "0.3.4",
3
+ "version": "0.3.5",
4
4
  "description": "Atlas: the delivery lifecycle, its crews and the Atlas tools, for any repository.",
5
5
  "author": {
6
6
  "name": "Arjun Khera"
package/door/cli.mjs CHANGED
@@ -54,18 +54,53 @@ Atlas ${ATLAS_VERSION}. Check version ${CHECK_VERSION}; it reads spec ${Object.k
54
54
  A person merges every file the tooling writer writes.
55
55
  `;
56
56
 
57
- // A switch never takes the next word as its value, so it may come before a
58
- // path: `atlas ste --share page.md` reads page.md (a miss on 6 October).
59
- const SWITCHES = new Set(['help', 'share', 'replace-unknown', 'workflow', 'dry-run', 'local', 'skip-global', 'yes']);
57
+ // Each flag of each command, declared once. A test holds this table equal to
58
+ // the flags the handlers read. Three kinds:
59
+ // - switch: never takes a value, so it may come before a path
60
+ // (`atlas ste --share page.md`, a miss on 6 October);
61
+ // - value: takes the next word, and a flag with no value stops the command;
62
+ // - optional: takes the next word unless it is a flag; alone it means the
63
+ // default (`--root` alone is this folder, `upgrade --version` alone is the
64
+ // newest release).
65
+ // Any other flag stops the command with exit 2. A misspelt `--shared` once
66
+ // skipped the privacy filter and printed GREEN (review of PR #107).
67
+ export const FLAGS = Object.freeze({
68
+ tooling: { root: 'optional', branch: 'optional', 'replace-unknown': 'switch', workflow: 'switch', 'dry-run': 'switch' },
69
+ status: { root: 'optional' },
70
+ 'kind-drift': { root: 'optional', 'record-kind': 'value', registry: 'value', repo: 'value' },
71
+ check: { root: 'optional', repo: 'value', 'fail-on': 'value' },
72
+ ste: { share: 'switch', terms: 'value' },
73
+ design: {},
74
+ install: { local: 'switch', from: 'value', 'skip-global': 'switch', yes: 'switch' },
75
+ upgrade: { version: 'optional', yes: 'switch' },
76
+ doctor: {},
77
+ });
60
78
 
61
- function options(argv) {
79
+ // Every command takes --help.
80
+ export function options(argv, declared = {}) {
81
+ const kinds = { help: 'switch', ...declared };
62
82
  const out = { _: [] };
63
83
  for (let index = 0; index < argv.length; index += 1) {
64
- const flag = argv[index];
65
- if (!flag.startsWith('--')) { out._.push(flag); continue; }
66
- const key = flag.slice(2);
84
+ const word = argv[index];
85
+ if (!word.startsWith('--')) { out._.push(word); continue; }
86
+ const equals = word.indexOf('=');
87
+ const key = word.slice(2, equals === -1 ? undefined : equals);
88
+ const kind = Object.hasOwn(kinds, key) ? kinds[key] : null;
89
+ if (!kind) throw new Error(`there is no flag "--${key}" for "atlas ${argv[0]}". Run "atlas help".`);
90
+ if (equals !== -1) {
91
+ if (kind === 'switch') throw new Error(`the flag "--${key}" takes no value.`);
92
+ if (equals === word.length - 1) throw new Error(`the flag "--${key}" needs a value.`);
93
+ out[key] = word.slice(equals + 1);
94
+ continue;
95
+ }
96
+ if (kind === 'switch') { out[key] = true; continue; }
67
97
  const value = argv[index + 1];
68
- if (SWITCHES.has(key) || value === undefined || value.startsWith('--')) { out[key] = true; continue; }
98
+ if (value === undefined || value.startsWith('--')) {
99
+ if (kind === 'value') throw new Error(`the flag "--${key}" needs a value.`);
100
+ out[key] = true;
101
+ continue;
102
+ }
103
+ if (value === '') throw new Error(`the flag "--${key}" needs a value.`);
69
104
  out[key] = value;
70
105
  index += 1;
71
106
  }
@@ -123,10 +158,10 @@ function statusCommand(chosen) {
123
158
 
124
159
  function kindDriftCommand(chosen) {
125
160
  const root = rootOf(chosen);
126
- let recordKind = chosen['record-kind'] === true ? null : chosen['record-kind'] ?? null;
127
- if (!recordKind && chosen.registry && chosen.registry !== true) {
161
+ let recordKind = chosen['record-kind'] ?? null;
162
+ if (!recordKind && chosen.registry) {
128
163
  const kinds = kindsFromRegistry(JSON.parse(readFileSync(resolve(chosen.registry), 'utf8')));
129
- const repo = chosen.repo === true ? null : chosen.repo;
164
+ const repo = chosen.repo ?? null;
130
165
  recordKind = repo ? kinds[repo] ?? null : null;
131
166
  if (!repo) {
132
167
  // One checkout, so only the registry row that names THIS repo can be
@@ -159,7 +194,8 @@ function steCommand(chosen) {
159
194
  const paths = chosen._.slice(1);
160
195
  if (!paths.length) throw new Error('give a file or a folder: atlas ste <path> [--share]');
161
196
  const share = Boolean(chosen.share);
162
- const terms = share ? loadPrivateTerms({ path: typeof chosen.terms === 'string' ? resolve(chosen.terms) : null }) : [];
197
+ if (chosen.terms !== undefined && !share) throw new Error('--terms works only with --share.');
198
+ const terms = share ? loadPrivateTerms({ path: chosen.terms !== undefined ? resolve(chosen.terms) : null }) : [];
163
199
  const results = checkPaths(paths.map((one) => resolve(one)), { share, terms });
164
200
  let count = 0;
165
201
  for (const { file, findings } of results) {
@@ -186,8 +222,8 @@ function designCommand(chosen) {
186
222
 
187
223
  function checkCommand(chosen) {
188
224
  const root = rootOf(chosen);
189
- const report = scan({ root, repoId: chosen.repo === true ? null : chosen.repo ?? null });
190
- const decision = verdict(report, chosen['fail-on'] === true ? 'error' : chosen['fail-on'] ?? 'error');
225
+ const report = scan({ root, repoId: chosen.repo ?? null });
226
+ const decision = verdict(report, chosen['fail-on'] ?? 'error');
191
227
  process.stdout.write(toText(report));
192
228
  line(`${decision.green ? 'GREEN' : 'RED'}: ${decision.why}`);
193
229
  return decision.green ? 0 : 1;
@@ -200,8 +236,10 @@ export async function main(argv = process.argv.slice(2)) {
200
236
  const result = spawnSync(process.execPath, [join(PACKAGE_ROOT, 'work', 'code-read.mjs'), ...argv.slice(1)], { stdio: 'inherit' });
201
237
  return result.status ?? 1;
202
238
  }
203
- const chosen = options(argv);
204
- if (!command || chosen.help || command === 'help') { process.stdout.write(HELP); return 0; }
239
+ if (!command || command === 'help' || command === '--help') { process.stdout.write(HELP); return 0; }
240
+ if (!Object.hasOwn(FLAGS, command)) throw new Error(`there is no verb "${command}". Run "atlas help".`);
241
+ const chosen = options(argv, FLAGS[command]);
242
+ if (chosen.help) { process.stdout.write(HELP); return 0; }
205
243
  switch (command) {
206
244
  case 'install': return install({ packageRoot: PACKAGE_ROOT, options: chosen, line });
207
245
  case 'upgrade': return upgrade({ packageRoot: PACKAGE_ROOT, options: chosen, line });
@@ -67,7 +67,7 @@ const findPlugin = (listed, id) => pluginRows(listed).find((row) => row.id === i
67
67
  export async function install({ packageRoot, options = {}, line = console.log, env = process.env, exec = defaultExec, ask = defaultAsk }) {
68
68
  const { name, version } = packageJson(packageRoot);
69
69
  const local = Boolean(options.local);
70
- const from = options.from && options.from !== true ? String(options.from) : null;
70
+ const from = options.from ? String(options.from) : null;
71
71
  const catalog = catalogFor({ name, version, local });
72
72
  const file = catalogFile(env);
73
73
  const before = existsSync(file) ? readFileSync(file, 'utf8') : null;
@@ -27,10 +27,15 @@ export const PRIVACY_PATTERNS = Object.freeze([
27
27
 
28
28
  const escape = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
29
29
 
30
- // The owner's private terms, or an empty list when the file is not there.
30
+ // The owner's private terms. The default file is optional: no file there
31
+ // means no terms. A file the caller names must exist, because a mistyped
32
+ // name once loaded no terms and passed the page (review of PR #107).
31
33
  export function loadPrivateTerms({ path = null, home = homedir() } = {}) {
32
34
  const file = path ?? join(home, PRIVATE_TERMS_PATH);
33
- if (!existsSync(file)) return [];
35
+ if (!existsSync(file)) {
36
+ if (path) throw new Error(`the private terms file ${path} does not exist.`);
37
+ return [];
38
+ }
34
39
  return readFileSync(file, 'utf8').split('\n').map((line) => line.trim()).filter((line) => line && !line.startsWith('#'));
35
40
  }
36
41
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arjunkhera/atlas",
3
- "version": "0.3.4",
3
+ "version": "0.3.5",
4
4
  "description": "Atlas: the delivery lifecycle, its crews and the Atlas tools, as a Claude Code plugin for any repository.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",