@vellumai/cli 0.11.2 → 0.11.3-staging.2

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.
Files changed (97) hide show
  1. package/knip.json +1 -0
  2. package/node_modules/@vellumai/environments/package.json +2 -1
  3. package/node_modules/@vellumai/environments/src/__tests__/cloud-assistant-hub-url.test.ts +38 -0
  4. package/node_modules/@vellumai/environments/src/__tests__/install-layout.test.ts +98 -0
  5. package/node_modules/@vellumai/environments/src/__tests__/package-boundary.test.ts +5 -5
  6. package/node_modules/@vellumai/environments/src/index.ts +17 -6
  7. package/node_modules/@vellumai/environments/src/install-layout.ts +49 -0
  8. package/node_modules/@vellumai/environments/src/seeds.ts +29 -0
  9. package/node_modules/@vellumai/ipc-server-utils/package.json +18 -0
  10. package/node_modules/@vellumai/ipc-server-utils/src/endpoint.test.ts +36 -0
  11. package/node_modules/@vellumai/ipc-server-utils/src/endpoint.ts +142 -0
  12. package/node_modules/@vellumai/ipc-server-utils/src/index.ts +18 -0
  13. package/node_modules/@vellumai/ipc-server-utils/src/ipc-framing.ts +295 -0
  14. package/node_modules/@vellumai/ipc-server-utils/src/listen-options.ts +3 -0
  15. package/node_modules/@vellumai/ipc-server-utils/src/socket-watchdog.test.ts +444 -0
  16. package/node_modules/@vellumai/ipc-server-utils/src/socket-watchdog.ts +236 -0
  17. package/node_modules/@vellumai/ipc-server-utils/tsconfig.json +20 -0
  18. package/node_modules/@vellumai/local-mode/node_modules/@vellumai/environments/package.json +2 -1
  19. package/node_modules/@vellumai/local-mode/node_modules/@vellumai/environments/src/__tests__/cloud-assistant-hub-url.test.ts +38 -0
  20. package/node_modules/@vellumai/local-mode/node_modules/@vellumai/environments/src/__tests__/install-layout.test.ts +98 -0
  21. package/node_modules/@vellumai/local-mode/node_modules/@vellumai/environments/src/__tests__/package-boundary.test.ts +5 -5
  22. package/node_modules/@vellumai/local-mode/node_modules/@vellumai/environments/src/index.ts +17 -6
  23. package/node_modules/@vellumai/local-mode/node_modules/@vellumai/environments/src/install-layout.ts +49 -0
  24. package/node_modules/@vellumai/local-mode/node_modules/@vellumai/environments/src/seeds.ts +29 -0
  25. package/node_modules/@vellumai/local-mode/node_modules/nanoid/LICENSE +20 -0
  26. package/node_modules/@vellumai/local-mode/node_modules/nanoid/README.md +38 -0
  27. package/node_modules/@vellumai/local-mode/node_modules/nanoid/bin/nanoid.js +55 -0
  28. package/node_modules/@vellumai/local-mode/node_modules/nanoid/index.browser.js +29 -0
  29. package/node_modules/@vellumai/local-mode/node_modules/nanoid/index.d.ts +106 -0
  30. package/node_modules/@vellumai/local-mode/node_modules/nanoid/index.js +47 -0
  31. package/node_modules/@vellumai/local-mode/node_modules/nanoid/nanoid.js +1 -0
  32. package/node_modules/@vellumai/local-mode/node_modules/nanoid/non-secure/index.d.ts +48 -0
  33. package/node_modules/@vellumai/local-mode/node_modules/nanoid/non-secure/index.js +21 -0
  34. package/node_modules/@vellumai/local-mode/node_modules/nanoid/package.json +46 -0
  35. package/node_modules/@vellumai/local-mode/node_modules/nanoid/url-alphabet/index.js +2 -0
  36. package/node_modules/@vellumai/local-mode/package.json +1 -0
  37. package/node_modules/@vellumai/local-mode/src/__tests__/environment.test.ts +63 -1
  38. package/node_modules/@vellumai/local-mode/src/__tests__/gateway-proxy.test.ts +503 -0
  39. package/node_modules/@vellumai/local-mode/src/__tests__/guardian-token.test.ts +203 -0
  40. package/node_modules/@vellumai/local-mode/src/__tests__/loopback-auth.test.ts +46 -1
  41. package/node_modules/@vellumai/local-mode/src/__tests__/package-boundary.test.ts +6 -4
  42. package/node_modules/@vellumai/local-mode/src/__tests__/pair.test.ts +621 -0
  43. package/node_modules/@vellumai/local-mode/src/__tests__/status.test.ts +5 -0
  44. package/node_modules/@vellumai/local-mode/src/__tests__/unpair.test.ts +232 -0
  45. package/node_modules/@vellumai/local-mode/src/config.ts +126 -20
  46. package/node_modules/@vellumai/local-mode/src/environment.ts +36 -15
  47. package/node_modules/@vellumai/local-mode/src/gateway-proxy.ts +313 -25
  48. package/node_modules/@vellumai/local-mode/src/guardian-token.ts +222 -13
  49. package/node_modules/@vellumai/local-mode/src/index.ts +35 -3
  50. package/node_modules/@vellumai/local-mode/src/lockfile-contract.test.ts +18 -0
  51. package/node_modules/@vellumai/local-mode/src/lockfile-contract.ts +72 -0
  52. package/node_modules/@vellumai/local-mode/src/lockfile.test.ts +130 -0
  53. package/node_modules/@vellumai/local-mode/src/lockfile.ts +134 -45
  54. package/node_modules/@vellumai/local-mode/src/pair.ts +354 -0
  55. package/node_modules/@vellumai/local-mode/src/paths.ts +49 -0
  56. package/node_modules/@vellumai/local-mode/src/status.ts +10 -8
  57. package/node_modules/@vellumai/local-mode/src/unpair.ts +101 -0
  58. package/node_modules/@vellumai/local-mode/src/util.ts +34 -0
  59. package/node_modules/@vellumai/service-contracts/src/channels.ts +39 -0
  60. package/node_modules/@vellumai/service-contracts/src/ingress.ts +10 -0
  61. package/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +6 -0
  62. package/package.json +3 -1
  63. package/src/__tests__/client-token.test.ts +26 -1
  64. package/src/__tests__/guardian-token.test.ts +4 -0
  65. package/src/__tests__/nginx-ingress-command.test.ts +176 -16
  66. package/src/__tests__/nginx-ingress.test.ts +1821 -101
  67. package/src/__tests__/pair.test.ts +104 -2
  68. package/src/__tests__/sleep.test.ts +7 -3
  69. package/src/__tests__/tailscale-tunnel.test.ts +0 -1
  70. package/src/__tests__/tunnel.test.ts +1099 -55
  71. package/src/__tests__/wake.test.ts +166 -99
  72. package/src/commands/client.ts +182 -27
  73. package/src/commands/connect/import.ts +35 -156
  74. package/src/commands/nginx-ingress.ts +87 -110
  75. package/src/commands/pair.ts +84 -21
  76. package/src/commands/tunnel.ts +196 -85
  77. package/src/commands/upgrade.ts +3 -2
  78. package/src/commands/wake.ts +5 -163
  79. package/src/index.ts +1 -1
  80. package/src/lib/__tests__/local-ces.test.ts +10 -1
  81. package/src/lib/cloudflare-tunnel.ts +3 -16
  82. package/src/lib/environments/__tests__/paths.test.ts +20 -1
  83. package/src/lib/environments/paths.ts +29 -41
  84. package/src/lib/environments/resolve.ts +7 -5
  85. package/src/lib/guardian-token.ts +22 -62
  86. package/src/lib/ingress-config.ts +25 -0
  87. package/src/lib/local.ts +77 -20
  88. package/src/lib/nginx-ingress.ts +523 -76
  89. package/src/lib/ngrok.ts +154 -61
  90. package/src/lib/orphan-detection.test.ts +167 -0
  91. package/src/lib/orphan-detection.ts +40 -0
  92. package/src/lib/pair.test.ts +87 -0
  93. package/src/lib/pair.ts +55 -0
  94. package/src/lib/platform-client.ts +8 -5
  95. package/src/lib/tailscale-tunnel.ts +7 -18
  96. package/src/lib/tunnel-edge.ts +114 -0
  97. package/src/lib/xdg-log.ts +2 -2
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Pins the environment-to-cloud-hub mapping in one place. Both consumers
3
+ * (the Capacitor shell's `server.url` and the CLI remote-web edge's
4
+ * `hubUrl`) read this helper, so this matrix is the single behavioral pin
5
+ * for the served URLs.
6
+ */
7
+
8
+ import { describe, expect, test } from "bun:test";
9
+
10
+ import { cloudAssistantHubUrl, SEEDS } from "../index.js";
11
+
12
+ describe("cloudAssistantHubUrl", () => {
13
+ test("cloud environments resolve to their own web origin", () => {
14
+ expect(cloudAssistantHubUrl("production")).toBe(
15
+ "https://www.vellum.ai/assistant",
16
+ );
17
+ expect(cloudAssistantHubUrl("staging")).toBe(
18
+ "https://staging-assistant.vellum.ai/assistant",
19
+ );
20
+ });
21
+
22
+ test("every other environment falls back to the dev SPA", () => {
23
+ for (const name of ["dev", "test", "local", "unknown-env", undefined]) {
24
+ expect(cloudAssistantHubUrl(name)).toBe(
25
+ "https://dev-assistant.vellum.ai/assistant",
26
+ );
27
+ }
28
+ });
29
+
30
+ test("cloud hub URLs derive from the seed web URLs", () => {
31
+ for (const name of ["production", "staging"]) {
32
+ expect(cloudAssistantHubUrl(name)).toBe(
33
+ `${SEEDS[name].webUrl}/assistant`,
34
+ );
35
+ }
36
+ expect(cloudAssistantHubUrl("local")).toBe(`${SEEDS.dev.webUrl}/assistant`);
37
+ });
38
+ });
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Tests for reading an install layout: telling a repo checkout from an
3
+ * installed package, and locating the `assistant` command that ships with an
4
+ * install.
5
+ */
6
+
7
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
8
+ import { tmpdir } from "node:os";
9
+ import { join } from "node:path";
10
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
11
+
12
+ import { findAssistantCommand, isRepoCheckoutPath } from "../index.js";
13
+
14
+ let root: string;
15
+
16
+ beforeEach(() => {
17
+ root = mkdtempSync(join(tmpdir(), "install-layout-"));
18
+ });
19
+
20
+ afterEach(() => {
21
+ rmSync(root, { recursive: true, force: true });
22
+ });
23
+
24
+ /** Create `<nodeModulesDir>/.bin/assistant` and return its path. */
25
+ function seedCommand(nodeModulesDir: string): string {
26
+ const binDir = join(nodeModulesDir, ".bin");
27
+ mkdirSync(binDir, { recursive: true });
28
+ const command = join(binDir, "assistant");
29
+ writeFileSync(command, "#!/usr/bin/env bun\n");
30
+ return command;
31
+ }
32
+
33
+ describe("isRepoCheckoutPath", () => {
34
+ test("an installed package is not a checkout", () => {
35
+ expect(
36
+ isRepoCheckoutPath(
37
+ "/Users/x/Library/Application Support/@vellumai/macos/cli/latest/node_modules/@vellumai/assistant/src/index.ts",
38
+ ),
39
+ ).toBe(false);
40
+ });
41
+
42
+ test("a repo checkout is", () => {
43
+ expect(
44
+ isRepoCheckoutPath("/Users/x/dev/vellum/assistant/src/index.ts"),
45
+ ).toBe(true);
46
+ });
47
+
48
+ test("a directory merely named node_modules-ish is not enough", () => {
49
+ expect(
50
+ isRepoCheckoutPath("/Users/x/node_modules_backup/a/src/index.ts"),
51
+ ).toBe(true);
52
+ });
53
+ });
54
+
55
+ describe("findAssistantCommand", () => {
56
+ test("finds the command shipped beside an installed package", () => {
57
+ const nodeModules = join(root, "cli", "latest", "node_modules");
58
+ const command = seedCommand(nodeModules);
59
+ const path = join(nodeModules, "@vellumai", "assistant", "src", "index.ts");
60
+
61
+ expect(findAssistantCommand(path)).toBe(command);
62
+ });
63
+
64
+ test("prefers a nested install over a hoisted one", () => {
65
+ const outer = join(root, "node_modules");
66
+ const outerCommand = seedCommand(outer);
67
+ const inner = join(outer, "vellum", "node_modules");
68
+ const innerCommand = seedCommand(inner);
69
+ const path = join(inner, "@vellumai", "assistant", "src", "index.ts");
70
+
71
+ expect(findAssistantCommand(path)).toBe(innerCommand);
72
+ expect(innerCommand).not.toBe(outerCommand);
73
+ });
74
+
75
+ test("falls back to a hoisted install when the nested one has no command", () => {
76
+ const outer = join(root, "node_modules");
77
+ const outerCommand = seedCommand(outer);
78
+ const inner = join(outer, "vellum", "node_modules");
79
+ mkdirSync(inner, { recursive: true });
80
+ const path = join(inner, "@vellumai", "assistant", "src", "index.ts");
81
+
82
+ expect(findAssistantCommand(path)).toBe(outerCommand);
83
+ });
84
+
85
+ test("returns null for a repo checkout", () => {
86
+ expect(
87
+ findAssistantCommand(join(root, "assistant", "src", "index.ts")),
88
+ ).toBeNull();
89
+ });
90
+
91
+ test("returns null when the install ships no assistant command", () => {
92
+ const nodeModules = join(root, "node_modules");
93
+ mkdirSync(join(nodeModules, ".bin"), { recursive: true });
94
+ const path = join(nodeModules, "@vellumai", "assistant", "src", "index.ts");
95
+
96
+ expect(findAssistantCommand(path)).toBeNull();
97
+ });
98
+ });
@@ -1,10 +1,10 @@
1
1
  /**
2
2
  * Package boundary tests for @vellumai/environments.
3
3
  *
4
- * This package is the lowest layer of the shared-packages hierarchy: pure
5
- * environment types and constants with no runtime dependencies. It must
6
- * stay a leaf so any consumer (CLI, assistant, local-mode host) can depend
7
- * on it without dragging in a dependency tree.
4
+ * This package is the lowest layer of the shared-packages hierarchy:
5
+ * environment types, constants, and small helpers over node builtins, with no
6
+ * runtime dependencies. It must stay a leaf so any consumer (CLI, assistant,
7
+ * local-mode host) can depend on it without dragging in a dependency tree.
8
8
  *
9
9
  * Enforces that the package:
10
10
  * 1. Imports only node builtins and its own relative modules — no `@vellumai/*`
@@ -79,7 +79,7 @@ describe("package boundary", () => {
79
79
  throw new Error(
80
80
  `Found ${violations.length} forbidden import(s) in @vellumai/environments:\n` +
81
81
  violations.map((v) => ` - ${v}`).join("\n") +
82
- "\n\n@vellumai/environments is a pure types/constants leaf and must\n" +
82
+ "\n\n@vellumai/environments is a dependency-free leaf and must\n" +
83
83
  "import only node builtins and its own relative modules.",
84
84
  );
85
85
  }
@@ -1,11 +1,22 @@
1
1
  /**
2
- * @vellumai/environments the single source of truth for Vellum's known
3
- * deployment environments (names, platform/web URLs, per-service port
4
- * blocks). Consumed by the CLI, the assistant daemon, and the local-mode
5
- * host library so the environment list is defined exactly once on the TS
6
- * side. The Swift client mirrors the same set in `VellumEnvironment.swift`.
2
+ * @vellumai/environments: the single source of truth for how a Vellum install
3
+ * is laid out and which deployment environment it belongs to.
7
4
  *
8
- * Intentionally free of runtime dependencies: pure types and constants only.
5
+ * Two things live here:
6
+ *
7
+ * - The known deployment environments (names, platform/web URLs, per-service
8
+ * port blocks). The Swift client mirrors the same set in
9
+ * `VellumEnvironment.swift`.
10
+ * - Install layout: whether a runtime is a repo checkout or an installed
11
+ * package, and where the `assistant` command it ships lives.
12
+ *
13
+ * Consumed by the CLI, the assistant daemon, and the local-mode host library
14
+ * so each answer is defined exactly once on the TS side.
15
+ *
16
+ * This is the lowest layer of the shared-packages hierarchy and stays a leaf:
17
+ * types, constants, and small helpers over node builtins, with no runtime
18
+ * dependencies of its own. See `__tests__/package-boundary.test.ts`.
9
19
  */
