@fourtwelvelabs/fetch-contentful 0.3.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/CHANGELOG.md +78 -0
- package/README.md +18 -5
- package/dist/cli/index.mjs +77 -16
- package/dist/cli/index.mjs.map +1 -1
- package/dist/index.cjs +10 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +45 -4
- package/dist/index.d.ts +45 -4
- package/dist/index.mjs +10 -8
- package/dist/index.mjs.map +1 -1
- package/docs/tada.md +27 -4
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,83 @@
|
|
|
1
1
|
# @fourtwelvelabs/fetch-contentful
|
|
2
2
|
|
|
3
|
+
## 0.4.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- fce4b46: The CLI now reads `.env.local` and `.env`.
|
|
8
|
+
|
|
9
|
+
`tada-init` and `tada-refresh` previously reported the space and token
|
|
10
|
+
missing in any project that keeps them in `.env.local` — which is most
|
|
11
|
+
Next.js projects. The message was technically true (nothing had populated
|
|
12
|
+
`process.env`, because Next reads that file only when Next itself boots) but
|
|
13
|
+
gave no hint that a file sitting in the same directory had been ignored.
|
|
14
|
+
|
|
15
|
+
Each setting now resolves from the first of: a flag, the shell environment,
|
|
16
|
+
`.env.local`, then `.env`. Anything genuinely exported still wins over a
|
|
17
|
+
file, so CI and shell overrides behave as before. A new `--env-file <path>`
|
|
18
|
+
reads somewhere else instead, and fails loudly if the path does not exist.
|
|
19
|
+
|
|
20
|
+
When configuration is still missing, the error now says which files were
|
|
21
|
+
read, or where it looked and found none.
|
|
22
|
+
|
|
23
|
+
Only the CLI does this. The library continues never to touch the disk:
|
|
24
|
+
reading files would break bundlers and edge runtimes, and platforms own that
|
|
25
|
+
job at runtime.
|
|
26
|
+
|
|
27
|
+
## 0.4.0
|
|
28
|
+
|
|
29
|
+
### Minor Changes
|
|
30
|
+
|
|
31
|
+
- 73b5ec2: Separate `deliveryToken` and `previewToken`, and stop reading the preview
|
|
32
|
+
token from a `NEXT_PUBLIC_` variable.
|
|
33
|
+
|
|
34
|
+
### The preview token can no longer leak into a client bundle
|
|
35
|
+
|
|
36
|
+
`readEnvSettings` used to fall back to
|
|
37
|
+
`NEXT_PUBLIC_CONTENTFUL_PREVIEW_ACCESS_TOKEN`. Next.js inlines
|
|
38
|
+
`NEXT_PUBLIC_` variables into client bundles by string-replacing the literal
|
|
39
|
+
`process.env.NEXT_PUBLIC_…` accesses this package ships, so a project that
|
|
40
|
+
set that variable had its preview token baked into public JavaScript —
|
|
41
|
+
whether or not any client code ever requested preview content. Importing the
|
|
42
|
+
library was enough.
|
|
43
|
+
|
|
44
|
+
The preview token is now resolved **only** from the unprefixed
|
|
45
|
+
`CONTENTFUL_PREVIEW_ACCESS_TOKEN`. In Next.js that name is still readable on
|
|
46
|
+
the server, so server-side preview keeps working; the migration is to drop
|
|
47
|
+
the `NEXT_PUBLIC_` prefix from that one variable. The space, environment and
|
|
48
|
+
delivery token keep their client-safe names — a delivery token is read-only
|
|
49
|
+
and commonly public.
|
|
50
|
+
|
|
51
|
+
If your app genuinely needs preview in the browser, pass `previewToken`
|
|
52
|
+
explicitly.
|
|
53
|
+
|
|
54
|
+
### `deliveryToken` and `previewToken`
|
|
55
|
+
|
|
56
|
+
A single `token` could not serve both modes: it applied to whichever mode was
|
|
57
|
+
active, so a factory configured with one sent the wrong token as soon as a
|
|
58
|
+
call passed `preview: true` — and there was no way to configure both
|
|
59
|
+
explicitly at all.
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
export const fetchContentful = createFetchContentful({
|
|
63
|
+
deliveryToken: process.env.CONTENTFUL_ACCESS_TOKEN,
|
|
64
|
+
previewToken: process.env.CONTENTFUL_PREVIEW_ACCESS_TOKEN,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
await fetchContentful(QUERY); // → deliveryToken
|
|
68
|
+
await fetchContentful(QUERY, { preview: true }); // → previewToken
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
`token` still works and still applies to the active mode, so existing code is
|
|
72
|
+
unaffected, but it is deprecated in favour of the two specific options.
|
|
73
|
+
`readEnvSettings()` gains `deliveryToken`; its `token` field remains as a
|
|
74
|
+
deprecated alias of the same value.
|
|
75
|
+
|
|
76
|
+
The GraphQL endpoint is unchanged — unlike Contentful's REST APIs, preview
|
|
77
|
+
uses the same `graphql.contentful.com` host and is selected by the token plus
|
|
78
|
+
the injected `preview: true` argument. (The REST locale lookup already
|
|
79
|
+
switched between `cdn.` and `preview.` hosts correctly.)
|
|
80
|
+
|
|
3
81
|
## 0.3.0
|
|
4
82
|
|
|
5
83
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -22,7 +22,7 @@ yarn add @fourtwelvelabs/fetch-contentful graphql
|
|
|
22
22
|
|
|
23
23
|
Settings resolve in this order — first hit wins, per setting:
|
|
24
24
|
|
|
25
|
-
1. **Per-call options** — `fetchContentful(query, { space,
|
|
25
|
+
1. **Per-call options** — `fetchContentful(query, { space, deliveryToken, ... })`
|
|
26
26
|
2. **Factory defaults** — set once with `createFetchContentful` (below)
|
|
27
27
|
3. **Environment variables** — framework-neutral names first, then `NEXT_PUBLIC_`-prefixed equivalents
|
|
28
28
|
|
|
@@ -34,14 +34,22 @@ If `space` or the appropriate token can't be resolved, the promise rejects with
|
|
|
34
34
|
| --- | --- | --- |
|
|
35
35
|
| `space` | `CONTENTFUL_SPACE_ID` | `NEXT_PUBLIC_CONTENTFUL_SPACE_ID` |
|
|
36
36
|
| `environment` | `CONTENTFUL_ENVIRONMENT` | `NEXT_PUBLIC_CONTENTFUL_ENVIRONMENT` |
|
|
37
|
-
|
|
|
38
|
-
|
|
|
37
|
+
| `deliveryToken` | `CONTENTFUL_ACCESS_TOKEN` | `NEXT_PUBLIC_CONTENTFUL_ACCESS_TOKEN` |
|
|
38
|
+
| `previewToken` | `CONTENTFUL_PREVIEW_ACCESS_TOKEN` | **none, by design** |
|
|
39
39
|
|
|
40
40
|
There is no environment variable for the locale: it defaults to **`en-US`**, and a project that wants a different one sets it once with `createFetchContentful` (below).
|
|
41
41
|
|
|
42
42
|
The library reads the already-populated `process.env`; loading `.env` / `.env.local` files from disk is your platform's job (Next.js, Vite, dotenv all do this), so precedence between those files always matches your framework's rules.
|
|
43
43
|
|
|
44
|
-
**In a Next.js project you don't need both names.** Set only the `NEXT_PUBLIC_` variant and it works everywhere: the server reads it like any other env var (the prefix only controls *client* exposure), and client bundles get it because the library reads these variables with literal `process.env.NEXT_PUBLIC_...` accesses — the exact pattern Next's build-time inliner string-replaces. Server-only projects (or any other framework) should use the neutral names.
|
|
44
|
+
**In a Next.js project you don't need both names.** Set only the `NEXT_PUBLIC_` variant and it works everywhere: the server reads it like any other env var (the prefix only controls *client* exposure), and client bundles get it because the library reads these variables with literal `process.env.NEXT_PUBLIC_...` accesses — the exact pattern Next's build-time inliner string-replaces. Server-only projects (or any other framework) should use the neutral names. This applies to the space, the environment and the delivery token; the preview token has no client-safe name, for the reason below.
|
|
45
|
+
|
|
46
|
+
### The preview token is never read from a `NEXT_PUBLIC_` variable
|
|
47
|
+
|
|
48
|
+
Next.js exposes `NEXT_PUBLIC_` variables to the browser by string-replacing literal `process.env.NEXT_PUBLIC_…` accesses at build time. This library contains such accesses, so if it read the preview token from a prefixed name, setting that variable would bake the token into your public JavaScript — whether or not any client code ever asked for preview content. Importing the library would be enough.
|
|
49
|
+
|
|
50
|
+
A delivery token is read-only and commonly public. A **preview token reads unpublished content**, so it is resolved only from the unprefixed `CONTENTFUL_PREVIEW_ACCESS_TOKEN`. In Next.js that name is still readable on the server — the prefix only controls *client* exposure — so server-side preview (Draft Mode, route handlers, server components) works unchanged.
|
|
51
|
+
|
|
52
|
+
If you have decided that your app genuinely needs preview in the browser, pass `previewToken` explicitly. Shipping it is then your deliberate choice rather than something the library did on your behalf.
|
|
45
53
|
|
|
46
54
|
### Project-level defaults with `createFetchContentful`
|
|
47
55
|
|
|
@@ -56,6 +64,10 @@ export const fetchContentful = createFetchContentful({
|
|
|
56
64
|
locale: 'de-DE', // override the 'en-US' default for the whole project
|
|
57
65
|
retries: 3,
|
|
58
66
|
});
|
|
67
|
+
|
|
68
|
+
// Configure both tokens once, and every call picks the right one:
|
|
69
|
+
// fetchContentful(QUERY) → deliveryToken
|
|
70
|
+
// fetchContentful(QUERY, { preview: true }) → previewToken
|
|
59
71
|
```
|
|
60
72
|
|
|
61
73
|
```ts
|
|
@@ -209,7 +221,8 @@ fetchContentful(query, {
|
|
|
209
221
|
preview: false, // default: false
|
|
210
222
|
locale: 'en-US', // default: 'en-US'. null sends no locale at all
|
|
211
223
|
validateLocale: true, // reject with LOCALE if locale isn't configured
|
|
212
|
-
|
|
224
|
+
deliveryToken: '...', // CDA token, used when preview is false
|
|
225
|
+
previewToken: '...', // CPA token, used when preview is true
|
|
213
226
|
|
|
214
227
|
// Request behavior
|
|
215
228
|
variables: { slug: 'home' },
|
package/dist/cli/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { existsSync, readFileSync, mkdirSync, writeFileSync } from 'fs';
|
|
3
3
|
import { resolve, dirname, relative, isAbsolute, sep, join } from 'path';
|
|
4
4
|
import { getIntrospectionQuery, printSchema, buildClientSchema } from 'graphql';
|
|
5
5
|
|
|
@@ -12,10 +12,14 @@ function readEnvSettings() {
|
|
|
12
12
|
return {
|
|
13
13
|
space: void 0,
|
|
14
14
|
environment: void 0,
|
|
15
|
+
deliveryToken: void 0,
|
|
15
16
|
token: void 0,
|
|
16
17
|
previewToken: void 0
|
|
17
18
|
};
|
|
18
19
|
}
|
|
20
|
+
const deliveryToken = orUndefined(
|
|
21
|
+
process.env.CONTENTFUL_ACCESS_TOKEN || process.env.NEXT_PUBLIC_CONTENTFUL_ACCESS_TOKEN
|
|
22
|
+
);
|
|
19
23
|
return {
|
|
20
24
|
space: orUndefined(
|
|
21
25
|
process.env.CONTENTFUL_SPACE_ID || process.env.NEXT_PUBLIC_CONTENTFUL_SPACE_ID
|
|
@@ -23,12 +27,10 @@ function readEnvSettings() {
|
|
|
23
27
|
environment: orUndefined(
|
|
24
28
|
process.env.CONTENTFUL_ENVIRONMENT || process.env.NEXT_PUBLIC_CONTENTFUL_ENVIRONMENT
|
|
25
29
|
),
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
previewToken: orUndefined(
|
|
30
|
-
process.env.CONTENTFUL_PREVIEW_ACCESS_TOKEN || process.env.NEXT_PUBLIC_CONTENTFUL_PREVIEW_ACCESS_TOKEN
|
|
31
|
-
)
|
|
30
|
+
deliveryToken,
|
|
31
|
+
token: deliveryToken,
|
|
32
|
+
// No `NEXT_PUBLIC_` fallback: see the note at the top of this file.
|
|
33
|
+
previewToken: orUndefined(process.env.CONTENTFUL_PREVIEW_ACCESS_TOKEN)
|
|
32
34
|
};
|
|
33
35
|
}
|
|
34
36
|
|
|
@@ -51,7 +53,8 @@ var VALUE_FLAGS = [
|
|
|
51
53
|
"tada-output",
|
|
52
54
|
"graphql-file",
|
|
53
55
|
"tsconfig",
|
|
54
|
-
"cwd"
|
|
56
|
+
"cwd",
|
|
57
|
+
"env-file"
|
|
55
58
|
];
|
|
56
59
|
function isBooleanFlag(name) {
|
|
57
60
|
return BOOLEAN_FLAGS.includes(name);
|
|
@@ -156,6 +159,55 @@ function formatBytes(bytes) {
|
|
|
156
159
|
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} kB`;
|
|
157
160
|
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
158
161
|
}
|
|
162
|
+
var ENV_FILES = [".env.local", ".env"];
|
|
163
|
+
var ASSIGNMENT = /^(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/;
|
|
164
|
+
function parseEnvFile(source) {
|
|
165
|
+
const values = {};
|
|
166
|
+
for (const rawLine of source.split(/\r?\n/)) {
|
|
167
|
+
const line = rawLine.trim();
|
|
168
|
+
if (line === "" || line.startsWith("#")) continue;
|
|
169
|
+
const match = ASSIGNMENT.exec(line);
|
|
170
|
+
if (!match) continue;
|
|
171
|
+
const key = match[1];
|
|
172
|
+
let value = match[2].trim();
|
|
173
|
+
const quote = value[0];
|
|
174
|
+
if ((quote === '"' || quote === "'") && value.length > 1 && value.endsWith(quote)) {
|
|
175
|
+
value = value.slice(1, -1);
|
|
176
|
+
if (quote === '"') value = value.replace(/\\n/g, "\n");
|
|
177
|
+
} else {
|
|
178
|
+
const comment = value.search(/\s#/);
|
|
179
|
+
if (comment !== -1) value = value.slice(0, comment).trimEnd();
|
|
180
|
+
}
|
|
181
|
+
values[key] = value;
|
|
182
|
+
}
|
|
183
|
+
return values;
|
|
184
|
+
}
|
|
185
|
+
function loadEnvFiles(directory, explicit) {
|
|
186
|
+
const load = { directory, files: [], applied: [] };
|
|
187
|
+
if (explicit !== void 0 && !existsSync(resolve(directory, explicit))) {
|
|
188
|
+
throw new CliError(`No env file at ${explicit}.`);
|
|
189
|
+
}
|
|
190
|
+
for (const candidate of explicit === void 0 ? ENV_FILES : [explicit]) {
|
|
191
|
+
const path = resolve(directory, candidate);
|
|
192
|
+
if (!existsSync(path)) continue;
|
|
193
|
+
load.files.push(candidate);
|
|
194
|
+
for (const [key, value] of Object.entries(
|
|
195
|
+
parseEnvFile(readFileSync(path, "utf8"))
|
|
196
|
+
)) {
|
|
197
|
+
if (!process.env[key]) {
|
|
198
|
+
process.env[key] = value;
|
|
199
|
+
load.applied.push(key);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return load;
|
|
204
|
+
}
|
|
205
|
+
function describeEnvFiles(load) {
|
|
206
|
+
if (load.files.length === 0) {
|
|
207
|
+
return `No ${ENV_FILES.join(" or ")} file was found in ${load.directory}, so only the shell environment was read. Point at one with --env-file if it lives elsewhere.`;
|
|
208
|
+
}
|
|
209
|
+
return `Read ${load.files.join(" and ")}; anything already set in the environment takes precedence over them.`;
|
|
210
|
+
}
|
|
159
211
|
var PACKAGE_NAME = "@fourtwelvelabs/fetch-contentful";
|
|
160
212
|
function relativeSpecifier(fromFile, toFile) {
|
|
161
213
|
const path = relative(dirname(fromFile), toFile).split(sep).join("/");
|
|
@@ -666,14 +718,17 @@ Other:
|
|
|
666
718
|
--dry-run Show what would change; write nothing
|
|
667
719
|
--force Overwrite an existing graphql file (tada-init)
|
|
668
720
|
--cwd <path> Run against another directory
|
|
721
|
+
--env-file <path> Read this file instead of .env.local / .env
|
|
669
722
|
-h, --help Show this help
|
|
670
723
|
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
724
|
+
Configuration is read from .env.local, then .env, then the shell \u2014 anything
|
|
725
|
+
already exported wins over a file, and flags win over everything. The
|
|
726
|
+
NEXT_PUBLIC_-prefixed names are accepted as fallbacks, exactly as the
|
|
727
|
+
library reads them at runtime.`;
|
|
728
|
+
function resolveConfig(args, envFiles) {
|
|
674
729
|
const env = readEnvSettings();
|
|
675
730
|
const space = args.values.space ?? env.space;
|
|
676
|
-
const token = args.values.token ?? env.
|
|
731
|
+
const token = args.values.token ?? env.deliveryToken;
|
|
677
732
|
const environment = args.values.environment ?? env.environment ?? "master";
|
|
678
733
|
const missing = [];
|
|
679
734
|
if (!space) {
|
|
@@ -691,7 +746,8 @@ function resolveConfig(args) {
|
|
|
691
746
|
`Missing required Contentful configuration:
|
|
692
747
|
- ${missing.join(
|
|
693
748
|
"\n - "
|
|
694
|
-
)}
|
|
749
|
+
)}
|
|
750
|
+
${describeEnvFiles(envFiles)}`
|
|
695
751
|
);
|
|
696
752
|
}
|
|
697
753
|
return { space, environment, token };
|
|
@@ -842,8 +898,9 @@ function reportPeers(paths, io) {
|
|
|
842
898
|
io.stdout(` ${installCommand(detectPackageManager(paths.root), missing)}`);
|
|
843
899
|
}
|
|
844
900
|
async function tadaInit(args, io) {
|
|
845
|
-
const config = resolveConfig(args);
|
|
846
901
|
const paths = resolvePaths(args, io);
|
|
902
|
+
const envFiles = loadEnvFiles(paths.root, args.values["env-file"]);
|
|
903
|
+
const config = resolveConfig(args, envFiles);
|
|
847
904
|
const dryRun = args.flags.has("dry-run");
|
|
848
905
|
const tsconfigAction = planTsconfig(paths);
|
|
849
906
|
const sdl = await fetchSchemaSdl({ ...config, fetch: io.fetch });
|
|
@@ -881,8 +938,9 @@ async function tadaInit(args, io) {
|
|
|
881
938
|
return 0;
|
|
882
939
|
}
|
|
883
940
|
async function tadaRefresh(args, io) {
|
|
884
|
-
const config = resolveConfig(args);
|
|
885
941
|
const paths = resolvePaths(args, io);
|
|
942
|
+
const envFiles = loadEnvFiles(paths.root, args.values["env-file"]);
|
|
943
|
+
const config = resolveConfig(args, envFiles);
|
|
886
944
|
const dryRun = args.flags.has("dry-run");
|
|
887
945
|
const sdl = await fetchSchemaSdl({ ...config, fetch: io.fetch });
|
|
888
946
|
const action = planSchema(sdl, paths);
|
|
@@ -916,7 +974,10 @@ async function dispatch(argv, io) {
|
|
|
916
974
|
}
|
|
917
975
|
function knownTokens(argv) {
|
|
918
976
|
const env = readEnvSettings();
|
|
919
|
-
const tokens = [
|
|
977
|
+
const tokens = [
|
|
978
|
+
env.deliveryToken,
|
|
979
|
+
env.previewToken
|
|
980
|
+
];
|
|
920
981
|
for (const [index, argument] of argv.entries()) {
|
|
921
982
|
if (argument.startsWith("--token=")) tokens.push(argument.slice(8));
|
|
922
983
|
else if (argument === "--token") tokens.push(argv[index + 1]);
|