@camox/cli 0.42.0 → 0.44.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 +146 -0
- package/dist/auth-core-4Jdf5q-u.mjs +86 -0
- package/dist/index.mjs +542 -62
- package/dist/lib/auth-core.d.mts +3 -3
- package/dist/lib/auth-core.mjs +1 -1
- package/package.json +3 -2
- package/templates/default/package.json +1 -0
- package/templates/default/scripts/README.md +35 -0
- package/templates/default/scripts/production-smoke.mjs +124 -0
- package/templates/default/vite.config.ts +2 -2
- package/dist/auth-core-cCuk5aZy.mjs +0 -63
package/README.md
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# Camox CLI
|
|
2
|
+
|
|
3
|
+
## Runtime lookup and monorepos
|
|
4
|
+
|
|
5
|
+
Runtime-backed commands (`status`, `pages`, `blocks`, `layouts`, `env`, `files`,
|
|
6
|
+
`preview`, and `logout`) accept `--cwd PATH`:
|
|
7
|
+
|
|
8
|
+
```sh
|
|
9
|
+
camox pages list --cwd apps/site
|
|
10
|
+
camox status --cwd apps/site
|
|
11
|
+
camox files upload --cwd apps/site --file ./hero.webp --alt "Product screenshot"
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
The CLI searches for `node_modules/.camox/runtime.json` starting in that directory
|
|
15
|
+
and walking upward. Without `--cwd`, lookup starts in the shell's current directory.
|
|
16
|
+
There is no workspace or child-directory discovery. Run the app's dev server or
|
|
17
|
+
build once to generate its runtime file.
|
|
18
|
+
|
|
19
|
+
Relative `--cwd` paths resolve against the shell's current directory. This option
|
|
20
|
+
only affects runtime lookup: other paths, such as `--file ./hero.webp`, still
|
|
21
|
+
resolve against the shell's current directory. Missing or non-directory paths fail
|
|
22
|
+
instead of falling back to the shell's directory. Environment selection is unchanged:
|
|
23
|
+
your development environment by default, with explicit `--production` where supported.
|
|
24
|
+
|
|
25
|
+
## Authenticated local draft preview
|
|
26
|
+
|
|
27
|
+
CLI login does not sign in a fresh browser. To verify draft writes without publishing:
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
camox preview --url http://localhost:3000/about --json
|
|
31
|
+
camox preview --cwd apps/site --url http://localhost:3000/about --json
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Open the returned `url` in the browser you use for verification. Wait for
|
|
35
|
+
`[data-camox-preview="ready"]`, which identifies the project and developer
|
|
36
|
+
environment after authenticated draft rendering. Errors appear as
|
|
37
|
+
`[data-camox-preview="error"]`; expired or consumed links require a fresh command.
|
|
38
|
+
The CLI and SDK must both support the preview handoff.
|
|
39
|
+
|
|
40
|
+
The URL transfers your signed-in session: treat it as a credential, do not share
|
|
41
|
+
or commit it, and use it within three minutes. Only HTTP(S) loopback URLs are
|
|
42
|
+
accepted. The destination must match the CLI's project, backend, and developer
|
|
43
|
+
environment. There is no production mode or automatic default-browser opening.
|
|
44
|
+
The local dev server must already be running.
|
|
45
|
+
|
|
46
|
+
## Page SEO
|
|
47
|
+
|
|
48
|
+
Use `pages get` to read metadata and `pages update` to edit it:
|
|
49
|
+
|
|
50
|
+
```sh
|
|
51
|
+
camox pages get --path /about --json
|
|
52
|
+
camox pages update --path /about --meta-title "About us" --meta-description "Meet the team."
|
|
53
|
+
camox pages update --id 42 --meta-description ""
|
|
54
|
+
camox pages update --id 42 --ai-seo on
|
|
55
|
+
camox pages update --id 42 --ai-seo off
|
|
56
|
+
camox pages get --path /about --live
|
|
57
|
+
camox pages publish --path /about
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
- Updates require exactly one of `--id` or `--path` and at least one update field.
|
|
61
|
+
Omitted fields are preserved; empty strings clear metadata.
|
|
62
|
+
- Setting either metadata field disables automatic SEO for **both** fields.
|
|
63
|
+
Manual metadata cannot be combined with `--ai-seo on`.
|
|
64
|
+
- `--ai-seo off` preserves current metadata. `--ai-seo on` schedules asynchronous
|
|
65
|
+
generation; the returned page contains current values, not necessarily generated
|
|
66
|
+
values. Use `pages get` to inspect them later.
|
|
67
|
+
- Changes affect the draft only. Publish separately to update live metadata.
|
|
68
|
+
`pages get --live` reads the published snapshot; without it, reads use the draft.
|
|
69
|
+
- Commands support `--project SLUG`, `--production`, and `--json`, with the usual
|
|
70
|
+
development-environment default and structured JSON output/errors. `--production`
|
|
71
|
+
selects the environment; it does not publish the draft.
|
|
72
|
+
|
|
73
|
+
See `camox pages update --help` for options and examples.
|
|
74
|
+
|
|
75
|
+
## Media
|
|
76
|
+
|
|
77
|
+
Run `camox login` and start your project's dev server (or build once) so the CLI
|
|
78
|
+
can discover the project's runtime configuration.
|
|
79
|
+
|
|
80
|
+
```sh
|
|
81
|
+
# Upload a local file with manual alt text.
|
|
82
|
+
camox files upload --file ./hero.webp --alt "Product screenshot" --json
|
|
83
|
+
|
|
84
|
+
# Download a remote file and store a copy in Camox.
|
|
85
|
+
camox files upload --url https://example.com/hero.webp --alt "Product screenshot"
|
|
86
|
+
|
|
87
|
+
# Optionally override the uploaded filename.
|
|
88
|
+
camox files upload --url https://example.com/download --filename hero.webp --ai-metadata off
|
|
89
|
+
|
|
90
|
+
# Inspect the returned ID and URL; use the ID in your block's media field.
|
|
91
|
+
camox files list
|
|
92
|
+
camox files get --id 123
|
|
93
|
+
|
|
94
|
+
camox files update --id 123 --filename hero.webp
|
|
95
|
+
camox files update --id 123 --alt "Updated description"
|
|
96
|
+
camox files update --id 123 --alt ""
|
|
97
|
+
camox files update --id 123 --ai-metadata on
|
|
98
|
+
|
|
99
|
+
# Replace the underlying file everywhere it is referenced, preserving its ID.
|
|
100
|
+
camox files update --id 123 --file ./new-hero.webp
|
|
101
|
+
camox files update --id 123 --url https://example.com/new-hero.webp
|
|
102
|
+
|
|
103
|
+
# Replace content and override metadata together.
|
|
104
|
+
camox files update --id 123 --file ./new-hero.webp --filename hero.webp --alt "New screenshot"
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
- Upload requires exactly one of `--file` or `--url`. URLs are copied, not linked.
|
|
108
|
+
- `--alt` disables AI metadata, including when its value is an empty string.
|
|
109
|
+
It cannot be combined with `--ai-metadata on`. Manual edits are protected from
|
|
110
|
+
already-running AI jobs.
|
|
111
|
+
- Without metadata flags, raster-image uploads use automatic metadata. Other
|
|
112
|
+
file types do not. AI may update both the filename and alt text asynchronously.
|
|
113
|
+
- `--ai-metadata on` schedules generation for raster images; success does not mean
|
|
114
|
+
generation has completed. Use `files get` to inspect the current record.
|
|
115
|
+
- Update requires at least one of `--file`, `--url`, `--filename`, `--alt` or
|
|
116
|
+
`--ai-metadata`. `--file` and `--url` are mutually exclusive; without either,
|
|
117
|
+
only metadata changes. Renaming alone preserves the file ID, URL and AI setting.
|
|
118
|
+
- Replacing content preserves the file ID and all `_fileId` references in the
|
|
119
|
+
selected environment. The URL changes to avoid stale caches. Direct URLs in
|
|
120
|
+
stored block/repeatable-item content are migrated within that environment;
|
|
121
|
+
URLs hardcoded outside Camox cannot be rewritten.
|
|
122
|
+
- Replacements preserve omitted alt text and AI settings. The filename defaults
|
|
123
|
+
to the new source filename; `--filename` overrides it. Binary metadata and
|
|
124
|
+
explicit metadata changes are saved together. If AI remains enabled, raster
|
|
125
|
+
replacements schedule generation on the original file ID; other types skip
|
|
126
|
+
generation. Disable AI to prevent future automatic renaming or alt changes.
|
|
127
|
+
- Files are shared assets, not page drafts: replacing one affects every reference
|
|
128
|
+
to it in the selected environment. No page publish step is required.
|
|
129
|
+
- Failed replacements leave the original file unchanged and remove the unused
|
|
130
|
+
uploaded binary. After a network timeout, use `files get --id ID` before retrying
|
|
131
|
+
because the server may already have completed the operation.
|
|
132
|
+
- All commands support `--project SLUG`, `--production`, and `--json`. The default
|
|
133
|
+
environment is `dev:<your-email>`, not production. IDs are scoped to the selected
|
|
134
|
+
project and environment.
|
|
135
|
+
- Uploads return the stored file record, including `id`, `url`, `mimeType`, `alt`,
|
|
136
|
+
and `aiMetadataEnabled`. Output is JSON; errors are structured JSON on stderr.
|
|
137
|
+
- Files are limited to 100 MiB. URL downloads allow at most five redirects and
|
|
138
|
+
have a two-minute timeout; uploads have a separate two-minute timeout.
|
|
139
|
+
Downloads stream through a temporary file that is removed afterward.
|
|
140
|
+
- Only HTTP(S) URLs without embedded credentials are supported. Signed query
|
|
141
|
+
URLs work and are not included in download errors. Camox credentials are never
|
|
142
|
+
sent to the source URL. Remote authentication flags and batch uploads are not
|
|
143
|
+
supported.
|
|
144
|
+
|
|
145
|
+
See `camox files --help`, `camox files upload --help`, and
|
|
146
|
+
`camox files update --help` for command options.
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
//#region src/lib/auth-core.ts
|
|
6
|
+
function globalAuthFile() {
|
|
7
|
+
return path.join(os.homedir(), ".camox", "auth.json");
|
|
8
|
+
}
|
|
9
|
+
function localAuthFile(cwd) {
|
|
10
|
+
let directory = path.resolve(cwd);
|
|
11
|
+
while (true) {
|
|
12
|
+
if (fs.existsSync(path.join(directory, ".git"))) {
|
|
13
|
+
const file = path.join(directory, ".camox", "auth.json");
|
|
14
|
+
return fs.existsSync(file) ? file : null;
|
|
15
|
+
}
|
|
16
|
+
const parent = path.dirname(directory);
|
|
17
|
+
if (parent === directory) return null;
|
|
18
|
+
directory = parent;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function normalizeUrl(url) {
|
|
22
|
+
return url.replace(/\/+$/, "");
|
|
23
|
+
}
|
|
24
|
+
function readAllTokens(file) {
|
|
25
|
+
if (!fs.existsSync(file)) return {};
|
|
26
|
+
const entries = JSON.parse(fs.readFileSync(file, "utf-8"));
|
|
27
|
+
if (!entries || typeof entries !== "object" || Array.isArray(entries)) throw new Error(`Invalid Camox credentials file: ${file}`);
|
|
28
|
+
return entries;
|
|
29
|
+
}
|
|
30
|
+
function writeAllTokens(file, tokens) {
|
|
31
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
32
|
+
fs.writeFileSync(file, JSON.stringify(tokens, null, 2), { mode: 384 });
|
|
33
|
+
fs.chmodSync(file, 384);
|
|
34
|
+
}
|
|
35
|
+
function credentialFile(authenticationUrl, cwd) {
|
|
36
|
+
const local = localAuthFile(cwd);
|
|
37
|
+
if (local && Object.hasOwn(readAllTokens(local), normalizeUrl(authenticationUrl))) return local;
|
|
38
|
+
return globalAuthFile();
|
|
39
|
+
}
|
|
40
|
+
/** Look up stored credentials for a specific Camox authentication backend. */
|
|
41
|
+
function readAuthTokenForUrl(authenticationUrl, cwd = process.cwd()) {
|
|
42
|
+
const entry = readAllTokens(credentialFile(authenticationUrl, cwd))[normalizeUrl(authenticationUrl)];
|
|
43
|
+
if (entry?.token && entry?.name && typeof entry.email === "string") return entry;
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
function writeAuthTokenForUrl(authenticationUrl, token, cwd = process.cwd()) {
|
|
47
|
+
const file = credentialFile(authenticationUrl, cwd);
|
|
48
|
+
const tokens = readAllTokens(file);
|
|
49
|
+
tokens[normalizeUrl(authenticationUrl)] = token;
|
|
50
|
+
writeAllTokens(file, tokens);
|
|
51
|
+
}
|
|
52
|
+
function removeAuthTokenForUrl(authenticationUrl, cwd = process.cwd()) {
|
|
53
|
+
const file = credentialFile(authenticationUrl, cwd);
|
|
54
|
+
const tokens = readAllTokens(file);
|
|
55
|
+
if (file !== globalAuthFile()) {
|
|
56
|
+
tokens[normalizeUrl(authenticationUrl)] = null;
|
|
57
|
+
writeAllTokens(file, tokens);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
delete tokens[normalizeUrl(authenticationUrl)];
|
|
61
|
+
if (Object.keys(tokens).length > 0) {
|
|
62
|
+
writeAllTokens(file, tokens);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
fs.rmSync(file, { force: true });
|
|
66
|
+
}
|
|
67
|
+
async function verifyOneTimeToken(apiUrl, token) {
|
|
68
|
+
const res = await fetch(`${normalizeUrl(apiUrl)}/api/auth/one-time-token/verify`, {
|
|
69
|
+
method: "POST",
|
|
70
|
+
headers: { "Content-Type": "application/json" },
|
|
71
|
+
body: JSON.stringify({ token })
|
|
72
|
+
});
|
|
73
|
+
if (!res.ok) throw new Error(`OTT verification failed: ${res.status}`);
|
|
74
|
+
const data = await res.json();
|
|
75
|
+
const user = data.user;
|
|
76
|
+
if (!user?.name) throw new Error("No user info in verification response");
|
|
77
|
+
const sessionToken = data.session?.token;
|
|
78
|
+
if (!sessionToken) throw new Error("No session token in verification response");
|
|
79
|
+
return {
|
|
80
|
+
name: user.name,
|
|
81
|
+
email: user.email ?? "",
|
|
82
|
+
token: sessionToken
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
//#endregion
|
|
86
|
+
export { writeAuthTokenForUrl as a, verifyOneTimeToken as i, readAuthTokenForUrl as n, removeAuthTokenForUrl as r, normalizeUrl as t };
|