├── .eslintignore ├── index.js ├── old ├── types │ ├── index.ts │ ├── global.d.ts │ ├── public.ts │ └── internal.ts ├── utils │ ├── errors.ts │ ├── fs-tools.ts │ ├── node.ts │ ├── preset.ts │ ├── babel.ts │ ├── register.ts │ ├── endpoints.ts │ └── options-handler.ts ├── gatsby │ ├── gatsby-node.ts │ └── gatsby-config.ts ├── docs │ ├── PROPERTY-BAG.md │ ├── GATSBY-CONFIG-UTILS.md │ ├── IMPLEMENTATION-EXAMPLES.md │ ├── EXTENDED-USAGE.md │ └── GATSBY-API.md ├── index.ts └── README.md ├── src ├── types │ ├── index.ts │ ├── global.d.ts │ ├── public.ts │ └── internal.ts ├── util │ ├── constants.ts │ ├── type-util.ts │ ├── output.ts │ ├── fs-tools.ts │ ├── node.ts │ ├── objects.ts │ └── project-meta.ts ├── lib │ ├── project │ │ ├── transpiler │ │ │ ├── restore-extensions.ts │ │ │ ├── allowed-files.ts │ │ │ ├── transpiler-settings.ts │ │ │ ├── index.ts │ │ │ ├── set-transpiler.ts │ │ │ └── import-handler.ts │ │ ├── project-settings.ts │ │ └── index.ts │ ├── serializer.ts │ ├── api-module.ts │ ├── plugin-transpiler.ts │ ├── process-plugins.ts │ └── include-plugins.ts ├── settings │ ├── prop-bag.ts │ ├── register.ts │ └── babel │ │ └── preset.ts └── index.ts ├── .yarn ├── install-state.gz ├── sdks │ ├── eslint │ │ ├── package.json │ │ ├── lib │ │ │ └── api.js │ │ └── bin │ │ │ └── eslint.js │ ├── integrations.yml │ └── typescript │ │ ├── package.json │ │ ├── bin │ │ ├── tsc │ │ └── tsserver │ │ └── lib │ │ ├── tsc.js │ │ ├── typescript.js │ │ ├── tsserver.js │ │ └── tsserverlibrary.js ├── plugins │ └── @yarnpkg │ │ └── plugin-echo-execute.cjs └── build-state.yml ├── .vscode ├── extensions.json ├── settings.json └── launch.json ├── tsconfig.build.json ├── .editorconfig ├── .yarnrc.yml ├── .eslintrc.js ├── LICENSE ├── tsconfig.json ├── .gitignore ├── package.json └── README.md /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .yarn -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require("./dist/index"); -------------------------------------------------------------------------------- /old/types/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./internal"; 2 | export * from "./public"; -------------------------------------------------------------------------------- /src/types/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./internal"; 2 | export * from "./public"; -------------------------------------------------------------------------------- /.yarn/install-state.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Js-Brecht/gatsby-plugin-ts-config/HEAD/.yarn/install-state.gz -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "arcanis.vscode-zipfs", 4 | "dbaeumer.vscode-eslint" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /.yarn/sdks/eslint/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eslint", 3 | "version": "1.0.2-sdk", 4 | "main": "./lib/api", 5 | "type": "commonjs" 6 | } 7 | -------------------------------------------------------------------------------- /.yarn/sdks/integrations.yml: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by @yarnpkg/sdks. 2 | # Manual changes might be lost! 3 | 4 | integrations: 5 | - vscode 6 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": [ 4 | "./src/**/*", 5 | ], 6 | "exclude": [ 7 | "./old/**/*" 8 | ] 9 | } -------------------------------------------------------------------------------- /.yarn/sdks/typescript/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typescript", 3 | "version": "4.3.5-sdk", 4 | "main": "./lib/typescript.js", 5 | "type": "commonjs" 6 | } 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | indent_style = space 6 | indent_size = 4 7 | insert_final_newline = false 8 | trim_trailing_whitespace = true 9 | 10 | [*.{yml,json,md}] 11 | indent_size = 2 12 | indent_style = space 13 | 14 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.nodePath": ".yarn/sdks", 3 | "typescript.tsdk": ".yarn/sdks/typescript/lib", 4 | "search.exclude": { 5 | "**/.yarn": true, 6 | "**/.pnp.*": true 7 | }, 8 | "typescript.enablePromptUseWorkspaceTsdk": true 9 | } 10 | -------------------------------------------------------------------------------- /old/utils/errors.ts: -------------------------------------------------------------------------------- 1 | export const throwError = (message: string, err?: string | Error) => { 2 | if (err instanceof Error) { 3 | err.message = [message, err.message].join("\n"); 4 | throw err; 5 | } 6 | throw new Error([message, err].join("\n")); 7 | }; -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | plugins: 2 | - path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs 3 | spec: "@yarnpkg/plugin-interactive-tools" 4 | - path: .yarn/plugins/@yarnpkg/plugin-echo-execute.cjs 5 | spec: "https://yarnplugins.com/plugin-echo-execute" 6 | 7 | yarnPath: .yarn/releases/yarn-sources.cjs 8 | -------------------------------------------------------------------------------- /src/util/constants.ts: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | import type { PackageJson } from "type-fest"; 3 | 4 | export const thisRoot = path.resolve(__dirname, "..", ".."); 5 | export const pkgJson: PackageJson = require( 6 | path.join(thisRoot, "package.json"), 7 | ); 8 | export const pluginName = pkgJson.name; 9 | 10 | export const apiTypeKeys = ["config", "node"] as const; -------------------------------------------------------------------------------- /src/util/type-util.ts: -------------------------------------------------------------------------------- 1 | import type { TranspileType, PluginModule, ApiType } from "@typeDefs/internal"; 2 | 3 | export const isBabelType = (type: TranspileType): type is "babel" => ( 4 | type === "babel" 5 | ); 6 | export const isTsNodeType = (type: TranspileType): type is "ts-node" => ( 7 | type === "ts-node" 8 | ); 9 | 10 | export const isGatsbyConfig = ( 11 | type: ApiType, 12 | mod: PluginModule, 13 | ): mod is PluginModule<"config"> => ( 14 | type === "config" 15 | ); -------------------------------------------------------------------------------- /old/types/global.d.ts: -------------------------------------------------------------------------------- 1 | declare module "@babel/register" { 2 | import { TransformOptions } from "@babel/core"; 3 | type IOnlyFn = (filename: string) => boolean; 4 | export interface IRegisterOptions extends TransformOptions { 5 | extensions?: string[]; 6 | only?: string | RegExp | IOnlyFn | null | Array< 7 | | string 8 | | RegExp 9 | | IOnlyFn 10 | >; 11 | } 12 | declare const register: (args: IRegisterOptions) => void; 13 | export declare const revert: () => void; 14 | export default register; 15 | } 16 | -------------------------------------------------------------------------------- /src/types/global.d.ts: -------------------------------------------------------------------------------- 1 | declare module "@babel/register" { 2 | import { TransformOptions } from "@babel/core"; 3 | type IOnlyFn = (filename: string) => boolean; 4 | export interface IRegisterOptions extends TransformOptions { 5 | extensions?: string[]; 6 | only?: string | RegExp | IOnlyFn | null | Array< 7 | | string 8 | | RegExp 9 | | IOnlyFn 10 | >; 11 | } 12 | declare const register: (args: IRegisterOptions) => void; 13 | export declare const revert: () => void; 14 | export default register; 15 | } 16 | -------------------------------------------------------------------------------- /src/lib/project/transpiler/restore-extensions.ts: -------------------------------------------------------------------------------- 1 | import { Module } from "@util/node"; 2 | import diff from "lodash/difference"; 3 | 4 | 5 | export const restoreExtensions = (restore: NodeJS.RequireExtensions) => { 6 | Object.entries(restore).forEach(([key, fn]) => { 7 | Module._extensions[key] = fn; 8 | }); 9 | 10 | const removeKeys = diff( 11 | Object.keys(Module._extensions), 12 | Object.keys(restore), 13 | ); 14 | if (removeKeys.length > 0) { 15 | removeKeys.forEach((remove) => { 16 | delete Module._extensions[remove]; 17 | }); 18 | } 19 | }; -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | const { typescript } = require("eslint"); 2 | 3 | module.exports = typescript({ 4 | "rules": { 5 | "@typescript-eslint/no-explicit-any": "off", 6 | "@typescript-eslint/explicit-function-return-type": "off", 7 | "@typescript-eslint/no-var-requires": "off", 8 | "no-prototype-builtins": "off", 9 | "@typescript-eslint/no-object-literal-type-assertion": "off", 10 | "@typescript-eslint/ban-types": "off", 11 | "@typescript-eslint/no-non-null-assertion": "off", 12 | "@typescript-eslint/no-empty-interface": "off", 13 | "valid-jsdoc": "off", 14 | }, 15 | }); -------------------------------------------------------------------------------- /src/settings/prop-bag.ts: -------------------------------------------------------------------------------- 1 | import merge from "lodash/mergeWith"; 2 | import type { PropertyBag } from "@typeDefs/internal"; 3 | 4 | const propBags: Record = {}; 5 | 6 | export const getPropBag = ( 7 | projectRoot: string, 8 | extendBag = {} as PropertyBag, 9 | ): PropertyBag => { 10 | const propBag = propBags[projectRoot] = ( 11 | propBags[projectRoot] || {} as PropertyBag 12 | ); 13 | if (extendBag) { 14 | // We want to mutate the prop bag, not replace it 15 | merge( 16 | propBag, 17 | extendBag, 18 | ); 19 | } 20 | return propBag; 21 | }; -------------------------------------------------------------------------------- /src/util/output.ts: -------------------------------------------------------------------------------- 1 | import { Logger } from "tslog"; 2 | import { serializeError } from "serialize-error"; 3 | 4 | import { pluginName } from "./constants"; 5 | 6 | export const logger = new Logger({ 7 | instanceName: pluginName, 8 | name: pluginName, 9 | displayDateTime: false, 10 | displayLogLevel: true, 11 | }); 12 | 13 | export class PluginError extends Error { 14 | constructor(err: string | Error) { 15 | super(`[${pluginName}]: ${err}`); 16 | 17 | if (err instanceof Error) { 18 | err.message = `[${pluginName}]: ${err.message}`; 19 | const copyErr = serializeError(err); 20 | Object.assign(this, copyErr); 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /.yarn/sdks/eslint/lib/api.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const {existsSync} = require(`fs`); 4 | const {createRequire, createRequireFromPath} = require(`module`); 5 | const {resolve} = require(`path`); 6 | 7 | const relPnpApiPath = "../../../../.pnp.cjs"; 8 | 9 | const absPnpApiPath = resolve(__dirname, relPnpApiPath); 10 | const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath); 11 | 12 | if (existsSync(absPnpApiPath)) { 13 | if (!process.versions.pnp) { 14 | // Setup the environment to be able to require eslint/lib/api.js 15 | require(absPnpApiPath).setup(); 16 | } 17 | } 18 | 19 | // Defer to the real eslint/lib/api.js your application uses 20 | module.exports = absRequire(`eslint/lib/api.js`); 21 | -------------------------------------------------------------------------------- /.yarn/sdks/typescript/bin/tsc: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const {existsSync} = require(`fs`); 4 | const {createRequire, createRequireFromPath} = require(`module`); 5 | const {resolve} = require(`path`); 6 | 7 | const relPnpApiPath = "../../../../.pnp.cjs"; 8 | 9 | const absPnpApiPath = resolve(__dirname, relPnpApiPath); 10 | const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath); 11 | 12 | if (existsSync(absPnpApiPath)) { 13 | if (!process.versions.pnp) { 14 | // Setup the environment to be able to require typescript/bin/tsc 15 | require(absPnpApiPath).setup(); 16 | } 17 | } 18 | 19 | // Defer to the real typescript/bin/tsc your application uses 20 | module.exports = absRequire(`typescript/bin/tsc`); 21 | -------------------------------------------------------------------------------- /.yarn/sdks/eslint/bin/eslint.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const {existsSync} = require(`fs`); 4 | const {createRequire, createRequireFromPath} = require(`module`); 5 | const {resolve} = require(`path`); 6 | 7 | const relPnpApiPath = "../../../../.pnp.cjs"; 8 | 9 | const absPnpApiPath = resolve(__dirname, relPnpApiPath); 10 | const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath); 11 | 12 | if (existsSync(absPnpApiPath)) { 13 | if (!process.versions.pnp) { 14 | // Setup the environment to be able to require eslint/bin/eslint.js 15 | require(absPnpApiPath).setup(); 16 | } 17 | } 18 | 19 | // Defer to the real eslint/bin/eslint.js your application uses 20 | module.exports = absRequire(`eslint/bin/eslint.js`); 21 | -------------------------------------------------------------------------------- /.yarn/sdks/typescript/lib/tsc.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const {existsSync} = require(`fs`); 4 | const {createRequire, createRequireFromPath} = require(`module`); 5 | const {resolve} = require(`path`); 6 | 7 | const relPnpApiPath = "../../../../.pnp.cjs"; 8 | 9 | const absPnpApiPath = resolve(__dirname, relPnpApiPath); 10 | const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath); 11 | 12 | if (existsSync(absPnpApiPath)) { 13 | if (!process.versions.pnp) { 14 | // Setup the environment to be able to require typescript/lib/tsc.js 15 | require(absPnpApiPath).setup(); 16 | } 17 | } 18 | 19 | // Defer to the real typescript/lib/tsc.js your application uses 20 | module.exports = absRequire(`typescript/lib/tsc.js`); 21 | -------------------------------------------------------------------------------- /.yarn/sdks/typescript/bin/tsserver: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const {existsSync} = require(`fs`); 4 | const {createRequire, createRequireFromPath} = require(`module`); 5 | const {resolve} = require(`path`); 6 | 7 | const relPnpApiPath = "../../../../.pnp.cjs"; 8 | 9 | const absPnpApiPath = resolve(__dirname, relPnpApiPath); 10 | const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath); 11 | 12 | if (existsSync(absPnpApiPath)) { 13 | if (!process.versions.pnp) { 14 | // Setup the environment to be able to require typescript/bin/tsserver 15 | require(absPnpApiPath).setup(); 16 | } 17 | } 18 | 19 | // Defer to the real typescript/bin/tsserver your application uses 20 | module.exports = absRequire(`typescript/bin/tsserver`); 21 | -------------------------------------------------------------------------------- /src/util/fs-tools.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs-extra"; 2 | import resolve from "enhanced-resolve"; 3 | 4 | export const fileExists = (fPath: string): fs.Stats | void => { 5 | try { 6 | const fStats = fs.statSync(fPath); 7 | if (fStats) return fStats; 8 | } catch (err) { 9 | // noop 10 | } 11 | }; 12 | 13 | export const getFile: typeof fileExists = (fpath) => ( 14 | fileExists(fpath) 15 | ); 16 | 17 | const moduleResolver = resolve.create.sync({ 18 | extensions: [".ts", ".js"], 19 | }); 20 | 21 | export const resolveFilePath = (startDir: string, moduleName: string): false | string => { 22 | try { 23 | return moduleResolver(startDir, moduleName); 24 | } catch (err) { 25 | return false; 26 | } 27 | }; -------------------------------------------------------------------------------- /src/util/node.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createRequire as nodeCreateRequire, 3 | createRequireFromPath as nodeCreateRequireFromPath, 4 | } from "module"; 5 | import BuiltinModule from "module"; 6 | import type { PluginModule, ApiType, BaseModuleType } from "@typeDefs/internal"; 7 | import type { TSConfigFn } from "@typeDefs/public"; 8 | 9 | export const preferDefault = (mod: BaseModuleType) => ( 10 | mod && "default" in mod && mod.default || mod 11 | ) as PluginModule | TSConfigFn; 12 | 13 | export const createRequire = nodeCreateRequire || nodeCreateRequireFromPath; 14 | 15 | interface IModule extends BuiltinModule { 16 | _extensions: NodeJS.RequireExtensions; 17 | } 18 | 19 | export const Module = BuiltinModule as unknown as IModule; -------------------------------------------------------------------------------- /.yarn/sdks/typescript/lib/typescript.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const {existsSync} = require(`fs`); 4 | const {createRequire, createRequireFromPath} = require(`module`); 5 | const {resolve} = require(`path`); 6 | 7 | const relPnpApiPath = "../../../../.pnp.cjs"; 8 | 9 | const absPnpApiPath = resolve(__dirname, relPnpApiPath); 10 | const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath); 11 | 12 | if (existsSync(absPnpApiPath)) { 13 | if (!process.versions.pnp) { 14 | // Setup the environment to be able to require typescript/lib/typescript.js 15 | require(absPnpApiPath).setup(); 16 | } 17 | } 18 | 19 | // Defer to the real typescript/lib/typescript.js your application uses 20 | module.exports = absRequire(`typescript/lib/typescript.js`); 21 | -------------------------------------------------------------------------------- /.yarn/plugins/@yarnpkg/plugin-echo-execute.cjs: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | module.exports = { 3 | name: "@yarnpkg/plugin-echo-execute", 4 | factory: function (require) { 5 | var plugin;(()=>{"use strict";var o={d:(t,e)=>{for(var r in e)o.o(e,r)&&!o.o(t,r)&&Object.defineProperty(t,r,{enumerable:!0,get:e[r]})},o:(o,t)=>Object.prototype.hasOwnProperty.call(o,t),r:o=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(o,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(o,"__esModule",{value:!0})}},t={};o.r(t),o.d(t,{default:()=>r});const e=require("@yarnpkg/core"),r={hooks:{wrapScriptExecution:async(o,t,r,a,n)=>async()=>(await e.StreamReport.start({configuration:t.configuration,json:!1,includeFooter:!1,stdout:n.stdout},async o=>{const r=e.formatUtils.applyColor(t.configuration,a,e.formatUtils.Type.NAME),i=e.formatUtils.applyColor(t.configuration,n.script,e.formatUtils.Type.CODE);o.reportInfo(e.MessageName.UNNAMED,`executing [${r}]: ${i}`)}),o())}};plugin=t})(); 6 | return plugin; 7 | } 8 | }; -------------------------------------------------------------------------------- /src/lib/serializer.ts: -------------------------------------------------------------------------------- 1 | const serializerCache = new Map(); 2 | 3 | const serializeObject = (obj: any) => ( 4 | JSON.stringify( 5 | obj, 6 | (key, val) => ( 7 | val && ( 8 | val instanceof RegExp || 9 | typeof val === "function" 10 | ) ? val.toString() : val 11 | ), 12 | ) 13 | ); 14 | 15 | class SerializerImpl { 16 | public serialize(input: any) { 17 | if (!input) return; 18 | if (typeof input === "string") return input; 19 | if (serializerCache.has(input)) { 20 | return serializerCache.get(input); 21 | } 22 | 23 | const serialized = serializeObject(input); 24 | serializerCache.set(input, serialized); 25 | return serialized; 26 | } 27 | 28 | public isEqual(obj1: any, obj2: any) { 29 | obj1 = this.serialize(obj1); 30 | obj2 = this.serialize(obj2); 31 | return obj1 === obj2; 32 | } 33 | } 34 | 35 | export const Serializer = new SerializerImpl(); -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Jeremy Albright 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/util/objects.ts: -------------------------------------------------------------------------------- 1 | import loMergeWith from "lodash/mergeWith"; 2 | import loMerge from "lodash/merge"; 3 | 4 | export const merge: typeof loMerge = ( 5 | ...objects: T 6 | ) => { 7 | const args = [ 8 | ...objects, 9 | (to: any, from: any): any => { 10 | if (to instanceof Array) { 11 | return to.concat(from); 12 | } 13 | }, 14 | ] as unknown as Parameters; 15 | return loMergeWith(...args); 16 | }; 17 | 18 | type ToArray = T extends Array 19 | ? T 20 | : Array 21 | 22 | export const arrayify = (input: T): ToArray => ( 23 | Array.isArray(input) ? input : [input] 24 | ) as ToArray; 25 | 26 | 27 | export const removeFromArray = (arr: T[], val: T, firstOnly = false) => { 28 | for (let idx = arr.length - 1; idx >= 0; --idx) { 29 | const cur = arr[idx]; 30 | if (cur === val) { 31 | arr.splice(idx, 1); 32 | if (firstOnly) return; 33 | } 34 | } 35 | }; 36 | 37 | export const popArray = (arr: T[], val: T) => { 38 | removeFromArray(arr, val, true); 39 | }; -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "sourceMap": true, 6 | "declaration": true, 7 | "outDir": "./dist", 8 | "preserveSymlinks": true, 9 | "removeComments": false, 10 | "skipLibCheck": true, 11 | "strict": true, 12 | "noImplicitAny": true, 13 | "strictNullChecks": true, 14 | "noImplicitThis": true, 15 | "alwaysStrict": true, 16 | "noImplicitReturns": false, 17 | "noFallthroughCasesInSwitch": true, 18 | "moduleResolution": "node", 19 | "esModuleInterop": true, 20 | "downlevelIteration": true, 21 | "plugins": [ 22 | { "transform": "@zerollup/ts-transform-paths" }, 23 | { "transform": "ts-transformer-keys/transformer" } 24 | ], 25 | "baseUrl": ".", 26 | "paths": { 27 | "@lib/*": ["src/lib/*"], 28 | "@util/*": ["src/util/*"], 29 | "@settings": ["src/settings"], 30 | "@settings/*": ["src/settings/*"], 31 | "@typeDefs/*": ["src/types/*"], 32 | "@typeDefs": ["src/types"], 33 | "@/*": ["src/*"] 34 | } 35 | }, 36 | "include": [ 37 | "./src/**/*", 38 | "./old/**/*" 39 | ], 40 | "exclude": [] 41 | } -------------------------------------------------------------------------------- /old/utils/fs-tools.ts: -------------------------------------------------------------------------------- 1 | import * as path from "path"; 2 | import * as fs from "fs"; 3 | import { keys } from "ts-transformer-keys"; 4 | import type { ValidExts } from "../types"; 5 | 6 | export const allExt = keys>(); 7 | 8 | export const getAbsoluteRelativeTo = (from: string, to?: string): string => { 9 | if (to && path.isAbsolute(to)) return to; 10 | const absolute = path.join( 11 | path.isAbsolute(from) ? from : path.resolve(from), 12 | to || "", 13 | ); 14 | return absolute; 15 | }; 16 | 17 | export const fileExists = (fPath: string): fs.Stats | void => { 18 | try { 19 | const fStats = fs.statSync(fPath); 20 | if (fStats) return fStats; 21 | } catch (err) { 22 | // noop 23 | } 24 | }; 25 | 26 | export const checkFileWithExts = (fPath: string, extensions: ValidExts[] = allExt): string => { 27 | for (const ext of extensions) { 28 | if (fileExists(fPath + ext)) return fPath + ext; 29 | } 30 | return ""; 31 | }; 32 | 33 | export const isDir = (fPath: string): boolean => { 34 | try { 35 | const fStats = fs.statSync(fPath); 36 | if (fStats.isDirectory()) return true; 37 | } catch (err) { 38 | // noop 39 | } 40 | 41 | return false; 42 | }; -------------------------------------------------------------------------------- /src/lib/project/transpiler/allowed-files.ts: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | 3 | const slash = (p: string) => ( 4 | p.endsWith(path.sep) ? p : p + path.sep 5 | ); 6 | 7 | class AllowedFilesImpl { 8 | private allowedDirs: string[] = []; 9 | 10 | public get len(): number { 11 | return this.allowedDirs.length - 1; 12 | } 13 | 14 | public addDir(dir: string) { 15 | this.allowedDirs.push(slash(dir)); 16 | } 17 | public removeDir(dir: string) { 18 | const len = this.len; 19 | this.allowedDirs.forEach((_, i) => { 20 | const idx = len - i; 21 | const cur = this.allowedDirs[idx]; 22 | if (cur === dir) { 23 | this.allowedDirs.splice(idx, 1); 24 | } 25 | }); 26 | } 27 | 28 | public allowed(filePath: string) { 29 | for (const cur of this.allowedDirs) { 30 | const isNodeModules = filePath.indexOf("node_modules") > -1; 31 | 32 | const isTsInNodeModules = ( 33 | isNodeModules && 34 | path.extname(filePath).startsWith(".ts") 35 | ); 36 | 37 | const isInAllowedDir = filePath.startsWith(cur); 38 | 39 | if (isInAllowedDir) { 40 | if (!isNodeModules) return true; 41 | if (isNodeModules && isTsInNodeModules) return true; 42 | } 43 | } 44 | } 45 | } 46 | 47 | export const AllowedFiles = new AllowedFilesImpl(); -------------------------------------------------------------------------------- /.yarn/build-state.yml: -------------------------------------------------------------------------------- 1 | # Warning: This file is automatically generated. Removing it is fine, but will 2 | # cause all your builds to become invalidated. 3 | 4 | # core-js@npm:2.6.11 5 | "15178ded27ab674ae2054269453d809bdb1d00b98392a34947b5d43ea7a5811e5674c2fda7d48bb653b24a3506b0a8aa126bbac861bdeba93438ec6c7efb2d9d": 6 | b575306f9c9f1ed8ea4fdd6247752a05f9b54134d191f761a03542a552dfe7ef26275fd9e31ccb59a1a892fbda1676a569a6a196d041025b65a4bb7dd996d881 7 | 8 | # core-js@npm:3.10.1 9 | "e4bd755b23a200a8c9787b7f1c9b405da4d00c9a9d492be35336f85c46a8153530059cef669d6ecb40fe44b74c6fd1ed484928da52a6a509859d5d3252c1c4cc": 10 | 04f53c7740656c6bd72875e7b087e52982d28e4685d8824b1a1c6145c318dcc5b8025d92509f37f3bcea0cf36a6506fc37f554595a5541f4e1ba120658aa5bf7 11 | 12 | # gatsby-cli@npm:2.9.0 13 | "c0d33e2f06f6c5efb3a874786563501d6350c8d6988c6737cf46d6239a81169104e02b8554fc9e2ca3034df8182e6903d58ad9723222d568df425af89e7a4c8d": 14 | af90a12259bbf63844079227e3b408021976605e2208b961d8f0ddf87690ec727c7ebc6a9258eb48a657ddb61ed445eda62acc6061341e0a515125c62a4c8768 15 | 16 | # gatsby-telemetry@npm:1.1.49 17 | "577da9cd9387e0ee209f78909c3d4829905d623cbe43edeece559e4f076290396d5934f5480590f28854d5db6c07ad59165a832d831500083aa030993ca1edad": 18 | 365556729ba3184d9dfb978769d425c025c408944d7a772f518a1fd643095cd1860e9607581dc5145ea11c98a51c4bee45424598c149ea848fb9910bae2938a3 19 | 20 | # gatsby@virtual:455ed1d2464b485cc86ccc4ab476e3f5467d6e4b22bcd693bb203b6cd5a170f39c93cb8846df1485e5f45852a3adfd0d5db454abd799e7e78a57a9d40b32555c#npm:2.19.23 21 | "376880a9caaaed2b1c1f7ca63434ddfc7a8c879daef5a32506d5818bb4de0f71a5cce5ffb960a1368fab735f7c81ac72f78979ea0935ca4e2eddf3b0e3aa826a": 22 | f4e544b87320078806655ea49e4d1faedf53b1bdbbd0928ec5c00724e51f2584aead68dbc86adc0dbdf237f353bc313871ebed869f7872af206d87b19dd17f62 23 | -------------------------------------------------------------------------------- /old/utils/node.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createRequire as nodeCreateRequire, 3 | createRequireFromPath as nodeCreateRequireFromPath, 4 | } from "module"; 5 | import RequireRegistrar from "./register"; 6 | import OptionsHandler from "./options-handler"; 7 | import { throwError } from "./errors"; 8 | 9 | import type { 10 | GatsbyConfigTypes, 11 | GatsbyEndpoints, 12 | EndpointReturnTypes, 13 | EndpointReturnObject, 14 | InferredConfigType, 15 | } from "../types"; 16 | 17 | export const preferDefault = (compiled: any) => compiled && compiled.default || compiled; 18 | 19 | export const tryRequireModule = ( 20 | configType: T, 21 | endpoints?: GatsbyEndpoints, 22 | startRegistrar = true, 23 | pluginName?: string, 24 | ): EndpointReturnTypes => { 25 | if (!endpoints || !endpoints[configType]) return {} as EndpointReturnTypes; 26 | const modulePath = endpoints[configType]![0]; 27 | let readModule = {} as EndpointReturnTypes; 28 | try { 29 | if (startRegistrar) RequireRegistrar.start(configType, pluginName); 30 | readModule = preferDefault(require(modulePath)); 31 | } catch (err) { 32 | throwError(`[gatsby-plugin-ts-config] An error occurred while reading your gatsby-${configType}!`, err); 33 | } finally { 34 | if (startRegistrar) RequireRegistrar.stop(); 35 | } 36 | return readModule; 37 | }; 38 | 39 | interface IGetModuleObject { 40 | < 41 | T extends EndpointReturnTypes, 42 | K extends InferredConfigType = InferredConfigType, 43 | >(mod: T): EndpointReturnObject; 44 | } 45 | 46 | export const getModuleObject: IGetModuleObject = (mod) => { 47 | if (mod instanceof Function) { 48 | return (mod as Function)(OptionsHandler.public(), OptionsHandler.propertyBag); 49 | } 50 | return mod; 51 | 52 | }; 53 | 54 | export const createRequire = nodeCreateRequire || nodeCreateRequireFromPath; -------------------------------------------------------------------------------- /old/utils/preset.ts: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | const r = (pkg: string): string => require.resolve(pkg); 3 | 4 | const preset = () => { 5 | const nodeVersion = process.version.split("v")[1]; 6 | const { NODE_ENV, BABEL_ENV } = process.env; 7 | 8 | const IS_TEST = (BABEL_ENV || NODE_ENV) === `test`; 9 | 10 | const nodeConfig = { 11 | corejs: 2, 12 | useBuiltIns: `entry`, 13 | targets: { 14 | node: nodeVersion, 15 | }, 16 | }; 17 | 18 | return { 19 | presets: [ 20 | [ 21 | r(`@babel/preset-env`), 22 | Object.assign( 23 | { 24 | loose: true, 25 | debug: false, 26 | shippedProposals: true, 27 | modules: `commonjs`, 28 | }, 29 | nodeConfig, 30 | ), 31 | ], 32 | r(`@babel/preset-react`), 33 | r(`@babel/preset-flow`), 34 | ], 35 | plugins: [ 36 | r(`@babel/plugin-proposal-nullish-coalescing-operator`), 37 | r(`@babel/plugin-proposal-optional-chaining`), 38 | [ 39 | r(`@babel/plugin-transform-runtime`), 40 | { 41 | absoluteRuntime: path.dirname(require.resolve("@babel/runtime/package.json")), 42 | }, 43 | ], 44 | r(`@babel/plugin-syntax-dynamic-import`), 45 | IS_TEST && r(`babel-plugin-dynamic-import-node`), 46 | ].filter(Boolean), 47 | overrides: [ 48 | { 49 | test: [`**/*.ts`], 50 | presets: [[r(`@babel/preset-typescript`), { isTSX: false }]], 51 | }, 52 | { 53 | test: [`**/*.tsx`], 54 | presets: [[r(`@babel/preset-typescript`), { 55 | isTSX: true, 56 | allExtensions: true, 57 | }]], 58 | }, 59 | ], 60 | }; 61 | }; 62 | 63 | export = preset; -------------------------------------------------------------------------------- /src/settings/register.ts: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | 3 | import { merge } from "@util/objects"; 4 | 5 | import type { TransformOptions as BabelOptions } from "@babel/core"; 6 | import type { RegisterOptions as TSNodeOptions } from "ts-node"; 7 | import type { 8 | TranspileType, 9 | TranspilerOptions, 10 | } from "@typeDefs/internal"; 11 | 12 | const getDefaultOptions = ( 13 | type: TranspileType, 14 | projectRoot: string, 15 | ): BabelOptions | TSNodeOptions => { 16 | switch (type) { 17 | case "babel": { 18 | return { 19 | sourceMaps: "inline", 20 | sourceRoot: projectRoot, 21 | cwd: projectRoot, 22 | babelrc: false, 23 | presets: [ 24 | // require.resolve("@babel/preset-typescript"), 25 | require.resolve("./babel/preset"), 26 | ], 27 | } as BabelOptions; 28 | } 29 | case "ts-node": { 30 | return { 31 | // project: path.join(projectRoot, "tsconfig.json"), 32 | skipProject: true, 33 | files: true, 34 | compilerOptions: { 35 | module: "commonjs", 36 | target: "es2015", 37 | allowJs: true, 38 | noEmit: true, 39 | declaration: false, 40 | importHelpers: true, 41 | resolveJsonModule: true, 42 | esModuleInterop: true, 43 | jsx: "preserve", 44 | // sourceMap: true, 45 | }, 46 | } as TSNodeOptions; 47 | } 48 | } 49 | }; 50 | 51 | export const getRegisterOptions = < 52 | T extends TranspileType, 53 | >( 54 | projectRoot: string, 55 | type: T, 56 | addOptions?: TranspilerOptions, 57 | ): TranspilerOptions => { 58 | const defaultOptions = ( 59 | getDefaultOptions(type, projectRoot) as TranspilerOptions 60 | ); 61 | return merge( 62 | {}, 63 | defaultOptions, 64 | addOptions, 65 | ); 66 | }; -------------------------------------------------------------------------------- /src/util/project-meta.ts: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | import findUp from "find-up"; 3 | import callsites from "callsites"; 4 | import { thisRoot } from "./constants"; 5 | import { PluginError } from "./output"; 6 | 7 | import type { PackageJson } from "type-fest"; 8 | 9 | export const getCallSite = () => ( 10 | callsites().find((site) => ( 11 | // Get the first call site that isn't a part of this plugin 12 | !site.getFileName()?.startsWith(thisRoot) 13 | )) 14 | ); 15 | 16 | type PackageJsonDetails = [string, PackageJson]; 17 | 18 | const pkgJsonStartCache: Record = {}; 19 | const pkgJsonCache: Record = {}; 20 | 21 | export const getProjectPkgJson = (start = process.cwd()): PackageJsonDetails | null => { 22 | if (start in pkgJsonStartCache) return pkgJsonStartCache[start]; 23 | 24 | const pkgJsonPath = findUp.sync("package.json", { 25 | cwd: start, 26 | }); 27 | return pkgJsonStartCache[start] = ( 28 | !pkgJsonPath 29 | ? null 30 | : pkgJsonCache[pkgJsonPath] = ( 31 | pkgJsonCache[pkgJsonPath] || [ 32 | path.dirname(pkgJsonPath), 33 | require(pkgJsonPath) as PackageJson, 34 | ] 35 | ) 36 | ); 37 | }; 38 | 39 | export type ProjectMeta = ReturnType; 40 | 41 | export const getProject = () => { 42 | const callSite = getCallSite(); 43 | const callFile = callSite?.getFileName(); 44 | if (!callFile) { 45 | throw new PluginError("Unable to determine call site"); 46 | } 47 | 48 | const callDir = path.dirname(callFile); 49 | const [projectRoot, pkgJson] = getProjectPkgJson(callDir) || []; 50 | if (!pkgJson || !projectRoot) { 51 | throw new PluginError("Unable to locate project root"); 52 | } 53 | 54 | const projectName = pkgJson.name; 55 | if (!projectName) { 56 | throw new PluginError("Unable to determine caller's project name"); 57 | } 58 | 59 | return { 60 | projectRoot, 61 | projectName, 62 | pkgJson, 63 | }; 64 | }; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn 69 | .yarn-integrity 70 | .yarn/cache 71 | 72 | # dotenv environment variables file 73 | .env 74 | .env.test 75 | 76 | # parcel-bundler cache (https://parceljs.org/) 77 | .cache 78 | 79 | # Next.js build output 80 | .next 81 | 82 | # Nuxt.js build / generate output 83 | .nuxt 84 | dist 85 | 86 | # Gatsby files 87 | .cache/ 88 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 89 | # https://nextjs.org/blog/next-9-1#public-directory-support 90 | # public 91 | 92 | # vuepress build output 93 | .vuepress/dist 94 | 95 | # Serverless directories 96 | .serverless/ 97 | 98 | # FuseBox cache 99 | .fusebox/ 100 | 101 | # DynamoDB Local files 102 | .dynamodb/ 103 | 104 | # TernJS port file 105 | .tern-port 106 | 107 | .npmrc -------------------------------------------------------------------------------- /src/settings/babel/preset.ts: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | const r = (pkg: string): string => require.resolve(pkg); 3 | 4 | const preset = () => { 5 | const nodeVersion = process.version.split("v")[1]; 6 | const { NODE_ENV, BABEL_ENV } = process.env; 7 | 8 | const IS_TEST = (BABEL_ENV || NODE_ENV) === `test`; 9 | 10 | const nodeConfig = { 11 | corejs: 2, 12 | useBuiltIns: `entry`, 13 | targets: { 14 | node: nodeVersion, 15 | }, 16 | }; 17 | 18 | return { 19 | presets: [ 20 | [ 21 | r(`@babel/preset-env`), 22 | Object.assign( 23 | { 24 | loose: true, 25 | debug: false, 26 | shippedProposals: true, 27 | modules: `commonjs`, 28 | }, 29 | nodeConfig, 30 | ), 31 | ], 32 | r(`@babel/preset-react`), 33 | r(`@babel/preset-flow`), 34 | ], 35 | plugins: [ 36 | r(`@babel/plugin-proposal-nullish-coalescing-operator`), 37 | r(`@babel/plugin-proposal-optional-chaining`), 38 | [ 39 | r(`@babel/plugin-transform-runtime`), 40 | { 41 | absoluteRuntime: path.dirname(require.resolve("@babel/runtime/package.json")), 42 | }, 43 | ], 44 | r(`@babel/plugin-syntax-dynamic-import`), 45 | IS_TEST && r(`babel-plugin-dynamic-import-node`), 46 | ].filter(Boolean), 47 | overrides: [ 48 | { 49 | test: [`**/*.ts`], 50 | presets: [[ 51 | r(`@babel/preset-typescript`), 52 | { isTSX: false }, 53 | "simple-typescript-module-preset", 54 | ]], 55 | }, 56 | { 57 | test: [`**/*.tsx`], 58 | presets: [[ 59 | r(`@babel/preset-typescript`), 60 | { 61 | isTSX: true, 62 | allExtensions: true, 63 | }, 64 | "tsx-typescript-module-preset", 65 | ]], 66 | }, 67 | ], 68 | }; 69 | }; 70 | 71 | export = preset; -------------------------------------------------------------------------------- /old/utils/babel.ts: -------------------------------------------------------------------------------- 1 | import * as path from "path"; 2 | import { 3 | ConfigItem, 4 | CreateConfigItemOptions, 5 | createConfigItem, 6 | } from "@babel/core"; 7 | 8 | type ICreatePresetProps = string | { 9 | name: string; 10 | options?: object; 11 | } 12 | export const createPresets: ( 13 | presets: ICreatePresetProps[], 14 | options?: CreateConfigItemOptions 15 | ) => ConfigItem[] = (presets, options) => { 16 | const configItems: ConfigItem[] = presets.map((curPreset) => { 17 | const presetName = typeof curPreset === "string" 18 | ? curPreset 19 | : curPreset.name; 20 | 21 | const presetPath = path.isAbsolute(presetName) 22 | ? presetName 23 | : require.resolve(presetName); 24 | 25 | const presetOpts = typeof curPreset === "string" 26 | ? {} 27 | : curPreset.options; 28 | 29 | const createOpts: CreateConfigItemOptions = { 30 | ...options, 31 | type: "preset", 32 | }; 33 | 34 | return createConfigItem( 35 | [ 36 | presetPath, 37 | presetOpts, 38 | ], 39 | createOpts, 40 | ); 41 | }); 42 | return configItems; 43 | }; 44 | 45 | // type PresetFn = (context: ConfigAPI, options: object) => TransformOptions; 46 | // type IAddOptsToPresetPlugin = (preset: PresetFn, pluginName: string, opts: object) => PresetFn; 47 | // export const addOptsToPreset: IAddOptsToPresetPlugin = (preset, name, opts) => { 48 | // const checkItemPath = (item: PluginItem): boolean => { 49 | // if (!(typeof item === 'string')) return false; 50 | // const checkPath = name.replace(/^[/\\]+|[/\\]+$/g, ''); 51 | // const pattern = new RegExp(`[/]${checkPath.replace(/[/]/g, '\\/')}[/]`, 'i'); 52 | // return pattern.test(item.replace(/\\/g, '/')); 53 | // }; 54 | 55 | // return (context, options = {}) => { 56 | // const presetResult = preset(context, options); 57 | // for (const collection of [presetResult.plugins, presetResult.presets]) { 58 | // if (collection) { 59 | // collection.forEach((item, idx) => { 60 | // if (checkItemPath(item)) { 61 | // collection![idx] = [ 62 | // item, 63 | // opts, 64 | // ]; 65 | // } 66 | // }); 67 | // } 68 | // } 69 | // return presetResult; 70 | // }; 71 | // }; -------------------------------------------------------------------------------- /old/gatsby/gatsby-node.ts: -------------------------------------------------------------------------------- 1 | import { GatsbyNode as RootGatsbyNode } from "gatsby"; 2 | import { 3 | tryRequireModule, 4 | getModuleObject, 5 | } from "../utils/node"; 6 | import { setupGatsbyEndpointProxies } from "../utils/endpoints"; 7 | import OptionsHandler from "../utils/options-handler"; 8 | import type { ITSConfigPluginOptions } from "../types"; 9 | 10 | const { endpoints, cacheDir } = OptionsHandler.get(); 11 | 12 | type GatsbyNode = Required; 13 | const gatsbyNodeModule = tryRequireModule("node", endpoints, false); 14 | const gatsbyNode = getModuleObject(gatsbyNodeModule); 15 | 16 | type GatsbyNodeFunctions = keyof GatsbyNode; 17 | type GatsbyNodeFnParameters = Parameters; 18 | type GatsbyNodeFnReturn = ReturnType; 19 | type GatsbyNodeFn = (...args: GatsbyNodeFnParameters) => GatsbyNodeFnReturn; 20 | 21 | const isFunction = (fn: any): fn is Function => ( 22 | typeof fn === "function" 23 | ); 24 | 25 | const wrapGatsbyNode = ( 26 | endpoint: T, 27 | cb: GatsbyNodeFn, 28 | ): GatsbyNodeFn => { 29 | return (...args) => { 30 | const callOriginal = () => { 31 | const ep = gatsbyNode[endpoint]; 32 | if (isFunction(ep)) { 33 | return (ep as Function)(...args); 34 | } 35 | }; 36 | const result = cb(...args); 37 | if (result instanceof Promise) { 38 | return result.then(() => { 39 | return callOriginal(); 40 | }) as GatsbyNodeFnReturn; 41 | } else { 42 | return callOriginal(); 43 | } 44 | }; 45 | }; 46 | 47 | export = { 48 | ...gatsbyNode, 49 | 50 | onPreInit: wrapGatsbyNode("onPreInit", (_, options) => { 51 | delete (options as ITSConfigPluginOptions).props; 52 | }), 53 | 54 | onCreateWebpackConfig: wrapGatsbyNode("onCreateWebpackConfig", ({ 55 | actions: { 56 | setWebpackConfig, 57 | }, 58 | }) => { 59 | setWebpackConfig({ 60 | resolve: { 61 | alias: { 62 | "gatsby-plugin-ts-config-cache": cacheDir, 63 | }, 64 | }, 65 | }); 66 | return; 67 | }), 68 | 69 | onPreBootstrap: wrapGatsbyNode("onPreBootstrap", () => { 70 | setupGatsbyEndpointProxies({ 71 | resolvedEndpoints: endpoints, 72 | distDir: __dirname, 73 | cacheDir, 74 | }); 75 | }), 76 | } as GatsbyNode; -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Pnpm | Launch Build", 11 | "skipFiles": [ 12 | "/**" 13 | ], 14 | "runtimeArgs": [ 15 | "--nolazy" 16 | ], 17 | "cwd": "${env:GATSBY_TEST_ROOT}", 18 | "program": "${env:GATSBY_TEST_ROOT}/node_modules/gatsby/dist/bin/gatsby.js", 19 | "args": [ 20 | "build" 21 | ], 22 | "sourceMaps": true, 23 | "sourceMapPathOverrides": { 24 | "webpack:///./~/*": "${workspaceRoot}/node_modules/*", 25 | "webpack:///./*": "${workspaceRoot}/*", 26 | "webpack:///*": "*" 27 | }, 28 | "console": "integratedTerminal" 29 | }, 30 | { 31 | "type": "node", 32 | "request": "launch", 33 | "name": "Pnpm | Launch Develop", 34 | "skipFiles": [ 35 | "/**" 36 | ], 37 | "runtimeArgs": [ 38 | "--nolazy" 39 | ], 40 | "cwd": "${env:GATSBY_TEST_ROOT}", 41 | "program": "${env:GATSBY_TEST_ROOT}/node_modules/gatsby/dist/bin/gatsby.js", 42 | "args": [ 43 | "develop" 44 | ], 45 | "sourceMaps": true, 46 | "console": "integratedTerminal" 47 | }, 48 | { 49 | "type": "node", 50 | "request": "launch", 51 | "name": "Yarn | Launch Build", 52 | "skipFiles": [ 53 | "/**", 54 | "**/.pnp.js" 55 | ], 56 | "env": { 57 | "VSCODE_LAUNCH_APP": "gatsby/dist/bin/gatsby.js" 58 | }, 59 | "runtimeArgs": [ 60 | "--nolazy", 61 | "-r", 62 | "${env:GATSBY_TEST_ROOT}/.pnp.js" 63 | ], 64 | "cwd": "${env:GATSBY_TEST_ROOT}", 65 | "program": "${env:GATSBY_TEST_ROOT}/.vscode/launch.js", 66 | "args": [ 67 | "build" 68 | ], 69 | "sourceMaps": true, 70 | "console": "integratedTerminal" 71 | }, 72 | { 73 | "type": "node", 74 | "request": "launch", 75 | "name": "Yarn | Launch Develop", 76 | "skipFiles": [ 77 | "/**", 78 | "**/.pnp.js" 79 | ], 80 | "env": { 81 | "VSCODE_LAUNCH_APP": "gatsby/dist/bin/gatsby.js" 82 | }, 83 | "runtimeArgs": [ 84 | "--nolazy", 85 | "-r", 86 | "${env:GATSBY_TEST_ROOT}/.pnp.js" 87 | ], 88 | "cwd": "${env:GATSBY_TEST_ROOT}", 89 | "program": "${env:GATSBY_TEST_ROOT}/.vscode/launch.js", 90 | "args": [ 91 | "develop" 92 | ], 93 | "sourceMaps": true, 94 | "console": "integratedTerminal" 95 | } 96 | ] 97 | } -------------------------------------------------------------------------------- /src/lib/api-module.ts: -------------------------------------------------------------------------------- 1 | import { isGatsbyConfig } from "@util/type-util"; 2 | import { preferDefault } from "@util/node"; 3 | import { resolveFilePath } from "@util/fs-tools"; 4 | 5 | import { processPluginCache } from "./process-plugins"; 6 | 7 | import type { Project } from "@lib/project"; 8 | import type { 9 | InitValue, 10 | PluginModule, 11 | ProjectPluginModule, 12 | TSConfigFn, 13 | TranspilerReturn, 14 | } from "@typeDefs"; 15 | 16 | interface IProcessApiModuleOptions { 17 | init: InitValue; 18 | project: T; 19 | unwrapApi: boolean; 20 | } 21 | 22 | export type ApiModuleProcessor = typeof processApiModule; 23 | 24 | export const processApiModule = ({ 25 | init, 26 | project, 27 | unwrapApi, 28 | }: IProcessApiModuleOptions) => { 29 | const projectRoot = project.projectRoot; 30 | const apiType = project.apiType; 31 | 32 | const { 33 | resolveImmediate = true, 34 | } = project.getApiOptions(apiType); 35 | 36 | let apiModule = preferDefault( 37 | project.transpiler( 38 | init, 39 | unwrapApi, 40 | ), 41 | ) as TranspilerReturn; 42 | 43 | let gatsbyNode: TSConfigFn<"node"> | undefined = undefined; 44 | 45 | if (apiType === "config") { 46 | const gatsbyNodePath = resolveFilePath(projectRoot, "./gatsby-node"); 47 | 48 | /** 49 | * We want to pre-process `gatsby-node` from `gatsby-config` because: 50 | * 51 | * 1. We want to get all of the chained imports from `gatsby-node`; and, 52 | * 2. We want to transpile it in case it is a `.ts` file, so that Gatsby 53 | * can consume it. 54 | */ 55 | if (gatsbyNodePath) { 56 | project.setApiOption("node", "resolveImmediate", false); 57 | gatsbyNode = processApiModule({ 58 | init: gatsbyNodePath, 59 | project: project.clone("node"), 60 | unwrapApi: true, 61 | }) as TSConfigFn<"node">; 62 | project.setApiOption("node", "resolveImmediate", true); 63 | } 64 | } 65 | 66 | if (typeof apiModule === "function" && resolveImmediate) { 67 | apiModule = project.resolveConfigFn(apiModule) as ProjectPluginModule; 68 | } 69 | 70 | if (typeof gatsbyNode === "function") { 71 | project.resolveConfigFn(gatsbyNode); 72 | } 73 | 74 | if (!apiModule) apiModule = {}; 75 | 76 | /** 77 | * Time to transpile/process local plugins 78 | */ 79 | if (isGatsbyConfig(apiType, apiModule) && typeof apiModule === "object") { 80 | const gatsbyConfig = apiModule as PluginModule<"config">; 81 | gatsbyConfig.plugins = processPluginCache( 82 | project, 83 | processApiModule, 84 | gatsbyConfig.plugins, 85 | ); 86 | } 87 | 88 | return apiModule as ProjectPluginModule; 89 | }; -------------------------------------------------------------------------------- /old/docs/PROPERTY-BAG.md: -------------------------------------------------------------------------------- 1 | ## Property bag 2 | 3 | The property bag is a mutable object that is passed between all of the functions that this plugin supports. 4 | 5 | ### Initial declaration of the property bag 6 | 7 | You may declare the property bag initially in the original declaration of this plugin. This is in the 8 | root `gatsby-config.js`: 9 | 10 | ```js 11 | // gatsby-config.js 12 | module.exports = { 13 | plugins: [ 14 | { 15 | resolve: "gatsby-plugin-ts-config", 16 | options: { 17 | props: { 18 | test: 1234 19 | } 20 | } 21 | } 22 | ] 23 | } 24 | 25 | // or 26 | const { generateConfig } = require("gatsby-plugin-ts-config"); 27 | 28 | module.exports = generateConfig( 29 | // Plugin options 30 | { 31 | configDir: ".gatsby", 32 | }, 33 | 34 | // Property bag 35 | { 36 | test: 1234 37 | } 38 | ); 39 | ``` 40 | 41 | More details about plugin declaration below. 42 | 43 | ### Receiving properties 44 | 45 | Every function that this plugin utilizes will receive the property bag as the second parameter. This includes: 46 | * The `gatsby-*` default export functions 47 | 48 | ```ts 49 | // .gatsby/gatsby-config.ts 50 | import { ITSConfigFn } from "gatsby-plugin-ts-config"; 51 | 52 | interface IPropBag { 53 | test: number; 54 | } 55 | 56 | export default ({ projectRoot }, props) => { 57 | console.log(props.test) // 1234 58 | console.log(projectRoot); // The process cwd 59 | 60 | // Object is mutable; this is saved 61 | props.foo = "asdf"; 62 | } as ITSConfigFn<"gatsby", IPropBag>; 63 | ``` 64 | 65 | ```ts 66 | // .gatsby/gatsby-config.ts 67 | import { ITSConfigFn } from "gatsby-plugin-ts-config"; 68 | 69 | interface IPropBag { 70 | test: number; 71 | foo: string; 72 | } 73 | 74 | export default ({ projectRoot }, props) => { 75 | console.log(props.test) // 1234 76 | console.log(props.foo) // asdf 77 | } as ITSConfigFn<"node", IPropBag>; 78 | ``` 79 | 80 | * The `includePlugins()` utility function 81 | 82 | ```ts 83 | // .gatsby/gatsby-config.ts 84 | import { includePlugins, IGatsbyPluginDef } from "gatsby-plugin-ts-config"; 85 | 86 | interface IPropBag { 87 | test: number; 88 | } 89 | 90 | includePlugins< 91 | // Defines a loosely typed plugin array 92 | IGatsbyPluginDef, 93 | // Defines the property bag received by the callback function 94 | IPropBag 95 | >([ /** some plugins */ ], ({ projectRoot }, { test }) => { 96 | console.log(test); // 1234 97 | }); 98 | ``` 99 | 100 | ### Order of executions 101 | 102 | Because it is mutable, it is important that you remember the order of executions: 103 | 104 | * `gatsby-config` default export function 105 | * `includePlugins`: 106 | * first parameter, if it is a function 107 | * second parameter 108 | * `gatsby-node` default export function 109 | -------------------------------------------------------------------------------- /old/docs/GATSBY-CONFIG-UTILS.md: -------------------------------------------------------------------------------- 1 | ## Gatsby Config Utilities 2 | 3 | [includePlugins]: #includeplugins 4 | 5 | 1. [includePlugins()][includePlugins] 6 | 7 | ### `includePlugins()` 8 | 9 | This utility function will allow you to declare Gatsby plugins using a more robust 10 | process. 11 | 12 | Because your Gatsby plugins are essentially being declared by this plugin, Gatsby 13 | won't be able to resolve any local plugins (using the `/plugins` directory). This function 14 | will allow you to resolve use those plugins. 15 | 16 | Because some local plugins may be written in Typescript, too, this function will transpile 17 | them before passing them on to Gatsby. 18 | 19 | #### Function Technical Details 20 | 21 | * There are few overloads for this function. 22 | 23 | 1. You may include an array of plugins in the first parameter, which takes the same shape as Gatsby's 24 | plugin array. 25 | * You may optionally include a **callback function** in the second parameter. It will receive all of the 26 | same parameters defined above for the `gatsby-*` as-a-function. It must return an array in the same 27 | shape as Gatsby's plugin array. 28 | 29 | 2. Or, you may include only the **callback function** in the first parameter. 30 | 31 | * Any plugins defined in the array in the first parameter will be resolved before the normal Gatsby plugin 32 | array is processed. This means that any plugins resolved this way will be available to the normal 33 | function export. 34 | 35 | * Any callback functions defined this way will be called, in sequence, after the normal Gatsby array has 36 | been processed. This means that all of the plugins from the previously defined array(s), and the normal 37 | Gatsby array will be available in the callback's parameters. 38 | 39 | * The ordering of the plugins will be the same as the order they are resolved. 40 | 41 | 1. Arrays resolved by this function 42 | 2. Standard Gatsby array 43 | 3. Callback function arrays 44 | 45 | #### Callback Function Parameter 46 | 47 | * Callback functions defined for use by the `includePlugins()` utility will receive the same parameters 48 | as the [`gatsby-*` default export functions](#gatsby--as-a-function-parameters) do. 49 | 50 | #### Generic Type Parameters 51 | 52 | 1. A union of the potential plugins (and their options). Structuring these options is made easier 53 | by the type utility, `IGatsbyPluginDef`. 54 | * The default value of this parameter represents a loosely typed plugin array, which is essentially 55 | the same as `IGatsbyPluginDef` without any of its type parameters. 56 | 57 | 2. The object type of the property bag that was defined in the `props` plugin option, or the second 58 | parameter passed to the `generateConfig()` function. 59 | 60 | ```js 61 | // Defaults 62 | import { IGatsbyPluginDef } from "gatsby-plugin-ts-config"; 63 | 64 | includePlugins> 65 | ``` -------------------------------------------------------------------------------- /old/docs/IMPLEMENTATION-EXAMPLES.md: -------------------------------------------------------------------------------- 1 | ## Examples 2 | 3 | * Using types in `gatsby-config` 4 | 5 | _One good example is the plugin [`gatsby-plugin-pnpm`](https://github.com/Js-Brecht/gatsby-plugin-pnpm), 6 | since it exports the interface for the options that are valid for it. They could be used like this:_ 7 | 8 | _Another example would be [`gatsby-source-filesystem`](https://github.com/gatsbyjs/gatsby/blob/c1368c06fb975bd792ebb8f9d0c5a5e4ebcba388/packages/gatsby-source-filesystem/index.d.ts#L100-L103). As you'll notice, its interface is already 9 | configured to use the `resolve` and `options` properties, so `IGatsbyPluginDef` wouldn't be needed._ 10 | 11 | ```ts 12 | import type { IPluginOptions as IPnpmPluginOptions } from 'gatsby-plugin-pnpm'; 13 | import type { FileSystemConfig } from 'gatsby-plugin-filesystem'; 14 | import { ITSConfigFn, IGatsbyPluginDef, includePlugins } from 'gatsby-plugin-ts-config'; 15 | 16 | type PluginDefs = ( 17 | | IGatsbyPluginDef<'gatsby-plugin-pnpm', IPnpmPluginOptions> 18 | | FileSystemConfig 19 | ) 20 | 21 | includePlugins( 22 | [ 23 | { 24 | resolve: 'gatsby-plugin-pnpm', // <-- this will be typed 25 | options: { 26 | ... // <-- These will be typed 27 | } 28 | } 29 | ], 30 | 31 | ({ projectRoot }) => ([ 32 | { 33 | resolve: 'gatsby-source-filesystem' // <-- this will be typed 34 | options: { 35 | ... // <-- These will be typed 36 | } 37 | } 38 | ]) 39 | ) 40 | 41 | // If you want to define a plugin that will receive the previous callback's 42 | // resolved plugins (gatsby-source-filesystem), then you can call the function 43 | // again 44 | 45 | includePlugins< 46 | | IGatsbyPluginDef<'foo-plugin-that-receives-filesystem-endpoints', IFooPluginOptions> 47 | >(({ 48 | endpoints, 49 | }) => ([ 50 | { 51 | resolve: 'foo-plugin-....', 52 | options: { 53 | pluginPaths: endpoints.plugin['gatsby-source-filesystem'].node, 54 | } 55 | } 56 | ])) 57 | 58 | const gatsbyConfig: ITSConfigFn<'config'> = ({ 59 | projectRoot 60 | }) => ({ 61 | siteMetadata: { 62 | title: `Some site title`, 63 | description: `This is a description of your site`, 64 | author: `@Js-Brecht`, 65 | }, 66 | plugins: [ 67 | { 68 | // All of your normal, untyped Gatsby plugins 69 | } 70 | ] 71 | }); 72 | 73 | export default gatsbyConfig; 74 | ``` 75 | 76 | * Something similar can be done for `gatsby-node`, but it is even simpler: 77 | 78 | ```ts 79 | import { SourceNodesArgs } from 'gatsby'; 80 | import { ITSConfigFn } from 'gatsby-plugin-ts-config'; 81 | 82 | const gatsbyNode: ITSConfigFn<'node'> = ({ 83 | projectRoot 84 | }) => ({ 85 | sourceNodes: async ({ 86 | actions, 87 | createNodeId, 88 | createContentDigest 89 | }: SourceNodesArgs): Promise => { 90 | const { createNode } = actions; 91 | /* Create your nodes here */ 92 | return; 93 | } 94 | }) 95 | 96 | export default gatsbyNode; 97 | ``` -------------------------------------------------------------------------------- /src/types/public.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | ApiType, 3 | RootPluginImports, 4 | PropertyBag, 5 | PluginModule, 6 | IGatsbyPluginWithOpts, 7 | } from "./internal"; 8 | 9 | /** 10 | * Options passed in the first parameter of a `gatsby-*` default export, 11 | * if one is defined and it is a function. 12 | */ 13 | export interface PublicOpts { 14 | imports: RootPluginImports; 15 | projectRoot: string; 16 | } 17 | 18 | /** 19 | * This interface can be used to define the function-style default 20 | * export of `gatsby-config` or `gatsby-node` 21 | * 22 | * @param {ApiType} TConfigType - What type of function this represents: 23 | * 24 | * * `config` 25 | * * `node` 26 | * 27 | * @param {JsonObject} TProps - Defines the second parameter of the function, 28 | * which represents an arbitrary property bag as defined by the plugin definition in 29 | * the original gatsby plugin array 30 | * 31 | * @example 32 | * ```ts 33 | * const gatsbyNode: ITSConfigFn<'node'> = (args) => ({ 34 | * sourceNodes: ({ actions }) => { 35 | * ... 36 | * } 37 | * }); 38 | * export default gatsbyNode; 39 | * ``` 40 | * 41 | * @example 42 | * ```ts 43 | * const gatsbyConfig: ITSConfigFn<'config', {}> = (args, props) => ({ 44 | * plugins: [ 45 | * ... 46 | * ] 47 | * }); 48 | * export default gatsbyConfig; 49 | * ``` 50 | */ 51 | export type TSConfigFn< 52 | TConfigType extends ApiType, 53 | TProps extends PropertyBag = PropertyBag 54 | > = { 55 | (args: PublicOpts, props: TProps): PluginModule; 56 | } 57 | 58 | /** 59 | * This interface can be used with the `includePlugins` utility function 60 | * to ensure that the options provided for any defined plugins are 61 | * strongly typed. 62 | * 63 | * @param {string} TName - a string literal that represents the plugin name 64 | * @param {object} TOptions - (optional) the possible options for the defined 65 | * plugin 66 | * 67 | * @example 68 | * ```ts 69 | * interface IBarPluginOptions { 70 | * barOption: 'qux'; 71 | * } 72 | * 73 | * includePlugins< 74 | * | GatsbyPlugin<'fooPlugin'> 75 | * | GatsbyPlugin<'barPlugin', IBarPluginOptions> 76 | * >([ 77 | * // These strings will be strongly typed 78 | * 'fooPlugin', 79 | * 80 | * // This object will be strongly typed 81 | * { 82 | * resolve: 'barPlugin', 83 | * options: { 84 | * barOption: 'qux', 85 | * } 86 | * } 87 | * ]) 88 | * ``` 89 | * 90 | * @remarks 91 | * If a plugin provides its own options interface in the form of 92 | * 93 | * ```ts 94 | * interface IFooPluginOptions { 95 | * resolve: 'fooPlugin'; 96 | * options: { 97 | * fooOption: 'bar'; 98 | * } 99 | * } 100 | * ``` 101 | * 102 | * It may be used in the place of an `GatsbyPlugin` definition 103 | */ 104 | export type GatsbyPlugin = Record> = ( 105 | | TName 106 | | IGatsbyPluginWithOpts 107 | | false 108 | ) -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gatsby-plugin-ts-config", 3 | "version": "2.1.3", 4 | "description": "Configure Gatsby to use Typescript configuration files", 5 | "main": "./index.js", 6 | "types": "./dist/index", 7 | "author": "Jeremy Albright", 8 | "license": "MIT", 9 | "keywords": [ 10 | "gatsby-plugin", 11 | "gatsby-config", 12 | "gatsby", 13 | "typescript" 14 | ], 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/Js-Brecht/gatsby-plugin-ts-config" 18 | }, 19 | "homepage": "https://github.com/Js-Brecht/gatsby-plugin-ts-config", 20 | "bugs": { 21 | "url": "https://github.com/Js-Brecht/gatsby-plugin-ts-config/issues" 22 | }, 23 | "files": [ 24 | "dist", 25 | "index.js", 26 | "README.md", 27 | "LICENSE" 28 | ], 29 | "scripts": { 30 | "purge": "rimraf node_modules && yarn clean", 31 | "clean": "rimraf './dist/*'", 32 | "build": "run-s clean build:prod", 33 | "build:prod": "ttsc -p tsconfig.build.json", 34 | "build:dev": "ttsc --sourceMap -p tsconfig.build.json", 35 | "watch": "run-s clean \"build:prod -- -w\"", 36 | "watch:dev": "run-s clean \"build:dev -- -w\"", 37 | "lint": "eslint -c .eslintrc.js .", 38 | "lint:fix": "eslint -c .eslintrc.js --fix ." 39 | }, 40 | "peerDependencies": { 41 | "gatsby": "~2.x.x || ~3.x.x", 42 | "typescript": "~4.x.x" 43 | }, 44 | "peerDependenciesMeta": { 45 | "@babel/runtime": { 46 | "optional": true 47 | }, 48 | "eslint": { 49 | "optional": true 50 | }, 51 | "typescript": { 52 | "optional": true 53 | } 54 | }, 55 | "dependencies": { 56 | "@babel/core": "^7.9.0", 57 | "@babel/plugin-proposal-nullish-coalescing-operator": "^7.10.1", 58 | "@babel/plugin-proposal-optional-chaining": "^7.10.3", 59 | "@babel/plugin-syntax-dynamic-import": "^7.8.3", 60 | "@babel/plugin-transform-runtime": "^7.10.3", 61 | "@babel/plugin-transform-typescript": "^7.10.3", 62 | "@babel/preset-env": "^7.10.3", 63 | "@babel/preset-flow": "^7.10.1", 64 | "@babel/preset-react": "^7.10.1", 65 | "@babel/preset-typescript": "^7.8.3", 66 | "@babel/register": "^7.8.6", 67 | "@babel/runtime": "^7.8.7", 68 | "babel-plugin-dynamic-import-node": "^2.3.3", 69 | "callsites": "^3.1.0", 70 | "enhanced-resolve": "^5.8.0", 71 | "find-up": "^5.0.0", 72 | "fs-extra": "^8.1.0", 73 | "lodash": "^4.17.21", 74 | "serialize-error": "^8.1.0", 75 | "ts-node": "^9.0.0", 76 | "tslog": "^3.2.0", 77 | "type-fest": "^0.12.0" 78 | }, 79 | "devDependencies": { 80 | "@babel/core": "^7.8.6", 81 | "@types/babel__core": "^7.1.6", 82 | "@types/fs-extra": "^8.1.0", 83 | "@types/lodash": "^4.14.172", 84 | "@types/node": "^13.7.2", 85 | "@types/webpack": "^4.41.6", 86 | "@yarnpkg/pnpify": "^2.4.0", 87 | "@yarnpkg/sdks": "^2.4.1-rc.4", 88 | "@zerollup/ts-transform-paths": "^1.7.18", 89 | "eslint": "npm:@jtechsvcs/eslint@^1.0.5", 90 | "gatsby": "^3.11.1", 91 | "npm-run-all": "^4.1.5", 92 | "rimraf": "^3.0.2", 93 | "ts-transformer-keys": "^0.4.1", 94 | "ttypescript": "^1.5.10", 95 | "typescript": "4.4.2" 96 | }, 97 | "dependenciesMeta": { 98 | "@jtechsvcs/eslint@1.0.5": { 99 | "unplugged": true 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/lib/project/transpiler/transpiler-settings.ts: -------------------------------------------------------------------------------- 1 | import { Project } from "@lib/project"; 2 | import { Serializer } from "@lib/serializer"; 3 | import { Module } from "@util/node"; 4 | import { ImportHandler } from "./import-handler"; 5 | 6 | import type { 7 | TranspilerArgs, 8 | TranspileType, 9 | } from "@typeDefs"; 10 | 11 | export type GenericArgs = TranspilerArgs; 12 | 13 | type CachedTranspilerSettings = { 14 | project: Project; 15 | args: GenericArgs; 16 | extensions?: NodeJS.RequireExtensions; 17 | } 18 | 19 | const settingsCache = new Map(); 20 | const previousSettings: [string, string][] = []; 21 | 22 | let currentSettings: CachedTranspilerSettings; 23 | 24 | const getSettingsKey = (optKey: string, project: Project) => [ 25 | optKey, 26 | project.options.hooks, 27 | ].map(Serializer.serialize).filter(Boolean).join(":"); 28 | const getCacheKey = (optKey: string, project: Project) => [ 29 | getSettingsKey(optKey, project), 30 | project.apiType, 31 | project.options.hooks, 32 | ].map(Serializer.serialize).filter(Boolean).join(":"); 33 | 34 | class TranspilerSettingsImpl { 35 | public get importHandler() { 36 | return ImportHandler.getCurrent(currentSettings.project); 37 | } 38 | public get ignoreHooks() { 39 | return currentSettings.project.options.hooks?.ignore; 40 | } 41 | 42 | public push( 43 | optKey: string, 44 | args: GenericArgs, 45 | project: Project, 46 | ) { 47 | const cacheKey = getCacheKey(optKey, project); 48 | const settingsKey = getSettingsKey(optKey, project); 49 | let sameSettings = false; 50 | 51 | if (settingsCache.has(cacheKey)) { 52 | const prevLen = previousSettings.length - 1; 53 | const latestSettingsKey = previousSettings[prevLen][1]; 54 | 55 | sameSettings = latestSettingsKey === settingsKey; 56 | currentSettings = settingsCache.get(cacheKey)!; 57 | } else { 58 | currentSettings = { 59 | project, 60 | args, 61 | }; 62 | settingsCache.set( 63 | cacheKey, 64 | currentSettings, 65 | ); 66 | } 67 | 68 | previousSettings.push([cacheKey, settingsKey]); 69 | 70 | if (sameSettings) return false; 71 | return currentSettings; 72 | } 73 | 74 | public pop() { 75 | let prevLen = previousSettings.length - 1; 76 | 77 | // No previously cached settings??? 78 | if (prevLen === -1) return -1; 79 | // Already on the base 80 | if (prevLen === 0) return false; 81 | 82 | previousSettings.pop(); 83 | prevLen--; 84 | const prevKey = previousSettings[prevLen][0]; 85 | 86 | const restoreSettings = settingsCache.get(prevKey); 87 | if (!restoreSettings) return false; 88 | 89 | return currentSettings = restoreSettings; 90 | } 91 | 92 | public saveExtensions(newExtensions: NodeJS.RequireExtensions) { 93 | if (currentSettings) { 94 | currentSettings.extensions = { ...newExtensions }; 95 | } 96 | } 97 | } 98 | 99 | export const TranspilerSettings = new TranspilerSettingsImpl(); -------------------------------------------------------------------------------- /src/types/internal.ts: -------------------------------------------------------------------------------- 1 | import type { GatsbyConfig, GatsbyNode } from "gatsby"; 2 | import type { TransformOptions as BabelOptions } from "@babel/core"; 3 | import type { RegisterOptions as TSNodeOptions } from "ts-node"; 4 | import type { JsonObject } from "type-fest"; 5 | 6 | import type { apiTypeKeys } from "@util/constants"; 7 | import type { Project, ProjectApiType } from "@lib/project"; 8 | import type { PublicOpts, GatsbyPlugin, TSConfigFn } from "./public"; 9 | 10 | export type IgnoreFn = (filename: string) => boolean; 11 | export type IgnoreHookFn = (filename: string, original: boolean) => ( 12 | boolean | void 13 | ); 14 | 15 | export type Hooks = { 16 | ignore?: IgnoreHookFn[]; 17 | } 18 | 19 | export type PropertyBag = JsonObject; 20 | export type ApiType = typeof apiTypeKeys[number]; 21 | export type TranspileType = "babel" | "ts-node"; 22 | export type TranspilerArgs< 23 | T extends TranspileType = "babel" 24 | > = { 25 | type: T; 26 | options: TranspilerOptions 27 | } 28 | export interface IInternalOptions { 29 | props?: PropertyBag; 30 | type?: TranspileType; 31 | hooks?: Hooks; 32 | } 33 | 34 | interface IInternalBabelOptions extends IInternalOptions { 35 | type?: "babel"; 36 | transpilerOptions?: BabelOptions; 37 | } 38 | interface IInternalTsNodeOptions extends IInternalOptions { 39 | type?: "ts-node"; 40 | transpilerOptions?: TSNodeOptions; 41 | } 42 | 43 | export type TsConfigPluginOptions = ( 44 | | IInternalBabelOptions 45 | | IInternalTsNodeOptions 46 | ); 47 | export type PluginOptionDiff = Omit; 48 | 49 | export type InitValue = string | (() => Record); 50 | export type NoFirstParameter = ( 51 | T extends (first: any, ...args: infer U) => infer R 52 | ? (...args: U) => R 53 | : T 54 | ); 55 | 56 | 57 | export type BaseModuleType = PluginModule | { 58 | default: TSConfigFn; 59 | } 60 | 61 | export type TranspilerOptions = 62 | T extends "babel" 63 | ? BabelOptions 64 | : T extends "ts-node" 65 | ? TSNodeOptions 66 | : never; 67 | 68 | export type TranspilerReturn> = ( 69 | BaseModuleType> 70 | ) 71 | 72 | export type ApiImports = { 73 | [K in ApiType]?: string[]; 74 | }; 75 | export type PluginImports = Record; 76 | export type RootPluginImports = ApiImports & { 77 | plugins?: PluginImports; 78 | }; 79 | export type ImportsCache = PluginImports; 80 | 81 | export type PluginModule = 82 | T extends "config" 83 | ? GatsbyConfig 84 | : T extends "node" 85 | ? GatsbyNode 86 | : unknown; 87 | export type ProjectPluginModule = ( 88 | PluginModule> 89 | ) 90 | 91 | export interface IPluginDetailsCallback< 92 | TReturn extends GatsbyPlugin = GatsbyPlugin, 93 | TProps extends PropertyBag = PropertyBag, 94 | > { 95 | (args: PublicOpts, props: TProps): TReturn[]; 96 | } 97 | 98 | export interface IGatsbyPluginWithOpts< 99 | TName extends string = string, 100 | TOptions extends Record = Record 101 | > { 102 | resolve: TName; 103 | options?: TOptions; 104 | } -------------------------------------------------------------------------------- /src/lib/plugin-transpiler.ts: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | import { getFile, resolveFilePath } from "@util/fs-tools"; 3 | import { createRequire } from "@util/node"; 4 | import { apiTypeKeys } from "@util/constants"; 5 | 6 | import { Project } from "@lib/project"; 7 | 8 | import type { PackageJson } from "type-fest"; 9 | import type { 10 | IGatsbyPluginWithOpts, 11 | } from "@typeDefs"; 12 | import type { ApiModuleProcessor } from "./api-module"; 13 | 14 | type ResolvePluginResult = { 15 | path: string; 16 | pkgJson: PackageJson; 17 | } 18 | 19 | export const resolvePlugin = ( 20 | relativeTo: string, 21 | pluginName: string, 22 | localOnly: boolean, 23 | ): ResolvePluginResult | void => { 24 | const scopedRequire = createRequire(`${relativeTo}/:internal:`); 25 | try { 26 | const pluginPath = path.dirname( 27 | scopedRequire.resolve(`${pluginName}/package.json`), 28 | ); 29 | return localOnly ? void 0 : { 30 | path: pluginPath, 31 | pkgJson: require(`${pluginPath}/package.json`), 32 | }; 33 | } catch (err) { 34 | const pluginDir = path.resolve(relativeTo, "plugins", pluginName); 35 | const pkgJsonPath = path.join(pluginDir, "package.json"); 36 | const pkgJson = getFile(pkgJsonPath); 37 | 38 | if (pkgJson && pkgJson.isFile()) { 39 | return { 40 | path: pluginDir, 41 | pkgJson: require(pkgJsonPath), 42 | }; 43 | } 44 | 45 | return; 46 | } 47 | }; 48 | 49 | export type PluginTranspileType = "all" | "local-only"; 50 | 51 | export const transpilePlugins = ( 52 | project: Project, 53 | type: PluginTranspileType, 54 | processApiModule: ApiModuleProcessor, 55 | plugins = [] as IGatsbyPluginWithOpts[], 56 | ) => { 57 | plugins.forEach((plugin) => { 58 | const pluginName = plugin.resolve; 59 | if (!pluginName) return; 60 | 61 | const projectRoot = project.projectRoot; 62 | 63 | const pluginDetails = resolvePlugin( 64 | projectRoot, 65 | pluginName, 66 | type === "local-only", 67 | ); 68 | if (!pluginDetails) return; // We shouldn't transpile this plugin 69 | 70 | const { 71 | path: pluginPath, 72 | pkgJson, 73 | } = pluginDetails; 74 | 75 | project.linkPluginImports(pluginName); 76 | 77 | apiTypeKeys.forEach((type) => { 78 | const gatsbyModuleName = `./gatsby-${type}`; 79 | const apiPath = resolveFilePath(pluginPath, gatsbyModuleName); 80 | if (!apiPath) return; // This `gatsby-*` file doesn't exist for this local plugin 81 | 82 | processApiModule({ 83 | init: apiPath, 84 | project: Project.getProject( 85 | { 86 | apiType: type, 87 | projectMeta: { 88 | projectRoot: pluginPath, 89 | projectName: pluginName, 90 | pkgJson, 91 | }, 92 | options: project.options, 93 | propBag: project.propBag, 94 | }, 95 | true, 96 | ), 97 | unwrapApi: type === "node", 98 | }); 99 | }); 100 | }); 101 | }; -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { PluginError } from "@util/output"; 2 | import { processApiModule } from "@lib/api-module"; 3 | 4 | import { Project } from "@lib/project"; 5 | 6 | import type { 7 | InitValue, 8 | ApiType, 9 | TsConfigPluginOptions, 10 | NoFirstParameter, 11 | PluginModule, 12 | } from "@typeDefs/internal"; 13 | 14 | export * from "./types/public"; 15 | 16 | export { includePlugins, getPlugins } from "@lib/include-plugins"; 17 | 18 | type UsePluginModule = NoFirstParameter; 19 | 20 | const useGatsbyPluginModule = ( 21 | apiType: ApiType, 22 | init: InitValue, 23 | options = {} as TsConfigPluginOptions, 24 | ): PluginModule => { 25 | const project = Project.getProject( 26 | { 27 | apiType, 28 | options, 29 | propBag: options.props, 30 | }, 31 | true, 32 | true, 33 | ); 34 | 35 | try { 36 | return processApiModule({ 37 | init, 38 | project, 39 | unwrapApi: true, 40 | }); 41 | } catch (err: any) { 42 | throw new PluginError(err); 43 | } 44 | }; 45 | 46 | 47 | /** 48 | * Imports/processes a `gatsby-config` module, and returns results to Gatsby 49 | * 50 | * @remarks 51 | * 52 | * - When used alone, this will support a `gatsby-config.ts` in your 53 | * project's root directory. 54 | * 55 | * - The `propBag` will the shared between `useGatsbyConfig` and `useGatsbyNode`, 56 | * and can be mutated by either. After processing `gatsby-config` & `gatsby-node` 57 | * your project's local plugins will be transpiled as well, and a copy of this 58 | * `propBag` will be passed to each. 59 | * 60 | * @param {InitValue} initValue 61 | * - Can be a string, pointing to a `gatsby-config.ts` file. Can be relative 62 | * or absolute. When relative, it is relative to your project's `package.json` 63 | * 64 | * - Can be a callback function, which can either `require()` another module 65 | * (causing it to be transpiled; default exports supported), or directly return 66 | * the object needed to return to Gatsby. 67 | * 68 | * @param {TsConfigPluginOptions} options - The collection of options to use 69 | * throughout this instance. These options will be shared with `useGatsbyNode` for 70 | * the current project or local plugin. 71 | */ 72 | export const useGatsbyConfig: UsePluginModule = (...args) => ( 73 | useGatsbyPluginModule("config", ...args) 74 | ); 75 | 76 | /** 77 | * Imports/processes a `gatsby-node` module, and returns results to Gatsby 78 | * 79 | * @remarks 80 | * 81 | * - When used without `useGatsbyConfig`, your project's local plugins 82 | * will not be transpiled. 83 | * 84 | * @param {InitValue} initValue - 85 | * - Can be a string, pointing to a `gatsby-node.ts` file. Can be relative 86 | * or absolute. When relative, it is relative to your project's `package.json` 87 | * 88 | * - Can be a callback function, which can either `require()` another module 89 | * (causing it to be transpiled; default exports supported), or directly return 90 | * the object needed to return to Gatsby. 91 | * 92 | * @param {TsConfigPluginOptions} options - The collection of options to use 93 | * throughout this instance. The same options defined in `useGatsbyConfig` will 94 | * be passed to `useGatsbyNode`, and additional options defined here will extend them. 95 | */ 96 | export const useGatsbyNode: UsePluginModule = (...args) => ( 97 | useGatsbyPluginModule("node", ...args) 98 | ); -------------------------------------------------------------------------------- /src/lib/process-plugins.ts: -------------------------------------------------------------------------------- 1 | import { Project } from "@lib/project"; 2 | import { transpilePlugins, PluginTranspileType } from "./plugin-transpiler"; 3 | 4 | import type { ApiModuleProcessor } from "./api-module"; 5 | import type { 6 | PropertyBag, 7 | GatsbyPlugin, 8 | IPluginDetailsCallback, 9 | IGatsbyPluginWithOpts, 10 | } from "@typeDefs"; 11 | 12 | export type { PluginTranspileType }; 13 | 14 | export interface IResolvePlugins { 15 | < 16 | T extends GatsbyPlugin = GatsbyPlugin, 17 | P extends PropertyBag = PropertyBag, 18 | >(plugins: T[] | IPluginDetailsCallback): TReturn; 19 | < 20 | T extends GatsbyPlugin = GatsbyPlugin, 21 | P extends PropertyBag = PropertyBag, 22 | >(plugins: T[], pluginsCb?: IPluginDetailsCallback): TReturn; 23 | } 24 | 25 | type PluginCache = { 26 | [project: string]: { 27 | normal: IGatsbyPluginWithOpts[]; 28 | resolver: IPluginDetailsCallback[]; 29 | } 30 | } 31 | 32 | const pluginCache: PluginCache = {}; 33 | 34 | export const expandPlugins = ( 35 | plugins: GatsbyPlugin[], 36 | ): IGatsbyPluginWithOpts[] => ( 37 | plugins 38 | .filter(Boolean) 39 | .map((p) => ( 40 | typeof p === "string" 41 | ? { resolve: p, options: {} } 42 | : p 43 | )) as IGatsbyPluginWithOpts[] 44 | ); 45 | 46 | export const processPlugins = < 47 | T extends GatsbyPlugin = GatsbyPlugin, 48 | P extends PropertyBag = PropertyBag, 49 | >( 50 | plugins = [] as (T | IPluginDetailsCallback)[], 51 | project: Project, 52 | apiModuleProcessor: ApiModuleProcessor, 53 | transpileType: PluginTranspileType, 54 | ): IGatsbyPluginWithOpts[] => { 55 | const usePlugins = expandPlugins(plugins.reduce((arr, pluginSet) => { 56 | if (typeof pluginSet === "function") { 57 | return arr.concat( 58 | project.resolveConfigFn( 59 | pluginSet as IPluginDetailsCallback, 60 | ) as GatsbyPlugin[], 61 | ); 62 | } 63 | arr.push(pluginSet); 64 | return arr; 65 | }, [] as GatsbyPlugin[])); 66 | 67 | transpilePlugins( 68 | project, 69 | transpileType, 70 | apiModuleProcessor, 71 | usePlugins, 72 | ); 73 | 74 | return usePlugins; 75 | }; 76 | 77 | export const processPluginCache = ( 78 | project: Project, 79 | apiModuleProcessor: ApiModuleProcessor, 80 | insertPlugins = [] as GatsbyPlugin[], 81 | ) => { 82 | const doProcessPlugins = ( 83 | plugins = [] as (GatsbyPlugin | IPluginDetailsCallback)[], 84 | allPlugins: boolean, 85 | ) => ( 86 | processPlugins( 87 | plugins, 88 | project, 89 | apiModuleProcessor, 90 | allPlugins ? "all" : "local-only", 91 | ) 92 | ); 93 | 94 | const pluginCache = getPluginsCache(project.projectRoot); 95 | return [ 96 | ...doProcessPlugins(pluginCache.normal, true), 97 | ...doProcessPlugins(insertPlugins, false), 98 | ...doProcessPlugins(pluginCache.resolver, true), 99 | ]; 100 | }; 101 | 102 | 103 | export const getPluginsCache = (projectRoot: string) => ( 104 | pluginCache[projectRoot] = ( 105 | pluginCache[projectRoot] || { 106 | normal: [], 107 | resolver: [], 108 | } 109 | ) 110 | ); -------------------------------------------------------------------------------- /old/index.ts: -------------------------------------------------------------------------------- 1 | import OptionsHandler from "./utils/options-handler"; 2 | 3 | import type { GatsbyConfig } from "gatsby"; 4 | import type { TSConfigSetupOptions, PropertyBag } from "./types"; 5 | 6 | type GeneratedGatsbyConfig = Pick; 7 | interface IGenerateConfig { 8 | (args: TSConfigSetupOptions, props?: PropertyBag): GeneratedGatsbyConfig; 9 | } 10 | 11 | export const generateConfig: IGenerateConfig = (options, props = {}) => { 12 | return { 13 | plugins: [ 14 | { 15 | resolve: `gatsby-plugin-ts-config`, 16 | options: { 17 | props, 18 | ...(options as Record), 19 | }, 20 | }, 21 | ], 22 | }; 23 | }; 24 | 25 | /** 26 | * Registers and processes plugins that will be provided to Gatsby when your site's 27 | * `gatsby-plugin` is read. 28 | * 29 | * * Resolves paths of each plugin relative to your default site's working directory 30 | * * Looks for plugins in the local `plugins` directory, as well as `node_modules` 31 | * * Compiles plugin endpoints that are written in Typescript 32 | * * Provides strong typing for plugin array/callback functions 33 | * 34 | * Can be used with two generic type parameters, 35 | * 36 | * * `PluginDefinitions` - Should be a union of various plugin declaration types, 37 | * in the format: 38 | * 39 | * ``` 40 | * string | { 41 | * resolve: string; 42 | * options: Record | PluginOptions 43 | * } 44 | * ``` 45 | * 46 | * * `Props` - Defines the structure of the second parameter of the callback 47 | * parameter type 48 | * 49 | * @example 50 | * 51 | * ```ts 52 | * includePlugins<( 53 | * // Plugin Definitions 54 | * | IGatsbyPluginDef<'foo', IFooPluginOptions> 55 | * | 'bar' 56 | * | { resolve: 'bar'; options: { qux: number }; } 57 | * ), 58 | * 59 | * // Property Bag 60 | * { 61 | * random: string; 62 | * properties: number[]; 63 | * } 64 | * >(arrayOfPlugins, ({ projectRoot }, {random, properties}) => { 65 | * // do something 66 | * }) 67 | * ``` 68 | * 69 | * @param {IGatsbyPluginDef[] | IPluginDetailsCallback} plugins - Can be either 70 | * a plugin array, or a callback function. The callback function receives 71 | * the same parameters as the `gatsby-config` or `gatsby-node` default export 72 | * functions. The plugin array must be in the same format that Gatsby itself 73 | * receives. 74 | * 75 | * @param {IPluginDetailsCallback} cb - (Optional) This second parameter can 76 | * only be a callback function. 77 | * 78 | * @remarks 79 | * * `cb`: 80 | * 81 | * `(args: PublicOpts, props: PropertyBag) => GatsbyPluginDef[]` 82 | * * `PublicOpts` - A collection of options/parameters that provide context 83 | * based on the runtime of this plugin 84 | * * `PropertyBag` - A collection of properties passed down from props option in 85 | * the original definition of this plugin. 86 | * 87 | * * Plugin ordering: 88 | * 89 | * Plugins registered this way change the order that plugin's are included in the 90 | * array fed to Gatsby. This will effect the order they are called, so you must 91 | * be aware of it. They will be included in this order: 92 | * 93 | * 1. Array form of the first parameter of this function 94 | * 2. Normal `gatsby-config` plugin array 95 | * 3. Plugins returned from the callback function parameter(s) in this function 96 | */ 97 | export const includePlugins = OptionsHandler.includePlugins; 98 | 99 | export * from "./types/public"; -------------------------------------------------------------------------------- /old/types/public.ts: -------------------------------------------------------------------------------- 1 | import { GatsbyConfig, GatsbyNode } from "gatsby"; 2 | import { RegisterOptions as TSNodeRegisterOptions } from "ts-node"; 3 | import { TransformOptions } from "@babel/core"; 4 | import { 5 | IGlobalOpts, 6 | PickLiteral, 7 | GatsbyConfigTypes, 8 | IGatsbyPluginWithOpts, 9 | PropertyBag, 10 | } from "./internal"; 11 | 12 | 13 | export interface ITSConfigPluginOptions { 14 | configDir?: string; 15 | projectRoot?: string; 16 | props?: PropertyBag; 17 | babel?: TransformOptions | boolean; 18 | tsNode?: TSNodeRegisterOptions | boolean; 19 | } 20 | export type TSConfigSetupOptions = Omit; 21 | 22 | export type PublicOpts = Pick 28 | 29 | type ITSConfigFnTypes = PickLiteral; 30 | type ITSConfigFnReturn = T extends "config" 31 | ? GatsbyConfig 32 | : GatsbyNode; 33 | 34 | /** 35 | * This interface can be used to define the function-style default 36 | * export of `gatsby-config` or `gatsby-node` 37 | * 38 | * @param {string} TConfigType - What type of function this represents: 39 | * 40 | * * `config` 41 | * * `node` 42 | * 43 | * @param {Record} TProps - Defines the second parameter of the function, 44 | * which represents an arbitrary property bag as defined by the plugin definition in 45 | * the original gatsby plugin array 46 | * 47 | * @example 48 | * ```ts 49 | * const gatsbyNode: ITSConfigFn<'node'> = (args) => ({ 50 | * sourceNodes: ({ actions }) => { 51 | * ... 52 | * } 53 | * }); 54 | * export default gatsbyNode; 55 | * ``` 56 | * 57 | * @example 58 | * ```ts 59 | * const gatsbyConfig: ITSConfigFn<'config', {}> = (args, props) => ({ 60 | * plugins: [ 61 | * ... 62 | * ] 63 | * }); 64 | * export default gatsbyConfig; 65 | * ``` 66 | */ 67 | export interface ITSConfigFn< 68 | TConfigType extends ITSConfigFnTypes, 69 | TProps extends PropertyBag = PropertyBag 70 | > { 71 | (args: PublicOpts, props: TProps): ITSConfigFnReturn; 72 | } 73 | 74 | /** 75 | * This interface can be used with the `includePlugins` utility function 76 | * to ensure that the options provided for any defined plugins are 77 | * strongly typed. 78 | * 79 | * @param {string} TName - a string literal that represents the plugin name 80 | * @param {object} TOptions - (optional) the possible options for the defined 81 | * plugin 82 | * 83 | * @example 84 | * ```ts 85 | * interface IBarPluginOptions { 86 | * barOption: 'qux'; 87 | * } 88 | * 89 | * includePlugins< 90 | * | IGatsbyPluginDef<'fooPlugin'> 91 | * | IGatsbyPluginDef<'barPlugin', IBarPluginOptions> 92 | * >([ 93 | * // These strings will be strongly typed 94 | * 'fooPlugin', 95 | * 96 | * // This object will be strongly typed 97 | * { 98 | * resolve: 'barPlugin', 99 | * options: { 100 | * barOption: 'qux', 101 | * } 102 | * } 103 | * ]) 104 | * ``` 105 | * 106 | * @remarks 107 | * If a plugin provides its own options interface in the form of 108 | * 109 | * ```ts 110 | * interface IFooPluginOptions { 111 | * resolve: 'fooPlugin'; 112 | * options: { 113 | * fooOption: 'bar'; 114 | * } 115 | * } 116 | * ``` 117 | * 118 | * It may be used in the place of an `IGatsbyPluginDef` definition 119 | */ 120 | export type IGatsbyPluginDef = Record> = ( 121 | | TName 122 | | IGatsbyPluginWithOpts 123 | ) -------------------------------------------------------------------------------- /old/types/internal.ts: -------------------------------------------------------------------------------- 1 | import type { GatsbyConfig, GatsbyNode, GatsbyBrowser, GatsbySSR } from "gatsby"; 2 | import type { TransformOptions } from "@babel/core"; 3 | import type { RegisterOptions as TSNodeRegisterOptions } from "ts-node"; 4 | import type { ITSConfigFn, PublicOpts, IGatsbyPluginDef } from "./public"; 5 | 6 | export type PropertyBag = Record; 7 | export type ValidExts = ".js" | ".ts" | ".jsx" | ".tsx"; 8 | export type RegisterType = "ts-node" | "babel"; 9 | export type GatsbyConfigTypes = "config" | "node" | "browser" | "ssr" 10 | export type ConfigTypes = GatsbyConfigTypes | "plugin"; 11 | export type EndpointResolutionSpec = GatsbyConfigTypes | { 12 | type: GatsbyConfigTypes; 13 | ext: ValidExts[]; 14 | } 15 | 16 | export type EndpointReturnTypes = 17 | T extends "config" 18 | ? GatsbyConfig | ITSConfigFn<"config"> 19 | : T extends "node" 20 | ? GatsbyNode | ITSConfigFn<"node"> 21 | : T extends "browser" 22 | ? GatsbyBrowser 23 | : T extends "ssr" 24 | ? GatsbySSR 25 | : unknown; 26 | 27 | export type EndpointReturnObject = 28 | T extends "config" 29 | ? GatsbyConfig 30 | : T extends "node" 31 | ? GatsbyNode 32 | : T extends "browser" 33 | ? GatsbyBrowser 34 | : T extends "ssr" 35 | ? GatsbySSR 36 | : unknown; 37 | 38 | export type InferredConfigType = 39 | T extends GatsbyConfig | ITSConfigFn<"config"> 40 | ? "config" 41 | : T extends GatsbyNode | ITSConfigFn<"node"> 42 | ? "node" 43 | : T extends GatsbyBrowser 44 | ? "browser" 45 | : T extends GatsbySSR 46 | ? "ssr" 47 | : unknown; 48 | 49 | export type PickLiteral = K extends T ? K : T; 50 | 51 | export type GatsbyEndpointResolverMapKeys = PickLiteral; 52 | export type GatsbyEndpointResolverKeys = Exclude; 53 | 54 | export type GatsbyEndpoints = { 55 | [K in GatsbyEndpointResolverKeys]?: string[]; 56 | } 57 | export interface IGatsbyEndpointResolverMap { 58 | [K: string]: GatsbyEndpoints; 59 | } 60 | export type GatsbyResolveChain = GatsbyEndpoints & { 61 | [K in GatsbyEndpointResolverMapKeys]?: IGatsbyEndpointResolverMap; 62 | }; 63 | export interface IGatsbyPluginWithOpts< 64 | TName extends string = string, 65 | TOptions extends Record = Record 66 | > { 67 | resolve: TName; 68 | options?: TOptions; 69 | } 70 | 71 | export interface IPluginDetails { 72 | name: string; 73 | path: string; 74 | options: Record; 75 | } 76 | export interface IPluginDetailsCallback< 77 | TReturn extends IGatsbyPluginDef = IGatsbyPluginDef, 78 | TProps extends PropertyBag = PropertyBag, 79 | > { 80 | (args: PublicOpts, props: TProps): TReturn[]; 81 | } 82 | 83 | export type RegisterOptions = 84 | T extends "ts-node" 85 | ? TSNodeRegisterOptions 86 | : T extends "babel" 87 | ? TransformOptions 88 | : never; 89 | 90 | export interface ICommonDirectories { 91 | projectRoot: string; 92 | configDir: string; 93 | cacheDir: string; 94 | pluginDir: string; 95 | } 96 | 97 | export interface IGlobalOpts extends ICommonDirectories { 98 | endpoints: GatsbyResolveChain; 99 | props: PropertyBag; 100 | babelOpts?: RegisterOptions<"babel">; 101 | tsNodeOpts?: RegisterOptions<"ts-node">; 102 | } 103 | -------------------------------------------------------------------------------- /old/utils/register.ts: -------------------------------------------------------------------------------- 1 | import { register } from "ts-node"; 2 | import babelRegister from "@babel/register"; 3 | import { 4 | RegisterOptions, 5 | RegisterType, 6 | ICommonDirectories, 7 | GatsbyEndpointResolverKeys, 8 | } from "../types"; 9 | import { throwError } from "./errors"; 10 | import optionsHandler from "./options-handler"; 11 | import BuiltinModule from "module"; 12 | 13 | interface IModule extends BuiltinModule { 14 | _extensions: NodeJS.RequireExtensions; 15 | } 16 | 17 | const Module = BuiltinModule as unknown as IModule; 18 | 19 | export type RegistrarProgramOpts = ICommonDirectories; 20 | 21 | export interface IRequireRegistrarProps { 22 | registerOpts: RegisterOptions; 23 | } 24 | 25 | class RequireRegistrar { 26 | private initialized = false; 27 | private registered = false; 28 | private active = false; 29 | private type!: T; 30 | private registerOpts!: RegisterOptions; 31 | private extensions = [".ts", ".tsx", ".js", ".jsx"]; 32 | private endpoint?: GatsbyEndpointResolverKeys; 33 | private pluginName?: string; 34 | private origExtensions = { 35 | ...Module._extensions, 36 | ".ts": Module._extensions[".js"], 37 | ".tsx": Module._extensions[".jsx"], 38 | } 39 | 40 | constructor() { 41 | this.ignore = this.ignore.bind(this); 42 | this.only = this.only.bind(this); 43 | } 44 | 45 | public get ext(): string[] { 46 | return this.extensions; 47 | } 48 | 49 | public init(type: T, props: IRequireRegistrarProps): void { 50 | this.type = type; 51 | this.registerOpts = props.registerOpts; 52 | this.initialized = true; 53 | } 54 | 55 | public start(endpoint: GatsbyEndpointResolverKeys, pluginName?: string): void { 56 | if (!this.initialized) 57 | throwError("[gatsby-plugin-ts-config] Compiler registration was started before it was initialized!", new Error()); 58 | this.active = true; 59 | this.endpoint = endpoint; 60 | this.pluginName = pluginName; 61 | if (!this.registered) this.register(); 62 | } 63 | 64 | public stop(): void { 65 | this.active = false; 66 | } 67 | 68 | public revert(): void { 69 | this.active = false; 70 | this.registered = false; 71 | Module._extensions = this.origExtensions; 72 | } 73 | 74 | private ignore(filename: string): boolean { 75 | if (!this.active) return true; 76 | if (filename.indexOf("node_modules") > -1) return true; 77 | if (filename.endsWith(".pnp.js")) return true; 78 | if (this.endpoint) 79 | optionsHandler.addChainedImport( 80 | this.endpoint, 81 | filename, 82 | this.pluginName, 83 | ); 84 | return false; 85 | } 86 | 87 | private only(filename: string): boolean { 88 | return !this.ignore(filename); 89 | } 90 | 91 | private register(): void { 92 | if (this.registered) return; 93 | 94 | switch (this.type) { 95 | case "ts-node": { 96 | const opts = this.registerOpts as RegisterOptions<"ts-node">; 97 | const tsNodeService = register(opts); 98 | tsNodeService.ignored = this.ignore; 99 | break; 100 | } 101 | case "babel": { 102 | const opts = this.registerOpts as RegisterOptions<"babel">; 103 | babelRegister({ 104 | ...opts, 105 | extensions: this.extensions, 106 | only: [this.only], 107 | }); 108 | break; 109 | } 110 | } 111 | this.registered = true; 112 | } 113 | } 114 | 115 | export default new RequireRegistrar(); 116 | -------------------------------------------------------------------------------- /old/gatsby/gatsby-config.ts: -------------------------------------------------------------------------------- 1 | import * as path from "path"; 2 | import { RegisterOptions } from "ts-node"; 3 | import { TransformOptions } from "@babel/core"; 4 | import { getAbsoluteRelativeTo } from "../utils/fs-tools"; 5 | import { 6 | resolveGatsbyEndpoints, 7 | allGatsbyEndpoints, 8 | ignoreRootEndpoints, 9 | compilePlugins, 10 | } from "../utils/endpoints"; 11 | import { tryRequireModule, getModuleObject } from "../utils/node"; 12 | import RequireRegistrar from "../utils/register"; 13 | import OptionsHandler from "../utils/options-handler"; 14 | 15 | import type { GatsbyConfig } from "gatsby"; 16 | import type { ITSConfigPluginOptions, GatsbyConfigTypes, EndpointResolutionSpec } from "../types"; 17 | 18 | export default (args = {} as ITSConfigPluginOptions): GatsbyConfig => { 19 | const projectRoot = getAbsoluteRelativeTo(args.projectRoot || process.cwd()); 20 | const configDir = getAbsoluteRelativeTo(projectRoot, args.configDir); 21 | const cacheDir = path.join(projectRoot, ".cache", "caches", "gatsby-plugin-ts-config"); 22 | const pluginDir = path.resolve(path.join(__dirname, "..", "..")); 23 | const propBag = args.props || {}; 24 | 25 | const ignore: GatsbyConfigTypes[] = []; 26 | const configEndpoint: EndpointResolutionSpec = { 27 | type: "config", 28 | ext: [".js", ".ts"], 29 | }; 30 | if (configDir === projectRoot) { 31 | ignore.push(...ignoreRootEndpoints.filter((nm) => !ignore.includes(nm))); 32 | configEndpoint.ext = [".ts"]; 33 | } 34 | 35 | const endpoints = resolveGatsbyEndpoints({ 36 | endpointSpecs: [ 37 | ...allGatsbyEndpoints.filter((nm) => !ignore.includes(nm) && nm !== "config"), 38 | ...(!ignore.includes("config") && [configEndpoint] || []), 39 | ], 40 | endpointRoot: configDir, 41 | }); 42 | 43 | const programOpts = { 44 | projectRoot, 45 | configDir, 46 | cacheDir, 47 | pluginDir, 48 | }; 49 | 50 | OptionsHandler.set( 51 | { 52 | ...programOpts, 53 | endpoints, 54 | }, 55 | propBag, 56 | ); 57 | 58 | if (args.babel || !args.tsNode) { 59 | const babelOpts: TransformOptions = OptionsHandler.setBabelOpts( 60 | typeof args.babel === "object" 61 | ? args.babel 62 | : undefined, 63 | ); 64 | 65 | RequireRegistrar.init("babel", { 66 | registerOpts: babelOpts, 67 | }); 68 | } else { 69 | const tsNodeOpts: RegisterOptions = OptionsHandler.setTsNodeOpts( 70 | typeof args.tsNode === "boolean" ? {} : args.tsNode, 71 | ); 72 | 73 | RequireRegistrar.init("ts-node", { 74 | registerOpts: tsNodeOpts, 75 | }); 76 | } 77 | 78 | 79 | // Prefetch gatsby-node here so that we can collect the import chain. 80 | // Doing it here also means we can revert the changes to require.extensions 81 | // before continuing with the build. The cached, transpiled source will be 82 | // retrieved later when it is required in gatsby-node 83 | const gatsbyNodeModule = tryRequireModule("node", endpoints); 84 | 85 | const gatsbyConfigModule = tryRequireModule("config", endpoints); 86 | 87 | compilePlugins({ 88 | plugins: OptionsHandler.plugins, 89 | }); 90 | 91 | const gatsbyConfig = getModuleObject(gatsbyConfigModule); 92 | 93 | OptionsHandler.includePlugins(gatsbyConfig?.plugins || []); 94 | OptionsHandler.doExtendPlugins(true); 95 | 96 | RequireRegistrar.revert(); 97 | 98 | return { 99 | ...gatsbyConfig, 100 | plugins: [ 101 | ...OptionsHandler.plugins.map((plugin) => ({ 102 | resolve: `${plugin.path}/package.json`, 103 | options: plugin.options, 104 | })), 105 | ].filter(Boolean), 106 | }; 107 | }; -------------------------------------------------------------------------------- /src/lib/project/transpiler/index.ts: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | import omit from "lodash/omit"; 3 | 4 | import { preferDefault } from "@util/node"; 5 | import { resolveFilePath } from "@util/fs-tools"; 6 | 7 | import { Serializer } from "@lib/serializer"; 8 | import { setTranspiler } from "./set-transpiler"; 9 | 10 | import type { Project } from "@lib/project"; 11 | import type { 12 | TranspilerArgs, 13 | TranspileType, 14 | InitValue, 15 | TranspilerReturn, 16 | TSConfigFn, 17 | } from "@typeDefs"; 18 | 19 | export { ImportHandler } from "./import-handler"; 20 | export type { ImportHandlerFn } from "./import-handler"; 21 | 22 | export type Transpiler = Project> = < 23 | T extends TranspileType = "babel" 24 | >( 25 | init: InitValue, 26 | unwrapApi: boolean, 27 | overrideArgs?: TranspilerArgs, 28 | ) => TranspilerReturn; 29 | 30 | export const getTranspiler = >( 31 | project: TProject, 32 | rootArgs: TranspilerArgs, 33 | ): Transpiler => { 34 | const rootKey = Serializer.serialize(rootArgs)!; 35 | 36 | return function transpile(init, unwrapApi, overrideArgs) { 37 | const projectRoot = project.projectRoot; 38 | 39 | const overrideKey = ( 40 | overrideArgs && Serializer.serialize(overrideArgs) 41 | ); 42 | 43 | const newTranspiler = !!( 44 | overrideKey && 45 | overrideKey !== rootKey 46 | ); 47 | 48 | const [transpilerKey, transpilerArgs] = newTranspiler 49 | ? [overrideKey!, overrideArgs!] as const 50 | : [rootKey, rootArgs] as const; 51 | 52 | const restore = setTranspiler( 53 | transpilerKey, 54 | transpilerArgs, 55 | project, 56 | ); 57 | 58 | try { 59 | if (typeof init === "function") { 60 | return omit(init(), ["__esModule"]) as TranspilerReturn; 61 | } else { 62 | const requirePath = resolveFilePath( 63 | projectRoot, 64 | path.resolve(projectRoot, init), 65 | ); 66 | 67 | if (!requirePath) { 68 | throw new Error([ 69 | `Unable to resolve module '${init}' from`, 70 | `Path: ${projectRoot}`, 71 | ].join("\n")); 72 | } 73 | 74 | const mod = require(requirePath); 75 | 76 | const resolveFn: TSConfigFn = (opts, props) => { 77 | let resolvedMod = preferDefault(mod); 78 | const exports = require.cache[requirePath]?.exports; 79 | 80 | if (!exports) { 81 | throw new Error([ 82 | `Unable to retrieve require cache for module '${requirePath}'.`, 83 | "This may indicate a serious issue", 84 | ].join("\n")); 85 | } 86 | 87 | if (!unwrapApi) { 88 | return ( 89 | require.cache[requirePath]!.exports = omit(mod, ["__esModule"]) 90 | ); 91 | } 92 | 93 | if (resolvedMod && typeof resolvedMod === "function") { 94 | resolvedMod = resolvedMod(opts, props); 95 | } 96 | 97 | return ( 98 | require.cache[requirePath]!.exports = omit( 99 | Object.assign({}, exports, resolvedMod), 100 | ["__esModule", "default"], 101 | ) 102 | ); 103 | }; 104 | 105 | return resolveFn as TranspilerReturn; 106 | } 107 | } finally { 108 | if (restore) restore(); 109 | } 110 | }; 111 | }; -------------------------------------------------------------------------------- /src/lib/project/project-settings.ts: -------------------------------------------------------------------------------- 1 | import { keys } from "ts-transformer-keys"; 2 | import get from "lodash/get"; 3 | import set from "lodash/set"; 4 | import pick from "lodash/pick"; 5 | 6 | import { Serializer } from "@lib/serializer"; 7 | import { getProject, ProjectMeta } from "@util/project-meta"; 8 | import { getPropBag } from "@settings/prop-bag"; 9 | 10 | import type { 11 | TsConfigPluginOptions, 12 | PluginOptionDiff, 13 | PropertyBag, 14 | ApiType, 15 | } from "@typeDefs"; 16 | 17 | export type ProjectOptions = Omit; 18 | 19 | export interface IInitSettings { 20 | projectMeta: ProjectMeta; 21 | options?: ProjectOptions; 22 | propBag?: PropertyBag; 23 | } 24 | 25 | export interface IBaseProjectSettings extends Required {} 26 | 27 | type ProjectSettingsCache = { 28 | [projectRoot: string]: { 29 | [apiType in ApiType]?: ProjectSettings; 30 | } 31 | } 32 | 33 | const projectSettingsCache: ProjectSettingsCache = {}; 34 | 35 | const setCachedSettings = ( 36 | apiType: ApiType, 37 | projectRoot: string, 38 | projectSettings: ProjectSettings, 39 | ) => ( 40 | set(projectSettingsCache, [projectRoot, apiType], projectSettings), 41 | projectSettings 42 | ); 43 | const getCachedSettings = ( 44 | apiTypes: ApiType[], 45 | projectRoot: string, 46 | ) => { 47 | for (const api of apiTypes) { 48 | const val = get(projectSettingsCache, [projectRoot, api]); 49 | if (val) return val; 50 | } 51 | }; 52 | 53 | const optionDiffKeys = keys(); 54 | 55 | export class ProjectSettings { 56 | public static getInstance( 57 | apiType: ApiType, 58 | newSettings: Partial, 59 | setCache: boolean, 60 | ) { 61 | const projectMeta = newSettings.projectMeta || getProject(); 62 | const { projectRoot } = projectMeta; 63 | 64 | const existing = getCachedSettings( 65 | [ 66 | apiType, 67 | apiType === "config" ? "node" : "config", 68 | ], 69 | projectRoot, 70 | ); 71 | 72 | if (existing) { 73 | const configOptions = pick(newSettings.options, optionDiffKeys); 74 | const options = pick(existing.options, optionDiffKeys); 75 | 76 | /** 77 | * The settings haven't changed. 78 | * Don't need a separate ProjectSettings instance 79 | */ 80 | if (Serializer.isEqual(configOptions, options)) { 81 | return { 82 | changed: false, 83 | settings: existing.merge(newSettings), 84 | }; 85 | } 86 | } 87 | 88 | const settings = new ProjectSettings({ 89 | ...newSettings, 90 | projectMeta, 91 | }); 92 | 93 | if (setCache) { 94 | setCachedSettings( 95 | apiType, 96 | projectRoot, 97 | settings, 98 | ); 99 | } 100 | 101 | return { 102 | changed: true, 103 | settings, 104 | }; 105 | } 106 | 107 | public readonly options: ProjectOptions; 108 | public readonly projectMeta: ProjectMeta; 109 | 110 | private _propBag!: PropertyBag; 111 | 112 | private constructor(input: IInitSettings) { 113 | if (!input.options) input.options = {}; 114 | if (!input.projectMeta) input.projectMeta = getProject(); 115 | 116 | this.projectMeta = input.projectMeta; 117 | this.options = input.options; 118 | 119 | this.merge(input); 120 | } 121 | 122 | public get propBag() { return this._propBag; } 123 | 124 | protected merge(input = {} as Partial) { 125 | const inputProps = input.propBag || ( 126 | (input.options as TsConfigPluginOptions)?.props 127 | ); 128 | 129 | this._propBag = getPropBag(this.projectMeta.projectRoot, inputProps); 130 | return this; 131 | } 132 | } -------------------------------------------------------------------------------- /src/lib/project/transpiler/set-transpiler.ts: -------------------------------------------------------------------------------- 1 | import babelRegister from "@babel/register"; 2 | import { register as tsNodeRegister } from "ts-node"; 3 | 4 | import { Project } from "@lib/project"; 5 | import { AllowedFiles } from "./allowed-files"; 6 | import { ImportHandler } from "./import-handler"; 7 | import { TranspilerSettings, GenericArgs } from "./transpiler-settings"; 8 | import { restoreExtensions } from "./restore-extensions"; 9 | 10 | import type { 11 | TranspilerArgs, 12 | TranspileType, 13 | TranspilerOptions, 14 | IgnoreFn, 15 | IgnoreHookFn, 16 | } from "@typeDefs"; 17 | import { Module } from "@util/node"; 18 | 19 | const extensions = [".ts", ".tsx", ".js", ".jsx"]; 20 | const origModuleExtensions = { 21 | ...Module._extensions, 22 | }; 23 | 24 | const ignoreRules: IgnoreFn[] = [ 25 | // Module must not be a node_modules dependency 26 | (fname) => /node_modules/.test(fname), 27 | ]; 28 | 29 | const getIgnore = ( 30 | filename: string, 31 | rules: (IgnoreFn | IgnoreHookFn)[], 32 | orig: boolean, 33 | ) => ( 34 | AllowedFiles.allowed(filename) 35 | ? false 36 | : rules.some((rule) => rule(filename, !!orig)) 37 | ); 38 | 39 | const ignore: IgnoreFn = (filename) => { 40 | if (filename.endsWith(".pnp.js")) return true; 41 | const importHandler = TranspilerSettings.importHandler; 42 | const hooks = TranspilerSettings.ignoreHooks; 43 | 44 | if (importHandler) { 45 | importHandler(filename); 46 | } 47 | 48 | const origIgnore = getIgnore(filename, ignoreRules, false); 49 | const ignoreFile = hooks 50 | ? getIgnore(filename, hooks, origIgnore) 51 | : origIgnore; 52 | return !!ignoreFile; 53 | }; 54 | 55 | const only: IgnoreFn = (filename) => { 56 | return !ignore(filename); 57 | }; 58 | 59 | const register = (args: GenericArgs) => { 60 | const { 61 | type: transpileType, 62 | options: transpilerOpts, 63 | } = args; 64 | 65 | restoreExtensions(origModuleExtensions); 66 | 67 | switch (transpileType) { 68 | case "ts-node": { 69 | const opts = transpilerOpts as TranspilerOptions<"ts-node">; 70 | const tsNodeService = tsNodeRegister(opts); 71 | tsNodeService.ignored = ignore; 72 | break; 73 | } 74 | case "babel": { 75 | const opts = transpilerOpts as TranspilerOptions<"babel">; 76 | babelRegister({ 77 | ...opts, 78 | extensions, 79 | only: [only], 80 | }); 81 | break; 82 | } 83 | } 84 | 85 | TranspilerSettings.saveExtensions(require.extensions); 86 | }; 87 | 88 | export const setTranspiler = ( 89 | optKey: string, 90 | transpileArgs: TranspilerArgs, 91 | project: Project, 92 | ) => { 93 | AllowedFiles.addDir(project.projectRoot); 94 | 95 | const restoreImportHandler = ImportHandler.push(project); 96 | const initialSettings = TranspilerSettings.push( 97 | optKey, 98 | transpileArgs, 99 | project, 100 | ); 101 | 102 | if (initialSettings) { 103 | const { 104 | args: registerArgs, 105 | } = initialSettings; 106 | 107 | register(registerArgs); 108 | } 109 | 110 | return () => { 111 | AllowedFiles.removeDir(project.projectRoot); 112 | restoreImportHandler(); 113 | 114 | const restoreSettings = TranspilerSettings.pop(); 115 | 116 | // No prior transpiler set. Just restore the original 117 | // module extensions 118 | if (restoreSettings === -1) { 119 | restoreExtensions(origModuleExtensions); 120 | return; 121 | } 122 | 123 | // The base transpiler is already active 124 | if (!restoreSettings) return; 125 | const { 126 | args: restoreArgs, 127 | extensions: restoreExt = origModuleExtensions, 128 | } = restoreSettings; 129 | 130 | switch (restoreArgs.type) { 131 | /** 132 | * In order to restore extensions for babel, we need to 133 | * re-register the transpiler. 134 | */ 135 | case "babel": { 136 | register(restoreArgs); 137 | break; 138 | } 139 | /** 140 | * ts-node should just be able to restore the prior set of 141 | * module extensions 142 | */ 143 | case "ts-node": { 144 | restoreExtensions(restoreExt); 145 | break; 146 | } 147 | } 148 | }; 149 | }; -------------------------------------------------------------------------------- /old/docs/EXTENDED-USAGE.md: -------------------------------------------------------------------------------- 1 | ## Extended Usage 2 | 3 | If, for some reason, you need to force this plugin to resolve files relative to a directory other than 4 | your process current working directory, then you can define the `projectRoot` option: 5 | 6 | ```js 7 | // gatsby-config.js 8 | const { generateConfig } = require('gatsby-plugin-ts-config'); 9 | module.exports = generateConfig({ 10 | projectRoot: __dirname, 11 | }); 12 | ``` 13 | 14 | When using this plugin, it is preferable to keep the configuration files out of your project root, because 15 | this helps avoid Gatsby node ownership conflicts. 16 | 17 | ```js 18 | // gatsby-config.js 19 | const { generateConfig } = require('gatsby-plugin-ts-config'); 20 | module.exports = generateConfig({ 21 | projectRoot: __dirname, // <- not required. If omitted, projectRoot will be process.cwd() 22 | configDir: '.gatsby' 23 | }); 24 | ``` 25 | 26 | This will make it look in my `.gatsby` subdirectory for all configuration files. So I might have the 27 | following: 28 | 29 | ```text 30 | . 31 | ├── .gatsby 32 | ├── gatsby-config.ts 33 | ├── gatsby-node.ts 34 | ├── gatsby-browser.ts 35 | └── gatsby-ssr.ts 36 | └── gatsby-config.js 37 | ``` 38 | 39 | --- 40 | 41 | ### `babel` specific usage details 42 | 43 | #### babel Options 44 | 45 | The babel configuration takes all of the same options you would expect 46 | to provide to babel itself. For specific details, please see the 47 | [babel options documentation](https://babeljs.io/docs/en/options#config-loading-options) 48 | 49 | Options that you give to this plugin for babel to use will not interfere with 50 | the options that you give to Gatsby. However, it's important to keep in mind 51 | that they **will** mix with the options that you put in a `.babelrc` in your 52 | project root. 53 | 54 | #### `babel` example usage 55 | 56 | Following is an example setup that will configure babel to be able to use the 57 | aliases defined in `tsconfig.compilerOptions.paths`. 58 | 59 | ```js 60 | // gatsby-config.js 61 | const { generateConfig } = require('gatsby-plugin-ts-config'); 62 | 63 | const path = require('path'); 64 | const tsconfig = require('./tsconfig.json'); 65 | const moduleResolverOpts = {}; 66 | if (tsconfig && tsconfig.compilerOptions) { 67 | if (tsconfig.compilerOptions.baseUrl) 68 | moduleResolverOpts.root = [path.resolve(__dirname, tsconfig.compilerOptions.baseUrl)]; 69 | if (tsconfig.compilerOptions.paths) 70 | moduleResolverOpts.alias = Object.entries(tsconfig.compilerOptions.paths).reduce((acc, [key, val]) => { 71 | acc[key.replace(/\/\*$/, '')] = val[0].replace(/\/\*$/, ''); 72 | return acc; 73 | }, {}); 74 | } 75 | 76 | module.exports = generateConfig({ 77 | configDir: '.gatsby', 78 | babel: { 79 | plugins: [ 80 | [ 81 | require.resolve('babel-plugin-module-resolver'), 82 | moduleResolverOpts, 83 | ], 84 | ], 85 | }, 86 | }); 87 | ``` 88 | 89 | --- 90 | 91 | ### `ts-node` specific usage details 92 | 93 | #### `ts-node` Options 94 | 95 | For valid options you may use to configure `ts-node`, see the [ts-node 96 | options documentation here](https://github.com/TypeStrong/ts-node#cli-and-programmatic-options) 97 | 98 | #### tsconfig.json 99 | 100 | By default, `gatsby-plugin-ts-config` will make `ts-node` use the `tsconfig.json` found in the 101 | `projectRoot`. If you want to define a different one, include it as one of the `tsNode` options: 102 | 103 | ```js 104 | // gatsby-config.js 105 | const { generateConfig } = require('gatsby-plugin-ts-config'); 106 | module.exports = generateConfig({ 107 | tsNode: { 108 | project: 'tsconfig.build.json' 109 | }, 110 | }); 111 | ``` 112 | 113 | Note: if you define this file, it will be resolved relative to the defined `projectRoot` (which is your 114 | `process.cwd()` by default), unless it is an absolute path. 115 | 116 | #### `ts-node` example usage 117 | 118 | Following is an example of `ts-node` usage that will configure a custom paths transformer for `ts-node`, 119 | enabling the usage of `tsconfig.compilerOptions.paths` in your Typescript code: 120 | 121 | ```js 122 | // gatsby-config.js 123 | const TsPathsTransformer = require('@zerollup/ts-transform-paths'); 124 | const { generateConfig } = require('gatsby-plugin-ts-config'); 125 | module.exports = generateConfig({ 126 | projectRoot: __dirname, 127 | configDir: '.gatsby', 128 | tsNode: { 129 | transformers: (program) => { 130 | const tsTransformPaths = TsPathsTransformer(program); 131 | return { 132 | before: [ 133 | tsTransformPaths.before, 134 | ], 135 | afterDeclarations: [ 136 | tsTransformPaths.afterDeclarations, 137 | ], 138 | }; 139 | }, 140 | }, 141 | }); 142 | ``` -------------------------------------------------------------------------------- /src/lib/project/transpiler/import-handler.ts: -------------------------------------------------------------------------------- 1 | // import { PluginError } from "@util/output"; 2 | import { popArray } from "@util/objects"; 3 | import type { Project } from "@lib/project"; 4 | import type { 5 | ApiType, 6 | ImportsCache, 7 | RootPluginImports, 8 | } from "@typeDefs"; 9 | 10 | export type ImportHandlerFn = (filename: string) => void; 11 | 12 | type HandlerCacheMeta = { 13 | type: ApiType; 14 | plugin: string; 15 | handler: ImportHandlerFn; 16 | } 17 | 18 | type ImportHandlerCache = { 19 | prevIndex: Record 20 | previous: HandlerCacheMeta[]; 21 | current?: HandlerCacheMeta; 22 | } 23 | 24 | const importHandlers: Record = {}; 25 | const handlerCache: ImportHandlerCache = { 26 | prevIndex: {}, 27 | previous: [], 28 | }; 29 | 30 | const getPrevKey = (apiType: ApiType, pluginName: string) => ( 31 | `${pluginName}:${apiType}` 32 | ); 33 | 34 | const importsCache: ImportsCache = {}; 35 | 36 | const getProjectInfo = (project: Project) => ({ 37 | apiType: project.apiType, 38 | projectName: project.projectName, 39 | }); 40 | 41 | class ImportHandlerImpl { 42 | public getProjectImports(projectName: string): RootPluginImports { 43 | return importsCache[projectName] || ( 44 | importsCache[projectName] = {} 45 | ); 46 | } 47 | 48 | public linkProjectPlugin(projectName: string, pluginName: string): void { 49 | const rootProject = this.getProjectImports(projectName); 50 | const pluginProject = this.getProjectImports(pluginName); 51 | 52 | const pluginLinks = rootProject.plugins || ( 53 | rootProject.plugins = {} 54 | ); 55 | 56 | // if (pluginLinks.hasOwnProperty(pluginName)) { 57 | // throw new PluginError([ 58 | // `Attempting to link plugin ${pluginName} to project ${projectName} failed.`, 59 | // `Plugin ${pluginName} has already been linked!`, 60 | // ].join("\n")); 61 | // } 62 | 63 | pluginLinks[pluginName] = pluginProject; 64 | } 65 | 66 | private pushPrev(handlerMeta: HandlerCacheMeta) { 67 | const key = getPrevKey(handlerMeta.type, handlerMeta.plugin); 68 | const prev = handlerCache.prevIndex[key] = ( 69 | handlerCache.prevIndex[key] || [] 70 | ); 71 | prev.push(handlerMeta); 72 | handlerCache.previous.push(handlerMeta); 73 | } 74 | private getPrev(apiType: ApiType, pluginName: string) { 75 | const key = getPrevKey(apiType, pluginName); 76 | return handlerCache.prevIndex[key] || []; 77 | } 78 | private popPrev() { 79 | const last = handlerCache.previous.pop(); 80 | if (!last) return; 81 | 82 | const { type, plugin } = last; 83 | const index = this.getPrev(type, plugin); 84 | popArray(index, last); 85 | 86 | return last; 87 | } 88 | 89 | public getCurrent(project: Project) { 90 | const { apiType, projectName } = getProjectInfo(project); 91 | const current = handlerCache.current; 92 | if (current) { 93 | if (current.type === apiType && current.plugin === projectName) { 94 | return current.handler; 95 | } 96 | } 97 | 98 | const prev = this.getPrev(apiType, projectName); 99 | return prev[prev.length - 1]?.handler; 100 | } 101 | 102 | 103 | private getImportHandler(project: Project) { 104 | const { apiType, projectName } = getProjectInfo(project); 105 | 106 | const cacheKey = `${apiType}:${projectName}`; 107 | if (importHandlers[cacheKey]) { 108 | return importHandlers[cacheKey]; 109 | } 110 | 111 | const projectImports = this.getProjectImports(projectName); 112 | const apiImports = projectImports[apiType] || ( 113 | projectImports[apiType] = [] 114 | ); 115 | 116 | return importHandlers[cacheKey] = (filename: string) => { 117 | apiImports.push(filename); 118 | }; 119 | } 120 | public push(project: Project) { 121 | const { apiType, projectName } = getProjectInfo(project); 122 | const newHandler = this.getImportHandler(project); 123 | const current = handlerCache.current; 124 | 125 | if (current && current.handler !== newHandler) { 126 | this.pushPrev(current); 127 | } 128 | 129 | const myMeta: HandlerCacheMeta = { 130 | type: apiType, 131 | plugin: projectName, 132 | handler: newHandler, 133 | }; 134 | 135 | handlerCache.current = myMeta; 136 | return () => { 137 | if (handlerCache.current === myMeta) { 138 | this.pop(); 139 | return; 140 | } 141 | 142 | const index = this.getPrev(apiType, projectName); 143 | popArray(index, myMeta); 144 | popArray(handlerCache.previous, myMeta); 145 | }; 146 | } 147 | 148 | public pop() { 149 | handlerCache.current = this.popPrev(); 150 | } 151 | 152 | public addImport: ImportHandlerFn = (filename) => { 153 | if (!handlerCache.current) return; 154 | return handlerCache.current.handler(filename); 155 | } 156 | } 157 | 158 | export const ImportHandler = new ImportHandlerImpl(); -------------------------------------------------------------------------------- /src/lib/include-plugins.ts: -------------------------------------------------------------------------------- 1 | import { Project } from "@lib/project"; 2 | import { 3 | expandPlugins, 4 | getPluginsCache, 5 | processPlugins, 6 | PluginTranspileType, 7 | } from "@lib/process-plugins"; 8 | import { processApiModule } from "@lib/api-module"; 9 | 10 | import { getProject } from "@util/project-meta"; 11 | import { arrayify } from "@util/objects"; 12 | 13 | import type { 14 | PropertyBag, 15 | GatsbyPlugin, 16 | IPluginDetailsCallback, 17 | IGatsbyPluginWithOpts, 18 | TsConfigPluginOptions, 19 | } from "@typeDefs"; 20 | 21 | export interface IResolvePlugins { 22 | < 23 | T extends GatsbyPlugin = GatsbyPlugin, 24 | P extends PropertyBag = PropertyBag, 25 | >(plugins: T[] | IPluginDetailsCallback): TReturn; 26 | < 27 | T extends GatsbyPlugin = GatsbyPlugin, 28 | P extends PropertyBag = PropertyBag, 29 | >(plugins: T[], pluginsCb?: IPluginDetailsCallback): TReturn; 30 | } 31 | 32 | /** 33 | * Registers and processes plugins that will be provided to Gatsby when your site's 34 | * `gatsby-plugin` is read. 35 | * 36 | * * Provides strong typing for plugin array/callback functions 37 | * 38 | * Can be used with two generic type parameters, 39 | * 40 | * * `PluginDefinitions` - Should be a union of various plugin declaration types, 41 | * in the format: 42 | * 43 | * ``` 44 | * string | { 45 | * resolve: string; 46 | * options: Record | PluginOptions 47 | * } 48 | * ``` 49 | * 50 | * * `Props` - Defines the structure of the second parameter of the callback 51 | * parameter type 52 | * 53 | * @example 54 | * 55 | * ```ts 56 | * type PluginDefinitions = ( 57 | * | GatsbyPlugin<'foo', IFooPluginOptions> 58 | * | 'bar' 59 | * | { resolve: 'bar'; options: { qux: number }; } 60 | * ) 61 | * 62 | * type PropertyBag = { 63 | * random: string; 64 | * properties: number[]; 65 | * } 66 | * 67 | * includePlugins( 68 | * arrayOfPlugins, 69 | * ({ projectRoot }, {random, properties}) => { 70 | * // Build plugin array 71 | * }) 72 | * ); 73 | * ``` 74 | * 75 | * @param {GatsbyPlugin[] | IPluginDetailsCallback} plugins - Can be either 76 | * a plugin array, or a callback function. The callback function receives 77 | * the same parameters as the `gatsby-config` or `gatsby-node` default export 78 | * functions. The plugin array must be in the same format that Gatsby itself 79 | * receives. 80 | * 81 | * @param {IPluginDetailsCallback} cb - (Optional) This second parameter can 82 | * only be a callback function. 83 | * 84 | * @remarks 85 | * * `cb`: 86 | * 87 | * `(args: PublicOpts, props: PropertyBag) => GatsbyPlugin[]` 88 | * * `PublicOpts` - A collection of options/parameters that provide context 89 | * based on the runtime of this plugin 90 | * * `PropertyBag` - A collection of properties passed down from props option in 91 | * the original definition of this plugin. 92 | * 93 | * * Plugin ordering: 94 | * 95 | * Plugins registered this way change the order that plugin's are included in the 96 | * array fed to Gatsby. This will effect the order they are called, so you must 97 | * be aware of it. They will be included in this order: 98 | * 99 | * 1. Array form of the first parameter of this function 100 | * 2. Normal `gatsby-config` plugin array 101 | * 3. Plugins returned from the callback function parameter(s) in this function 102 | */ 103 | export const includePlugins: IResolvePlugins = < 104 | T extends GatsbyPlugin = GatsbyPlugin, 105 | P extends PropertyBag = PropertyBag, 106 | >( 107 | plugins: T[] | IPluginDetailsCallback, 108 | pluginsCb?: IPluginDetailsCallback, 109 | ) => { 110 | const { projectRoot } = getProject(); 111 | const cache = getPluginsCache(projectRoot); 112 | 113 | if (plugins instanceof Array) { 114 | cache.normal.push(...expandPlugins(plugins)); 115 | } else { 116 | pluginsCb = plugins; 117 | } 118 | 119 | if (pluginsCb) { 120 | cache.resolver.push( 121 | pluginsCb as unknown as IPluginDetailsCallback, 122 | ); 123 | } 124 | }; 125 | 126 | const doProcessPlugins = ( 127 | type: PluginTranspileType, 128 | plugins: GatsbyPlugin[] | IPluginDetailsCallback, 129 | opts?: GetPluginOpts, 130 | ): IGatsbyPluginWithOpts[] => { 131 | const project = Project.getProject( 132 | { 133 | apiType: "config", 134 | options: opts, 135 | }, 136 | false, 137 | ); 138 | 139 | const usePlugins = ( 140 | Array.isArray(plugins) || 141 | typeof plugins === "function" 142 | ) && arrayify(plugins) || []; 143 | 144 | return processPlugins( 145 | usePlugins, 146 | project, 147 | processApiModule, 148 | type, 149 | ); 150 | }; 151 | 152 | type GetPluginOpts = TsConfigPluginOptions; 153 | 154 | /** 155 | * Immediately processes and returns a collection of plugins, or a 156 | * plugin resolver function. 157 | * 158 | * All plugins passed to this function will be transpiled immediately. 159 | */ 160 | export function getPlugins< 161 | T extends GatsbyPlugin = GatsbyPlugin, 162 | P extends PropertyBag = PropertyBag, 163 | >( 164 | plugins: T[] | IPluginDetailsCallback, 165 | opts?: GetPluginOpts, 166 | ): IGatsbyPluginWithOpts[] { 167 | return doProcessPlugins( 168 | "all", 169 | plugins, 170 | opts, 171 | ); 172 | } -------------------------------------------------------------------------------- /old/docs/GATSBY-API.md: -------------------------------------------------------------------------------- 1 | ## Writing Gatsby API Endpoints 2 | 3 | ### `gatsby-browser` and `gatsby-ssr` 4 | 5 | If these files are located in your `projectRoot`, then they will be skipped by this plugin 6 | because Gatsby is able to process them by default, through Webpack. 7 | 8 | ### `gatsby-config` and `gatsby-node` 9 | 10 | These files can be created two ways. 11 | 12 | 1. The first is by exporting a single object or series of 13 | named exports, the same way you normally would. 14 | 15 | * You may also export this object as the _**default export**_, if you 16 | wish. It is supported by this plugin, and it has better support in 17 | Typescript itself. 18 | 19 | ```ts 20 | // gatsby-config.ts 21 | export default { 22 | siteMetadata: { 23 | siteUrl: "https://foobar.com", 24 | }, 25 | plugins: [ 26 | ... 27 | ] 28 | } 29 | ``` 30 | 31 | 2. The second method allows you to export a single function as the default export. This function 32 | will receive a single object as the first and only parameter. The object properties are 33 | defined below 34 | 35 | ### `gatsby-*` as-a-function parameters 36 | 37 | 1. First parameter: `{object}` 38 | * `projectRoot`: `{string}` 39 | * The resolved pathname that you either defined in the plugin options, or that was calculated 40 | automatically. 41 | 42 | * `configDir`: `{string}` 43 | * The location of your configuration files. If you do not define one in the plugin options, then 44 | this will be the same as your `projectRoot`. 45 | 46 | * `cacheDir`: `{string}` 47 | * The cache folder location that the plugin will use to store any of the files that it needs to. 48 | * `endpoints`: `{object}` 49 | * A collection of the fully qualified paths for all of the Gatsby configuration files that have been 50 | resolved, and that will be called, by this plugin. This will be equal to any of the endpoints that 51 | you have in your `configDir`, with one exception: 52 | * If your `configDir` is the same as the `projectRoot`, then `gatsby-ssr` and `gastby-browser` will 53 | **not** be included in these resolved endpoints, because they will not be called by this plugin. 54 | * Properties: 55 | * config: `{string[]}` - The list of chained requires/imports that were performed by `gatsby-config` 56 | * node: `{string[]}` - The list of chained requires/imports that were performed by `gatsby-node` 57 | * browser: `{string[]}` - The resolved path of the `gatsby-browser` api endpoint 58 | * ssr: `{string[]}` - The resolved path of the `gatsby-ssr` api endpoint 59 | * plugin - Contains the collection of all the plugins that have been resolved by the `includePlugins()` function, 60 | defined below. `name` will be the registered name of the plugin, and the `config`, `node`, `browser`, 61 | and `ssr` properties will be the same as defined above. 62 | * Type: 63 | 64 | ```js 65 | { 66 | [name: string]: { 67 | config: string[]; 68 | node: string[]; 69 | browser: string[]; 70 | ssr: string[]; 71 | } 72 | } 73 | ``` 74 | 75 | * If the `browser` or `ssr` properties are included, they will only have one index, containing the 76 | location of the endpoint that was resolved. This is because transpiling & compiling of those modules 77 | is done by Gatsby itself, not this module. 78 | * The first index of each property will always be your local Gatsby endpoint. All following indexes will be 79 | the files that were required/imported by that one 80 | 81 | 2. Second parameter: `{object}` - The Property Bag 82 | * This parameter contains the object that you passed in as the `props` plugin option in the original plugin 83 | declaration array, or as the second parameter you passed to `generateConfig()` 84 | 85 | **More information can be found in the [Property Bag](./PROPERTY-BAG.md) chapter** 86 | 87 | ### `gatsby-config` utilites 88 | 89 | 1. `includePlugins`: This function allows you to register plugins with strongly typed options. Using it 90 | also enables advanced plugin resolution, allowing you to automatically resolve local plugins. Any 91 | plugins resolved this way will also be compiled, so if you have local plugins written in Typescript, they 92 | will be transpiled so that they can be consumed by Gatsby. 93 | 94 | **Please see the [Gatsby Config Utilities](./GATSBY-CONFIG-UTILS.md) chapter for more information** 95 | 96 | ### Type utilities 97 | 98 | A couple of utility interfaces are exported by this plugin to make it easier to create 99 | type-safe functions in `gatsby-node` and `gatsby-config`: 100 | 101 | * `ITSConfigFn`: Interface that describes the shape of the `gatsby-config` or `gatsby-node` 102 | default export functions. Accepts two parameter: 103 | * The string parameter for the function type ('config' | 'node') 104 | * The type of the property bag, which is represented by the second parameter in the function. 105 | 106 | ```ts 107 | interface IPropertyBag { 108 | test: number; 109 | } 110 | 111 | export const gatsbyConfig: ITSConfigFn<"config", IPropertyBag> = ( 112 | { projectRoot }, 113 | { test } 114 | ) => { 115 | console.log(test); 116 | return { 117 | plugins: [ 118 | "foo-plugin" 119 | ] 120 | } 121 | } 122 | 123 | export default gatsbyConfig; 124 | ``` 125 | 126 | * `IGatsbyPluginDef`: Utility type that makes it easy to merge a plugin's defined types 127 | into your plugins object array. Accepts two parameters: 128 | * The name of the plugin, which will be used in the `resolve` property 129 | * The interface for the plugin's options 130 | -------------------------------------------------------------------------------- /old/utils/endpoints.ts: -------------------------------------------------------------------------------- 1 | import * as path from "path"; 2 | import * as fs from "fs-extra"; 3 | import { checkFileWithExts, allExt, fileExists, isDir } from "./fs-tools"; 4 | import { createRequire, tryRequireModule } from "./node"; 5 | 6 | import type { 7 | GatsbyEndpoints, 8 | GatsbyConfigTypes, 9 | EndpointResolutionSpec, 10 | IPluginDetails, 11 | } from "../types"; 12 | 13 | export const gatsbyEndpointProxies: GatsbyConfigTypes[] = ["browser", "ssr"]; 14 | export const gatsbyConfigEndpoints: GatsbyConfigTypes[] = ["config", "node"]; 15 | export const allGatsbyEndpoints: GatsbyConfigTypes[] = [ 16 | ...gatsbyConfigEndpoints, 17 | ...gatsbyEndpointProxies, 18 | ]; 19 | export const ignoreRootEndpoints: GatsbyConfigTypes[] = [ 20 | ...gatsbyEndpointProxies, 21 | ]; 22 | 23 | 24 | export const configEndpointSpecs: EndpointResolutionSpec[] = [ 25 | { 26 | type: "config", 27 | ext: [".js", ".ts"], 28 | }, 29 | { 30 | type: "node", 31 | ext: [".js", ".ts"], 32 | }, 33 | ]; 34 | 35 | // ************************************* 36 | 37 | export interface IResolveEndpointProps { 38 | endpointSpecs: EndpointResolutionSpec[]; 39 | endpointRoot: string; 40 | } 41 | 42 | /** 43 | * This will look in `configDir` for the specified Gatsby endpoints, and 44 | * return the resolved paths to each 45 | * 46 | * @param {IResolveEndpointProps} resolveProps What endpoints to resolve, and from where 47 | * 48 | * * `endpointSpecs`: What endpoints to resolve 49 | * * `configDir`: Where to resolve the endpoints 50 | * @returns {GatsbyEndpoints} The resolved endpoints 51 | */ 52 | export const resolveGatsbyEndpoints = ({ 53 | endpointSpecs, 54 | endpointRoot, 55 | }: IResolveEndpointProps): GatsbyEndpoints => { 56 | const resolved: GatsbyEndpoints = {}; 57 | 58 | for (const endpoint of endpointSpecs) { 59 | const endpointType = typeof endpoint === "string" ? endpoint : endpoint.type; 60 | const endpointExt = typeof endpoint === "string" ? allExt : endpoint.ext; 61 | const endpointFile = `gatsby-${endpointType}`; 62 | const configFile = checkFileWithExts(path.join(endpointRoot, endpointFile), endpointExt); 63 | if (configFile) { 64 | resolved[endpointType] = [configFile]; 65 | } 66 | } 67 | 68 | return resolved; 69 | }; 70 | 71 | // *************************************** 72 | 73 | export interface IMakeGatsbyEndpointProps { 74 | resolvedEndpoints: GatsbyEndpoints; 75 | distDir: string; 76 | cacheDir: string; 77 | } 78 | 79 | 80 | /** 81 | * If defined `apiEndpoints` exist in the user's config directory, 82 | * then copy this plugin's `dist` version to the user's cache directory 83 | * so that they can proxy the request from this plugin to the user's 84 | * endpoints. 85 | * 86 | * @param {IMakeGatsbyEndpointProps} setupEndpointProps 87 | * 88 | * * `resolvedEndpoints`: The collection of endpoints that have been resolved 89 | * in the user's configuration directory 90 | * * `distDir`: The location of files that can be copied to this user's cache 91 | * * `cacheDir`: The location to write the proxy module 92 | */ 93 | export const setupGatsbyEndpointProxies = ({ 94 | resolvedEndpoints, 95 | cacheDir, 96 | }: IMakeGatsbyEndpointProps): void => { 97 | for (const setupApi of gatsbyEndpointProxies) { 98 | const endpointFile = `gatsby-${setupApi}.js`; 99 | const targetFile = path.join(cacheDir, endpointFile); 100 | 101 | let moduleSrc; 102 | if (setupApi in resolvedEndpoints) { 103 | // If User endpoint was resolved, then write out the proxy 104 | // module that will point to the user's 105 | const resolvedPath = resolvedEndpoints[setupApi]![0] as string; 106 | moduleSrc = `module.exports = require("${ 107 | resolvedPath.replace(/\\/g, "\\\\") 108 | }");`; 109 | } else { 110 | // User endpoint was not resolved, so just write an empty module 111 | moduleSrc = `module.exports = {};`; 112 | } 113 | fs.ensureDirSync(path.dirname(targetFile)); 114 | fs.writeFileSync(targetFile, moduleSrc); 115 | } 116 | }; 117 | 118 | // *********************************************** 119 | 120 | interface IResolvePluginPathProps { 121 | projectRoot: string; 122 | pluginName: string; 123 | } 124 | 125 | export const resolvePluginPath = ({ 126 | projectRoot, 127 | pluginName, 128 | }: IResolvePluginPathProps): string => { 129 | const scopedRequire = createRequire(`${projectRoot}/`); 130 | try { 131 | const pluginPath = path.dirname( 132 | scopedRequire.resolve(`${pluginName}/package.json`), 133 | ); 134 | return pluginPath; 135 | } catch (err) { 136 | const localPluginsDir = path.resolve(projectRoot, "plugins"); 137 | const pluginDir = path.join(localPluginsDir, pluginName); 138 | if (isDir(path.join(localPluginsDir, pluginName))) { 139 | const pkgJson = fileExists(path.join(pluginDir, "package.json")); 140 | if (pkgJson && pkgJson.isFile()) { 141 | return pluginDir; 142 | } 143 | } 144 | return ""; 145 | } 146 | }; 147 | 148 | interface ICompilePluginsProps { 149 | plugins: IPluginDetails[]; 150 | } 151 | 152 | export const compilePlugins = ({ 153 | plugins, 154 | }: ICompilePluginsProps) => { 155 | for (const pluginDetails of plugins) { 156 | const pluginEndpoints = resolveGatsbyEndpoints({ 157 | endpointSpecs: configEndpointSpecs, 158 | endpointRoot: pluginDetails.path, 159 | }); 160 | for (const type of gatsbyConfigEndpoints) { 161 | const pluginEndpointModule = tryRequireModule(type, pluginEndpoints, true, pluginDetails.name); 162 | } 163 | } 164 | }; -------------------------------------------------------------------------------- /.yarn/sdks/typescript/lib/tsserver.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const {existsSync} = require(`fs`); 4 | const {createRequire, createRequireFromPath} = require(`module`); 5 | const {resolve} = require(`path`); 6 | 7 | const relPnpApiPath = "../../../../.pnp.cjs"; 8 | 9 | const absPnpApiPath = resolve(__dirname, relPnpApiPath); 10 | const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath); 11 | 12 | const moduleWrapper = tsserver => { 13 | if (!process.versions.pnp) { 14 | return tsserver; 15 | } 16 | 17 | const {isAbsolute} = require(`path`); 18 | const pnpApi = require(`pnpapi`); 19 | 20 | const isVirtual = str => str.match(/\/(\$\$virtual|__virtual__)\//); 21 | const normalize = str => str.replace(/\\/g, `/`).replace(/^\/?/, `/`); 22 | 23 | const dependencyTreeRoots = new Set(pnpApi.getDependencyTreeRoots().map(locator => { 24 | return `${locator.name}@${locator.reference}`; 25 | })); 26 | 27 | // VSCode sends the zip paths to TS using the "zip://" prefix, that TS 28 | // doesn't understand. This layer makes sure to remove the protocol 29 | // before forwarding it to TS, and to add it back on all returned paths. 30 | 31 | function toEditorPath(str) { 32 | // We add the `zip:` prefix to both `.zip/` paths and virtual paths 33 | if (isAbsolute(str) && !str.match(/^\^zip:/) && (str.match(/\.zip\//) || isVirtual(str))) { 34 | // We also take the opportunity to turn virtual paths into physical ones; 35 | // this makes it much easier to work with workspaces that list peer 36 | // dependencies, since otherwise Ctrl+Click would bring us to the virtual 37 | // file instances instead of the real ones. 38 | // 39 | // We only do this to modules owned by the the dependency tree roots. 40 | // This avoids breaking the resolution when jumping inside a vendor 41 | // with peer dep (otherwise jumping into react-dom would show resolution 42 | // errors on react). 43 | // 44 | const resolved = isVirtual(str) ? pnpApi.resolveVirtual(str) : str; 45 | if (resolved) { 46 | const locator = pnpApi.findPackageLocator(resolved); 47 | if (locator && dependencyTreeRoots.has(`${locator.name}@${locator.reference}`)) { 48 | str = resolved; 49 | } 50 | } 51 | 52 | str = normalize(str); 53 | 54 | if (str.match(/\.zip\//)) { 55 | switch (hostInfo) { 56 | // Absolute VSCode `Uri.fsPath`s need to start with a slash. 57 | // VSCode only adds it automatically for supported schemes, 58 | // so we have to do it manually for the `zip` scheme. 59 | // The path needs to start with a caret otherwise VSCode doesn't handle the protocol 60 | // 61 | // Ref: https://github.com/microsoft/vscode/issues/105014#issuecomment-686760910 62 | // 63 | case `vscode`: { 64 | str = `^zip:${str}`; 65 | } break; 66 | 67 | // To make "go to definition" work, 68 | // We have to resolve the actual file system path from virtual path 69 | // and convert scheme to supported by [vim-rzip](https://github.com/lbrayner/vim-rzip) 70 | case `coc-nvim`: { 71 | str = normalize(resolved).replace(/\.zip\//, `.zip::`); 72 | str = resolve(`zipfile:${str}`); 73 | } break; 74 | 75 | // Support neovim native LSP and [typescript-language-server](https://github.com/theia-ide/typescript-language-server) 76 | // We have to resolve the actual file system path from virtual path, 77 | // everything else is up to neovim 78 | case `neovim`: { 79 | str = normalize(resolved).replace(/\.zip\//, `.zip::`); 80 | str = `zipfile:${str}`; 81 | } break; 82 | 83 | default: { 84 | str = `zip:${str}`; 85 | } break; 86 | } 87 | } 88 | } 89 | 90 | return str; 91 | } 92 | 93 | function fromEditorPath(str) { 94 | return process.platform === `win32` 95 | ? str.replace(/^\^?zip:\//, ``) 96 | : str.replace(/^\^?zip:/, ``); 97 | } 98 | 99 | // Force enable 'allowLocalPluginLoads' 100 | // TypeScript tries to resolve plugins using a path relative to itself 101 | // which doesn't work when using the global cache 102 | // https://github.com/microsoft/TypeScript/blob/1b57a0395e0bff191581c9606aab92832001de62/src/server/project.ts#L2238 103 | // VSCode doesn't want to enable 'allowLocalPluginLoads' due to security concerns but 104 | // TypeScript already does local loads and if this code is running the user trusts the workspace 105 | // https://github.com/microsoft/vscode/issues/45856 106 | const ConfiguredProject = tsserver.server.ConfiguredProject; 107 | const {enablePluginsWithOptions: originalEnablePluginsWithOptions} = ConfiguredProject.prototype; 108 | ConfiguredProject.prototype.enablePluginsWithOptions = function() { 109 | this.projectService.allowLocalPluginLoads = true; 110 | return originalEnablePluginsWithOptions.apply(this, arguments); 111 | }; 112 | 113 | // And here is the point where we hijack the VSCode <-> TS communications 114 | // by adding ourselves in the middle. We locate everything that looks 115 | // like an absolute path of ours and normalize it. 116 | 117 | const Session = tsserver.server.Session; 118 | const {onMessage: originalOnMessage, send: originalSend} = Session.prototype; 119 | let hostInfo = `unknown`; 120 | 121 | Object.assign(Session.prototype, { 122 | onMessage(/** @type {string} */ message) { 123 | const parsedMessage = JSON.parse(message) 124 | 125 | if ( 126 | parsedMessage != null && 127 | typeof parsedMessage === `object` && 128 | parsedMessage.arguments && 129 | typeof parsedMessage.arguments.hostInfo === `string` 130 | ) { 131 | hostInfo = parsedMessage.arguments.hostInfo; 132 | } 133 | 134 | return originalOnMessage.call(this, JSON.stringify(parsedMessage, (key, value) => { 135 | return typeof value === `string` ? fromEditorPath(value) : value; 136 | })); 137 | }, 138 | 139 | send(/** @type {any} */ msg) { 140 | return originalSend.call(this, JSON.parse(JSON.stringify(msg, (key, value) => { 141 | return typeof value === `string` ? toEditorPath(value) : value; 142 | }))); 143 | } 144 | }); 145 | 146 | return tsserver; 147 | }; 148 | 149 | if (existsSync(absPnpApiPath)) { 150 | if (!process.versions.pnp) { 151 | // Setup the environment to be able to require typescript/lib/tsserver.js 152 | require(absPnpApiPath).setup(); 153 | } 154 | } 155 | 156 | // Defer to the real typescript/lib/tsserver.js your application uses 157 | module.exports = moduleWrapper(absRequire(`typescript/lib/tsserver.js`)); 158 | -------------------------------------------------------------------------------- /src/lib/project/index.ts: -------------------------------------------------------------------------------- 1 | import get from "lodash/get"; 2 | import set from "lodash/set"; 3 | 4 | import { 5 | getTranspiler, 6 | Transpiler, 7 | ImportHandler, 8 | ImportHandlerFn, 9 | } from "./transpiler"; 10 | 11 | import { getRegisterOptions } from "@settings/register"; 12 | 13 | import { 14 | ProjectSettings, 15 | IInitSettings, 16 | } from "./project-settings"; 17 | 18 | import type { 19 | TranspileType, 20 | TranspilerOptions, 21 | ApiType, 22 | TSConfigFn, 23 | PropertyBag, 24 | } from "@typeDefs"; 25 | 26 | interface IGetProjectSettings extends IInitSettings { 27 | apiType?: ApiType; 28 | } 29 | 30 | interface IModuleOptions { 31 | resolveImmediate?: boolean; 32 | } 33 | 34 | type ApiOptions = { 35 | [projectRoot: string]: { 36 | [api in ApiType]?: IModuleOptions; 37 | } 38 | } 39 | 40 | const apiOptionsCache: ApiOptions = {}; 41 | 42 | type ProjectCache = { 43 | [projectRoot: string]: { 44 | [apiType in ApiType]?: Project; 45 | } 46 | } 47 | 48 | const projectCache: ProjectCache = {}; 49 | 50 | const setProjectCache = ( 51 | apiType: ApiType, 52 | projectRoot: string, 53 | project: Project, 54 | ) => ( 55 | set(projectCache, [projectRoot, apiType], project), 56 | project 57 | ); 58 | const getProjectCache = ( 59 | apiType: ApiType, 60 | projectRoot: string, 61 | ) => ( 62 | get(projectCache, [projectRoot, apiType]) 63 | ); 64 | 65 | export type ProjectApiType = ( 66 | T extends Project 67 | ? TApiType 68 | : never 69 | ) 70 | 71 | export class Project { 72 | public static getProject( 73 | input: Partial, 74 | setCache: boolean, 75 | forceCache = false, 76 | ): Project { 77 | const { apiType = "config" } = input; 78 | 79 | const { 80 | changed: settingsChanged, 81 | settings, 82 | } = ProjectSettings.getInstance(apiType, input, setCache); 83 | const { projectRoot } = settings.projectMeta; 84 | 85 | const cachedProject = getProjectCache(apiType, projectRoot); 86 | const useProject = settingsChanged || !cachedProject 87 | ? new Project(apiType, settings) 88 | : cachedProject!; 89 | 90 | // Only cache the first requested one. 91 | if ((setCache || forceCache) && !cachedProject) { 92 | setProjectCache( 93 | apiType, 94 | projectRoot, 95 | useProject, 96 | ); 97 | } 98 | 99 | return useProject as Project; 100 | } 101 | 102 | private _transpiler!: Transpiler; 103 | private _registerOptions!: TranspilerOptions; 104 | 105 | private constructor( 106 | public readonly apiType: TApiType, 107 | protected readonly settings: ProjectSettings, 108 | ) {} 109 | 110 | public get projectName() { return this.projectMeta.projectName; } 111 | public get projectRoot() { return this.projectMeta.projectRoot; } 112 | public get options() { return this.settings.options; } 113 | public get projectMeta() { return this.settings.projectMeta; } 114 | public get propBag() { return this.settings.propBag; } 115 | 116 | /** 117 | * Creates & stores a transpiler function instance. 118 | * 119 | * Successive calls to this getter will always return the same value 120 | */ 121 | public get transpiler(): Transpiler { 122 | if (!this._transpiler) { 123 | this._transpiler = getTranspiler(this, { 124 | type: this.options.type || "babel", 125 | options: this.registerOptions, 126 | }); 127 | } 128 | 129 | return this._transpiler; 130 | } 131 | 132 | /** 133 | * Stores & returns the options for the `register()` function. 134 | * 135 | * Successive calles to this getter will always return the same value. 136 | */ 137 | public get registerOptions(): TranspilerOptions { 138 | if (!this._registerOptions) { 139 | const { 140 | type, 141 | transpilerOptions, 142 | } = this.options; 143 | 144 | this._registerOptions = getRegisterOptions( 145 | this.projectRoot, 146 | type || "babel", 147 | transpilerOptions || {}, 148 | ); 149 | } 150 | 151 | return this._registerOptions; 152 | } 153 | 154 | /** 155 | * Clones the current project, with a new ApiType. 156 | * 157 | * If an instance already exists for the `ApiType` with settings that match these new ones, 158 | * that cached instance will be used. 159 | */ 160 | public clone( 161 | newApiType: T, 162 | newProps = {} as PropertyBag, 163 | ) { 164 | return Project.getProject( 165 | { 166 | ...this.settings, 167 | apiType: newApiType, 168 | propBag: newProps, 169 | }, 170 | false, 171 | false, 172 | ); 173 | } 174 | 175 | /** 176 | * Set persisted option for a (possibly) different api module. 177 | * 178 | * These are globally accessible for the current project. 179 | */ 180 | public setApiOption( 181 | apiType: ApiType, 182 | option: K, 183 | value: IModuleOptions[K], 184 | ) { 185 | set(apiOptionsCache, [this.projectRoot, apiType, option], value); 186 | } 187 | 188 | /** 189 | * Returns the collection of options for the desired `ApiType` 190 | */ 191 | public getApiOptions(apiType: ApiType): IModuleOptions { 192 | return get(apiOptionsCache, [this.projectRoot, apiType], {}); 193 | } 194 | 195 | /** 196 | * Returns a specific option for the desired `ApiType`. 197 | */ 198 | public getApiOption( 199 | apiType: ApiType, 200 | option: K, 201 | ): IModuleOptions[K] { 202 | return get(apiOptionsCache, [this.projectRoot, apiType, option]); 203 | } 204 | 205 | public linkPluginImports(pluginName: string) { 206 | return ImportHandler.linkProjectPlugin(this.projectName, pluginName); 207 | } 208 | 209 | public resolveConfigFn< 210 | C extends TSConfigFn 211 | >(cb: C) { 212 | return cb( 213 | { 214 | projectRoot: this.projectRoot, 215 | imports: ImportHandler.getProjectImports(this.projectName), 216 | }, 217 | this.propBag, 218 | ); 219 | } 220 | } -------------------------------------------------------------------------------- /.yarn/sdks/typescript/lib/tsserverlibrary.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const {existsSync} = require(`fs`); 4 | const {createRequire, createRequireFromPath} = require(`module`); 5 | const {resolve} = require(`path`); 6 | 7 | const relPnpApiPath = "../../../../.pnp.cjs"; 8 | 9 | const absPnpApiPath = resolve(__dirname, relPnpApiPath); 10 | const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath); 11 | 12 | const moduleWrapper = tsserver => { 13 | if (!process.versions.pnp) { 14 | return tsserver; 15 | } 16 | 17 | const {isAbsolute} = require(`path`); 18 | const pnpApi = require(`pnpapi`); 19 | 20 | const isVirtual = str => str.match(/\/(\$\$virtual|__virtual__)\//); 21 | const normalize = str => str.replace(/\\/g, `/`).replace(/^\/?/, `/`); 22 | 23 | const dependencyTreeRoots = new Set(pnpApi.getDependencyTreeRoots().map(locator => { 24 | return `${locator.name}@${locator.reference}`; 25 | })); 26 | 27 | // VSCode sends the zip paths to TS using the "zip://" prefix, that TS 28 | // doesn't understand. This layer makes sure to remove the protocol 29 | // before forwarding it to TS, and to add it back on all returned paths. 30 | 31 | function toEditorPath(str) { 32 | // We add the `zip:` prefix to both `.zip/` paths and virtual paths 33 | if (isAbsolute(str) && !str.match(/^\^zip:/) && (str.match(/\.zip\//) || isVirtual(str))) { 34 | // We also take the opportunity to turn virtual paths into physical ones; 35 | // this makes it much easier to work with workspaces that list peer 36 | // dependencies, since otherwise Ctrl+Click would bring us to the virtual 37 | // file instances instead of the real ones. 38 | // 39 | // We only do this to modules owned by the the dependency tree roots. 40 | // This avoids breaking the resolution when jumping inside a vendor 41 | // with peer dep (otherwise jumping into react-dom would show resolution 42 | // errors on react). 43 | // 44 | const resolved = isVirtual(str) ? pnpApi.resolveVirtual(str) : str; 45 | if (resolved) { 46 | const locator = pnpApi.findPackageLocator(resolved); 47 | if (locator && dependencyTreeRoots.has(`${locator.name}@${locator.reference}`)) { 48 | str = resolved; 49 | } 50 | } 51 | 52 | str = normalize(str); 53 | 54 | if (str.match(/\.zip\//)) { 55 | switch (hostInfo) { 56 | // Absolute VSCode `Uri.fsPath`s need to start with a slash. 57 | // VSCode only adds it automatically for supported schemes, 58 | // so we have to do it manually for the `zip` scheme. 59 | // The path needs to start with a caret otherwise VSCode doesn't handle the protocol 60 | // 61 | // Ref: https://github.com/microsoft/vscode/issues/105014#issuecomment-686760910 62 | // 63 | case `vscode`: { 64 | str = `^zip:${str}`; 65 | } break; 66 | 67 | // To make "go to definition" work, 68 | // We have to resolve the actual file system path from virtual path 69 | // and convert scheme to supported by [vim-rzip](https://github.com/lbrayner/vim-rzip) 70 | case `coc-nvim`: { 71 | str = normalize(resolved).replace(/\.zip\//, `.zip::`); 72 | str = resolve(`zipfile:${str}`); 73 | } break; 74 | 75 | // Support neovim native LSP and [typescript-language-server](https://github.com/theia-ide/typescript-language-server) 76 | // We have to resolve the actual file system path from virtual path, 77 | // everything else is up to neovim 78 | case `neovim`: { 79 | str = normalize(resolved).replace(/\.zip\//, `.zip::`); 80 | str = `zipfile:${str}`; 81 | } break; 82 | 83 | default: { 84 | str = `zip:${str}`; 85 | } break; 86 | } 87 | } 88 | } 89 | 90 | return str; 91 | } 92 | 93 | function fromEditorPath(str) { 94 | return process.platform === `win32` 95 | ? str.replace(/^\^?zip:\//, ``) 96 | : str.replace(/^\^?zip:/, ``); 97 | } 98 | 99 | // Force enable 'allowLocalPluginLoads' 100 | // TypeScript tries to resolve plugins using a path relative to itself 101 | // which doesn't work when using the global cache 102 | // https://github.com/microsoft/TypeScript/blob/1b57a0395e0bff191581c9606aab92832001de62/src/server/project.ts#L2238 103 | // VSCode doesn't want to enable 'allowLocalPluginLoads' due to security concerns but 104 | // TypeScript already does local loads and if this code is running the user trusts the workspace 105 | // https://github.com/microsoft/vscode/issues/45856 106 | const ConfiguredProject = tsserver.server.ConfiguredProject; 107 | const {enablePluginsWithOptions: originalEnablePluginsWithOptions} = ConfiguredProject.prototype; 108 | ConfiguredProject.prototype.enablePluginsWithOptions = function() { 109 | this.projectService.allowLocalPluginLoads = true; 110 | return originalEnablePluginsWithOptions.apply(this, arguments); 111 | }; 112 | 113 | // And here is the point where we hijack the VSCode <-> TS communications 114 | // by adding ourselves in the middle. We locate everything that looks 115 | // like an absolute path of ours and normalize it. 116 | 117 | const Session = tsserver.server.Session; 118 | const {onMessage: originalOnMessage, send: originalSend} = Session.prototype; 119 | let hostInfo = `unknown`; 120 | 121 | Object.assign(Session.prototype, { 122 | onMessage(/** @type {string} */ message) { 123 | const parsedMessage = JSON.parse(message) 124 | 125 | if ( 126 | parsedMessage != null && 127 | typeof parsedMessage === `object` && 128 | parsedMessage.arguments && 129 | typeof parsedMessage.arguments.hostInfo === `string` 130 | ) { 131 | hostInfo = parsedMessage.arguments.hostInfo; 132 | } 133 | 134 | return originalOnMessage.call(this, JSON.stringify(parsedMessage, (key, value) => { 135 | return typeof value === `string` ? fromEditorPath(value) : value; 136 | })); 137 | }, 138 | 139 | send(/** @type {any} */ msg) { 140 | return originalSend.call(this, JSON.parse(JSON.stringify(msg, (key, value) => { 141 | return typeof value === `string` ? toEditorPath(value) : value; 142 | }))); 143 | } 144 | }); 145 | 146 | return tsserver; 147 | }; 148 | 149 | if (existsSync(absPnpApiPath)) { 150 | if (!process.versions.pnp) { 151 | // Setup the environment to be able to require typescript/lib/tsserverlibrary.js 152 | require(absPnpApiPath).setup(); 153 | } 154 | } 155 | 156 | // Defer to the real typescript/lib/tsserverlibrary.js your application uses 157 | module.exports = moduleWrapper(absRequire(`typescript/lib/tsserverlibrary.js`)); 158 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [babel-docs]: https://babeljs.io/docs/en/options#config-loading-options 2 | [tsnode-docs]: https://github.com/TypeStrong/ts-node#cli-and-programmatic-options 3 | 4 | ## Configure Gatsby to use Typescript for configuration files 5 | 6 | [![paypal](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=J3ZGS46A4C5QA¤cy_code=USD&source=url) 7 | 8 | > This plugin will allow you to write your `gatsby-*` configuration files in Typescript. 9 | 10 | --- 11 | 12 | > For v1 documentation, see the [old docs](./old/README.md) 13 | 14 | ### Installation 15 | 16 | * Install using your package manager 17 | 18 | ```shell 19 | npm install -D gatsby-plugin-ts-config 20 | ``` 21 | 22 | --- 23 | 24 | ### Usage 25 | 26 | The cleanest way to use this plugin is to use `gatsby-config.js` and `gatsby-node.js` 27 | as pointers to your `.ts` files that you keep in another directory. This isn't required, 28 | though. All you need initially is `gatsby-config.js` 29 | 30 | To point `gatsby-config.js` and/or `gatsby-node.js` to `.ts` files: 31 | 32 | ```js 33 | // gatsby-config.js 34 | const { useGatsbyConfig } = require("gatsby-plugin-ts-config"); 35 | 36 | // For static analysis purposes, you can use a callback with a require() statement 37 | module.exports = useGatsbyConfig(() => require("./config/gatsby-config"), opts); 38 | 39 | // A simpler method is to just use the filename 40 | module.exports = useGatsbyConfig("./config/gatsby-config", opts); 41 | 42 | // Or you can just return the `gatsby-config` object from the callback 43 | module.exports = useGatsbyConfig( 44 | () => ({ 45 | siteMetadata: { 46 | ... 47 | }, 48 | plugins: [ 49 | { 50 | resolve: ..., 51 | options: ..., 52 | } 53 | ] 54 | }), 55 | opts 56 | ) 57 | ``` 58 | 59 | Once `useGatsbyConfig` is called from `gatsby-config`, `gatsby-node.ts` can exist in your site's 60 | root directory. However, if you do not wish to have your `gatsby-config` in Typescript, `useGatsbyConfig` is 61 | not required. You can use this plugin directly from `gatsby-node` if you wish. 62 | 63 | ```js 64 | // gatsby-node.js 65 | const { useGatsbyNode } = require("gatsby-plugin-ts-config"); 66 | 67 | // All of the same usage patterns for `useGatsbyConfig` are valid for `useGatsbyNode` 68 | // as well 69 | module.exports = useGatsbyNode(() => require("./config/gatsby-node"), opts); 70 | ``` 71 | 72 | ### Options 73 | 74 | * `props`: `Object` 75 | 76 | This "property bag" is an object that can take any shape you wish. When a `gatsby-*` module is defined 77 | with a function for a default export, these `props` will be passed in the second parameter. 78 | 79 | The property bag is mutable, so any changes you make to it will be passed to the next module 80 | 81 | * Each project gets its own property bag. They do not mix, which means `props` defined by your default 82 | site will not be passed down to plugins. 83 | * One difference when using local plugins: The property bag will be **_copied_** and then passed to the 84 | local plugin. 85 | * If `props` is defined in both `useGatsbyConfig` and `useGatsbyNode`, the values in `useGatsbyNode` will be 86 | **_merged_** into the property bag before being passed on to default export of the module. 87 | 88 | * `type`: `"babel" | "ts-node"` 89 | 90 | Determines which transpiler to use. 91 | 92 | * `transpilerOptions`: `Object` 93 | 94 | Any additional options you'd like to provide to the transpiler 95 | 96 | * When `type === "babel"`: See the [babel options documentation][babel-docs] 97 | * When `type === "ts-node"`: See the [ts-node options documentation][tsnode-docs] 98 | 99 | * `hooks`: `Object` 100 | 101 | Allows you to hook into certain processes. 102 | 103 | * `ignore`: `Array` 104 | 105 | `IgnoreHookFn = (filename: string, origIgnore: boolean) => boolean` 106 | 107 | Override the rule set used to tell the transpiler to ignore files. 108 | 109 | * Receives two parameters: 110 | 111 | 1. The file name to check (fully qualified) 112 | 2. The original ignore value 113 | 114 | * Return a falsy or truthy value. It will be converted to boolean. 115 | * To use the value that would have been chosen by the internal process, return the second parameter. 116 | * The first `true` or truthy value to be returned from your rule set will be used. The rest of the rule 117 | set will be ignored 118 | 119 | ### Default exports 120 | 121 | The default export is supported for your `gatsby-*.ts` files. This is important to note, because Typescript 122 | prefers that you use either the default export, or named exports. 123 | 124 | While named exports are absolutely supported as well, some people may prefer to build their module object 125 | and then export it all at once. In that case, you may use the default export. 126 | 127 | In other cases, you may want to perform some more advanced actions during the module processing. For this, 128 | you may export a function as the default export. They will be called in order 129 | (`gatsby-config` -> `gatsby-node`), and used to set the module's exports so that Gatsby can read them. 130 | 131 | #### Default export function 132 | 133 | `gatsby-config.ts` or `gatsby-node.ts` may export a function as the default export. This will be called with 134 | some details regarding the transpiling process, as well as some helpful information about the current project. 135 | 136 | These modules may export this function as the default export whether or not they are in the root of your 137 | site, as is the Gatsby standard. However, since this plugin needs to get kicked off by one of the 138 | `useGatsby*` plugins, `gatsby-config` may not be accessible from the root. 139 | 140 | These functions should return the object that Gatsby generally expects. For `gatsby-config`, it would be 141 | the same object you would define in `gatsby-config.js`. For `gatsby-node`, it would be the `gatsby-node` 142 | APIs. 143 | 144 | #### Function parameters 145 | 146 | The default export function will receive two parameters: 147 | 148 | 1. The transpiler & project information 149 | * `projectRoot`: The absolute path to the current project. 150 | * `imports`: All of the imports used by your `gatsby-*` modules. 151 | * This is structured by API Type, and then by plugin + API Type 152 | * `config`: `string[]` 153 | * `node`: `string[]` 154 | * `plugins`: `Object` 155 | * `[pluginName: string]`: `Object` 156 | * `config`: `string[]` 157 | * `node`: `string[]` 158 | 159 | 2. The property bag defined in the bootstrap (`useGatsby*`) functions. 160 | 161 | ### Contributing / Issues 162 | 163 | If you feel a feature is missing, or you find a bug, please feel free to file an issue 164 | at . 165 | 166 | I would also welcome any additions anybody would like to make. 167 | 168 | ### Donations 169 | 170 | If you enjoyed using this plugin, and you'd like to help support its development, you're welcome to donate! 171 | 172 | [![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=J3ZGS46A4C5QA¤cy_code=USD&source=url) 173 | -------------------------------------------------------------------------------- /old/README.md: -------------------------------------------------------------------------------- 1 | ## Configure Gatsby to use Typescript for configuration files 2 | 3 | [![paypal](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=J3ZGS46A4C5QA¤cy_code=USD&source=url) 4 | 5 | > This plugin will allow you to write your `gatsby-*` configuration files in Typescript. 6 | 7 | --- 8 | 9 | Table of Contents: 10 | 1. Basic Information 11 | 1. [Installation](#installation) 12 | 2. [Usage](#usage) 13 | 3. [Plugin Options](#plugin-options) 14 | 4. [Determining the Interpreter](#determining-the-interpreter) 15 | 5. [Site Implementation](#site-implementation) 16 | 17 | 2. Additional Information 18 | 1. [Extended Usage](./docs/EXTENDED-USAGE.md) 19 | 2. [Gatsby API](./docs/GATSBY-API.md) 20 | 3. [Property Bag](./docs/PROPERTY-BAG.md) 21 | 4. [Gatsby Config Utilities](./docs/GATSBY-CONFIG-UTILS.md) 22 | 23 | --- 24 | 25 | ### Installation 26 | 27 | * Install using your project manager 28 | 29 | ```shell 30 | npm install -D gatsby-plugin-ts-config 31 | ``` 32 | 33 | --- 34 | 35 | > ## IMPORTANT: 36 | > 37 | > Before reading below, please note that it is recommended for you to define a `configDir` in 38 | > the plugin options. 39 | > 40 | > Because of the process this plugin has to follow so that it can interpret your typescript 41 | > configuration files, some conflicts may occur regarding node ownership if you keep your 42 | > `gatsby-node.ts` in the project root. In order to place that file in a sub-directory, you 43 | > will need to define a `configDir`, and you will also need to put the rest of your 44 | > configurations in the same place. 45 | 46 | --- 47 | 48 | ### Usage 49 | 50 | You will still need to define, at minimum, one `.js` Gatsby configuration: `gatsby-config.js`. 51 | 52 | As usual, it needs to be in the root of your project. You will then use _**it**_ to call 53 | this plugin, and the rest of your configuration will be in Typescript files. 54 | 55 | * The easy way 56 | 57 | * You can use the `generateConfig` utility that is exported by `gatsby-plugin-ts-config` to set 58 | up the plugin array the way it needs to be. 59 | 60 | * Doing it this way allows your IDE to read the type interface for the options, so you can use 61 | intellisense while defining the plugin options. 62 | 63 | ```js 64 | // gatsby-config.js 65 | const { generateConfig } = require('gatsby-plugin-ts-config'); 66 | module.exports = generateConfig(); 67 | ``` 68 | 69 | * The common way 70 | 71 | * _This can also be done the normal way. The utility above just makes it easy_ 72 | 73 | ```js 74 | // gatsby-config.js 75 | module.exports = { 76 | plugins: [ 77 | `gatsby-plugin-ts-config`, 78 | ] 79 | } 80 | ``` 81 | 82 | * Configuration complete 83 | 84 | * After that, all of your configuration files can be written in Typescript: 85 | 86 | * gatsby-browser.ts 87 | * gatsby-config.ts 88 | * gatsby-node.ts 89 | * gatsby-ssr.ts 90 | 91 | **For extended usage examples, see the [Extended Usage](./docs/EXTENDED-USAGE.md) chapter** 92 | 93 | --- 94 | 95 | ### Plugin Options 96 | 97 | * `projectRoot`: `{string}` 98 | * Default: `process.cwd()` 99 | * This defines your project's root directory for the plugin. All folder/file resolutions will be performed 100 | relative to this directory. 101 | 102 | * `configDir`: `{string}` 103 | * Default: `projectRoot` 104 | * You can define a folder, relative to `projectRoot`, that will store your Typescript configuration files. 105 | If you do not define this option, then it will automatically use `projectRoot`. 106 | 107 | * `babel`: `{boolean|TransformOptions}` 108 | * Default: `true` 109 | * Setting this to `true`, or an object, will cause the plugin to use `babel` for transforming 110 | typescript configurations. 111 | * If an object is defined, it must contain options valid for `babel`. See the 112 | [babel options documentation](https://babeljs.io/docs/en/options#config-loading-options) for 113 | a description of the available options. Anything you can put in `.babelrc` can be put here. 114 | * See [Determining the interpreter](#determining-the-interpreter) below for details on how 115 | the interpeter is chosen 116 | 117 | * `tsNode`: `{boolean|RegisterOptions}` 118 | * Default: `false` 119 | * Setting this to `true` or an object will cause `ts-node` to be used, so long as `babel` is 120 | a falsy value. 121 | * If an object, you may use any options that are valid for `ts-node`'s `register()` function. 122 | See the [ts-node options documentation here](https://github.com/TypeStrong/ts-node#cli-and-programmatic-options) 123 | * See [Determining the interpreter](#determining-the-interpreter) below for details on how 124 | the interpeter is chosen 125 | 126 | * `props`: `{object}` 127 | * Default: `{}` 128 | * Reference: [docs](docs/PROPERTY-BAG.md) 129 | * This object will be passed to the default functions that are exported from `gatsby-config` and `gatsby-node`, 130 | as well as any `includePlugins()` calls. 131 | * This is meant to contain a dynamic context; anything you may want to pass around to the various functions that 132 | this plugin supports. 133 | * This object is **mutable**, so any changes you make to it after it has been passed to a function will be 134 | persistent. 135 | * If you are using the `generateConfig()` plugin declaration, this will be represented by that function's 136 | second parameter: 137 | 138 | ```js 139 | // gatsby-config.js 140 | module.exports = generateConfig({}, { test: 1234 }); 141 | ``` 142 | 143 | ```ts 144 | // .gatsby/gatsby-config.ts 145 | export default ({ projectRoot }, { test }) => { 146 | console.log(test) // 1234 147 | } 148 | ``` 149 | 150 | --- 151 | 152 | ### Determining the interpreter 153 | 154 | > Babel takes priority, so will be the default interpreter. 155 | 156 | 1. If `babel` is a truthy value, or `tsNode` is a falsy value, then `babel` will be chosen. 157 | 158 | 2. If `babel` is a truthy value, and `tsNode` is a truthy value, then `babel` will be chosen. 159 | 160 | 3. If `babel` is a falsy value, and `tsNode` is a truthy value, then `ts-node` will be chosen. 161 | 162 | _For the moment, there is no way to layer `ts-node` -> `babel`, but the feature may be included 163 | in a later release_ 164 | 165 | --- 166 | 167 | ### Site Implementation 168 | 169 | The primary purpose of this plugin is to allow you to write your `gatsby-config` and `gatsby-node` 170 | in Typescript. To that end, your `gatsby-config` and `gatsby-node` may follow the standard 171 | Gatsby API pattern. 172 | 173 | However, this plugin also supports some more advanced features that you may find useful: 174 | 175 | * `gatsby-*` default export functions 176 | * You are no longer restricted to exporting a simple object from your root project's 177 | `gatsby-config`. Your Typescript `gatsby-config` and `gatsby-node` may export a function 178 | as the default export. 179 | * The default export functions receive various parameters that may be useful to you. 180 | 181 | * Property Bag 182 | * A mutable object that is passed around to all of the functions that this plugin supports 183 | * Allows you to share information between `gatsby-config` functions and `gatsby-node`. 184 | 185 | * `includePlugins()` 186 | * A utility function that allows more advanced declaration of Gatsby plugins. 187 | * Adds support for transpiling local plugins 188 | 189 | **For more information, see the [Gatsby API](./docs/GATSBY-API.md) chapter** 190 | 191 | --- 192 | 193 | ### Contributing / Issues 194 | 195 | If you feel a feature is missing, or you find a bug, please feel free to file an issue 196 | at . 197 | 198 | I would also welcome any additions anybody would like to make. 199 | 200 | ### Donations 201 | 202 | If you enjoyed using this plugin, and you'd like to help support its development, you're welcome to donate! 203 | 204 | [![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=J3ZGS46A4C5QA¤cy_code=USD&source=url) 205 | -------------------------------------------------------------------------------- /old/utils/options-handler.ts: -------------------------------------------------------------------------------- 1 | import mergeWith from "lodash/mergeWith"; 2 | import { keys } from "ts-transformer-keys"; 3 | import { TsConfigJson } from "type-fest"; 4 | import { TransformOptions as BabelTransformOptions } from "@babel/core"; 5 | import { RegisterOptions as TSNodeRegisterOptions } from "ts-node"; 6 | import { getAbsoluteRelativeTo } from "../utils/fs-tools"; 7 | import { compilePlugins } from "./endpoints"; 8 | 9 | import type { 10 | IGlobalOpts, 11 | PublicOpts, 12 | GatsbyEndpointResolverKeys, 13 | IGatsbyPluginDef, 14 | IGatsbyPluginWithOpts, 15 | IPluginDetails, 16 | IPluginDetailsCallback, 17 | PropertyBag, 18 | } from "../types"; 19 | import { resolvePluginPath } from "./endpoints"; 20 | 21 | const publicProps = keys(); 22 | 23 | export interface IResolvePlugins { 24 | < 25 | T extends IGatsbyPluginDef = IGatsbyPluginDef, 26 | P extends PropertyBag = PropertyBag, 27 | >(plugins: T[] | IPluginDetailsCallback): void; 28 | < 29 | T extends IGatsbyPluginDef = IGatsbyPluginDef, 30 | P extends PropertyBag = PropertyBag, 31 | >(plugins: T[], pluginsCb?: IPluginDetailsCallback): void; 32 | } 33 | 34 | class OptionsHandler { 35 | private opts = {} as IGlobalOpts; 36 | private _propertyBag = {} as PropertyBag; 37 | private _plugins: IPluginDetails[] = []; 38 | private _pluginCb: IPluginDetailsCallback[] = []; 39 | private _extendedPlugins: IPluginDetails[] = []; 40 | 41 | public set(args: Partial, props: PropertyBag) { 42 | this.opts = { 43 | ...this.opts, 44 | ...args, 45 | }; 46 | this._propertyBag = props; 47 | } 48 | 49 | private doResolvePlugins = (plugins: T[]): IPluginDetails[] => { 50 | return plugins 51 | .reduce((acc, plugin) => { 52 | const curPlugin = typeof plugin === 'string' 53 | ? plugin as string 54 | : plugin as IGatsbyPluginWithOpts | false; 55 | if (!curPlugin) return acc; 56 | 57 | const pluginDetails = (typeof curPlugin === 'string' 58 | ? { 59 | name: curPlugin, 60 | options: {}, 61 | } : { 62 | name: curPlugin.resolve, 63 | options: curPlugin.options, 64 | }) as IPluginDetails; 65 | pluginDetails.path = resolvePluginPath({ 66 | projectRoot: this.opts.projectRoot, 67 | pluginName: pluginDetails.name, 68 | }); 69 | if (pluginDetails.path) { 70 | acc.push(pluginDetails); 71 | } else { 72 | throw `[gatsby-plugin-ts-config] Unable to locate plugin ${pluginDetails.name}`; 73 | } 74 | return acc; 75 | }, [] as IPluginDetails[]) 76 | .filter(Boolean); 77 | } 78 | 79 | public doExtendPlugins = (compile = false): void => { 80 | if (this.extendedPlugins.length > 0) return; 81 | const pluginDetails = this._pluginCb.reduce((acc, pluginCb) => { 82 | const plugins = this.doResolvePlugins( 83 | pluginCb(this.public(), this.propertyBag), 84 | ); 85 | if (compile) { 86 | compilePlugins({ 87 | plugins, 88 | }); 89 | } 90 | acc.push(...plugins); 91 | return acc; 92 | }, [] as IPluginDetails[]); 93 | this._extendedPlugins.push(...pluginDetails); 94 | this._plugins.push(...this._extendedPlugins); 95 | } 96 | 97 | public includePlugins: IResolvePlugins = < 98 | T extends IGatsbyPluginDef = IGatsbyPluginDef, 99 | P extends PropertyBag = PropertyBag, 100 | >( 101 | plugins: T[] | IPluginDetailsCallback, 102 | pluginsCb?: IPluginDetailsCallback, 103 | ) => { 104 | if (plugins instanceof Array) { 105 | this._plugins.push(...this.doResolvePlugins(plugins)); 106 | } else { 107 | this._pluginCb.push(plugins); 108 | } 109 | if (pluginsCb) { 110 | this._pluginCb.push(pluginsCb as IPluginDetailsCallback); 111 | } 112 | } 113 | 114 | private copyPlugins = (plugins: IPluginDetails[]): IPluginDetails[] => { 115 | return [ 116 | ...plugins.map((plugin) => ({ 117 | ...plugin, 118 | })), 119 | ]; 120 | } 121 | public get plugins(): IPluginDetails[] { 122 | return this.copyPlugins(this._plugins); 123 | } 124 | public get extendedPlugins(): IPluginDetails[] { 125 | return this.copyPlugins(this._extendedPlugins); 126 | } 127 | 128 | private addPluginChainedImport = ( 129 | endpoint: GatsbyEndpointResolverKeys, 130 | name: string, 131 | filePath: string, 132 | ) => { 133 | const endpoints = this.opts.endpoints; 134 | if (!endpoints.plugin) endpoints.plugin = {}; 135 | if (!endpoints.plugin[name]) endpoints.plugin[name] = {}; 136 | if (!endpoints.plugin[name][endpoint]) endpoints.plugin[name][endpoint] = []; 137 | endpoints.plugin[name][endpoint]?.push(filePath); 138 | } 139 | 140 | public addChainedImport = ( 141 | endpoint: GatsbyEndpointResolverKeys, 142 | filepath: string, 143 | pluginName?: string, 144 | ) => { 145 | if (pluginName) { 146 | return this.addPluginChainedImport(endpoint, pluginName, filepath); 147 | } 148 | if (!this.opts.endpoints[endpoint]) this.opts.endpoints[endpoint] = []; 149 | if (this.opts.endpoints[endpoint]![0] === filepath) return; 150 | this.opts.endpoints[endpoint]!.push(filepath); 151 | } 152 | 153 | public get = (): IGlobalOpts => { 154 | return this.opts; 155 | } 156 | 157 | public public = (): PublicOpts => { 158 | const entries = Object.entries(this.opts); 159 | const publicOpts = entries 160 | .filter(([key]) => publicProps.includes(key as keyof PublicOpts)) 161 | .reduce((acc, [key, val]) => { 162 | acc[key as keyof PublicOpts] = val; 163 | return acc; 164 | }, {} as PublicOpts); 165 | return publicOpts; 166 | } 167 | 168 | public get propertyBag(): PropertyBag { 169 | return this._propertyBag; 170 | } 171 | 172 | private mergeOptionsWithConcat = (to: any, from: any): any => { 173 | if (to instanceof Array) { 174 | return to.concat(from); 175 | } 176 | } 177 | 178 | public setTsNodeOpts = (opts: TSNodeRegisterOptions = {}): Required["tsNodeOpts"] => { 179 | const compilerOptions: TsConfigJson["compilerOptions"] = { 180 | module: "commonjs", 181 | target: "es2015", 182 | allowJs: true, 183 | noEmit: true, 184 | declaration: false, 185 | importHelpers: true, 186 | resolveJsonModule: true, 187 | jsx: "preserve", 188 | // sourceMap: true, 189 | }; 190 | 191 | this.opts.tsNodeOpts = mergeWith( 192 | { 193 | project: getAbsoluteRelativeTo(this.opts.projectRoot, opts.project || "tsconfig.json"), 194 | files: true, 195 | compilerOptions, 196 | }, 197 | opts, 198 | this.mergeOptionsWithConcat, 199 | ); 200 | return this.opts.tsNodeOpts; 201 | } 202 | 203 | public setBabelOpts = (opts?: BabelTransformOptions): Required["babelOpts"] => { 204 | this.opts.babelOpts = mergeWith( 205 | { 206 | sourceMaps: "inline", 207 | sourceRoot: this.opts.projectRoot, 208 | cwd: this.opts.projectRoot, 209 | presets: [ 210 | require.resolve("@babel/preset-typescript"), 211 | require.resolve("./preset"), 212 | // addOptsToPreset( 213 | // require('babel-preset-gatsby-package'), 214 | // '@babel/plugin-transform-runtime', 215 | // { 216 | // absoluteRuntime: path.dirname(require.resolve('@babel/runtime/package.json')), 217 | // }, 218 | // ), 219 | ], 220 | }, 221 | opts, 222 | this.mergeOptionsWithConcat, 223 | ); 224 | return this.opts.babelOpts; 225 | } 226 | } 227 | 228 | export default new OptionsHandler(); --------------------------------------------------------------------------------