@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 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
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+
3
+ import cli from '../src/cli.js';
4
+
5
+ cli();
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,4 @@
1
+ /**
2
+ * TODO - authenticate to Clerk to fetch keys and persist them to the config file
3
+ */
4
+ export async function auth() {}
@@ -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
+ }