@fourtwelvelabs/fetch-contentful 0.4.0 → 1.0.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.
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { mkdirSync, writeFileSync, existsSync, readFileSync } from 'fs';
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
 
@@ -13,7 +13,6 @@ function readEnvSettings() {
13
13
  space: void 0,
14
14
  environment: void 0,
15
15
  deliveryToken: void 0,
16
- token: void 0,
17
16
  previewToken: void 0
18
17
  };
19
18
  }
@@ -28,7 +27,6 @@ function readEnvSettings() {
28
27
  process.env.CONTENTFUL_ENVIRONMENT || process.env.NEXT_PUBLIC_CONTENTFUL_ENVIRONMENT
29
28
  ),
30
29
  deliveryToken,
31
- token: deliveryToken,
32
30
  // No `NEXT_PUBLIC_` fallback: see the note at the top of this file.
33
31
  previewToken: orUndefined(process.env.CONTENTFUL_PREVIEW_ACCESS_TOKEN)
34
32
  };
@@ -53,7 +51,8 @@ var VALUE_FLAGS = [
53
51
  "tada-output",
54
52
  "graphql-file",
55
53
  "tsconfig",
56
- "cwd"
54
+ "cwd",
55
+ "env-file"
57
56
  ];
58
57
  function isBooleanFlag(name) {
59
58
  return BOOLEAN_FLAGS.includes(name);
@@ -158,6 +157,53 @@ function formatBytes(bytes) {
158
157
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} kB`;
159
158
  return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
160
159
  }
160
+ var ENV_FILES = [".env.local", ".env"];
161
+ var ASSIGNMENT = /^(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/;
162
+ function parseEnvFile(source) {
163
+ const values = {};
164
+ for (const rawLine of source.split(/\r?\n/)) {
165
+ const line = rawLine.trim();
166
+ if (line === "" || line.startsWith("#")) continue;
167
+ const match = ASSIGNMENT.exec(line);
168
+ if (!match) continue;
169
+ const key = match[1];
170
+ let value = match[2].trim();
171
+ const quote = value[0];
172
+ if ((quote === '"' || quote === "'") && value.length > 1 && value.endsWith(quote)) {
173
+ value = value.slice(1, -1);
174
+ if (quote === '"') value = value.replace(/\\n/g, "\n");
175
+ } else {
176
+ const comment = value.search(/\s#/);
177
+ if (comment !== -1) value = value.slice(0, comment).trimEnd();
178
+ }
179
+ values[key] = value;
180
+ }
181
+ return values;
182
+ }
183
+ function loadEnvFiles(directory, explicit) {
184
+ const load = { directory, files: [], applied: [] };
185
+ if (explicit !== void 0 && !existsSync(resolve(directory, explicit))) {
186
+ throw new CliError(`No env file at ${explicit}.`);
187
+ }
188
+ for (const candidate of explicit === void 0 ? ENV_FILES : [explicit]) {
189
+ const path = resolve(directory, candidate);
190
+ if (!existsSync(path)) continue;
191
+ load.files.push(candidate);
192
+ for (const [key, value] of Object.entries(parseEnvFile(readFileSync(path, "utf8")))) {
193
+ if (!process.env[key]) {
194
+ process.env[key] = value;
195
+ load.applied.push(key);
196
+ }
197
+ }
198
+ }
199
+ return load;
200
+ }
201
+ function describeEnvFiles(load) {
202
+ if (load.files.length === 0) {
203
+ 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.`;
204
+ }
205
+ return `Read ${load.files.join(" and ")}; anything already set in the environment takes precedence over them.`;
206
+ }
161
207
  var PACKAGE_NAME = "@fourtwelvelabs/fetch-contentful";
162
208
  function relativeSpecifier(fromFile, toFile) {
163
209
  const path = relative(dirname(fromFile), toFile).split(sep).join("/");
@@ -212,26 +258,19 @@ async function fetchSchemaSdl(options) {
212
258
  });
213
259
  } catch (cause) {
214
260
  throw new CliError(
215
- `Could not reach Contentful at ${url}: ${redact(
216
- String(cause),
217
- options.token
218
- )}`
261
+ `Could not reach Contentful at ${url}: ${redact(String(cause), options.token)}`
219
262
  );
220
263
  }
