@supertokens-plugins/rownd-nodejs 0.2.0 → 0.3.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,136 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
18
+ // If the importer is in node compatibility mode or this is not an ESM
19
+ // file that has been converted to a CommonJS file using a Babel-
20
+ // compatible transform (i.e. "__esModule" has not been set), then set
21
+ // "default" to the CommonJS "module.exports" for node compatibility.
22
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
23
+ mod
24
+ ));
25
+
26
+ // scripts/initConfig.ts
27
+ var fs = __toESM(require("fs/promises"));
28
+ var import_node_path = require("path");
29
+ var BULK_MIGRATE_CONFIG_TEMPLATE = `# Maximum number of users to process. Remove this field to process everything.
30
+ limit: 100
31
+
32
+ checkpoint:
33
+ # File used to persist migration progress so a later run can resume.
34
+ file: ./rownd-migration-checkpoint.json
35
+ # When true, continue from the saved checkpoint instead of starting over.
36
+ resume: false
37
+
38
+ retry:
39
+ # Number of attempts for retryable HTTP failures such as 429 and 5xx.
40
+ maxAttempts: 5
41
+ # Base backoff delay in milliseconds before exponential retry waits.
42
+ initialDelayMs: 500
43
+
44
+ rownd:
45
+ appId: <APP_ID>
46
+ appKey: <APP_KEY>
47
+ appSecret: <APP_SECRET>
48
+ # Required when using generate-plugin-config --include-sub-brands.
49
+ # bearerToken: <ROWND_API_BEARER_TOKEN>
50
+ # Number of Rownd users to request per page.
51
+ pageSize: 100
52
+
53
+ supertokens:
54
+ connectionURI: <CONNECTION_URI>
55
+ apiKey: <API_KEY>
56
+ # Number of users to send to SuperTokens per bulk import request.
57
+ batchSize: 500
58
+ `;
59
+ function printHelp() {
60
+ console.log(`Usage: rownd-nodejs init-config --output <path> [options]
61
+
62
+ Options:
63
+ --type <type> Config template type. Only "bulk-migrate" is supported.
64
+ -o, --output <path> Destination path for the config file
65
+ --force Overwrite the destination if it already exists
66
+ -h, --help Show this help message`);
67
+ }
68
+ function parseArgs() {
69
+ const args = process.argv.slice(2);
70
+ const options = {
71
+ type: "bulk-migrate",
72
+ output: "",
73
+ force: false
74
+ };
75
+ for (let i = 0; i < args.length; i += 1) {
76
+ const arg = args[i];
77
+ if (arg === "--help" || arg === "-h") {
78
+ printHelp();
79
+ return void 0;
80
+ }
81
+ if (arg === "--type") {
82
+ const value = args[i + 1];
83
+ if (!value) {
84
+ throw new Error("Missing value for --type");
85
+ }
86
+ options.type = value;
87
+ i += 1;
88
+ } else if (arg === "--output" || arg === "-o") {
89
+ const value = args[i + 1];
90
+ if (!value) {
91
+ throw new Error(`Missing value for ${arg}`);
92
+ }
93
+ options.output = value;
94
+ i += 1;
95
+ } else if (arg === "--force") {
96
+ options.force = true;
97
+ } else {
98
+ throw new Error(`Unknown or incomplete argument: ${arg}`);
99
+ }
100
+ }
101
+ if (options.type !== "bulk-migrate") {
102
+ throw new Error('Only "bulk-migrate" config templates are supported.');
103
+ }
104
+ if (!options.output) {
105
+ throw new Error("Missing required --output <path>");
106
+ }
107
+ return options;
108
+ }
109
+ async function fileExists(path) {
110
+ try {
111
+ await fs.access(path);
112
+ return true;
113
+ } catch {
114
+ return false;
115
+ }
116
+ }
117
+ async function run() {
118
+ const options = parseArgs();
119
+ if (!options) {
120
+ return;
121
+ }
122
+ const outputPath = (0, import_node_path.resolve)(options.output);
123
+ if (!options.force && await fileExists(outputPath)) {
124
+ throw new Error(`${outputPath} already exists. Use --force to overwrite it.`);
125
+ }
126
+ await fs.mkdir((0, import_node_path.dirname)(outputPath), { recursive: true });
127
+ await fs.writeFile(outputPath, BULK_MIGRATE_CONFIG_TEMPLATE, {
128
+ encoding: "utf8",
129
+ flag: options.force ? "w" : "wx"
130
+ });
131
+ console.log(`Wrote bulk migration config template to ${outputPath}`);
132
+ }
133
+ run().catch((error) => {
134
+ console.error(error instanceof Error ? error.message : String(error));
135
+ process.exitCode = 1;
136
+ });
@@ -0,0 +1,295 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __export = (target, all) => {
10
+ for (var name in all)
11
+ __defProp(target, name, { get: all[name], enumerable: true });
12
+ };
13
+ var __copyProps = (to, from, except, desc) => {
14
+ if (from && typeof from === "object" || typeof from === "function") {
15
+ for (let key of __getOwnPropNames(from))
16
+ if (!__hasOwnProp.call(to, key) && key !== except)
17
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
18
+ }
19
+ return to;
20
+ };
21
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
22
+ // If the importer is in node compatibility mode or this is not an ESM
23
+ // file that has been converted to a CommonJS file using a Babel-
24
+ // compatible transform (i.e. "__esModule" has not been set), then set
25
+ // "default" to the CommonJS "module.exports" for node compatibility.
26
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
27
+ mod
28
+ ));
29
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
30
+
31
+ // scripts/setupCoreInstance.ts
32
+ var setupCoreInstance_exports = {};
33
+ __export(setupCoreInstance_exports, {
34
+ fetchRowndOidcClients: () => fetchRowndOidcClients,
35
+ provisionSuperTokensInfrastructure: () => provisionSuperTokensInfrastructure,
36
+ runCli: () => runCli
37
+ });
38
+ module.exports = __toCommonJS(setupCoreInstance_exports);
39
+ var import_zod2 = require("zod");
40
+
41
+ // scripts/scriptUtils.ts
42
+ var fs = __toESM(require("fs/promises"));
43
+ var import_node_path = require("path");
44
+ var import_yaml = require("yaml");
45
+ var import_zod = require("zod");
46
+ var SCRIPT_DIR = __dirname;
47
+ var ConfigSchema = import_zod.z.object({
48
+ limit: import_zod.z.preprocess(
49
+ (value) => value === void 0 ? Number.POSITIVE_INFINITY : value,
50
+ import_zod.z.union([import_zod.z.literal(Number.POSITIVE_INFINITY), import_zod.z.number().int().positive()])
51
+ ),
52
+ checkpoint: import_zod.z.object({
53
+ file: import_zod.z.string(),
54
+ resume: import_zod.z.boolean().default(false)
55
+ }),
56
+ retry: import_zod.z.object({
57
+ maxAttempts: import_zod.z.number().int().positive(),
58
+ initialDelayMs: import_zod.z.number().int().positive()
59
+ }),
60
+ rownd: import_zod.z.object({
61
+ appId: import_zod.z.string(),
62
+ appKey: import_zod.z.string(),
63
+ appSecret: import_zod.z.string(),
64
+ bearerToken: import_zod.z.string().optional(),
65
+ pageSize: import_zod.z.number().int().positive()
66
+ }),
67
+ supertokens: import_zod.z.object({
68
+ connectionURI: import_zod.z.string(),
69
+ apiKey: import_zod.z.string().optional(),
70
+ batchSize: import_zod.z.number().int().positive()
71
+ }),
72
+ thirdPartyProviders: import_zod.z.array(
73
+ import_zod.z.object({
74
+ thirdPartyId: import_zod.z.string(),
75
+ name: import_zod.z.string().optional(),
76
+ clients: import_zod.z.array(
77
+ import_zod.z.object({
78
+ clientType: import_zod.z.string().optional(),
79
+ clientId: import_zod.z.string(),
80
+ clientSecret: import_zod.z.string().optional(),
81
+ scope: import_zod.z.array(import_zod.z.string()).optional()
82
+ })
83
+ )
84
+ })
85
+ ).optional()
86
+ });
87
+ var RowndUserRecordSchema = import_zod.z.looseObject({
88
+ user_id: import_zod.z.string().optional(),
89
+ rownd_user: import_zod.z.string().optional(),
90
+ subject: import_zod.z.string().optional(),
91
+ state: import_zod.z.string().optional(),
92
+ auth_level: import_zod.z.string().optional(),
93
+ data: import_zod.z.looseObject({
94
+ user_id: import_zod.z.string(),
95
+ email: import_zod.z.string().optional(),
96
+ phone_number: import_zod.z.string().optional(),
97
+ google_id: import_zod.z.string().optional(),
98
+ apple_id: import_zod.z.string().optional(),
99
+ first_name: import_zod.z.string().optional(),
100
+ last_name: import_zod.z.string().optional()
101
+ }),
102
+ verified_data: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).default({}),
103
+ attributes: import_zod.z.record(import_zod.z.string(), import_zod.z.array(import_zod.z.string())).optional(),
104
+ groups: import_zod.z.array(import_zod.z.unknown()).optional(),
105
+ meta: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional(),
106
+ connection_map: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional()
107
+ });
108
+ var RowndUserListItemSchema = import_zod.z.object({
109
+ data: RowndUserRecordSchema
110
+ });
111
+ var RowndUsersPageSchema = import_zod.z.object({
112
+ results: import_zod.z.array(RowndUserRecordSchema).optional(),
113
+ data: import_zod.z.array(RowndUserListItemSchema).optional(),
114
+ total_results: import_zod.z.number().optional()
115
+ }).superRefine((value, context) => {
116
+ if (!value.results && !value.data) {
117
+ context.addIssue({
118
+ code: import_zod.z.ZodIssueCode.custom,
119
+ message: "Rownd API response is missing a results/data array."
120
+ });
121
+ }
122
+ });
123
+ function formatIssuePath(path) {
124
+ if (path.length === 0) {
125
+ return "<root>";
126
+ }
127
+ return path.map((segment, index) => {
128
+ if (typeof segment === "number") {
129
+ return `[${segment}]`;
130
+ }
131
+ return index === 0 ? segment : `.${segment}`;
132
+ }).join("");
133
+ }
134
+ function formatZodError(error) {
135
+ return error.issues.map(
136
+ (issue) => `${formatIssuePath(issue.path.filter((segment) => typeof segment === "string" || typeof segment === "number"))}: ${issue.message}`
137
+ ).join("\n");
138
+ }
139
+ function parseConfig(rawConfig, configDir = SCRIPT_DIR) {
140
+ const parsed = ConfigSchema.parse(rawConfig);
141
+ const checkpointFile = (0, import_node_path.isAbsolute)(parsed.checkpoint.file) ? parsed.checkpoint.file : (0, import_node_path.resolve)(configDir, parsed.checkpoint.file);
142
+ return {
143
+ limit: parsed.limit,
144
+ checkpoint: {
145
+ file: checkpointFile,
146
+ resume: parsed.checkpoint.resume
147
+ },
148
+ retry: parsed.retry,
149
+ rownd: parsed.rownd,
150
+ supertokens: parsed.supertokens,
151
+ thirdPartyProviders: parsed.thirdPartyProviders
152
+ };
153
+ }
154
+ async function loadConfig(configFilePath) {
155
+ const configFile = await fs.readFile(configFilePath, "utf8");
156
+ return parseConfig((0, import_yaml.parse)(configFile), (0, import_node_path.dirname)(configFilePath));
157
+ }
158
+ function parseRequiredConfigArg(args) {
159
+ for (let i = 0; i < args.length; i += 1) {
160
+ const arg = args[i];
161
+ if (arg === "--config" || arg === "-c") {
162
+ const value = args[i + 1];
163
+ if (!value) {
164
+ throw new Error(`Missing value for ${arg}`);
165
+ }
166
+ return (0, import_node_path.resolve)(value);
167
+ }
168
+ }
169
+ throw new Error("Missing required --config <path>");
170
+ }
171
+ function hasHelpArg(args) {
172
+ return args.includes("--help") || args.includes("-h");
173
+ }
174
+
175
+ // scripts/setupCoreInstance.ts
176
+ function printHelp() {
177
+ console.log(`Usage: rownd-nodejs setup-core --config <path>
178
+
179
+ Options:
180
+ -c, --config <path> Path to the bulk migration config file
181
+ -h, --help Show this help message`);
182
+ }
183
+ async function provisionSuperTokensInfrastructure(config, oidcClients) {
184
+ const headers = {
185
+ "Content-Type": "application/json"
186
+ };
187
+ if (config.supertokens.apiKey) {
188
+ headers["api-key"] = config.supertokens.apiKey;
189
+ }
190
+ for (const oidcClient of oidcClients) {
191
+ for (const credential of oidcClient.credentials ?? []) {
192
+ console.log(
193
+ `Provisioning SuperTokens OAuth2 Client: ${credential.client_id}`
194
+ );
195
+ const oauthRes = await fetch(
196
+ new URL(
197
+ "/recipe/oauth/clients",
198
+ config.supertokens.connectionURI
199
+ ).toString(),
200
+ {
201
+ method: "POST",
202
+ headers,
203
+ body: JSON.stringify({
204
+ clientName: oidcClient.name || credential.client_id || "Rownd Client",
205
+ clientId: credential.client_id,
206
+ clientSecret: credential.secret,
207
+ redirectUris: oidcClient.config.redirect_uris || [],
208
+ postLogoutRedirectUris: oidcClient.config.post_logout_uris || [],
209
+ scope: oidcClient.config.allowed_scopes?.join(" "),
210
+ responseTypes: ["code", "token", "id_token"],
211
+ grantTypes: [
212
+ "authorization_code",
213
+ "refresh_token",
214
+ "client_credentials",
215
+ "implicit"
216
+ ],
217
+ tokenEndpointAuthMethod: "client_secret_post"
218
+ })
219
+ }
220
+ );
221
+ if (!oauthRes.ok) {
222
+ const errorText = await oauthRes.text();
223
+ if (!errorText.includes("already exists") && !errorText.includes("Duplicate")) {
224
+ throw new Error(
225
+ `Failed to create OAuth2 Client ${credential.client_id}: ${oauthRes.status} ${errorText}`
226
+ );
227
+ }
228
+ }
229
+ }
230
+ }
231
+ }
232
+ var RowndOidcClientSchema = import_zod2.z.object({
233
+ id: import_zod2.z.string(),
234
+ name: import_zod2.z.string(),
235
+ config: import_zod2.z.object({
236
+ redirect_uris: import_zod2.z.array(import_zod2.z.string()).optional(),
237
+ post_logout_uris: import_zod2.z.array(import_zod2.z.string()).optional(),
238
+ allowed_scopes: import_zod2.z.array(import_zod2.z.string()).optional()
239
+ }),
240
+ credentials: import_zod2.z.array(
241
+ import_zod2.z.object({
242
+ client_id: import_zod2.z.string(),
243
+ secret: import_zod2.z.string().optional(),
244
+ app_variant_id: import_zod2.z.string().optional()
245
+ })
246
+ ).optional()
247
+ });
248
+ async function fetchRowndOidcClients(config) {
249
+ const url = new URL(
250
+ `/api/applications/${config.rownd.appId}/oidc-clients`,
251
+ "https://app.rownd.io"
252
+ );
253
+ const response = await fetch(url.toString(), {
254
+ headers: {
255
+ "x-rownd-app-key": config.rownd.appKey,
256
+ "x-rownd-app-secret": config.rownd.appSecret
257
+ }
258
+ });
259
+ if (!response.ok) {
260
+ throw new Error(
261
+ `Rownd API error: ${response.status} ${await response.text()}`
262
+ );
263
+ }
264
+ const page = import_zod2.z.object({ results: import_zod2.z.array(RowndOidcClientSchema) }).parse(await response.json());
265
+ return page.results;
266
+ }
267
+ async function runCli() {
268
+ const args = process.argv.slice(2);
269
+ if (hasHelpArg(args)) {
270
+ printHelp();
271
+ return;
272
+ }
273
+ const config = await loadConfig(parseRequiredConfigArg(args));
274
+ const oidcClients = await fetchRowndOidcClients(config);
275
+ await provisionSuperTokensInfrastructure(config, oidcClients);
276
+ console.log("Successfully provisioned SuperTokens infrastructure.");
277
+ }
278
+ if (require.main === module) {
279
+ runCli().catch((error) => {
280
+ if (error instanceof import_zod2.z.ZodError) {
281
+ console.error(formatZodError(error));
282
+ } else {
283
+ console.error(
284
+ error instanceof Error ? error.message : "Provisioning failed"
285
+ );
286
+ }
287
+ process.exitCode = 1;
288
+ });
289
+ }
290
+ // Annotate the CommonJS export names for ESM import in node:
291
+ 0 && (module.exports = {
292
+ fetchRowndOidcClients,
293
+ provisionSuperTokensInfrastructure,
294
+ runCli
295
+ });
package/package.json CHANGED
@@ -1,18 +1,21 @@
1
1
  {
2
2
  "name": "@supertokens-plugins/rownd-nodejs",
3
- "version": "0.2.0",
3
+ "version": "0.3.0-beta.0",
4
4
  "description": "Rownd User Migration Plugin for SuperTokens",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
7
7
  "types": "./dist/index.d.ts",
8
+ "bin": {
9
+ "rownd-nodejs": "./dist/cli.js"
10
+ },
8
11
  "files": [
9
- "dist",
10
- "src",
11
- "scripts"
12
+ "dist"
12
13
  ],
13
14
  "scripts": {
14
- "build": "tsup src/index.ts --format cjs,esm --dts",
15
- "bulk-import": "tsx scripts/bulkMigrate.ts",
15
+ "build": "rm -rf dist && tsup",
16
+ "cli": "npm run build && node dist/cli.js",
17
+ "migrate-local-then-managed": "tsx scripts/migrateLocalThenManaged.ts",
18
+ "diff-users": "tsx scripts/diffUsers.ts",
16
19
  "lint": "eslint ./src --ext .ts",
17
20
  "test": "TEST_MODE=testing vitest run --pool=forks"
18
21
  },
@@ -25,7 +28,9 @@
25
28
  "license": "Apache-2.0",
26
29
  "dependencies": {
27
30
  "@opentelemetry/api": "^1.9.0",
28
- "@rownd/node": "^3.0.4"
31
+ "@rownd/node": "^3.0.4",
32
+ "yaml": "^2.8.3",
33
+ "zod": "^4.3.6"
29
34
  },
30
35
  "peerDependencies": {
31
36
  "supertokens-node": ">=23.0.0"
@@ -40,9 +45,7 @@
40
45
  "tsup": "^8.0.2",
41
46
  "tsx": "^4.21.0",
42
47
  "typescript": "^5.7.3",
43
- "vitest": "^1.3.1",
44
- "yaml": "^2.8.3",
45
- "zod": "^4.3.6"
48
+ "vitest": "^1.3.1"
46
49
  },
47
50
  "publishConfig": {
48
51
  "access": "public"