@canra/cli 0.5.0 → 0.6.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.
Files changed (3) hide show
  1. package/README.md +2 -0
  2. package/canra.js +9 -7
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -34,6 +34,8 @@ canra search json
34
34
  | `canra logout` | Remove saved credentials |
35
35
  | `canra version` / `canra help` | Version and usage |
36
36
 
37
+ `<owner>` is a user or organization name in any case: `Canra/json` and `canra/json` are the same spec, saved under the lowercase owner in `.canra/specs/`.
38
+
37
39
  ## Vendoring specs
38
40
 
39
41
  Commit `.canra/` to source control. The saved specs are the record of which spec versions your project implemented (a version is exact and immutable). After `canra update`, `git diff .canra/` shows what changed in each spec.
package/canra.js CHANGED
@@ -89,11 +89,11 @@ async function fetchRegistry(url, opts) {
89
89
 
90
90
  // --- Spec references ---
91
91
 
92
- // Mirror the server's naming rules (route-utils.js USERNAME_RE, specs-api.js
92
+ // Mirror the server's naming rules (username.js USERNAME_RE and grandfathered names, specs-api.js
93
93
  // NAME_RE). Owner and name become local path segments under .canra/specs/, so
94
94
  // anything path-like ("..", ".", backslashes) must be rejected here — the CLI is
95
95
  // meant to be driven by agents whose arguments may come from untrusted content.
96
- const OWNER_RE = /^[a-z][a-z0-9_-]{1,38}[a-z0-9]$/;
96
+ const OWNER_RE = /^(?:(?=.{1,39}$)[a-z0-9]+(?:-[a-z0-9]+)*|[a-z][a-z0-9_-]{1,38}[a-z0-9])$/i;
97
97
  const NAME_RE = /^[a-z][a-z0-9-]*$/;
98
98
  const SUGGESTION_ID_RE = /^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/;
99
99
 
@@ -197,7 +197,7 @@ function refuseSymlinks(paths) {
197
197
 
198
198
  function localSpecPaths(owner, name) {
199
199
  const specsDir = path.join(LOCAL_DIR, "specs");
200
- const ownerDir = path.join(specsDir, owner);
200
+ const ownerDir = path.join(specsDir, owner.toLowerCase());
201
201
  return [LOCAL_DIR, specsDir, ownerDir, path.join(ownerDir, name)];
202
202
  }
203
203
 
@@ -207,8 +207,10 @@ function frontmatterFunctionCount(text) {
207
207
  return list ? (list[1].match(/"(?:[^"\\]|\\.)*"/g) || []).length : 0;
208
208
  }
209
209
 
210
+ // Owner directories are lowercased everywhere (here, localSpecPaths, deleteLocalSpec) so Canra/json and
211
+ // canra/json are one local spec on case-sensitive and case-insensitive filesystems alike.
210
212
  function localSpecDir(owner, name) {
211
- return path.join(LOCAL_DIR, "specs", owner, name);
213
+ return path.join(LOCAL_DIR, "specs", owner.toLowerCase(), name);
212
214
  }
213
215
 
214
216
  async function downloadSpec(registry, { owner, name, version }) {
@@ -674,7 +676,7 @@ function deleteLocalSpec(owner, name) {
674
676
  if (!dir.startsWith(specsRoot + path.sep)) throw new Error(`unexpected local path for ${owner}/${name}`);
675
677
  refuseSymlinks(localSpecPaths(owner, name));
676
678
  fs.rmSync(dir, { recursive: true, force: true });
677
- const ownerDir = path.join(LOCAL_DIR, "specs", owner);
679
+ const ownerDir = path.join(LOCAL_DIR, "specs", owner.toLowerCase());
678
680
  if (fs.readdirSync(ownerDir).length === 0) fs.rmdirSync(ownerDir);
679
681
  }
680
682
 
@@ -710,7 +712,7 @@ async function cmdUpdate(specArg) {
710
712
 
711
713
  let refs = scanLocalSpecs();
712
714
  if (only) {
713
- refs = refs.filter((r) => r.owner === only.owner && r.name === only.name);
715
+ refs = refs.filter((r) => r.owner.toLowerCase() === only.owner.toLowerCase() && r.name === only.name);
714
716
  if (refs.length === 0) {
715
717
  console.error(`Error: ${only.owner}/${only.name} is not fetched locally. Run \`canra fetch\` first.`);
716
718
  process.exit(1);
@@ -741,7 +743,7 @@ async function cmdUpdate(specArg) {
741
743
  if (!OWNER_RE.test(toOwner) || (target && target[2] !== row.name)) {
742
744
  throw new Error(`unexpected move target '${printable(row.moved_to)}'`);
743
745
  }
744
- const moving = toOwner !== row.owner;
746
+ const moving = toOwner.toLowerCase() !== row.owner.toLowerCase();
745
747
  if (moving && fs.existsSync(localSpecDir(toOwner, row.name))) {
746
748
  throw new Error(`${toOwner}/${row.name} is already fetched locally; remove one of the two copies`);
747
749
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canra/cli",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "CLI for Canra, the registry of machine-readable specs: fetch, search, and publish specs.",
5
5
  "keywords": [
6
6
  "canra",