221
264
  if (response.status === 401 || response.status === 403) {
222
265
  throw new CliError(
223
- `Contentful rejected the access token (HTTP ${String(
224
- response.status
225
- )}).
266
+ `Contentful rejected the access token (HTTP ${String(response.status)}).
226
267
  Check that the token is a Content Delivery API token for space "${options.space}", and that it has access to environment "${options.environment}".`
227
268
  );
228
269
  }
229
270
  if (!response.ok) {
230
271
  const body = bodySnippet(await response.text().catch(() => ""), options.token);
231
- throw new CliError(
232
- `Contentful responded with HTTP ${String(response.status)}.
233
- ${body}`
234
- );
272
+ throw new CliError(`Contentful responded with HTTP ${String(response.status)}.
273
+ ${body}`);
235
274
  }
236
275
  let payload;
237
276
  try {
@@ -242,10 +281,7 @@ async function fetchSchemaSdl(options) {
242
281
  if (payload.errors && payload.errors.length > 0) {
243
282
  const messages = payload.errors.map((error) => error.message ?? "unknown error").join("; ");
244
283
  throw new CliError(
245
- `Contentful returned GraphQL errors during introspection: ${redact(
246
- messages,
247
- options.token
248
- )}`
284
+ `Contentful returned GraphQL errors during introspection: ${redact(messages, options.token)}`
249
285
  );
250
286
  }
251
287
  if (!payload.data) {
@@ -258,9 +294,7 @@ async function fetchSchemaSdl(options) {
258
294
  `;
259
295
  } catch (cause) {
260
296
  throw new CliError(
261
- `Could not build a schema from Contentful's introspection response: ${String(
262
- cause
263
- )}`
297
+ `Could not build a schema from Contentful's introspection response: ${String(cause)}`
264
298
  );
265
299
  }
266
300
  }
@@ -293,9 +327,7 @@ function installCommand(manager, packages) {
293
327
  function missingDependencies(cwd, packages) {
294
328
  let manifest;
295
329
  try {
296
- manifest = JSON.parse(
297
- readFileSync(join(cwd, "package.json"), "utf8")
298
- );
330
+ manifest = JSON.parse(readFileSync(join(cwd, "package.json"), "utf8"));
299
331
  } catch {
300
332
  return [...packages];
301
333
  }
@@ -356,9 +388,7 @@ var Scanner = class {
356
388
  }
357
389
  expect(character) {
358
390
  if (this.peek() !== character) {
359
- throw new JsoncError(
360
- `Expected "${character}" at offset ${String(this.index)}.`
361
- );
391
+ throw new JsoncError(`Expected "${character}" at offset ${String(this.index)}.`);
362
392
  }
363
393
  this.index += 1;
364
394
  }
@@ -366,9 +396,7 @@ var Scanner = class {
366
396
  const node = this.parseValue();
367
397
  this.skipTrivia();
368
398
  if (this.index !== this.source.length) {
369
- throw new JsoncError(
370
- `Unexpected trailing content at offset ${String(this.index)}.`
371
- );
399
+ throw new JsoncError(`Unexpected trailing content at offset ${String(this.index)}.`);
372
400
  }
373
401
  return node;
374
402
  }
@@ -488,9 +516,7 @@ function renderPluginEntry(indent, unit, config) {
488
516
  "{",
489
517
  `${indent}${unit}"name": ${JSON.stringify(GRAPHQLSP_PLUGIN)},`,
490
518
  `${indent}${unit}"schema": ${JSON.stringify(config.schema)},`,
491
- `${indent}${unit}"tadaOutputLocation": ${JSON.stringify(
492
- config.tadaOutputLocation
493
- )}`,
519
+ `${indent}${unit}"tadaOutputLocation": ${JSON.stringify(config.tadaOutputLocation)}`,
494
520
  `${indent}}`
495
521
  ].join("\n");
496
522
  }
@@ -518,11 +544,7 @@ ${closeIndent}`
518
544
  function renderPluginsArray(indent, unit, config) {
519
545
  const entryIndent = `${indent}${unit}`;
520
546
  return `[
521
- ${entryIndent}${renderPluginEntry(
522
- entryIndent,
523
- unit,
524
- config
525
- )}
547
+ ${entryIndent}${renderPluginEntry(entryIndent, unit, config)}
526
548
  ${indent}]`;
527
549
  }
528
550
  function patchTsconfig(source, config) {
@@ -592,9 +614,7 @@ ${indent}}`,
592
614
  text: JSON.stringify(value)
593
615
  });
594
616
  } else {
595
- edits.push(
596
- insertMember(source, existing, key, () => JSON.stringify(value), unit)
597
- );
617
+ edits.push(insertMember(source, existing, key, () => JSON.stringify(value), unit));
598
618
  }
599
619
  }
600
620
  const patched = applyEdits(source, edits);
@@ -624,11 +644,7 @@ ${indent}${renderPluginEntry(indent, unit, config)}`
624
644
  start: plugins.value.start + 1,
625
645
  end: plugins.value.end - 1,
626
646
  text: `
627
- ${entryIndent}${renderPluginEntry(
628
- entryIndent,
629
- unit,
630
- config
631
- )}
647
+ ${entryIndent}${renderPluginEntry(entryIndent, unit, config)}
632
648
  ${closeIndent}`
633
649
  }
634
650
  ])
@@ -668,11 +684,14 @@ Other:
668
684
  --dry-run Show what would change; write nothing
669
685
  --force Overwrite an existing graphql file (tada-init)
670
686
  --cwd <path> Run against another directory
687
+ --env-file <path> Read this file instead of .env.local / .env
671
688
  -h, --help Show this help
672
689
 
673
- The NEXT_PUBLIC_-prefixed variable names are read as fallbacks, exactly as
674
- the library reads them at runtime.`;
675
- function resolveConfig(args) {
690
+ Configuration is read from .env.local, then .env, then the shell \u2014 anything
691
+ already exported wins over a file, and flags win over everything. The
692
+ NEXT_PUBLIC_-prefixed names are accepted as fallbacks, exactly as the
693
+ library reads them at runtime.`;
694
+ function resolveConfig(args, envFiles) {
676
695
  const env = readEnvSettings();
677
696
  const space = args.values.space ?? env.space;
678
697
  const token = args.values.token ?? env.deliveryToken;
@@ -693,7 +712,8 @@ function resolveConfig(args) {
693
712
  `Missing required Contentful configuration:
694
713
  - ${missing.join(
695
714
  "\n - "
696
- )}`
715
+ )}
716
+ ${describeEnvFiles(envFiles)}`
697
717
  );