10
20
  export * from "./types.js";
11
21
  export * from "./seeds.js";
22
+ export * from "./install-layout.js";
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Reading the layout a Vellum runtime is installed in.
3
+ *
4
+ * The daemon is started either from a developer's repo checkout or from an
5
+ * installed npm package (`<install>/node_modules/@vellumai/assistant/src/
6
+ * index.ts`, which is what the desktop app runs). The CLI needs the layout to
7
+ * decide whether a launch is a dev run and what to put on the daemon's PATH;
8
+ * the daemon needs it to decide what to install as the `assistant` command.
9
+ * Both ask the same two questions, so they ask them here.
10
+ */
11
+
12
+ import { existsSync } from "node:fs";
13
+ import { join, sep } from "node:path";
14
+
15
+ const NODE_MODULES_SEGMENT = `${sep}node_modules${sep}`;
16
+
17
+ /**
18
+ * Whether `path` sits in a developer's repo checkout rather than inside an
19
+ * installed package.
20
+ */
21
+ export function isRepoCheckoutPath(path: string): boolean {
22
+ return !path.includes(NODE_MODULES_SEGMENT);
23
+ }
24
+
25
+ /**
26
+ * The `assistant` command shipped with the install that contains `path`, or
27
+ * null when there is none (a repo checkout, or an install whose dependency
28
+ * graph omits the package declaring the bin).
29
+ *
30
+ * Searches innermost `node_modules` outward, since a nested install shadows a
31
+ * hoisted one.
32
+ */
33
+ export function findAssistantCommand(path: string): string | null {
34
+ let index = path.lastIndexOf(NODE_MODULES_SEGMENT);
35
+
36
+ while (index !== -1) {
37
+ const nodeModulesDir = path.slice(
38
+ 0,
39
+ index + NODE_MODULES_SEGMENT.length - 1,
40
+ );
41
+ const command = join(nodeModulesDir, ".bin", "assistant");
42
+ if (existsSync(command)) {
43
+ return command;
44
+ }
45
+ index = path.lastIndexOf(NODE_MODULES_SEGMENT, index - 1);
46
+ }
47
+
48
+ return null;
49
+ }
@@ -24,6 +24,35 @@ function portBlock(base: number): PortMap {
24
24
  };
