arri 0.57.2 → 0.57.4

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/README.md CHANGED
@@ -5,19 +5,62 @@ Command line interface for ARRI-RPC
5
5
  ## Table of Contents
6
6
 
7
7
  - [Commands](#commands)
8
+ - [Running code generators](#running-code-generators)
8
9
  - [Usage with @arrirpc/server](#usage-with-arrirpcserver)
9
10
 
10
11
  ## Commands
11
12
 
12
13
  Run `arri --help` to get a full list of commands
13
14
 
14
- | Cmd | Description |
15
- | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
16
- | codegen [input-file] | Run generate clients specified in the `arri.config.ts`. See [here](/README.md#creating-schemas-for-custom-server-implementations) for details. |
17
- | init [dir] | Scaffold an arri app/library |
18
- | list | List available arri versions |
19
- | use [version] | Update arri dependencies to a specific version (checks recursively for package.json, pubspec.yaml, cargo.toml, etc) |
20
- | version | Print current CLI version |
15
+ | Cmd | Description |
16
+ | -------------------- | ------------------------------------------------------------------------------------------------------------------- |
17
+ | codegen [input-file] | Run generate clients specified in the `arri.config.ts`. See [here](#running-arri-codegen) for details. |
18
+ | init [dir] | Scaffold an arri app/library |
19
+ | list | List available arri versions |
20
+ | use [version] | Update arri dependencies to a specific version (checks recursively for package.json, pubspec.yaml, cargo.toml, etc) |
21
+ | version | Print current CLI version |
22
+
23
+ ## Running code generators
24
+
25
+ Before running codegen you need to setup a valid Arri Config
26
+
27
+ ```ts
28
+ // arri.config.ts
29
+ import { defineConfig, generators } from "arri";
30
+
31
+ export default defineConfig({
32
+ generators: [
33
+ generators.dartClient({
34
+ clientName: "Client",
35
+ outputPath: "<some-output-file>",
36
+ }),
37
+ generators.typescriptClient({
38
+ clientName: "Client",
39
+ outputPath: "<some-output-file>",
40
+ }),
41
+ // etc...
42
+ ],
43
+ });
44
+ ```
45
+
46
+ Run the codegen command
47
+
48
+ ```bash
49
+ # run generators from an app definition file
50
+ # app definition files can be TS, JS, or JSON
51
+ arri codegen AppDefinition.ts
52
+ arri codegen AppDefinition.json
53
+
54
+ # run generators from an app definition JSON http endpoint
55
+ arri codegen https://example.com/__definition
56
+ ```
57
+
58
+ ### Codegen Flags
59
+
60
+ | Flag | Aliases | Description |
61
+ | -------- | ------- | ------------------------------------------------------------------------------------------------------------------------- |
62
+ | --config | -c | Path to an arri config. Default is `arri.config.ts` |
63
+ | --watch | -w | Watch an app definition file for changes. Only works on files. It will throw an error if invoked against an HTTP endpoint |
21
64
 
22
65
  ## Usage with @arrirpc/server
23
66
 
package/dist/cli.cjs CHANGED
@@ -11,7 +11,8 @@ const esbuild = require('esbuild');
11
11
  const esbuildPluginReplace = require('esbuild-plugin-replace');
12
12
  const path = require('pathe');
13
13
  const prettier = require('prettier');
14
- const dev = require('./shared/arri.42cdc72b.cjs');
14
+ const dev = require('./shared/arri.09919c6d.cjs');
15
+ const chokidar = require('chokidar');
15
16
  const ofetch = require('ofetch');
16
17
  const Degit = require('degit');
17
18
  const enquirer = require('enquirer');
@@ -236,74 +237,136 @@ const codegen = citty.defineCommand({
236
237
  type: "string",
237
238
  alias: ["c"],
238
239
  default: "arri.config.ts"
240
+ },
241
+ watch: {
242
+ type: "boolean",
243
+ alias: ["w"],
244
+ default: false
239
245
  }
240
246
  },
241
247
  async run({ args }) {
248
+ if (!fs__default$1.existsSync(path__default.resolve(args.config))) {
249
+ dev.logger.error(
250
+ `Unable to load arri config at ${args.config}. Please specify a valid config path with --config`
251
+ );
252
+ process.exit(1);
253
+ }
254
+ const { config } = await c12.loadConfig({
255
+ configFile: path__default.resolve(args.config)
256
+ });
257
+ if (!dev.isArriConfig(config)) {
258
+ dev.logger.error(`Invalid arri config at ${args.config}`);
259
+ process.exit(1);
260
+ }
261
+ if (!config.generators?.length) {
262
+ dev.logger.error(`No generators specified in ${args.config}`);
263
+ process.exit(1);
264
+ }
242
265
  const isUrl = args.schema.startsWith("http://") || args.schema.startsWith("https://");
243
- const isTs = args.schema.endsWith(".ts");
244
- const isJs = args.schema.endsWith(".js");
245
266
  let def;
246
267
  if (isUrl) {
268
+ if (args.watch)
269
+ throw new Error("Cannot watch a URL");
247
270
  const result = await ofetch.ofetch(args.schema);
248
271
  if (!codegenUtils.isAppDefinition(result)) {
249
272
  throw new Error(`Invalid App Definition at ${args.schema}`);
250
273
  }
251
274
  def = result;
252
275
  } else {
253
- if (!fs__default$1.existsSync(args.schema)) {
254
- throw new Error(`Unable to find ${args.schema}`);
255
- }
256
- if (isTs || isJs) {
257
- const schemaResult = await c12.loadConfig({
258
- configFile: args.schema
259
- });
260
- if (!codegenUtils.isAppDefinition(schemaResult.config)) {
261
- throw new Error(`Invalid App Definition at ${args.schema}`);
262
- }
263
- def = schemaResult.config;
264
- } else {
265
- const jsonString = JSON.parse(
266
- fs__default$1.readFileSync(args.schema, { encoding: "utf-8" })
267
- );
268
- if (!codegenUtils.isAppDefinition(jsonString)) {
269
- throw new Error(`Invalid App Definition at ${args.schema}`);
276
+ try {
277
+ def = await getAppDefinitionFromFile(args.schema);
278
+ } catch (err) {
279
+ if (!args.watch) {
280
+ throw err;
270
281
  }
271
- def = jsonString;
282
+ dev.logger.error(err);
272
283
  }
273
284
  }
274
- if (!def) {
285
+ if (!def && !args.watch) {
275
286
  throw new Error(`Unable to find App Definition at ${args.schema}`);
276
287
  }
277
- if (!fs__default$1.existsSync(path__default.resolve(args.config))) {
278
- throw new Error(
279
- `Unable to load arri config at ${args.config}. Please specify a valid config path with --config`
288
+ if (def) {
289
+ const initialStartTime = /* @__PURE__ */ new Date();
290
+ await runGenerators(def, config.generators ?? []);
291
+ dev.logger.success(
292
+ `Generators completed in ${( new Date()).getTime() - initialStartTime.getTime()}ms`
280
293
  );
281
294
  }
282
- const configPath = path__default.resolve(args.config);
283
- const { config } = await c12.loadConfig({ configFile: configPath });
284
- if (!dev.isArriConfig(config)) {
285
- throw new Error(`Invalid arri config at ${args.config}`);
286
- }
287
- if (!config.generators?.length) {
288
- dev.logger.warn(`No generators specified in ${args.config}`);
289
- process.exit(1);
295
+ if (!args.watch)
296
+ process.exit(0);
297
+ const watcher = chokidar.watch(args.schema, { ignoreInitial: true });
298
+ dev.logger.info(`Watching ${args.schema} for changes...`);
299
+ watcher.on("add", async (_, __) => {
300
+ try {
301
+ dev.logger.info(`Change detected`);
302
+ const startTime = /* @__PURE__ */ new Date();
303
+ await runGenerators(
304
+ await getAppDefinitionFromFile(args.schema),
305
+ config.generators ?? []
306
+ );
307
+ dev.logger.success(
308
+ `Generators completed in ${( new Date()).getTime() - startTime.getTime()}ms`
309
+ );
310
+ } catch (err) {
311
+ dev.logger.error(err);
312
+ }
313
+ });
314
+ watcher.on("change", async (_, __) => {
315
+ try {
316
+ dev.logger.info(`Change detected in AppDefinition file.`);
317
+ const startTime = /* @__PURE__ */ new Date();
318
+ await runGenerators(
319
+ await getAppDefinitionFromFile(args.schema),
320
+ config.generators ?? []
321
+ );
322
+ dev.logger.success(
323
+ `Generators completed in ${( new Date()).getTime() - startTime.getTime()}ms`
324
+ );
325
+ } catch (err) {
326
+ dev.logger.error(err);
327
+ }
328
+ });
329
+ }
330
+ });
331
+ async function getAppDefinitionFromFile(file) {
332
+ if (!fs__default$1.existsSync(file)) {
333
+ throw new Error(`Unable to find ${file}`);
334
+ }
335
+ const isTs = file.endsWith(".ts");
336
+ const isJs = file.endsWith(".js");
337
+ if (isTs || isJs) {
338
+ const schemaResult = await c12.loadConfig({
339
+ configFile: file
340
+ });
341
+ if (!codegenUtils.isAppDefinition(schemaResult.config)) {
342
+ throw new Error(`Invalid App Definition at ${file}`);
290
343
  }
291
- dev.logger.info(`Generating ${config.generators?.length} client(s)`);
292
- await Promise.allSettled(
293
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
294
- config.generators.map(
295
- (gen) => gen.generator(
296
- def ?? {
297
- schemaVersion: "0.0.6",
298
- procedures: {},
299
- definitions: {}
300
- }
301
- )
302
- )
344
+ return schemaResult.config;
345
+ } else {
346
+ const parsingResult = JSON.parse(
347
+ fs__default$1.readFileSync(file, { encoding: "utf-8" })
303
348
  );
304
- process.exit(0);
349
+ if (!codegenUtils.isAppDefinition(parsingResult)) {
350
+ throw new Error(`Invalid App Definition at ${file}`);
351
+ }
352
+ return parsingResult;
305
353
  }
306
- });
354
+ }
355
+ async function runGenerators(def, generators) {
356
+ dev.logger.info(`Generating ${generators?.length} client(s)`);
357
+ await Promise.allSettled(
358
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
359
+ generators.map(
360
+ (gen) => gen.generator(
361
+ def ?? {
362
+ schemaVersion: "0.0.6",
363
+ procedures: {},
364
+ definitions: {}
365
+ }
366
+ )
367
+ )
368
+ );
369
+ }
307
370
 
308
371
  const init = citty.defineCommand({
309
372
  meta: {
package/dist/cli.mjs CHANGED
@@ -9,7 +9,8 @@ import { build as build$1 } from 'esbuild';
9
9
  import { replace } from 'esbuild-plugin-replace';
10
10
  import path from 'pathe';
11
11
  import prettier from 'prettier';
12
- import { d as defaultConfig, l as logger, s as setupWorkingDir, c as createAppWithRoutesModule, O as OUT_CODEGEN, e as OUT_SERVER_ENTRY, G as GEN_APP_FILE, f as OUT_APP_FILE, i as isArriConfig, g as getArriPackageMetadata, h as dev } from './shared/arri.e6e562ed.mjs';
12
+ import { d as defaultConfig, l as logger, s as setupWorkingDir, c as createAppWithRoutesModule, O as OUT_CODEGEN, e as OUT_SERVER_ENTRY, G as GEN_APP_FILE, f as OUT_APP_FILE, i as isArriConfig, g as getArriPackageMetadata, h as dev } from './shared/arri.339f2f92.mjs';
13
+ import { watch } from 'chokidar';
13
14
  import { ofetch } from 'ofetch';
14
15
  import Degit from 'degit';
15
16
  import enquirer from 'enquirer';
@@ -223,74 +224,136 @@ const codegen = defineCommand({
223
224
  type: "string",
224
225
  alias: ["c"],
225
226
  default: "arri.config.ts"
227
+ },
228
+ watch: {
229
+ type: "boolean",
230
+ alias: ["w"],
231
+ default: false
226
232
  }
227
233
  },
228
234
  async run({ args }) {
235
+ if (!fs$1.existsSync(path.resolve(args.config))) {
236
+ logger.error(
237
+ `Unable to load arri config at ${args.config}. Please specify a valid config path with --config`
238
+ );
239
+ process.exit(1);
240
+ }
241
+ const { config } = await loadConfig({
242
+ configFile: path.resolve(args.config)
243
+ });
244
+ if (!isArriConfig(config)) {
245
+ logger.error(`Invalid arri config at ${args.config}`);
246
+ process.exit(1);
247
+ }
248
+ if (!config.generators?.length) {
249
+ logger.error(`No generators specified in ${args.config}`);
250
+ process.exit(1);
251
+ }
229
252
  const isUrl = args.schema.startsWith("http://") || args.schema.startsWith("https://");
230
- const isTs = args.schema.endsWith(".ts");
231
- const isJs = args.schema.endsWith(".js");
232
253
  let def;
233
254
  if (isUrl) {
255
+ if (args.watch)
256
+ throw new Error("Cannot watch a URL");
234
257
  const result = await ofetch(args.schema);
235
258
  if (!isAppDefinition(result)) {
236
259
  throw new Error(`Invalid App Definition at ${args.schema}`);
237
260
  }
238
261
  def = result;
239
262
  } else {
240
- if (!fs$1.existsSync(args.schema)) {
241
- throw new Error(`Unable to find ${args.schema}`);
242
- }
243
- if (isTs || isJs) {
244
- const schemaResult = await loadConfig({
245
- configFile: args.schema
246
- });
247
- if (!isAppDefinition(schemaResult.config)) {
248
- throw new Error(`Invalid App Definition at ${args.schema}`);
249
- }
250
- def = schemaResult.config;
251
- } else {
252
- const jsonString = JSON.parse(
253
- fs$1.readFileSync(args.schema, { encoding: "utf-8" })
254
- );
255
- if (!isAppDefinition(jsonString)) {
256
- throw new Error(`Invalid App Definition at ${args.schema}`);
263
+ try {
264
+ def = await getAppDefinitionFromFile(args.schema);
265
+ } catch (err) {
266
+ if (!args.watch) {
267
+ throw err;
257
268
  }
258
- def = jsonString;
269
+ logger.error(err);
259
270
  }
260
271
  }
261
- if (!def) {
272
+ if (!def && !args.watch) {
262
273
  throw new Error(`Unable to find App Definition at ${args.schema}`);
263
274
  }
264
- if (!fs$1.existsSync(path.resolve(args.config))) {
265
- throw new Error(
266
- `Unable to load arri config at ${args.config}. Please specify a valid config path with --config`
275
+ if (def) {
276
+ const initialStartTime = /* @__PURE__ */ new Date();
277
+ await runGenerators(def, config.generators ?? []);
278
+ logger.success(
279
+ `Generators completed in ${( new Date()).getTime() - initialStartTime.getTime()}ms`
267
280
  );
268
281
  }
269
- const configPath = path.resolve(args.config);
270
- const { config } = await loadConfig({ configFile: configPath });
271
- if (!isArriConfig(config)) {
272
- throw new Error(`Invalid arri config at ${args.config}`);
273
- }
274
- if (!config.generators?.length) {
275
- logger.warn(`No generators specified in ${args.config}`);
276
- process.exit(1);
282
+ if (!args.watch)
283
+ process.exit(0);
284
+ const watcher = watch(args.schema, { ignoreInitial: true });
285
+ logger.info(`Watching ${args.schema} for changes...`);
286
+ watcher.on("add", async (_, __) => {
287
+ try {
288
+ logger.info(`Change detected`);
289
+ const startTime = /* @__PURE__ */ new Date();
290
+ await runGenerators(
291
+ await getAppDefinitionFromFile(args.schema),
292
+ config.generators ?? []
293
+ );
294
+ logger.success(
295
+ `Generators completed in ${( new Date()).getTime() - startTime.getTime()}ms`
296
+ );
297
+ } catch (err) {
298
+ logger.error(err);
299
+ }
300
+ });
301
+ watcher.on("change", async (_, __) => {
302
+ try {
303
+ logger.info(`Change detected in AppDefinition file.`);
304
+ const startTime = /* @__PURE__ */ new Date();
305
+ await runGenerators(
306
+ await getAppDefinitionFromFile(args.schema),
307
+ config.generators ?? []
308
+ );
309
+ logger.success(
310
+ `Generators completed in ${( new Date()).getTime() - startTime.getTime()}ms`
311
+ );
312
+ } catch (err) {
313
+ logger.error(err);
314
+ }
315
+ });
316
+ }
317
+ });
318
+ async function getAppDefinitionFromFile(file) {
319
+ if (!fs$1.existsSync(file)) {
320
+ throw new Error(`Unable to find ${file}`);
321
+ }
322
+ const isTs = file.endsWith(".ts");
323
+ const isJs = file.endsWith(".js");
324
+ if (isTs || isJs) {
325
+ const schemaResult = await loadConfig({
326
+ configFile: file
327
+ });
328
+ if (!isAppDefinition(schemaResult.config)) {
329
+ throw new Error(`Invalid App Definition at ${file}`);
277
330
  }
278
- logger.info(`Generating ${config.generators?.length} client(s)`);
279
- await Promise.allSettled(
280
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
281
- config.generators.map(
282
- (gen) => gen.generator(
283
- def ?? {
284
- schemaVersion: "0.0.6",
285
- procedures: {},
286
- definitions: {}
287
- }
288
- )
289
- )
331
+ return schemaResult.config;
332
+ } else {
333
+ const parsingResult = JSON.parse(
334
+ fs$1.readFileSync(file, { encoding: "utf-8" })
290
335
  );
291
- process.exit(0);
336
+ if (!isAppDefinition(parsingResult)) {
337
+ throw new Error(`Invalid App Definition at ${file}`);
338
+ }
339
+ return parsingResult;
292
340
  }
293
- });
341
+ }
342
+ async function runGenerators(def, generators) {
343
+ logger.info(`Generating ${generators?.length} client(s)`);
344
+ await Promise.allSettled(
345
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
346
+ generators.map(
347
+ (gen) => gen.generator(
348
+ def ?? {
349
+ schemaVersion: "0.0.6",
350
+ procedures: {},
351
+ definitions: {}
352
+ }
353
+ )
354
+ )
355
+ );
356
+ }
294
357
 
295
358
  const init = defineCommand({
296
359
  meta: {
package/dist/index.cjs CHANGED
@@ -4,19 +4,19 @@ const codegenDart = require('@arrirpc/codegen-dart');
4
4
  const codegenKotlin = require('@arrirpc/codegen-kotlin');
5
5
  const codegenRust = require('@arrirpc/codegen-rust');
6
6
  const codegenTs = require('@arrirpc/codegen-ts');
7
- const dev = require('./shared/arri.42cdc72b.cjs');
7
+ const dev = require('./shared/arri.09919c6d.cjs');
8
8
  const codegenUtils = require('@arrirpc/codegen-utils');
9
9
  require('node:fs');
10
- require('node:fs/promises');
11
10
  require('@joshmossas/listhen');
12
11
  require('c12');
13
12
  require('citty');
14
13
  require('esbuild');
15
14
  require('h3');
16
- require('ofetch');
17
15
  require('pathe');
16
+ require('node:fs/promises');
18
17
  require('@arrirpc/schema');
19
18
  require('consola');
19
+ require('ofetch');
20
20
  require('prettier');
21
21
 
22
22
  const generators = {
package/dist/index.d.cts CHANGED
@@ -34,6 +34,11 @@ interface ArriConfig {
34
34
  * Use this to add directories outside of the srcDir that should trigger a dev server reload
35
35
  */
36
36
  additionalWatchDirs?: string[];
37
+ /**
38
+ * If you want to serve both https and http on the dev server
39
+ */
40
+ httpWithHttps?: boolean;
41
+ httpWithHttpsPort?: number;
37
42
  };
38
43
  }
39
44
  declare function isArriConfig(input: unknown): input is ArriConfig;
package/dist/index.d.mts CHANGED
@@ -34,6 +34,11 @@ interface ArriConfig {
34
34
  * Use this to add directories outside of the srcDir that should trigger a dev server reload
35
35
  */
36
36
  additionalWatchDirs?: string[];
37
+ /**
38
+ * If you want to serve both https and http on the dev server
39
+ */
40
+ httpWithHttps?: boolean;
41
+ httpWithHttpsPort?: number;
37
42
  };
38
43
  }
39
44
  declare function isArriConfig(input: unknown): input is ArriConfig;
package/dist/index.d.ts CHANGED
@@ -34,6 +34,11 @@ interface ArriConfig {
34
34
  * Use this to add directories outside of the srcDir that should trigger a dev server reload
35
35
  */
36
36
  additionalWatchDirs?: string[];
37
+ /**
38
+ * If you want to serve both https and http on the dev server
39
+ */
40
+ httpWithHttps?: boolean;
41
+ httpWithHttpsPort?: number;
37
42
  };
38
43
  }
39
44
  declare function isArriConfig(input: unknown): input is ArriConfig;
package/dist/index.mjs CHANGED
@@ -2,19 +2,19 @@ import { dartClientGenerator } from '@arrirpc/codegen-dart';
2
2
  import { kotlinClientGenerator } from '@arrirpc/codegen-kotlin';
3
3
  import { rustClientGenerator } from '@arrirpc/codegen-rust';
4
4
  import { typescriptClientGenerator } from '@arrirpc/codegen-ts';
5
- export { D as DEV_DEFINITION_ENDPOINT, d as defaultConfig, b as defineConfig, i as isArriConfig, a as isResolvedArriConfig } from './shared/arri.e6e562ed.mjs';
5
+ export { D as DEV_DEFINITION_ENDPOINT, d as defaultConfig, b as defineConfig, i as isArriConfig, a as isResolvedArriConfig } from './shared/arri.339f2f92.mjs';
6
6
  export { createAppDefinition } from '@arrirpc/codegen-utils';
7
7
  import 'node:fs';
8
- import 'node:fs/promises';
9
8
  import '@joshmossas/listhen';
10
9
  import 'c12';
11
10
  import 'citty';
12
11
  import 'esbuild';
13
12
  import 'h3';
14
- import 'ofetch';
15
13
  import 'pathe';
14
+ import 'node:fs/promises';
16
15
  import '@arrirpc/schema';
17
16
  import 'consola';
17
+ import 'ofetch';
18
18
  import 'prettier';
19
19
 
20
20
  const generators = {
@@ -1,17 +1,17 @@
1
1
  'use strict';
2
2
 
3
3
  const fs = require('node:fs');
4
- const fs$1 = require('node:fs/promises');
5
- const codegenUtils = require('@arrirpc/codegen-utils');
6
4
  const listhen = require('@joshmossas/listhen');
7
5
  const c12 = require('c12');
8
6
  const citty = require('citty');
9
7
  const esbuild = require('esbuild');
10
- require('h3');
11
- const ofetch = require('ofetch');
8
+ const h3 = require('h3');
12
9
  const path = require('pathe');
10
+ const fs$1 = require('node:fs/promises');
11
+ const codegenUtils = require('@arrirpc/codegen-utils');
13
12
  const schema = require('@arrirpc/schema');
14
13
  const consola = require('consola');
14
+ const ofetch = require('ofetch');
15
15
  const prettier = require('prettier');
16
16
 
17
17
  function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
@@ -28,14 +28,13 @@ function _interopNamespaceCompat(e) {
28
28
  return n;
29
29
  }
30
30
 
31
- const fs__default = /*#__PURE__*/_interopDefaultCompat(fs$1);
32
31
  const esbuild__namespace = /*#__PURE__*/_interopNamespaceCompat(esbuild);
33
32
  const path__default = /*#__PURE__*/_interopDefaultCompat(path);
33
+ const fs__default = /*#__PURE__*/_interopDefaultCompat(fs$1);
34
34
  const prettier__default = /*#__PURE__*/_interopDefaultCompat(prettier);
35
35
 
36
36
  const logger = consola.createConsola().withTag("arri");
37
37
  const GEN_APP_FILE = "__arri_app.ts";
38
- const GEN_SERVER_ENTRY_FILE = "__arri_server.ts";
39
38
  const OUT_APP_FILE = "app.mjs";
40
39
  const OUT_SERVER_ENTRY = "server.mjs";
41
40
  const OUT_CODEGEN = "codegen.mjs";
@@ -311,7 +310,6 @@ const dev = citty.defineCommand({
311
310
  }
312
311
  },
313
312
  async run({ args }) {
314
- process.env.ARRI_DEV_MODE = "true";
315
313
  const { config } = await c12.loadConfig({
316
314
  configFile: path__default.resolve(args.config)
317
315
  });
@@ -329,23 +327,7 @@ const dev = citty.defineCommand({
329
327
  await startDevServer(config);
330
328
  }
331
329
  });
332
- const startListener = (config, showQr = false) => listhen.listenAndWatch(path__default.resolve(config.rootDir, ".output", OUT_SERVER_ENTRY), {
333
- public: true,
334
- port: config.port,
335
- logger,
336
- qr: showQr,
337
- https: config.https,
338
- http2: config.http2,
339
- ws: true
340
- });
341
330
  async function bundleFilesContext(config) {
342
- const serverContent = `import app from './${OUT_APP_FILE}';
343
-
344
- export default app.h3App;`;
345
- await fs__default.writeFile(
346
- path__default.resolve(config.rootDir, ".output", OUT_SERVER_ENTRY),
347
- serverContent
348
- );
349
331
  return await esbuild__namespace.context({
350
332
  ...config.esbuild,
351
333
  entryPoints: [
@@ -360,29 +342,90 @@ export default app.h3App;`;
360
342
  platform: config.esbuild.platform ?? "node"
361
343
  });
362
344
  }
345
+ async function createDevServer(config) {
346
+ const h3$1 = await import('h3');
347
+ const app = h3$1.createApp();
348
+ const dynamicHandler = h3.dynamicEventHandler(
349
+ () => "<div>initializing server...</div>"
350
+ );
351
+ app.use(dynamicHandler);
352
+ let ws;
353
+ const listener = await listhen.listen(h3.toNodeListener(app), {
354
+ port: config.port,
355
+ https: config.https,
356
+ http2: config.http2,
357
+ public: true,
358
+ ws: {
359
+ resolve(info) {
360
+ if (ws?.resolve) {
361
+ return ws.resolve(info);
362
+ }
363
+ return {};
364
+ }
365
+ }
366
+ });
367
+ let secondaryListener;
368
+ if (config.devServer.httpWithHttps) {
369
+ secondaryListener = await listhen.listen(h3.toNodeListener(app), {
370
+ port: config.devServer.httpWithHttpsPort ?? config.port + 1,
371
+ https: false,
372
+ http2: config.http2,
373
+ public: true,
374
+ ws: {
375
+ resolve(info) {
376
+ if (ws?.resolve) {
377
+ return ws.resolve(info);
378
+ }
379
+ return {};
380
+ }
381
+ },
382
+ qr: false,
383
+ showURL: false
384
+ });
385
+ logger.info(
386
+ `Serving unencrypted traffic from port ${secondaryListener.address.port}`
387
+ );
388
+ }
389
+ async function reload() {
390
+ const appEntry = (await import(path__default.resolve(
391
+ config.rootDir,
392
+ ".output",
393
+ `${OUT_APP_FILE}?version=${Date.now()}`
394
+ ))).default;
395
+ dynamicHandler.set(h3.fromNodeMiddleware(appEntry.h3App.handler));
396
+ ws = appEntry.h3App.websocket;
397
+ return appEntry.getAppDefinition();
398
+ }
399
+ await reload();
400
+ return {
401
+ h3App: app,
402
+ listener,
403
+ secondaryListener,
404
+ reload,
405
+ ws
406
+ };
407
+ }
363
408
  async function startDevServer(config) {
364
409
  await setupWorkingDir(config);
365
410
  const watcher = await import('chokidar');
366
411
  let fileWatcher;
367
- await Promise.all([
368
- createEntryModule(config),
369
- createAppWithRoutesModule(config)
370
- ]);
412
+ await createAppWithRoutesModule(config);
371
413
  const context = await bundleFilesContext(config);
372
414
  await context.rebuild();
373
- const listener = await startListener(config, true);
374
- setTimeout(async () => {
375
- await generateClients(config);
376
- }, 1e3);
415
+ const devServer = await createDevServer(config);
416
+ let appDef = await devServer.reload();
417
+ let appDefStr = JSON.stringify(appDef);
418
+ if (config.generators.length) {
419
+ logger.info(`Running generators...`);
420
+ await generateClientsFromDefinition(appDef, config);
421
+ } else {
422
+ logger.warn(`No generators specified in config. Skipping codegen.`);
423
+ }
377
424
  const cleanExit = async () => {
378
425
  process.exit();
379
426
  };
380
427
  process.on("exit", async () => {
381
- await Promise.allSettled([
382
- listener.close(),
383
- fileWatcher?.close(),
384
- context.dispose()
385
- ]);
428
+ await Promise.allSettled([fileWatcher?.close(), context.dispose()]);
386
429
  });
387
430
  process.on("SIGINT", cleanExit);
388
431
  process.on("SIGTERM", cleanExit);
@@ -425,66 +468,48 @@ async function startDevServer(config) {
425
468
  return;
426
469
  }
427
470
  await createAppWithRoutesModule(config);
428
- let bundleCreated = false;
429
471
  try {
472
+ const reloadStart = (/* @__PURE__ */ new Date()).getTime();
430
473
  logger.log(
431
474
  "Change detected. Bundling files and restarting server...."
432
475
  );
433
476
  await context.rebuild();
434
- bundleCreated = true;
477
+ const newAppDef = await devServer.reload();
478
+ const newAppDefStr = JSON.stringify(newAppDef);
479
+ logger.log(
480
+ `Server restarted in ${( new Date()).getTime() - reloadStart}ms`
481
+ );
482
+ if (config.generators.length && newAppDefStr !== appDefStr) {
483
+ logger.info(
484
+ `App Definition updated. Regenerating clients...`
485
+ );
486
+ await generateClientsFromDefinition(newAppDef, config);
487
+ appDef = newAppDef;
488
+ appDefStr = newAppDefStr;
489
+ }
435
490
  } catch (err) {
436
491
  logger.error("ERROR", err);
437
- bundleCreated = false;
438
- }
439
- if (bundleCreated && config.generators.length) {
440
- setTimeout(async () => {
441
- await generateClients(config);
442
- bundleCreated = false;
443
- }, 1e3);
444
492
  }
445
493
  });
446
494
  }
447
495
  await load();
448
496
  }
449
- async function generateClients(config) {
497
+ async function generateClientsFromDefinition(appDef, config) {
498
+ const startTime = (/* @__PURE__ */ new Date()).getTime();
450
499
  try {
451
- const protocol = config.https === true || typeof config.https === "object" ? "https" : "http";
452
- if (protocol === "https") {
453
- process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
454
- }
455
- const result = await ofetch.ofetch(
456
- `${protocol}://127.0.0.1:${config.port}${DEV_DEFINITION_ENDPOINT}`
457
- );
458
- if (!codegenUtils.isAppDefinition(result)) {
459
- return;
460
- }
461
- logger.log(`Generating client code...`);
462
500
  const clientCount = config.generators.length;
463
501
  await Promise.allSettled(
464
502
  config.generators.map(
465
- (generator) => generator.generator(result, true)
503
+ (generator) => generator.generator(appDef, true)
466
504
  )
467
505
  );
468
506
  logger.success(
469
- `Generated ${clientCount} client${clientCount === 1 ? "" : "s"}`
507
+ `Generated ${clientCount} client${clientCount === 1 ? "" : "s"} in ${( new Date()).getTime() - startTime}ms`
470
508
  );
471
509
  } catch (err) {
472
510
  logger.error(err);
473
511
  }
474
512
  }
475
- async function createEntryModule(config) {
476
- const appModule = path__default.resolve(config.rootDir, config.srcDir, config.entry);
477
- const appImportParts = path__default.relative(path__default.resolve(config.rootDir, config.srcDir), appModule).split(".");
478
- appImportParts.pop();
479
- const virtualEntry = `import { toNodeListener } from '@arrirpc/server';
480
- import app from './${GEN_APP_FILE}';
481
-
482
- export default app.h3App;`;
483
- await fs__default.writeFile(
484
- path__default.resolve(config.rootDir, config.buildDir, GEN_SERVER_ENTRY_FILE),
485
- virtualEntry
486
- );
487
- }
488
513
 
489
514
  exports.DEV_DEFINITION_ENDPOINT = DEV_DEFINITION_ENDPOINT;
490
515
  exports.GEN_APP_FILE = GEN_APP_FILE;
@@ -1,20 +1,19 @@
1
1
  import { existsSync } from 'node:fs';
2
- import fs from 'node:fs/promises';
3
- import { camelCase, removeDisallowedChars, kebabCase, isAppDefinition } from '@arrirpc/codegen-utils';
4
- import { listenAndWatch } from '@joshmossas/listhen';
2
+ import { listen } from '@joshmossas/listhen';
5
3
  import { loadConfig } from 'c12';
6
4
  import { defineCommand } from 'citty';
7
5
  import * as esbuild from 'esbuild';
8
- import 'h3';
9
- import { ofetch } from 'ofetch';
6
+ import { dynamicEventHandler, toNodeListener, fromNodeMiddleware } from 'h3';
10
7
  import path from 'pathe';
8
+ import fs from 'node:fs/promises';
9
+ import { camelCase, removeDisallowedChars, kebabCase } from '@arrirpc/codegen-utils';
11
10
  import { a, ValidationError } from '@arrirpc/schema';
12
11
  import { createConsola } from 'consola';
12
+ import { ofetch } from 'ofetch';
13
13
  import prettier from 'prettier';
14
14
 
15
15
  const logger = createConsola().withTag("arri");
16
16
  const GEN_APP_FILE = "__arri_app.ts";
17
- const GEN_SERVER_ENTRY_FILE = "__arri_server.ts";
18
17
  const OUT_APP_FILE = "app.mjs";
19
18
  const OUT_SERVER_ENTRY = "server.mjs";
20
19
  const OUT_CODEGEN = "codegen.mjs";
@@ -290,7 +289,6 @@ const dev = defineCommand({
290
289
  }
291
290
  },
292
291
  async run({ args }) {
293
- process.env.ARRI_DEV_MODE = "true";
294
292
  const { config } = await loadConfig({
295
293
  configFile: path.resolve(args.config)
296
294
  });
@@ -308,23 +306,7 @@ const dev = defineCommand({
308
306
  await startDevServer(config);
309
307
  }
310
308
  });
311
- const startListener = (config, showQr = false) => listenAndWatch(path.resolve(config.rootDir, ".output", OUT_SERVER_ENTRY), {
312
- public: true,
313
- port: config.port,
314
- logger,
315
- qr: showQr,
316
- https: config.https,
317
- http2: config.http2,
318
- ws: true
319
- });
320
309
  async function bundleFilesContext(config) {
321
- const serverContent = `import app from './${OUT_APP_FILE}';
322
-
323
- export default app.h3App;`;
324
- await fs.writeFile(
325
- path.resolve(config.rootDir, ".output", OUT_SERVER_ENTRY),
326
- serverContent
327
- );
328
310
  return await esbuild.context({
329
311
  ...config.esbuild,
330
312
  entryPoints: [
@@ -339,29 +321,90 @@ export default app.h3App;`;
339
321
  platform: config.esbuild.platform ?? "node"
340
322
  });
341
323
  }
324
+ async function createDevServer(config) {
325
+ const h3 = await import('h3');
326
+ const app = h3.createApp();
327
+ const dynamicHandler = dynamicEventHandler(
328
+ () => "<div>initializing server...</div>"
329
+ );
330
+ app.use(dynamicHandler);
331
+ let ws;
332
+ const listener = await listen(toNodeListener(app), {
333
+ port: config.port,
334
+ https: config.https,
335
+ http2: config.http2,
336
+ public: true,
337
+ ws: {
338
+ resolve(info) {
339
+ if (ws?.resolve) {
340
+ return ws.resolve(info);
341
+ }
342
+ return {};
343
+ }
344
+ }
345
+ });
346
+ let secondaryListener;
347
+ if (config.devServer.httpWithHttps) {
348
+ secondaryListener = await listen(toNodeListener(app), {
349
+ port: config.devServer.httpWithHttpsPort ?? config.port + 1,
350
+ https: false,
351
+ http2: config.http2,
352
+ public: true,
353
+ ws: {
354
+ resolve(info) {
355
+ if (ws?.resolve) {
356
+ return ws.resolve(info);
357
+ }
358
+ return {};
359
+ }
360
+ },
361
+ qr: false,
362
+ showURL: false
363
+ });
364
+ logger.info(
365
+ `Serving unencrypted traffic from port ${secondaryListener.address.port}`
366
+ );
367
+ }
368
+ async function reload() {
369
+ const appEntry = (await import(path.resolve(
370
+ config.rootDir,
371
+ ".output",
372
+ `${OUT_APP_FILE}?version=${Date.now()}`
373
+ ))).default;
374
+ dynamicHandler.set(fromNodeMiddleware(appEntry.h3App.handler));
375
+ ws = appEntry.h3App.websocket;
376
+ return appEntry.getAppDefinition();
377
+ }
378
+ await reload();
379
+ return {
380
+ h3App: app,
381
+ listener,
382
+ secondaryListener,
383
+ reload,
384
+ ws
385
+ };
386
+ }
342
387
  async function startDevServer(config) {
343
388
  await setupWorkingDir(config);
344
389
  const watcher = await import('chokidar');
345
390
  let fileWatcher;
346
- await Promise.all([
347
- createEntryModule(config),
348
- createAppWithRoutesModule(config)
349
- ]);
391
+ await createAppWithRoutesModule(config);
350
392
  const context = await bundleFilesContext(config);
351
393
  await context.rebuild();
352
- const listener = await startListener(config, true);
353
- setTimeout(async () => {
354
- await generateClients(config);
355
- }, 1e3);
394
+ const devServer = await createDevServer(config);
395
+ let appDef = await devServer.reload();
396
+ let appDefStr = JSON.stringify(appDef);
397
+ if (config.generators.length) {
398
+ logger.info(`Running generators...`);
399
+ await generateClientsFromDefinition(appDef, config);
400
+ } else {
401
+ logger.warn(`No generators specified in config. Skipping codegen.`);
402
+ }
356
403
  const cleanExit = async () => {
357
404
  process.exit();
358
405
  };
359
406
  process.on("exit", async () => {
360
- await Promise.allSettled([
361
- listener.close(),
362
- fileWatcher?.close(),
363
- context.dispose()
364
- ]);
407
+ await Promise.allSettled([fileWatcher?.close(), context.dispose()]);
365
408
  });
366
409
  process.on("SIGINT", cleanExit);
367
410
  process.on("SIGTERM", cleanExit);
@@ -404,65 +447,47 @@ async function startDevServer(config) {
404
447
  return;
405
448
  }
406
449
  await createAppWithRoutesModule(config);
407
- let bundleCreated = false;
408
450
  try {
451
+ const reloadStart = (/* @__PURE__ */ new Date()).getTime();
409
452
  logger.log(
410
453
  "Change detected. Bundling files and restarting server...."
411
454
  );
412
455
  await context.rebuild();
413
- bundleCreated = true;
456
+ const newAppDef = await devServer.reload();
457
+ const newAppDefStr = JSON.stringify(newAppDef);
458
+ logger.log(
459
+ `Server restarted in ${( new Date()).getTime() - reloadStart}ms`
460
+ );
461
+ if (config.generators.length && newAppDefStr !== appDefStr) {
462
+ logger.info(
463
+ `App Definition updated. Regenerating clients...`
464
+ );
465
+ await generateClientsFromDefinition(newAppDef, config);
466
+ appDef = newAppDef;
467
+ appDefStr = newAppDefStr;
468
+ }
414
469
  } catch (err) {
415
470
  logger.error("ERROR", err);
416
- bundleCreated = false;
417
- }
418
- if (bundleCreated && config.generators.length) {
419
- setTimeout(async () => {
420
- await generateClients(config);
421
- bundleCreated = false;
422
- }, 1e3);
423
471
  }
424
472
  });
425
473
  }
426
474
  await load();
427
475
  }
428
- async function generateClients(config) {
476
+ async function generateClientsFromDefinition(appDef, config) {
477
+ const startTime = (/* @__PURE__ */ new Date()).getTime();
429
478
  try {
430
- const protocol = config.https === true || typeof config.https === "object" ? "https" : "http";
431
- if (protocol === "https") {
432
- process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
433
- }
434
- const result = await ofetch(
435
- `${protocol}://127.0.0.1:${config.port}${DEV_DEFINITION_ENDPOINT}`
436
- );
437
- if (!isAppDefinition(result)) {
438
- return;
439
- }
440
- logger.log(`Generating client code...`);
441
479
  const clientCount = config.generators.length;
442
480
  await Promise.allSettled(
443
481
  config.generators.map(
444
- (generator) => generator.generator(result, true)
482
+ (generator) => generator.generator(appDef, true)
445
483
  )
446
484
  );
447
485
  logger.success(
448
- `Generated ${clientCount} client${clientCount === 1 ? "" : "s"}`
486
+ `Generated ${clientCount} client${clientCount === 1 ? "" : "s"} in ${( new Date()).getTime() - startTime}ms`
449
487
  );
450
488
  } catch (err) {
451
489
  logger.error(err);
452
490
  }
453
491
  }
454
- async function createEntryModule(config) {
455
- const appModule = path.resolve(config.rootDir, config.srcDir, config.entry);
456
- const appImportParts = path.relative(path.resolve(config.rootDir, config.srcDir), appModule).split(".");
457
- appImportParts.pop();
458
- const virtualEntry = `import { toNodeListener } from '@arrirpc/server';
459
- import app from './${GEN_APP_FILE}';
460
-
461
- export default app.h3App;`;
462
- await fs.writeFile(
463
- path.resolve(config.rootDir, config.buildDir, GEN_SERVER_ENTRY_FILE),
464
- virtualEntry
465
- );
466
- }
467
492
 
468
493
  export { DEV_DEFINITION_ENDPOINT as D, GEN_APP_FILE as G, OUT_CODEGEN as O, isResolvedArriConfig as a, defineConfig as b, createAppWithRoutesModule as c, defaultConfig as d, OUT_SERVER_ENTRY as e, OUT_APP_FILE as f, getArriPackageMetadata as g, dev as h, isArriConfig as i, logger as l, setupWorkingDir as s };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arri",
3
- "version": "0.57.2",
3
+ "version": "0.57.4",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -39,12 +39,12 @@
39
39
  "ofetch": "^1.3.4",
40
40
  "pathe": "^1.1.2",
41
41
  "prettier": "^3.3.3",
42
- "@arrirpc/codegen-dart": "0.57.2",
43
- "@arrirpc/codegen-kotlin": "0.57.2",
44
- "@arrirpc/codegen-ts": "0.57.2",
45
- "@arrirpc/codegen-rust": "0.57.2",
46
- "@arrirpc/schema": "0.57.2",
47
- "@arrirpc/codegen-utils": "0.57.2"
42
+ "@arrirpc/codegen-dart": "0.57.4",
43
+ "@arrirpc/codegen-rust": "0.57.4",
44
+ "@arrirpc/schema": "0.57.4",
45
+ "@arrirpc/codegen-kotlin": "0.57.4",
46
+ "@arrirpc/codegen-utils": "0.57.4",
47
+ "@arrirpc/codegen-ts": "0.57.4"
48
48
  },
49
49
  "devDependencies": {
50
50
  "@types/degit": "^2.8.6"