698
718
  }
699
719
  return { space, environment, token };
@@ -760,9 +780,7 @@ function planTsconfig(paths) {
760
780
  }
761
781
  function planGraphqlModule(paths, force) {
762
782
  const before = readIfExists(paths.graphqlFile);
763
- const after = renderGraphqlModule(
764
- relativeSpecifier(paths.graphqlFile, paths.tadaOutput)
765
- );
783
+ const after = renderGraphqlModule(relativeSpecifier(paths.graphqlFile, paths.tadaOutput));
766
784
  if (before !== void 0 && !force) {
767
785
  return {
768
786
  kind: "skip",
@@ -829,9 +847,7 @@ function apply(action) {
829
847
  function reportManual(action, paths, io) {
830
848
  if (action.kind !== "manual") return;
831
849
  io.stdout("");
832
- io.stdout(
833
- `${display(paths.root, action.path)} was left untouched because ${action.reason}.`
834
- );
850
+ io.stdout(`${display(paths.root, action.path)} was left untouched because ${action.reason}.`);
835
851
  io.stdout('Add this entry to "compilerOptions.plugins" by hand:');
836
852
  io.stdout("");
837
853
  for (const line of action.block.split("\n")) io.stdout(` ${line}`);
@@ -844,8 +860,9 @@ function reportPeers(paths, io) {
844
860
  io.stdout(` ${installCommand(detectPackageManager(paths.root), missing)}`);
845
861
  }
846
862
  async function tadaInit(args, io) {
847
- const config = resolveConfig(args);
848
863
  const paths = resolvePaths(args, io);
864
+ const envFiles = loadEnvFiles(paths.root, args.values["env-file"]);
865
+ const config = resolveConfig(args, envFiles);
849
866
  const dryRun = args.flags.has("dry-run");
850
867
  const tsconfigAction = planTsconfig(paths);
851
868
  const sdl = await fetchSchemaSdl({ ...config, fetch: io.fetch });
@@ -868,32 +885,23 @@ async function tadaInit(args, io) {
868
885
  io.stdout("Next steps:");
869
886
  io.stdout(` 1. Restart the TypeScript server so ${GRAPHQLSP_PLUGIN} loads.`);
870
887
  io.stdout(
871
- ` 2. Write a query with the \`graphql\` helper from ${display(
872
- paths.root,
873
- paths.graphqlFile
874
- )},`
875
- );
876
- io.stdout(
877
- ` then pass it to fetchContentful from ${PACKAGE_NAME}.`
888
+ ` 2. Write a query with the \`graphql\` helper from ${display(paths.root, paths.graphqlFile)},`
878
889
  );
890
+ io.stdout(` then pass it to fetchContentful from ${PACKAGE_NAME}.`);
879
891
  io.stdout(" Result and variable types are inferred from the document.");
880
- io.stdout(
881
- " 3. After a content-model change, run `fetch-contentful tada-refresh`."
882
- );
892
+ io.stdout(" 3. After a content-model change, run `fetch-contentful tada-refresh`.");
883
893
  return 0;
884
894
  }
885
895
  async function tadaRefresh(args, io) {
886
- const config = resolveConfig(args);
887
896
  const paths = resolvePaths(args, io);
897
+ const envFiles = loadEnvFiles(paths.root, args.values["env-file"]);
898
+ const config = resolveConfig(args, envFiles);
888
899
  const dryRun = args.flags.has("dry-run");
889
900
  const sdl = await fetchSchemaSdl({ ...config, fetch: io.fetch });
890
901
  const action = planSchema(sdl, paths);
891
902
  if (action.kind === "unchanged") {
892
903
  io.stdout(
893
- `Schema is already up to date \u2014 ${display(
894
- paths.root,
895
- paths.schema
896
- )} left untouched.`
904
+ `Schema is already up to date \u2014 ${display(paths.root, paths.schema)} left untouched.`
897
905
  );
898
906
  return 0;
899
907
  }
@@ -918,10 +926,7 @@ async function dispatch(argv, io) {
918
926
  }
919
927
  function knownTokens(argv) {
920
928
  const env = readEnvSettings();
921
- const tokens = [
922
- env.deliveryToken,
923
- env.previewToken
924
- ];
929
+ const tokens = [env.deliveryToken, env.previewToken];
925
930
  for (const [index, argument] of argv.entries()) {
926
931
  if (argument.startsWith("--token=")) tokens.push(argument.slice(8));
927
932
  else if (argument === "--token") tokens.push(argv[index + 1]);