25
25
  }
26
26
 
27
+ /**
28
+ * Base URL of the cloud-hosted assistant SPA (the "hub") for a deployment
29
+ * environment. The Capacitor mobile shell bakes this into native builds as
30
+ * `server.url`, and the CLI's remote-web edge stamps it into the served
31
+ * config as `hubUrl`, so the environment-to-hub mapping lives here exactly
32
+ * once.
33
+ *
34
+ * Only the cloud deployments (production, staging) serve the hosted SPA at
35
+ * their own web origin; every other environment (dev, test, local, unknown)
36
+ * falls back to the dev SPA, whose host serves remote clients that cannot
37
+ * reach a local or per-developer web URL.
38
+ *
39
+ * The `/assistant` suffix is deliberate: booting on the bare host lands on
40
+ * the marketing page, whose CTA redirects to `www.vellum.ai/assistant` and
41
+ * bounces non-prod shells off their own host.
42
+ *
43
+ * NOTE: this module is loaded by Capacitor's single-file TS config loader
44
+ * (via the `@vellumai/environments/seeds` subpath), which cannot follow the
45
+ * package index's `.js`-suffixed runtime re-exports. Keep this file's
46
+ * imports type-only so it stays loadable on its own.
47
+ */
48
+ export function cloudAssistantHubUrl(envName: string | undefined): string {
49
+ const seed =
50
+ envName === "production" || envName === "staging"
51
+ ? SEEDS[envName]
52
+ : SEEDS.dev;
53
+ return `${seed.webUrl}/assistant`;
54
+ }
55
+
27
56
  /**
28
57
  * Built-in environment definitions and the source of truth for the
29
58
  * set of known environment names.
@@ -0,0 +1,20 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright 2017 Andrey Sitnik <andrey@sitnik.ru>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,38 @@
1
+ # Nano ID
2
+
3
+ <img src="https://ai.github.io/nanoid/logo.svg" align="right"
4
+ alt="Nano ID logo by Anton Lovchikov" width="180" height="94">
5
+
6
+ **English** | [日本語](./README.ja.md) | [Русский](./README.ru.md) | [简体中文](./README.zh-CN.md) | [Bahasa Indonesia](./README.id-ID.md) | [한국어](./README.ko.md) | [العربية](./README.ar.md)
7
+
8
+ A tiny, secure, URL-friendly, unique string ID generator for JavaScript.
9
+
10
+ > “An amazing level of senseless perfectionism,
11
+ > which is simply impossible not to respect.”
12
+
13
+ * **Small.** 118 bytes (minified and brotlied). No dependencies.
14
+ [Size Limit] controls the size.
15
+ * **Safe.** It uses hardware random generator. Can be used in clusters.
16
+ * **Short IDs.** It uses a larger alphabet than UUID (`A-Za-z0-9_-`).
17
+ So ID size was reduced from 36 to 21 symbols.
18
+ * **Portable.** Nano ID was ported
19
+ to over [20 programming languages](./README.md#other-programming-languages).
20
+
21
+ ```js
22
+ import { nanoid } from 'nanoid'
23
+ model.id = nanoid() //=> "V1StGXR8_Z5jdHi6B-myT"
24
+ ```
25
+
26
+ ---
27
+
28
+ <img src="https://cdn.evilmartians.com/badges/logo-no-label.svg" alt="" width="22" height="16" />  Made at <b><a href="https://evilmartians.com/devtools?utm_source=nanoid&utm_campaign=devtools-button&utm_medium=github">Evil Martians</a></b>, product consulting for <b>developer tools</b>.
29
+
30
+ ---
31
+
32
+ [online tool]: https://gitpod.io/#https://github.com/ai/nanoid/
33
+ [with Babel]: https://developer.epages.com/blog/coding/how-to-transpile-node-modules-with-babel-and-webpack-in-a-monorepo/
34
+ [Size Limit]: https://github.com/ai/size-limit
35
+
36
+
37
+ ## Docs
38
+ Read full docs **[here](https://github.com/ai/nanoid#readme)**.
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env node
2
+ import { readFileSync } from 'node:fs'
3
+ import { dirname, join } from 'node:path'
4
+ import { fileURLToPath } from 'node:url'
5
+ import { customAlphabet, nanoid } from '../index.js'
6
+ function print(msg) {
7
+ process.stdout.write(msg + '\n')
8
+ }
9
+ function error(msg) {
10
+ process.stderr.write(msg + '\n')
11
+ process.exit(1)
12
+ }
13
+ if (process.argv.includes('--version') || process.argv.includes('-v')) {
14
+ let root = dirname(fileURLToPath(import.meta.url))
15
+ let pkg = JSON.parse(readFileSync(join(root, '..', 'package.json'), 'utf8'))
16
+ print(pkg.version)
17
+ process.exit()
18
+ }
19
+ if (process.argv.includes('--help') || process.argv.includes('-h')) {
20
+ print(`Usage
21
+ $ nanoid [options]
22
+ Options
23
+ -s, --size Generated ID size
24
+ -a, --alphabet Alphabet to use
25
+ -v, --version Show version number
26
+ -h, --help Show this help
27
+ Examples
28
+ $ nanoid -s 15
29
+ S9sBF77U6sDB8Yg
30
+ $ nanoid --size 10 --alphabet abc
31
+ bcabababca`)
32
+ process.exit()
33
+ }
34
+ let alphabet, size
35
+ for (let i = 2; i < process.argv.length; i++) {
36
+ let arg = process.argv[i]
37
+ if (arg === '--size' || arg === '-s') {
38
+ size = Number(process.argv[i + 1])
39
+ i += 1
40
+ if (Number.isNaN(size) || size <= 0) {
41
+ error('Size must be positive integer')
42
+ }
43
+ } else if (arg === '--alphabet' || arg === '-a') {
44
+ alphabet = process.argv[i + 1]
45
+ i += 1
46
+ } else {
47
+ error('Unknown argument ' + arg)
48
+ }
49
+ }
50
+ if (alphabet) {
51
+ let customNanoid = customAlphabet(alphabet, size)
52
+ print(customNanoid())
53
+ } else {
54
+ print(nanoid(size))
55
+ }
@@ -0,0 +1,29 @@
1
+ /* @ts-self-types="./index.d.ts" */
2
+ import { urlAlphabet as scopedUrlAlphabet } from './url-alphabet/index.js'
3
+ export { urlAlphabet } from './url-alphabet/index.js'
4
+ export let random = bytes => crypto.getRandomValues(new Uint8Array(bytes))
5
+ export let customRandom = (alphabet, defaultSize, getRandom) => {
6
+ let mask = (2 << Math.log2(alphabet.length - 1)) - 1
7
+ let step = -~((1.6 * mask * defaultSize) / alphabet.length)
8
+ return (size = defaultSize) => {
9
+ let id = ''
10
+ while (true) {
11
+ let bytes = getRandom(step)
12
+ let j = step | 0
13
+ while (j--) {
14
+ id += alphabet[bytes[j] & mask] || ''
15
+ if (id.length >= size) return id
16
+ }
17
+ }
18
+ }
19
+ }
20
+ export let customAlphabet = (alphabet, size = 21) =>
21
+ customRandom(alphabet, size | 0, random)
22
+ export let nanoid = (size = 21) => {
23
+ let id = ''
24
+ let bytes = crypto.getRandomValues(new Uint8Array((size |= 0)))
25
+ while (size--) {
26
+ id += scopedUrlAlphabet[bytes[size] & 63]
27
+ }
28
+ return id
29
+ }
@@ -0,0 +1,106 @@
1
+ /**
2
+ * A tiny, secure, URL-friendly, unique string ID generator for JavaScript
3
+ * with hardware random generator.
4
+ *
5
+ * ```js
6
+ * import { nanoid } from 'nanoid'
7
+ * model.id = nanoid() //=> "V1StGXR8_Z5jdHi6B-myT"
8
+ * ```
9
+ *
10
+ * @module
11
+ */
12
+
13
+ /**
14
+ * Generate secure URL-friendly unique ID.
15
+ *
16
+ * By default, the ID will have 21 symbols to have a collision probability
17
+ * similar to UUID v4.
18
+ *
19
+ * ```js
20
+ * import { nanoid } from 'nanoid'
21
+ * model.id = nanoid() //=> "Uakgb_J5m9g-0JDMbcJqL"
22
+ * ```
23
+ *
24
+ * @param size Size of the ID. The default size is 21.
25
+ * @typeparam Type The ID type to replace `string` with some opaque type.
26
+ * @returns A random string.
27
+ */
28
+ export function nanoid<Type extends string>(size?: number): Type
29
+
30
+ /**
31
+ * Generate secure unique ID with custom alphabet.
32
+ *
33
+ * Alphabet must contain 256 symbols or less. Otherwise, the generator
34
+ * will not be secure.
35
+ *
36
+ * @param alphabet Alphabet used to generate the ID.
37
+ * @param defaultSize Size of the ID. The default size is 21.
38
+ * @typeparam Type The ID type to replace `string` with some opaque type.
39
+ * @returns A random string generator.
40
+ *
41
+ * ```js
42
+ * import { customAlphabet } from 'nanoid'
43
+ * const nanoid = customAlphabet('0123456789абвгдеё', 5)
44
+ * nanoid() //=> "8ё56а"
45
+ * ```
46
+ */
47
+ export function customAlphabet<Type extends string>(
48
+ alphabet: string,
49
+ defaultSize?: number
50
+ ): (size?: number) => Type
51
+
52
+ /**
53
+ * Generate unique ID with custom random generator and alphabet.
54
+ *
55
+ * Alphabet must contain 256 symbols or less. Otherwise, the generator
56
+ * will not be secure.
57
+ *
58
+ * ```js
59
+ * import { customRandom } from 'nanoid'
60
+ *
61
+ * const nanoid = customRandom('abcdef', 5, size => {
62
+ * const random = []
63
+ * for (let i = 0; i < size; i++) {
64
+ * random.push(randomByte())
65
+ * }
66
+ * return random
67
+ * })
68
+ *
69
+ * nanoid() //=> "fbaef"
70
+ * ```
71
+ *
72
+ * @param alphabet Alphabet used to generate a random string.
73
+ * @param size Size of the random string.
74
+ * @param random A random bytes generator.
75
+ * @typeparam Type The ID type to replace `string` with some opaque type.
76
+ * @returns A random string generator.
77
+ */
78
+ export function customRandom<Type extends string>(
79
+ alphabet: string,
80
+ size: number,
81
+ random: (bytes: number) => Uint8Array
82
+ ): (size?: number) => Type
83
+
84
+ /**
85
+ * URL safe symbols.
86
+ *
87
+ * ```js
88
+ * import { urlAlphabet } from 'nanoid'
89
+ * const nanoid = customAlphabet(urlAlphabet, 10)
90
+ * nanoid() //=> "Uakgb_J5m9"
91
+ * ```
92
+ */
93
+ export const urlAlphabet: string
94
+
95
+ /**
96
+ * Generate an array of random bytes collected from hardware noise.
97
+ *
98
+ * ```js
99
+ * import { customRandom, random } from 'nanoid'
100
+ * const nanoid = customRandom("abcdef", 5, random)
101
+ * ```
102
+ *
103
+ * @param bytes Size of the array.
104
+ * @returns An array of random bytes.
105
+ */
106
+ export function random(bytes: number): Uint8Array
@@ -0,0 +1,47 @@
1
+ import { webcrypto as crypto } from 'node:crypto'
2
+ import { urlAlphabet as scopedUrlAlphabet } from './url-alphabet/index.js'
3
+ export { urlAlphabet } from './url-alphabet/index.js'
4
+ const POOL_SIZE_MULTIPLIER = 128
5
+ let pool, poolOffset
6
+ function fillPool(bytes) {
7
+ if (!pool || pool.length < bytes) {
8
+ pool = Buffer.allocUnsafe(bytes * POOL_SIZE_MULTIPLIER)
9
+ crypto.getRandomValues(pool)
10
+ poolOffset = 0
11
+ } else if (poolOffset + bytes > pool.length) {
12
+ crypto.getRandomValues(pool)
13
+ poolOffset = 0
14
+ }
15
+ poolOffset += bytes
16
+ }
17
+ export function random(bytes) {
18
+ fillPool((bytes |= 0))
19
+ return pool.subarray(poolOffset - bytes, poolOffset)
20
+ }
21
+ export function customRandom(alphabet, defaultSize, getRandom) {
22
+ let mask = (2 << (31 - Math.clz32((alphabet.length - 1) | 1))) - 1
23
+ let step = Math.ceil((1.6 * mask * defaultSize) / alphabet.length)
24
+ return (size = defaultSize) => {
25
+ if (!size) return ''
26
+ let id = ''
27
+ while (true) {
28
+ let bytes = getRandom(step)
29
+ let i = step
30
+ while (i--) {
31
+ id += alphabet[bytes[i] & mask] || ''
32
+ if (id.length >= size) return id
33
+ }
34
+ }
35
+ }
36
+ }
37
+ export function customAlphabet(alphabet, size = 21) {
38
+ return customRandom(alphabet, size, random)
39
+ }
40
+ export function nanoid(size = 21) {
41
+ fillPool((size |= 0))
42
+ let id = ''
43
+ for (let i = poolOffset - size; i < poolOffset; i++) {
44
+ id += scopedUrlAlphabet[pool[i] & 63]
45
+ }
46
+ return id
47
+ }
@@ -0,0 +1 @@
1
+ let a="useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";export let nanoid=(e=21)=>{let t="",r=crypto.getRandomValues(new Uint8Array(e|=0));for(;e--;)t+=a[63&r[e]];return t};
@@ -0,0 +1,48 @@
1
+ /**
2
+ * By default, Nano ID uses hardware random bytes generation for security
3
+ * and low collision probability. If you are not so concerned with security,
4
+ * you can use it for environments without hardware random generators.
5
+ *
6
+ * ```js
7
+ * import { nanoid } from 'nanoid/non-secure'
8
+ * const id = nanoid() //=> "Uakgb_J5m9g-0JDMbcJqLJ"
9
+ * ```
10
+ *
11
+ * @module
12
+ */
13
+
14
+ /**
15
+ * Generate URL-friendly unique ID. This method uses the non-secure
16
+ * predictable random generator with bigger collision probability.
17
+ *
18
+ * ```js
19
+ * import { nanoid } from 'nanoid/non-secure'
20
+ * model.id = nanoid() //=> "Uakgb_J5m9g-0JDMbcJqL"
21
+ * ```
22
+ *
23
+ * @param size Size of the ID. The default size is 21.
24
+ * @typeparam Type The ID type to replace `string` with some opaque type.
25
+ * @returns A random string.
26
+ */
27
+ export function nanoid<Type extends string>(size?: number): Type
28
+
29
+ /**
30
+ * Generate a unique ID based on a custom alphabet.
31
+ * This method uses the non-secure predictable random generator
32
+ * with bigger collision probability.
33
+ *
34
+ * @param alphabet Alphabet used to generate the ID.
35
+ * @param defaultSize Size of the ID. The default size is 21.
36
+ * @typeparam Type The ID type to replace `string` with some opaque type.
37
+ * @returns A random string generator.
38
+ *
39
+ * ```js
40
+ * import { customAlphabet } from 'nanoid/non-secure'
41
+ * const nanoid = customAlphabet('0123456789абвгдеё', 5)
42
+ * model.id = nanoid() //=> "8ё56а"
43
+ * ```
44
+ */
45
+ export function customAlphabet<Type extends string>(
46
+ alphabet: string,
47
+ defaultSize?: number
48
+ ): (size?: number) => Type
@@ -0,0 +1,21 @@
1
+ /* @ts-self-types="./index.d.ts" */
2
+ let urlAlphabet =
3
+ 'useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict'
4
+ export let customAlphabet = (alphabet, defaultSize = 21) => {
5
+ return (size = defaultSize) => {
6
+ let id = ''
7
+ let i = size | 0
8
+ while (i--) {
9
+ id += alphabet[(Math.random() * alphabet.length) | 0]
10
+ }
11
+ return id
12
+ }
13
+ }
14
+ export let nanoid = (size = 21) => {
15
+ let id = ''
16
+ let i = size | 0
17
+ while (i--) {
18
+ id += urlAlphabet[(Math.random() * 64) | 0]
19
+ }
20
+ return id
21
+ }