@clerk/dev-cli 0.0.1-canary.v5356e51
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.cjs +8 -0
- package/README.md +88 -0
- package/bin/cli.js +5 -0
- package/jsconfig.json +24 -0
- package/package.json +34 -0
- package/schema.json +47 -0
- package/src/cli.js +69 -0
- package/src/codemods/index.js +19 -0
- package/src/codemods/transforms/add-clerkjsurl-to-provider.cjs +32 -0
- package/src/commands/auth.js +4 -0
- package/src/commands/config.js +26 -0
- package/src/commands/init.js +50 -0
- package/src/commands/set-instance.js +20 -0
- package/src/commands/set-root.js +21 -0
- package/src/commands/setup.js +202 -0
- package/src/commands/watch.js +77 -0
- package/src/utils/getClerkPackages.js +23 -0
- package/src/utils/getConfiguration.js +31 -0
- package/src/utils/getDependencies.js +12 -0
- package/src/utils/getMonorepoRoot.js +21 -0
package/.eslintrc.cjs
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
root: true,
|
|
3
|
+
extends: ['@clerk/custom/node', '@clerk/custom/typescript', '@clerk/custom/jest'],
|
|
4
|
+
rules: {
|
|
5
|
+
// allowList all environment variables since we don't use Turborepo for clerk-dev anyway.
|
|
6
|
+
'turbo/no-undeclared-env-vars': ['error', { allowList: ['.*'] }],
|
|
7
|
+
},
|
|
8
|
+
};
|
package/README.md
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# `clerk-dev` CLI
|
|
2
|
+
|
|
3
|
+
The `clerk-dev` CLI is a tool designed to simplify the process of iterating on packages within the `clerk/javascript` repository within sample applications, such as customer reproductions. It allows for the installation of the monorepo versions of packages, and supports features such as hot module reloading for increased development velocity.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npm install --global @clerk/dev-cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
If you haven't already, install `turbo` globally.
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
npm install --global turbo
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## First time setup
|
|
18
|
+
|
|
19
|
+
After installing `@clerk/dev` globally, you'll need to initialise a configuration file and tell the CLI where to find your local copy of the `clerk/javascript` repository.
|
|
20
|
+
|
|
21
|
+
1. Initialise the configuration file which will be located at `~/.config/clerk/dev.json`:
|
|
22
|
+
|
|
23
|
+
```shell
|
|
24
|
+
clerk-dev init
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
2. Navigate to your local clone of `clerk/javascript` and run `set-root`:
|
|
28
|
+
|
|
29
|
+
```shell
|
|
30
|
+
clerk-dev set-root
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
You're all set now to run the day-to-day commands 🎉
|
|
34
|
+
|
|
35
|
+
## Adding instances & changing the configuration
|
|
36
|
+
|
|
37
|
+
During the first time setup a `~/.config/clerk/dev.json` file was created. Its object contains a `activeInstance` and `instances` key. You can add additional instances by adding a new key to the `instances` object.
|
|
38
|
+
|
|
39
|
+
You can use the `set-instance` command to switch between `activeInstance` afterwards:
|
|
40
|
+
|
|
41
|
+
```shell
|
|
42
|
+
clerk-dev set-instance yourName
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Per-Project Setup
|
|
46
|
+
|
|
47
|
+
In each project you'd like to use with the monorepo versions of Clerk packages, `clerk-dev` can perform one-time framework setup such as installing the monorepo versions of packages and configuring the framework to use your Clerk keys.
|
|
48
|
+
|
|
49
|
+
To perform framework setup, run:
|
|
50
|
+
|
|
51
|
+
```sh
|
|
52
|
+
clerk-dev setup
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
If you aren't working on `@clerk/clerk-js`, and do not want to customize the `clerkJSUrl`, pass `--no-js`.
|
|
56
|
+
|
|
57
|
+
```sh
|
|
58
|
+
clerk-dev setup --no-js
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
If you want to skip the installation of monorepo versions of packages, pass `--skip-install`.
|
|
62
|
+
|
|
63
|
+
```sh
|
|
64
|
+
clerk-dev setup --skip-install
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Running
|
|
68
|
+
|
|
69
|
+
Once your project has been configured to use the monorepo versions of your dependencies, you can start the watchers for each dependency by running:
|
|
70
|
+
|
|
71
|
+
```sh
|
|
72
|
+
clerk-dev watch
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
This will run the `build` task for any `@clerk/*` packages in the `package.json` of the current working directory, including any of their dependencies.
|
|
76
|
+
|
|
77
|
+
> [!NOTE]
|
|
78
|
+
> On macOS, this command will automatically spawn a new Terminal.app window running the dev task for `clerk-js`. On other operating systems, you will need to run the following command in a new terminal:
|
|
79
|
+
>
|
|
80
|
+
> ```sh
|
|
81
|
+
> clerk-dev watch --js
|
|
82
|
+
> ```
|
|
83
|
+
|
|
84
|
+
If you do not want to spawn the watcher for `@clerk/clerk-js`, you can instead pass `--no-js`.
|
|
85
|
+
|
|
86
|
+
```sh
|
|
87
|
+
clerk-dev watch --no-js
|
|
88
|
+
```
|
package/bin/cli.js
ADDED
package/jsconfig.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"declaration": true,
|
|
4
|
+
"declarationMap": false,
|
|
5
|
+
"esModuleInterop": true,
|
|
6
|
+
"forceConsistentCasingInFileNames": true,
|
|
7
|
+
"importHelpers": true,
|
|
8
|
+
"isolatedModules": true,
|
|
9
|
+
"moduleResolution": "NodeNext",
|
|
10
|
+
"module": "NodeNext",
|
|
11
|
+
"noImplicitReturns": true,
|
|
12
|
+
"noUnusedLocals": true,
|
|
13
|
+
"noUnusedParameters": true,
|
|
14
|
+
"resolveJsonModule": true,
|
|
15
|
+
"sourceMap": false,
|
|
16
|
+
"strict": true,
|
|
17
|
+
"target": "ES2020",
|
|
18
|
+
"outDir": "dist",
|
|
19
|
+
"types": ["jest"],
|
|
20
|
+
"checkJs": true
|
|
21
|
+
},
|
|
22
|
+
"exclude": ["node_modules"],
|
|
23
|
+
"include": ["src/"]
|
|
24
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@clerk/dev-cli",
|
|
3
|
+
"version": "0.0.1-canary.v5356e51",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"author": "Clerk",
|
|
7
|
+
"homepage": "https://clerk.com/",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/clerk/javascript.git",
|
|
11
|
+
"directory": "packages/dev-cli"
|
|
12
|
+
},
|
|
13
|
+
"main": "bin/cli.js",
|
|
14
|
+
"bin": {
|
|
15
|
+
"clerk-dev": "bin/cli.js"
|
|
16
|
+
},
|
|
17
|
+
"engines": {
|
|
18
|
+
"node": ">=18.17.0"
|
|
19
|
+
},
|
|
20
|
+
"scripts": {
|
|
21
|
+
"lint": "eslint src/"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"commander": "^12.1.0",
|
|
25
|
+
"dotenv": "^16.4.5",
|
|
26
|
+
"globby": "^14.0.2",
|
|
27
|
+
"jscodeshift": "^0.16.1"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@clerk/eslint-config-custom": "*",
|
|
31
|
+
"@types/node": "^20.14.8",
|
|
32
|
+
"typescript": "*"
|
|
33
|
+
}
|
|
34
|
+
}
|
package/schema.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$ref": "#/definitions/Configuration",
|
|
4
|
+
"definitions": {
|
|
5
|
+
"Configuration": {
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"properties": {
|
|
9
|
+
"$schema": { "type": "string" },
|
|
10
|
+
"root": {
|
|
11
|
+
"anyOf": [{ "type": "string" }, { "type": "null" }]
|
|
12
|
+
},
|
|
13
|
+
"activeInstance": {
|
|
14
|
+
"type": "string"
|
|
15
|
+
},
|
|
16
|
+
"instances": {
|
|
17
|
+
"type": "object",
|
|
18
|
+
"additionalProperties": {
|
|
19
|
+
"$ref": "#/definitions/InstanceConfiguration"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"required": ["root", "activeInstance", "instances"],
|
|
24
|
+
"title": "Configuration"
|
|
25
|
+
},
|
|
26
|
+
"InstanceConfiguration": {
|
|
27
|
+
"type": "object",
|
|
28
|
+
"additionalProperties": false,
|
|
29
|
+
"properties": {
|
|
30
|
+
"secretKey": {
|
|
31
|
+
"type": "string"
|
|
32
|
+
},
|
|
33
|
+
"publishableKey": {
|
|
34
|
+
"type": "string"
|
|
35
|
+
},
|
|
36
|
+
"fapiUrl": {
|
|
37
|
+
"type": "string"
|
|
38
|
+
},
|
|
39
|
+
"bapiUrl": {
|
|
40
|
+
"type": "string"
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"required": ["secretKey", "publishableKey", "fapiUrl", "bapiUrl"],
|
|
44
|
+
"title": "InstanceConfiguration"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
package/src/cli.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
|
|
3
|
+
import { config } from './commands/config.js';
|
|
4
|
+
import { init } from './commands/init.js';
|
|
5
|
+
import { setInstance } from './commands/set-instance.js';
|
|
6
|
+
import { setRoot } from './commands/set-root.js';
|
|
7
|
+
import { setup } from './commands/setup.js';
|
|
8
|
+
import { watch } from './commands/watch.js';
|
|
9
|
+
|
|
10
|
+
export default function cli() {
|
|
11
|
+
const program = new Command();
|
|
12
|
+
|
|
13
|
+
program.name('clerk-dev').description('CLI to make developing Clerk packages easier').version('0.0.0');
|
|
14
|
+
|
|
15
|
+
program
|
|
16
|
+
.command('init')
|
|
17
|
+
.description('Perform initial one-time machine setup')
|
|
18
|
+
.action(async () => {
|
|
19
|
+
await init();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
program
|
|
23
|
+
.command('config')
|
|
24
|
+
.description('Open the clerk-dev config file in EDITOR or VISUAL')
|
|
25
|
+
.action(async () => {
|
|
26
|
+
await config();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
program
|
|
30
|
+
.command('set-root')
|
|
31
|
+
.description(
|
|
32
|
+
'Set the location of your checkout of the clerk/javascript repository to the current working directory',
|
|
33
|
+
)
|
|
34
|
+
.action(async () => {
|
|
35
|
+
await setRoot();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
program
|
|
39
|
+
.command('set-instance')
|
|
40
|
+
.description('Set the active instance to the provided instance name')
|
|
41
|
+
.argument('<name>', 'name of instance listed in dev.json')
|
|
42
|
+
.action(async instance => {
|
|
43
|
+
await setInstance({ instance });
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
program
|
|
47
|
+
.command('setup')
|
|
48
|
+
.description(
|
|
49
|
+
'Install the monorepo versions of Clerk packages listed in the package.json file and perform framework configuration for the current working directory',
|
|
50
|
+
)
|
|
51
|
+
.option('--no-js', 'do not customize the clerkJSUrl')
|
|
52
|
+
.option('--skip-install', 'only perform framework configuration; do not install monorepo versions of packages')
|
|
53
|
+
.action(async ({ js, skipInstall }) => {
|
|
54
|
+
await setup({ js, skipInstall });
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
program
|
|
58
|
+
.command('watch')
|
|
59
|
+
.description(
|
|
60
|
+
'Start the dev tasks for all Clerk packages listed in the package.json file in the current working directory, including clerk-js (unless --no-js is specified)',
|
|
61
|
+
)
|
|
62
|
+
.option('--js', 'only start the watcher for clerk-js')
|
|
63
|
+
.option('--no-js', 'do not spawn the clerk-js watcher (macOS only)')
|
|
64
|
+
.action(async ({ js }) => {
|
|
65
|
+
await watch({ js });
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
program.parseAsync();
|
|
69
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
|
|
3
|
+
import { run } from 'jscodeshift/src/Runner.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
*
|
|
7
|
+
* @param {string} transform
|
|
8
|
+
* @param {string[]} paths
|
|
9
|
+
*/
|
|
10
|
+
export async function applyCodemod(transform, paths) {
|
|
11
|
+
const pathToTransform = join(import.meta.dirname, 'transforms', transform);
|
|
12
|
+
return await run(pathToTransform, paths, {
|
|
13
|
+
ignorePattern: ['**/node_modules/**', '**/dist/**'],
|
|
14
|
+
extensions: 'tsx,ts,jsx,js',
|
|
15
|
+
parser: 'tsx',
|
|
16
|
+
silent: true,
|
|
17
|
+
runInBand: true,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const CLERK_JS_URL_PROP = 'clerkJSUrl';
|
|
2
|
+
const CLERK_JS_URL = 'http://localhost:4000/npm/clerk.browser.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
*
|
|
6
|
+
* @param {import('jscodeshift').FileInfo} file
|
|
7
|
+
* @param {import('jscodeshift').API} api
|
|
8
|
+
*/
|
|
9
|
+
module.exports = function transformer(file, api) {
|
|
10
|
+
const j = api.jscodeshift;
|
|
11
|
+
const root = j(file.source);
|
|
12
|
+
|
|
13
|
+
const clerkProvider = root.findJSXElements('ClerkProvider');
|
|
14
|
+
|
|
15
|
+
if (clerkProvider.size() > 0) {
|
|
16
|
+
clerkProvider.forEach(clerkProviderElement => {
|
|
17
|
+
const attrs = clerkProviderElement.get('attributes');
|
|
18
|
+
const hasClerkJSUrlProp = attrs.value.some(n => n.name.name === CLERK_JS_URL_PROP);
|
|
19
|
+
if (hasClerkJSUrlProp) {
|
|
20
|
+
for (const attr of attrs.value) {
|
|
21
|
+
if (attr.name.name === CLERK_JS_URL_PROP) {
|
|
22
|
+
attr.value = j.literal(CLERK_JS_URL);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
} else {
|
|
26
|
+
attrs.push(j.jsxAttribute(j.jsxIdentifier(CLERK_JS_URL_PROP), j.literal(CLERK_JS_URL)));
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return root.toSource();
|
|
32
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
|
|
3
|
+
import { CONFIG_FILE } from '../utils/getConfiguration.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Opens the clerk-dev config file in VISUAL or EDITOR.
|
|
7
|
+
*/
|
|
8
|
+
export async function config() {
|
|
9
|
+
if (process.env.VISUAL) {
|
|
10
|
+
spawn(process.env.VISUAL, [CONFIG_FILE], {
|
|
11
|
+
stdio: 'inherit',
|
|
12
|
+
env: {
|
|
13
|
+
...process.env,
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
} else if (process.env.EDITOR) {
|
|
17
|
+
spawn(process.env.EDITOR, [CONFIG_FILE], {
|
|
18
|
+
stdio: 'inherit',
|
|
19
|
+
env: {
|
|
20
|
+
...process.env,
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
} else {
|
|
24
|
+
console.error(`Unable to open clerk-dev config file. Make sure the EDITOR or VISUAL environment variable is set.`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { writeFile } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
|
|
5
|
+
import { CONFIG_FILE } from '../utils/getConfiguration.js';
|
|
6
|
+
import { getCLIRoot } from '../utils/getMonorepoRoot.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Performs one-time machine-level configuration tasks such as creating a blank config file.
|
|
10
|
+
*/
|
|
11
|
+
export async function init() {
|
|
12
|
+
const cliRoot = getCLIRoot();
|
|
13
|
+
|
|
14
|
+
/** @type import('../utils/getConfiguration.js').Configuration */
|
|
15
|
+
const configuration = {
|
|
16
|
+
$schema: join(cliRoot, 'packages', 'dev', 'schema.json'),
|
|
17
|
+
root: null,
|
|
18
|
+
activeInstance: 'default',
|
|
19
|
+
instances: {
|
|
20
|
+
default: {
|
|
21
|
+
secretKey: 'sk_REPLACE_WITH_SECRET_KEY',
|
|
22
|
+
publishableKey: 'pk_REPLACE_WITH_PUBLISHABLE_KEY',
|
|
23
|
+
fapiUrl: 'REPLACE_WITH_FAPI_URL',
|
|
24
|
+
bapiUrl: 'https://api.clerk.com',
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
await writeFile(CONFIG_FILE, JSON.stringify(configuration, null, 2), 'utf-8');
|
|
30
|
+
|
|
31
|
+
if (process.env.VISUAL) {
|
|
32
|
+
spawn(process.env.VISUAL, [CONFIG_FILE], {
|
|
33
|
+
stdio: 'inherit',
|
|
34
|
+
env: {
|
|
35
|
+
...process.env,
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
console.log(`Configuration file written to ${CONFIG_FILE}.`);
|
|
39
|
+
} else if (process.env.EDITOR) {
|
|
40
|
+
spawn(process.env.EDITOR, [CONFIG_FILE], {
|
|
41
|
+
stdio: 'inherit',
|
|
42
|
+
env: {
|
|
43
|
+
...process.env,
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
console.log(`Configuration file written to ${CONFIG_FILE}.`);
|
|
47
|
+
} else {
|
|
48
|
+
console.log(`Configuration file written to ${CONFIG_FILE}. Replace with your values.`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { writeFile } from 'node:fs/promises';
|
|
2
|
+
|
|
3
|
+
import { CONFIG_FILE, getConfiguration } from '../utils/getConfiguration.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Sets the active instance to the provided instance name.
|
|
7
|
+
* @param {object} args
|
|
8
|
+
* @param {string} args.instance
|
|
9
|
+
*/
|
|
10
|
+
export async function setInstance({ instance }) {
|
|
11
|
+
const config = await getConfiguration();
|
|
12
|
+
|
|
13
|
+
if (!(instance in config.instances)) {
|
|
14
|
+
throw new Error(`Instance "${instance}" not found in dev.json.`);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const newConfig = { ...config, activeInstance: instance };
|
|
18
|
+
await writeFile(CONFIG_FILE, JSON.stringify(newConfig, null, 2), 'utf-8');
|
|
19
|
+
console.log(`clerk-dev activeInstance set to ${instance}.`);
|
|
20
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { readFile, writeFile } from 'node:fs/promises';
|
|
2
|
+
|
|
3
|
+
import { CONFIG_FILE, getConfiguration } from '../utils/getConfiguration.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Sets the location of the clerk/javascript monorepo to the machine-level config file.
|
|
7
|
+
*/
|
|
8
|
+
export async function setRoot() {
|
|
9
|
+
const config = await getConfiguration();
|
|
10
|
+
const cwd = process.cwd();
|
|
11
|
+
|
|
12
|
+
const packageJSON = await readFile('package.json', 'utf-8');
|
|
13
|
+
const pkg = JSON.parse(packageJSON);
|
|
14
|
+
if (pkg.name !== '@clerk/javascript') {
|
|
15
|
+
throw new Error('clerk-dev set-root needs to be run within a local checkout of the clerk/javascript repository.');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const newConfig = { ...config, root: process.cwd() };
|
|
19
|
+
await writeFile(CONFIG_FILE, JSON.stringify(newConfig, null, 2), 'utf-8');
|
|
20
|
+
console.log(`clerk-dev root set to ${cwd}.`);
|
|
21
|
+
}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { readFile, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
|
|
6
|
+
import { parse } from 'dotenv';
|
|
7
|
+
|
|
8
|
+
import { applyCodemod } from '../codemods/index.js';
|
|
9
|
+
import { getClerkPackages } from '../utils/getClerkPackages.js';
|
|
10
|
+
import { getConfiguration } from '../utils/getConfiguration.js';
|
|
11
|
+
import { getDependencies } from '../utils/getDependencies.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Returns `true` if the cwd contains a file named `filename`, otherwise returns `false`.
|
|
15
|
+
* @param {string} filename
|
|
16
|
+
* @returns {boolean}
|
|
17
|
+
*/
|
|
18
|
+
function hasFile(filename) {
|
|
19
|
+
return existsSync(join(process.cwd(), filename));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Returns `true` if `packages` contains a `dependency` key, otherwise `false`.
|
|
24
|
+
* @param {Record<string, string> | undefined} packages
|
|
25
|
+
* @param {string} dependency
|
|
26
|
+
* @returns {boolean}
|
|
27
|
+
*/
|
|
28
|
+
function hasPackage(packages, dependency) {
|
|
29
|
+
if (!packages) {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
return dependency in packages;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Returns a string corresponding to the framework detected in the cwd.
|
|
37
|
+
* @returns {Promise<string>}
|
|
38
|
+
*/
|
|
39
|
+
async function detectFramework() {
|
|
40
|
+
const { dependencies, devDependencies } = await getDependencies(join(process.cwd(), 'package.json'));
|
|
41
|
+
|
|
42
|
+
const IS_NEXT = hasFile('next.config.js') || hasFile('next.config.mjs');
|
|
43
|
+
if (IS_NEXT) {
|
|
44
|
+
return 'nextjs';
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const IS_REMIX = hasFile('remix.config.js') || hasFile('remix.config.mjs');
|
|
48
|
+
if (IS_REMIX) {
|
|
49
|
+
return 'remix';
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const IS_VITE = hasPackage(dependencies, 'vite') || hasPackage(devDependencies, 'vite');
|
|
53
|
+
if (IS_VITE) {
|
|
54
|
+
return 'vite';
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
throw new Error('unable to determine framework');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Returns the active instance of the provided `configuration`.
|
|
62
|
+
* @param {import('../utils/getConfiguration.js').Configuration} configuration
|
|
63
|
+
* @returns {Promise<import('../utils/getConfiguration.js').InstanceConfiguration>}
|
|
64
|
+
*/
|
|
65
|
+
async function getInstanceConfiguration(configuration) {
|
|
66
|
+
const { activeInstance, instances } = configuration;
|
|
67
|
+
return instances[activeInstance];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Generates a .env file string.
|
|
72
|
+
* @param {Record<string, string>} envConfiguration
|
|
73
|
+
* @returns {string}
|
|
74
|
+
*/
|
|
75
|
+
function buildEnvFile(envConfiguration) {
|
|
76
|
+
return (
|
|
77
|
+
Object.entries(envConfiguration)
|
|
78
|
+
.map(([key, value]) => [key, JSON.stringify(value)].join('='))
|
|
79
|
+
.join('\n') + '\n'
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Parses the file at `filename` with dotenv.
|
|
85
|
+
* @param {string} filename
|
|
86
|
+
* @returns {Promise<import('dotenv').DotenvParseOutput>}
|
|
87
|
+
*/
|
|
88
|
+
async function readEnvFile(filename) {
|
|
89
|
+
const contents = await readFile(filename, 'utf-8');
|
|
90
|
+
const envConfig = parse(contents);
|
|
91
|
+
return envConfig;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Write the provided `config` to the .env file at `filename`, merging with any existing entries.
|
|
96
|
+
* @param {string} filename
|
|
97
|
+
* @param {Record<string, string>} config
|
|
98
|
+
*/
|
|
99
|
+
async function mergeEnvFiles(filename, config) {
|
|
100
|
+
const envFileExists = hasFile(filename);
|
|
101
|
+
const existingEnv = envFileExists ? await readEnvFile(filename) : {};
|
|
102
|
+
await writeFile(
|
|
103
|
+
join(process.cwd(), filename),
|
|
104
|
+
buildEnvFile({
|
|
105
|
+
...existingEnv,
|
|
106
|
+
...config,
|
|
107
|
+
}),
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Installs the monorepo versions of the Clerk dependencies listed in the `package.json` file of the cwd.
|
|
113
|
+
* @returns {Promise<void>}
|
|
114
|
+
*/
|
|
115
|
+
async function linkDependencies() {
|
|
116
|
+
const { dependencies } = await getDependencies(join(process.cwd(), 'package.json'));
|
|
117
|
+
if (!dependencies) {
|
|
118
|
+
throw new Error('you have no dependencies');
|
|
119
|
+
}
|
|
120
|
+
const clerkPackages = await getClerkPackages();
|
|
121
|
+
|
|
122
|
+
const dependenciesToBeInstalled = Object.keys(dependencies)
|
|
123
|
+
.filter(dep => dep in clerkPackages)
|
|
124
|
+
.map(clerkDep => clerkPackages[clerkDep]);
|
|
125
|
+
|
|
126
|
+
const args = ['install', '--no-audit', '--no-fund', ...dependenciesToBeInstalled];
|
|
127
|
+
|
|
128
|
+
return new Promise((resolve, reject) => {
|
|
129
|
+
const child = spawn('npm', args, {
|
|
130
|
+
stdio: 'inherit',
|
|
131
|
+
env: {
|
|
132
|
+
...process.env,
|
|
133
|
+
ADBLOCK: '1',
|
|
134
|
+
DISABLE_OPENCOLLECTIVE: '1',
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
child.on('close', code => {
|
|
139
|
+
if (code !== 0) {
|
|
140
|
+
reject();
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
resolve();
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Installs the monorepo-versions of Clerk dependencies listed in the `package.json` file of the current working
|
|
150
|
+
* directory, and performs framework configuration tasks necessary for local development with the monorepo packages.
|
|
151
|
+
* @param {object} args
|
|
152
|
+
* @param {boolean | undefined} args.js If `false`, do not customize the clerkJSUrl.
|
|
153
|
+
* @param {boolean | undefined} args.skipInstall If `true`, do not install monorepo versions of packages.
|
|
154
|
+
*/
|
|
155
|
+
export async function setup({ js = true, skipInstall = false }) {
|
|
156
|
+
if (!skipInstall) {
|
|
157
|
+
console.log('Installing monorepo versions of Clerk packages from package.json...');
|
|
158
|
+
await linkDependencies();
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const config = await getConfiguration();
|
|
162
|
+
const instance = await getInstanceConfiguration(config);
|
|
163
|
+
|
|
164
|
+
const framework = await detectFramework();
|
|
165
|
+
switch (framework) {
|
|
166
|
+
case 'nextjs': {
|
|
167
|
+
console.log('Next.js detected, writing environment variables to .env.local...');
|
|
168
|
+
await mergeEnvFiles('.env.local', {
|
|
169
|
+
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: instance.publishableKey,
|
|
170
|
+
CLERK_SECRET_KEY: instance.secretKey,
|
|
171
|
+
...(js ? { NEXT_PUBLIC_CLERK_JS_URL: 'http://localhost:4000/npm/clerk.browser.js' } : {}),
|
|
172
|
+
});
|
|
173
|
+
break;
|
|
174
|
+
}
|
|
175
|
+
case 'remix': {
|
|
176
|
+
console.log('Remix detected, writing environment variables to .env...');
|
|
177
|
+
await mergeEnvFiles('.env', {
|
|
178
|
+
CLERK_PUBLISHABLE_KEY: instance.publishableKey,
|
|
179
|
+
CLERK_SECRET_KEY: instance.secretKey,
|
|
180
|
+
});
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
case 'vite': {
|
|
184
|
+
console.log('Vite detected, writing environment variables to .env...');
|
|
185
|
+
await mergeEnvFiles('.env', {
|
|
186
|
+
VITE_CLERK_PUBLISHABLE_KEY: instance.publishableKey,
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
if (js) {
|
|
190
|
+
console.log('Adding clerkJSUrl to ClerkProvider...');
|
|
191
|
+
const res = await applyCodemod('add-clerkjsurl-to-provider.cjs', [process.cwd()]);
|
|
192
|
+
if (res.ok === 0) {
|
|
193
|
+
console.warn('warning: could not find a ClerkProvider to edit. Please add clerkJSUrl manually.');
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
default: {
|
|
199
|
+
throw new Error('unable to determine framework');
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
|
|
4
|
+
import { getClerkPackages } from '../utils/getClerkPackages.js';
|
|
5
|
+
import { getDependencies } from '../utils/getDependencies.js';
|
|
6
|
+
import { getMonorepoRoot } from '../utils/getMonorepoRoot.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Starts long-running watchers for Clerk dependencies.
|
|
10
|
+
* @param {object} args
|
|
11
|
+
* @param {boolean | undefined} args.js If `true`, only spawn the builder for `@clerk/clerk-js`.
|
|
12
|
+
* @returns {Promise<void>}
|
|
13
|
+
*/
|
|
14
|
+
export async function watch({ js }) {
|
|
15
|
+
const { dependencies, devDependencies } = await getDependencies(join(process.cwd(), 'package.json'));
|
|
16
|
+
const clerkPackages = Object.keys(await getClerkPackages());
|
|
17
|
+
|
|
18
|
+
const packagesInPackageJSON = [...Object.keys(dependencies ?? {}), ...Object.keys(devDependencies ?? {})];
|
|
19
|
+
const clerkPackagesInPackageJSON = packagesInPackageJSON.filter(p => clerkPackages.includes(p));
|
|
20
|
+
|
|
21
|
+
const filterArgs = clerkPackagesInPackageJSON.map(p => `--filter=${p}`);
|
|
22
|
+
|
|
23
|
+
const args = ['watch', 'build', ...filterArgs];
|
|
24
|
+
|
|
25
|
+
const cwd = await getMonorepoRoot();
|
|
26
|
+
|
|
27
|
+
if (js) {
|
|
28
|
+
return new Promise((resolve, reject) => {
|
|
29
|
+
const child = spawn(
|
|
30
|
+
'turbo',
|
|
31
|
+
['run', 'dev', '--filter=@clerk/clerk-js', '--', '--env', 'devOrigin=http://localhost:4000'],
|
|
32
|
+
{
|
|
33
|
+
cwd,
|
|
34
|
+
stdio: 'inherit',
|
|
35
|
+
env: { ...process.env },
|
|
36
|
+
},
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
child.on('close', code => {
|
|
40
|
+
if (code !== 0) {
|
|
41
|
+
reject();
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
resolve();
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (typeof js === 'undefined') {
|
|
50
|
+
// On macOS, we spawn a new Terminal.app instance containing the watcher for clerk-js. This is because clerk-js is
|
|
51
|
+
// not declared as a dependency for any other packages, so Turborepo is unable to automatically start it.
|
|
52
|
+
if (process.platform === 'darwin') {
|
|
53
|
+
spawn('osascript', [
|
|
54
|
+
'-e',
|
|
55
|
+
`tell app "Terminal" to do script "cd ${cwd} && turbo run dev --filter=@clerk/clerk-js -- --env devOrigin=http://localhost:4000"`,
|
|
56
|
+
]);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return new Promise((resolve, reject) => {
|
|
61
|
+
const child = spawn('turbo', args, {
|
|
62
|
+
cwd,
|
|
63
|
+
stdio: 'inherit',
|
|
64
|
+
env: {
|
|
65
|
+
...process.env,
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
child.on('close', code => {
|
|
70
|
+
if (code !== 0) {
|
|
71
|
+
reject();
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
resolve();
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { dirname, join, posix, resolve } from 'node:path';
|
|
3
|
+
|
|
4
|
+
import { globby } from 'globby';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Generates an object with keys of package names and values of absolute paths to the package folder.
|
|
8
|
+
* @returns {Promise<Record<string, string>>}
|
|
9
|
+
*/
|
|
10
|
+
export async function getClerkPackages() {
|
|
11
|
+
const monorepoRoot = resolve(join(import.meta.dirname, '..', '..', '..', '..'));
|
|
12
|
+
/** @type {Record<string, string>} */
|
|
13
|
+
const packages = {};
|
|
14
|
+
const clerkPackages = await globby([posix.join(monorepoRoot, 'packages', '*', 'package.json'), '!*node_modules*']);
|
|
15
|
+
for (const packageJSON of clerkPackages) {
|
|
16
|
+
const { name } = JSON.parse(await readFile(packageJSON, 'utf-8'));
|
|
17
|
+
if (name) {
|
|
18
|
+
packages[name] = dirname(packageJSON);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return packages;
|
|
23
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @typedef {object} InstanceConfiguration
|
|
7
|
+
* @prop {string} secretKey
|
|
8
|
+
* @prop {string} publishableKey
|
|
9
|
+
* @prop {string} fapiUrl
|
|
10
|
+
* @prop {string} bapiUrl
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @typedef {object} Configuration
|
|
15
|
+
* @prop {string} $schema
|
|
16
|
+
* @prop {string | null} root
|
|
17
|
+
* @prop {string} activeInstance
|
|
18
|
+
* @prop {Record<string, InstanceConfiguration>} instances
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
export const CONFIG_FILE = join(homedir(), '.config', 'clerk', 'dev.json');
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Gets the contents of the clerk-dev configuration file.
|
|
25
|
+
* @returns {Promise<Configuration>}
|
|
26
|
+
*/
|
|
27
|
+
export async function getConfiguration() {
|
|
28
|
+
const configFileJSON = await readFile(CONFIG_FILE, 'utf-8');
|
|
29
|
+
const configuration = JSON.parse(configFileJSON);
|
|
30
|
+
return configuration;
|
|
31
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Gets the `dependencies` and `devDependencies` entries of the provided package.json.
|
|
5
|
+
* @param {string} pathToPackageJSON
|
|
6
|
+
* @returns {Promise<{ dependencies?: Record<string, string>, devDependencies?: Record<string, string>}>}
|
|
7
|
+
*/
|
|
8
|
+
export async function getDependencies(pathToPackageJSON) {
|
|
9
|
+
const packageJSON = await readFile(pathToPackageJSON, 'utf-8');
|
|
10
|
+
const { dependencies, devDependencies } = JSON.parse(packageJSON);
|
|
11
|
+
return { dependencies, devDependencies };
|
|
12
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { join, resolve } from 'node:path';
|
|
2
|
+
|
|
3
|
+
import { getConfiguration } from './getConfiguration.js';
|
|
4
|
+
|
|
5
|
+
export function getCLIRoot() {
|
|
6
|
+
return resolve(join(import.meta.dirname, '..', '..', '..', '..'));
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Gets the `root` property of the clerk-dev configuration file, falling back to the folder containing the source
|
|
11
|
+
* for the running instance of clerk-dev.
|
|
12
|
+
* @returns {Promise<string>}
|
|
13
|
+
*/
|
|
14
|
+
export async function getMonorepoRoot() {
|
|
15
|
+
const config = await getConfiguration();
|
|
16
|
+
if (config.root) {
|
|
17
|
+
return config.root;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return getCLIRoot();
|
|
21
|
+
}
|