@endo-ui/license 1.0.0 → 1.0.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.
package/LICENSE.md CHANGED
File without changes
package/README.md CHANGED
@@ -1,40 +1,44 @@
1
1
  # @endo-ui/license
2
2
 
3
- Private Node-only activation and Angular 22 build enforcement for EndoUI. Nothing in this
3
+ Public Node-only activation and Angular 22 build enforcement for EndoUI. Nothing in this
4
4
  package is imported into the browser application or its production runtime.
5
5
 
6
- ## Configure an Angular workspace
6
+ ## Local activation
7
7
 
8
- After installing `@endo-ui/core` and this package from the private `@endo-ui` namespace:
8
+ Install the public activation package in the customer project first:
9
9
 
10
10
  ```bash
11
- npx ng add @endo-ui/license
11
+ npm install --save-dev @endo-ui/license
12
12
  ```
13
13
 
14
- The schematic replaces Angular's application and development-server builders with EndoUI
15
- wrappers. The wrappers verify a signed local authorization before delegating to Angular's
16
- official builders.
17
-
18
- ## Developer activation
14
+ Then provide the npm read token through an approved local environment/secret mechanism and run
15
+ the project-local CLI:
19
16
 
20
17
  ```bash
18
+ export NPM_TOKEN="<customer npm read token>"
21
19
  npx endoui-license activate \
20
+ --license-id "<license-id>" \
22
21
  --developer "jane@customer.com"
23
22
  ```
24
23
 
25
- The command securely prompts for the customer license key. The email identifies the named
26
- developer Seat; it is not an EndoUI login account. The customer license key is sent only to
27
- ECAP's licensing service and is not cached by this package. For non-interactive environments,
28
- provide it through the protected `ENDOUI_LICENSE_KEY` environment variable.
24
+ If `NPM_TOKEN` is absent, an interactive terminal securely prompts for it. `--npm-token` is also
25
+ supported for controlled automation. Command-line secrets can be exposed through shell history or
26
+ process inspection, so `NPM_TOKEN` is preferred. The token is validated but never persisted or
27
+ printed. Activation
28
+ validates the named developer Seat, creates the signed local lease, manages the project `.npmrc`,
29
+ configures Angular's licensed builders, generates CI bootstrap scripts, and finally installs the
30
+ private `@endo-ui/core` package.
29
31
 
30
- ## CI activation
32
+ Local activation fails before validation or project mutation when `@endo-ui/license` is not
33
+ already installed in the project. CI uses the generated isolated exact-version bootstrap instead.
31
34
 
32
- Store `ENDOUI_LICENSE_KEY` in the CI provider's protected secret storage, then run:
35
+ ## CI activation
33
36
 
34
- ```bash
35
- npx endoui-license ci-activate
36
- npx ng build
37
- ```
37
+ Commit the generated `scripts/endoui-license-ci.sh` and `scripts/endoui-license-ci.ps1` files.
38
+ Store `NPM_TOKEN` and `ENDOUI_LICENSE_ID` in the CI provider's protected secret storage, then run
39
+ the platform-appropriate generated script immediately after checkout and before dependency
40
+ installation.
38
41
 
39
- CI authorization does not consume a human Seat. The signed authorization is cached outside the
40
- application source and remains valid for its bounded offline period.
42
+ CI authorization does not consume a human Seat. The scripts bootstrap the exact license-tool
43
+ version that generated them, validate npm and ECAP access, and cache a signed authorization for
44
+ the bounded build period. They contain no credentials.
@@ -0,0 +1,77 @@
1
+ import { activateCi, activateDeveloper } from "./api.js";
2
+ import { configureAngularWorkspace } from "./angular-config.js";
3
+ import { generateCiScripts } from "./ci-scripts.js";
4
+ import { installCore } from "./core-install.js";
5
+ import { verifyLease } from "./lease.js";
6
+ import { validateNpmAccess } from "./npm-access.js";
7
+ import { ensureEndoUiNpmrc } from "./npmrc.js";
8
+ import { resolveNpmToken } from "./npm-token.js";
9
+ import { resolveLicensePackageVersion } from "./package-version.js";
10
+ import { writeLease } from "./storage.js";
11
+ const defaults = {
12
+ version: resolveLicensePackageVersion,
13
+ resolveToken: explicitToken => resolveNpmToken({
14
+ explicitToken,
15
+ environmentToken: process.env["NPM_TOKEN"]
16
+ }),
17
+ validateNpm: validateNpmAccess,
18
+ developerLease: activateDeveloper,
19
+ ciLease: activateCi,
20
+ verify: verifyLease,
21
+ writeLease,
22
+ npmrc: ensureEndoUiNpmrc,
23
+ angular: configureAngularWorkspace,
24
+ scripts: generateCiScripts,
25
+ install: installCore
26
+ };
27
+ export function resolveCiInputs(environment) {
28
+ const npmToken = environment["NPM_TOKEN"]?.trim();
29
+ if (!npmToken)
30
+ throw new Error("CI activation requires NPM_TOKEN.");
31
+ const licenseId = environment["ENDOUI_LICENSE_ID"]?.trim();
32
+ if (!licenseId)
33
+ throw new Error("CI activation requires ENDOUI_LICENSE_ID.");
34
+ return { npmToken, licenseId };
35
+ }
36
+ function major(version) {
37
+ const value = Number(version.split(".", 1)[0]);
38
+ if (!Number.isSafeInteger(value) || value <= 0) {
39
+ throw new Error(`EndoUI package version ${version} is not eligible for activation.`);
40
+ }
41
+ return value;
42
+ }
43
+ function assertLicense(lease, licenseId, kind) {
44
+ if (lease.licenseId !== licenseId || lease.kind !== kind) {
45
+ throw new Error("The signed EndoUI authorization does not match this activation request.");
46
+ }
47
+ }
48
+ export async function activateLocal(input, overrides = {}) {
49
+ const dependencies = { ...defaults, ...overrides };
50
+ const cwd = input.cwd ?? process.cwd();
51
+ const version = dependencies.version();
52
+ const npmToken = await dependencies.resolveToken(input.npmToken);
53
+ // All external authorization checks complete before the first project or lease mutation.
54
+ await dependencies.validateNpm(npmToken, version);
55
+ const signedLease = await dependencies.developerLease(input.licenseId, input.developer, version);
56
+ const lease = dependencies.verify(signedLease, version);
57
+ assertLicense(lease, input.licenseId, "developer");
58
+ if (lease.user !== input.developer) {
59
+ throw new Error("The signed EndoUI authorization does not match the named developer.");
60
+ }
61
+ await dependencies.npmrc(cwd);
62
+ await dependencies.angular(cwd);
63
+ await dependencies.scripts({ packageVersion: version, customer: lease.customer, licenseId: input.licenseId }, cwd);
64
+ await dependencies.install(cwd, version, npmToken);
65
+ await dependencies.writeLease(major(version), signedLease);
66
+ return { version, lease };
67
+ }
68
+ export async function activateForCi(input, overrides = {}) {
69
+ const dependencies = { ...defaults, ...overrides };
70
+ const version = dependencies.version();
71
+ await dependencies.validateNpm(input.npmToken, version);
72
+ const signedLease = await dependencies.ciLease(input.licenseId, version);
73
+ const lease = dependencies.verify(signedLease, version);
74
+ assertLicense(lease, input.licenseId, "ci");
75
+ await dependencies.writeLease(major(version), signedLease);
76
+ return { version, lease };
77
+ }
@@ -0,0 +1,47 @@
1
+ import { readFile, writeFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ function configureTarget(target, expected, replacement) {
4
+ if (!target)
5
+ return false;
6
+ if (target.builder === replacement)
7
+ return false;
8
+ if (target.builder !== expected && !target.builder?.startsWith("@endo-ui/license:")) {
9
+ throw new Error(`Cannot replace custom Angular builder ${target.builder ?? "(missing)"}.`);
10
+ }
11
+ target.builder = replacement;
12
+ return true;
13
+ }
14
+ export function configureAngularWorkspaceObject(workspace) {
15
+ let found = false;
16
+ let changed = false;
17
+ for (const project of Object.values(workspace.projects ?? {})) {
18
+ if (project.projectType !== "application")
19
+ continue;
20
+ const targets = project.architect ?? project.targets;
21
+ if (!targets)
22
+ continue;
23
+ if (targets["build"] || targets["serve"])
24
+ found = true;
25
+ changed = configureTarget(targets["build"], "@angular/build:application", "@endo-ui/license:application") || changed;
26
+ changed = configureTarget(targets["serve"], "@angular/build:dev-server", "@endo-ui/license:dev-server") || changed;
27
+ }
28
+ if (!found)
29
+ throw new Error("No Angular 22 application build or serve targets were found.");
30
+ return changed;
31
+ }
32
+ export async function configureAngularWorkspace(cwd = process.cwd()) {
33
+ const file = path.join(cwd, "angular.json");
34
+ let workspace;
35
+ try {
36
+ workspace = JSON.parse(await readFile(file, "utf8"));
37
+ }
38
+ catch (error) {
39
+ if (error instanceof SyntaxError)
40
+ throw new Error("angular.json is not valid JSON.");
41
+ throw error;
42
+ }
43
+ const changed = configureAngularWorkspaceObject(workspace);
44
+ if (changed)
45
+ await writeFile(file, `${JSON.stringify(workspace, null, 2)}\n`, "utf8");
46
+ return changed;
47
+ }
package/bin/api.js CHANGED
@@ -1,12 +1,11 @@
1
1
  const DEFAULT_SERVICE_URL = "https://license.ecapsoftware.com";
2
- async function requestLease(pathname, licenseKey, body) {
2
+ async function requestLease(pathname, body) {
3
3
  const baseUrl = process.env['ENDOUI_LICENSE_URL'] ?? DEFAULT_SERVICE_URL;
4
4
  const response = await fetch(new URL(pathname, baseUrl), {
5
5
  method: "POST",
6
6
  headers: {
7
7
  Accept: "application/json",
8
- "Content-Type": "application/json",
9
- Authorization: `License ${licenseKey}`
8
+ "Content-Type": "application/json"
10
9
  },
11
10
  body: JSON.stringify(body)
12
11
  });
@@ -23,12 +22,13 @@ async function requestLease(pathname, licenseKey, body) {
23
22
  }
24
23
  return value.lease;
25
24
  }
26
- export function activateDeveloper(licenseKey, developerEmail, packageVersion) {
27
- return requestLease("/v1/endoui/leases/developer", licenseKey, {
25
+ export function activateDeveloper(licenseId, developerEmail, packageVersion) {
26
+ return requestLease("/v1/endoui/leases/developer", {
27
+ licenseId,
28
28
  developerEmail,
29
29
  packageVersion
30
30
  });
31
31
  }
32
- export function activateCi(licenseKey, packageVersion) {
33
- return requestLease("/v1/endoui/leases/ci", licenseKey, { packageVersion });
32
+ export function activateCi(licenseId, packageVersion) {
33
+ return requestLease("/v1/endoui/leases/ci", { licenseId, packageVersion });
34
34
  }
File without changes
File without changes
@@ -1,8 +1,8 @@
1
1
  import { resolveEndoUiVersion } from "../package-version.js";
2
2
  import { verifyLease } from "../lease.js";
3
3
  import { readLease } from "../storage.js";
4
- export async function verifyAuthorization() {
5
- const version = resolveEndoUiVersion();
4
+ export async function verifyAuthorization(overrides = {}) {
5
+ const version = (overrides.resolveVersion ?? resolveEndoUiVersion)();
6
6
  const major = Number(version.split(".", 1)[0]);
7
- verifyLease(await readLease(major), version);
7
+ (overrides.verify ?? verifyLease)(await (overrides.read ?? readLease)(major), version);
8
8
  }
@@ -0,0 +1,79 @@
1
+ import { chmod, mkdir, writeFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ export function maskLicenseId(_licenseId) {
4
+ return "********";
5
+ }
6
+ function safeComment(value) {
7
+ return value.replace(/[\r\n]+/g, " ").trim();
8
+ }
9
+ function header(metadata) {
10
+ const generated = (metadata.generatedAt ?? new Date()).toISOString().slice(0, 10);
11
+ return [
12
+ `# Generated by @endo-ui/license@${metadata.packageVersion}`,
13
+ `# Customer: ${safeComment(metadata.customer)}`,
14
+ `# License ID: ${maskLicenseId(metadata.licenseId)}`,
15
+ `# Generated: ${generated}`,
16
+ "#",
17
+ "# Do not edit manually. Re-run EndoUI activation to regenerate."
18
+ ].join("\n");
19
+ }
20
+ export function createShellCiScript(metadata) {
21
+ const version = metadata.packageVersion;
22
+ return `#!/usr/bin/env sh
23
+ set -eu
24
+
25
+ ${header(metadata)}
26
+
27
+ if [ -z "\${NPM_TOKEN:-}" ]; then
28
+ echo "EndoUI CI activation requires NPM_TOKEN." >&2
29
+ exit 1
30
+ fi
31
+ if [ -z "\${ENDOUI_LICENSE_ID:-}" ]; then
32
+ echo "EndoUI CI activation requires ENDOUI_LICENSE_ID." >&2
33
+ exit 1
34
+ fi
35
+
36
+ BOOTSTRAP_DIR="\${TMPDIR:-/tmp}/endoui-license-$$"
37
+ cleanup() { rm -rf "$BOOTSTRAP_DIR"; }
38
+ trap cleanup EXIT HUP INT TERM
39
+ mkdir -p "$BOOTSTRAP_DIR"
40
+ npm install --prefix "$BOOTSTRAP_DIR" --no-save "@endo-ui/license@${version}"
41
+ node "$BOOTSTRAP_DIR/node_modules/@endo-ui/license/bin/cli.js" ci-activate
42
+ `;
43
+ }
44
+ export function createPowerShellCiScript(metadata) {
45
+ const version = metadata.packageVersion;
46
+ return `$ErrorActionPreference = "Stop"
47
+
48
+ ${header(metadata)}
49
+
50
+ if ([string]::IsNullOrWhiteSpace($env:NPM_TOKEN)) {
51
+ throw "EndoUI CI activation requires NPM_TOKEN."
52
+ }
53
+ if ([string]::IsNullOrWhiteSpace($env:ENDOUI_LICENSE_ID)) {
54
+ throw "EndoUI CI activation requires ENDOUI_LICENSE_ID."
55
+ }
56
+
57
+ $bootstrapDirectory = Join-Path ([System.IO.Path]::GetTempPath()) ("endoui-license-" + [guid]::NewGuid())
58
+ try {
59
+ New-Item -ItemType Directory -Path $bootstrapDirectory | Out-Null
60
+ npm install --prefix $bootstrapDirectory --no-save "@endo-ui/license@${version}"
61
+ if ($LASTEXITCODE -ne 0) { throw "Unable to bootstrap @endo-ui/license." }
62
+ node (Join-Path $bootstrapDirectory "node_modules/@endo-ui/license/bin/cli.js") ci-activate
63
+ if ($LASTEXITCODE -ne 0) { throw "EndoUI CI activation failed." }
64
+ } finally {
65
+ if (Test-Path -LiteralPath $bootstrapDirectory) {
66
+ Remove-Item -LiteralPath $bootstrapDirectory -Recurse -Force
67
+ }
68
+ }
69
+ `;
70
+ }
71
+ export async function generateCiScripts(metadata, cwd = process.cwd()) {
72
+ const directory = path.join(cwd, "scripts");
73
+ await mkdir(directory, { recursive: true });
74
+ const shellPath = path.join(directory, "endoui-license-ci.sh");
75
+ await writeFile(shellPath, createShellCiScript(metadata), { encoding: "utf8", mode: 0o755 });
76
+ await writeFile(path.join(directory, "endoui-license-ci.ps1"), createPowerShellCiScript(metadata), "utf8");
77
+ if (process.platform !== "win32")
78
+ await chmod(shellPath, 0o755);
79
+ }
package/bin/cli.js CHANGED
@@ -1,13 +1,22 @@
1
1
  #!/usr/bin/env node
2
- import { activateCi, activateDeveloper } from "./api.js";
3
- import { createInterface } from "node:readline/promises";
4
- import { Writable } from "node:stream";
2
+ import { activateForCi, activateLocal, resolveCiInputs } from "./activation.js";
5
3
  import { verifyLease } from "./lease.js";
6
- import { resolveEndoUiVersion } from "./package-version.js";
7
- import { readLease, writeLease } from "./storage.js";
4
+ import { resolveEndoUiVersion, resolveInstalledLicenseVersion, resolveLicensePackageVersion } from "./package-version.js";
5
+ import { readLease } from "./storage.js";
8
6
  function argument(name) {
9
7
  const index = process.argv.indexOf(name);
10
- return index >= 0 ? process.argv[index + 1] : undefined;
8
+ if (index < 0)
9
+ return undefined;
10
+ const value = process.argv[index + 1];
11
+ if (!value || value.startsWith("--"))
12
+ throw new Error(`${name} requires a value.`);
13
+ return value;
14
+ }
15
+ function required(value, message) {
16
+ const normalized = value?.trim();
17
+ if (!normalized)
18
+ throw new Error(message);
19
+ return normalized;
11
20
  }
12
21
  function packageMajor(version) {
13
22
  const major = Number(version.split(".", 1)[0]);
@@ -16,64 +25,54 @@ function packageMajor(version) {
16
25
  }
17
26
  return major;
18
27
  }
19
- async function licenseCredential() {
20
- const configured = process.env['ENDOUI_LICENSE_KEY'];
21
- if (configured)
22
- return configured;
23
- if (!process.stdin.isTTY || !process.stdout.isTTY) {
24
- throw new Error("ENDOUI_LICENSE_KEY is required in non-interactive environments.");
25
- }
26
- let muted = false;
27
- const output = new Writable({
28
- write(chunk, encoding, callback) {
29
- if (!muted)
30
- process.stdout.write(chunk, encoding);
31
- callback();
32
- }
33
- });
34
- const prompt = createInterface({ input: process.stdin, output, terminal: true });
35
- try {
36
- const answer = prompt.question("Customer license key: ");
37
- muted = true;
38
- const value = (await answer).trim();
39
- process.stdout.write("\n");
40
- if (!value)
41
- throw new Error("A customer license key is required.");
42
- return value;
43
- }
44
- finally {
45
- prompt.close();
46
- }
47
- }
48
28
  async function activate() {
49
- const licenseKey = await licenseCredential();
50
- const developerEmail = argument("--developer") ?? process.env['ENDOUI_DEVELOPER_EMAIL'];
51
- if (!developerEmail) {
52
- throw new Error("Activation requires --developer.");
29
+ const installedVersion = resolveInstalledLicenseVersion();
30
+ const runningVersion = resolveLicensePackageVersion();
31
+ if (installedVersion !== runningVersion) {
32
+ throw new Error(`The project has @endo-ui/license@${installedVersion}, but activation is running ` +
33
+ `@endo-ui/license@${runningVersion}. Run the project-local command with npx endoui-license.`);
53
34
  }
54
- const normalizedEmail = developerEmail.trim().toLowerCase();
55
- const version = resolveEndoUiVersion();
56
- const major = packageMajor(version);
57
- const lease = await activateDeveloper(licenseKey, normalizedEmail, version);
58
- verifyLease(lease, version);
59
- await writeLease(major, lease);
60
- console.log(`EndoUI ${major}.x activated for ${normalizedEmail}.`);
35
+ const licenseId = required(argument("--license-id"), "Activation requires --license-id.");
36
+ const developer = required(argument("--developer") ?? process.env["ENDOUI_DEVELOPER_EMAIL"], "Activation requires --developer.").toLowerCase();
37
+ if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(developer)) {
38
+ throw new Error("--developer must be a valid email address.");
39
+ }
40
+ await activateLocal({
41
+ licenseId,
42
+ developer,
43
+ npmToken: argument("--npm-token")
44
+ });
45
+ console.log(`
46
+ EndoUI activation successful.
47
+
48
+ ✓ npm access verified
49
+ ✓ EndoUI license verified
50
+ ✓ Developer authorization created
51
+ ✓ Angular licensed builders configured
52
+ ✓ .npmrc configured
53
+ ✓ CI activation scripts generated
54
+ ✓ @endo-ui/core installed
55
+
56
+ Commit the generated CI scripts and .npmrc changes to your repository.
57
+
58
+ Keep NPM_TOKEN out of source control. Provide it through your approved
59
+ local secret mechanism and your CI secret store.
60
+
61
+ CI must provide:
62
+ NPM_TOKEN
63
+ ENDOUI_LICENSE_ID
64
+
65
+ Run the generated EndoUI CI activation script immediately after checkout
66
+ and before npm ci/npm install.`);
61
67
  }
62
68
  async function activateBuild() {
63
- const licenseKey = process.env['ENDOUI_LICENSE_KEY'];
64
- if (!licenseKey)
65
- throw new Error("CI activation requires ENDOUI_LICENSE_KEY.");
66
- const version = resolveEndoUiVersion();
67
- const major = packageMajor(version);
68
- const lease = await activateCi(licenseKey, version);
69
- verifyLease(lease, version);
70
- await writeLease(major, lease);
71
- console.log(`EndoUI ${version} CI authorization activated.`);
69
+ const { npmToken, licenseId } = resolveCiInputs(process.env);
70
+ const result = await activateForCi({ licenseId, npmToken });
71
+ console.log(`EndoUI ${result.version} CI authorization activated.`);
72
72
  }
73
73
  async function check() {
74
74
  const version = resolveEndoUiVersion();
75
- const major = packageMajor(version);
76
- const lease = verifyLease(await readLease(major), version);
75
+ const lease = verifyLease(await readLease(packageMajor(version)), version);
77
76
  const refreshNotice = Math.floor(Date.now() / 1000) >= lease.refreshAfter
78
77
  ? " Refresh activation when connectivity is available."
79
78
  : "";
@@ -92,7 +91,7 @@ async function main() {
92
91
  return;
93
92
  default:
94
93
  console.log("Usage: endoui-license <activate|ci-activate|verify> " +
95
- "[--developer <email>]");
94
+ "[--license-id <id>] [--developer <email>] [--npm-token <token>]");
96
95
  }
97
96
  }
98
97
  main().catch((error) => {
@@ -0,0 +1,44 @@
1
+ import { spawn } from "node:child_process";
2
+ const MAX_STDERR_LENGTH = 65_536;
3
+ function redact(value, npmToken) {
4
+ return npmToken ? value.split(npmToken).join("[REDACTED]") : value;
5
+ }
6
+ export function npmInstallFailure(specification, exitCode, stderr, npmToken, signal) {
7
+ const reason = redact(stderr.trim(), npmToken) ||
8
+ (signal ? `npm was terminated by signal ${signal}.` : "npm did not provide error output.");
9
+ return new Error(`Failed to install ${specification}.\n` +
10
+ `npm exit code: ${exitCode ?? "unknown"}\n` +
11
+ `Reason:\n${reason}`);
12
+ }
13
+ export async function installCore(cwd = process.cwd(), version, npmToken) {
14
+ const command = process.platform === "win32" ? "npm.cmd" : "npm";
15
+ await new Promise((resolve, reject) => {
16
+ const specification = version ? `@endo-ui/core@${version}` : "@endo-ui/core";
17
+ let stderr = "";
18
+ const child = spawn(command, ["install", specification], {
19
+ cwd,
20
+ env: {
21
+ ...process.env,
22
+ ...(npmToken ? { NPM_TOKEN: npmToken } : {})
23
+ },
24
+ stdio: ["inherit", "inherit", "pipe"],
25
+ shell: false
26
+ });
27
+ child.stderr?.setEncoding("utf8");
28
+ child.stderr?.on("data", (chunk) => {
29
+ stderr = `${stderr}${chunk}`.slice(-MAX_STDERR_LENGTH);
30
+ });
31
+ child.once("error", error => reject(new Error(`Unable to start npm while installing ${specification}. ${error.message}`)));
32
+ child.once("exit", (code, signal) => {
33
+ const safeStderr = redact(stderr, npmToken);
34
+ if (code === 0) {
35
+ if (safeStderr)
36
+ process.stderr.write(safeStderr);
37
+ resolve();
38
+ }
39
+ else {
40
+ reject(npmInstallFailure(specification, code, stderr, npmToken, signal));
41
+ }
42
+ });
43
+ });
44
+ }
package/bin/lease.js CHANGED
@@ -26,6 +26,8 @@ export function verifyLease(token, packageVersion, now = Date.now(), trust = {
26
26
  const major = Number(packageVersion.split(".", 1)[0]);
27
27
  if (lease.iss !== trust.issuer || lease.aud !== "@endo-ui/license" || lease.product !== "endoui" || lease.package !== "@endo-ui/core" || lease.majorVersion !== major || lease.entitlement !== `endoui-v${major}`)
28
28
  throw new Error("The authorization does not cover this EndoUI package.");
29
+ if (typeof lease.licenseId !== "string" || !lease.licenseId || typeof lease.customer !== "string" || !lease.customer)
30
+ throw new Error("The authorization is missing required customer details.");
29
31
  if (!Number.isSafeInteger(lease.nbf) || !Number.isSafeInteger(lease.exp))
30
32
  throw new Error("The authorization has invalid timestamps.");
31
33
  if (seconds < lease.nbf)
@@ -0,0 +1,34 @@
1
+ export async function validateNpmAccess(token, version, request = fetch) {
2
+ let response;
3
+ try {
4
+ response = await request("https://registry.npmjs.org/@endo-ui%2Fcore", {
5
+ headers: {
6
+ Accept: "application/vnd.npm.install-v1+json",
7
+ Authorization: `Bearer ${token}`
8
+ }
9
+ });
10
+ }
11
+ catch {
12
+ throw new Error("Unable to contact npm to validate access to @endo-ui/core.");
13
+ }
14
+ if (response.ok) {
15
+ let metadata;
16
+ try {
17
+ metadata = await response.json();
18
+ }
19
+ catch {
20
+ throw new Error("npm returned invalid package metadata for @endo-ui/core.");
21
+ }
22
+ if (!metadata.versions?.[version]) {
23
+ throw new Error(`@endo-ui/core@${version} is not available to this npm token.`);
24
+ }
25
+ return;
26
+ }
27
+ if (response.status === 401) {
28
+ throw new Error("The npm token is invalid, expired, or revoked.");
29
+ }
30
+ if (response.status === 403 || response.status === 404) {
31
+ throw new Error("The npm token does not have read access to @endo-ui/core.");
32
+ }
33
+ throw new Error(`npm access validation failed with HTTP ${response.status}.`);
34
+ }
@@ -0,0 +1,37 @@
1
+ import { createInterface } from "node:readline/promises";
2
+ import { Writable } from "node:stream";
3
+ async function securePrompt() {
4
+ let muted = false;
5
+ const output = new Writable({
6
+ write(chunk, encoding, callback) {
7
+ if (!muted)
8
+ process.stdout.write(chunk, encoding);
9
+ callback();
10
+ }
11
+ });
12
+ const prompt = createInterface({ input: process.stdin, output, terminal: true });
13
+ try {
14
+ const answer = prompt.question("npm token: ");
15
+ muted = true;
16
+ const value = (await answer).trim();
17
+ process.stdout.write("\n");
18
+ return value;
19
+ }
20
+ finally {
21
+ prompt.close();
22
+ }
23
+ }
24
+ export async function resolveNpmToken(options = {}) {
25
+ const explicit = options.explicitToken?.trim();
26
+ if (explicit)
27
+ return explicit;
28
+ const environment = options.environmentToken?.trim();
29
+ if (environment)
30
+ return environment;
31
+ if (options.interactive ?? (process.stdin.isTTY && process.stdout.isTTY)) {
32
+ const prompted = (await (options.prompt ?? securePrompt)()).trim();
33
+ if (prompted)
34
+ return prompted;
35
+ }
36
+ throw new Error("An npm token is required. Set NPM_TOKEN or provide --npm-token in an approved automation environment.");
37
+ }
package/bin/npmrc.js ADDED
@@ -0,0 +1,40 @@
1
+ import { readFile, writeFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ export const NPMRC_BEGIN = "# BEGIN ENDOUI MANAGED CONFIG";
4
+ export const NPMRC_END = "# END ENDOUI MANAGED CONFIG";
5
+ export const NPMRC_REGION = `${NPMRC_BEGIN}\n@endo-ui:registry=https://registry.npmjs.org/\n//registry.npmjs.org/:_authToken=\${NPM_TOKEN}\n${NPMRC_END}`;
6
+ export function updateNpmrcContent(content) {
7
+ const begins = [...content.matchAll(new RegExp(NPMRC_BEGIN, "g"))];
8
+ const ends = [...content.matchAll(new RegExp(NPMRC_END, "g"))];
9
+ if (begins.length === 0 && ends.length === 0) {
10
+ const separator = content.length === 0 ? "" : content.endsWith("\n") ? "\n" : "\n\n";
11
+ return { content: `${content}${separator}${NPMRC_REGION}\n`, changed: true };
12
+ }
13
+ if (begins.length !== 1 || ends.length !== 1 || begins[0].index >= ends[0].index) {
14
+ throw new Error("The EndoUI managed .npmrc markers are malformed or ambiguous.");
15
+ }
16
+ const start = begins[0].index;
17
+ const end = ends[0].index + NPMRC_END.length;
18
+ const existing = content.slice(start, end);
19
+ if (existing === NPMRC_REGION)
20
+ return { content, changed: false };
21
+ return {
22
+ content: `${content.slice(0, start)}${NPMRC_REGION}${content.slice(end)}`,
23
+ changed: true
24
+ };
25
+ }
26
+ export async function ensureEndoUiNpmrc(cwd = process.cwd()) {
27
+ const file = path.join(cwd, ".npmrc");
28
+ let original = "";
29
+ try {
30
+ original = await readFile(file, "utf8");
31
+ }
32
+ catch (error) {
33
+ if (!(error instanceof Error && "code" in error && error.code === "ENOENT"))
34
+ throw error;
35
+ }
36
+ const updated = updateNpmrcContent(original);
37
+ if (updated.changed)
38
+ await writeFile(file, updated.content, "utf8");
39
+ return updated.changed;
40
+ }
@@ -1,9 +1,37 @@
1
1
  import { createRequire } from "node:module";
2
+ import { readFileSync } from "node:fs";
2
3
  import path from "node:path";
3
- export function resolveEndoUiVersion(cwd = process.cwd()) { const require = createRequire(path.join(cwd, "package.json")); let file; try {
4
- file = require.resolve("@endo-ui/core/package.json");
4
+ function packageVersion(packageName, cwd) {
5
+ const require = createRequire(path.join(cwd, "package.json"));
6
+ let file;
7
+ try {
8
+ file = require.resolve(`${packageName}/package.json`);
9
+ }
10
+ catch {
11
+ throw new Error(`Unable to find ${packageName} from the current project.`);
12
+ }
13
+ const value = require(file);
14
+ if (value.name !== packageName || typeof value.version !== "string") {
15
+ throw new Error(`${packageName} has invalid package metadata.`);
16
+ }
17
+ return value.version;
18
+ }
19
+ export function resolveEndoUiVersion(cwd = process.cwd()) {
20
+ return packageVersion("@endo-ui/core", cwd);
21
+ }
22
+ export function resolveInstalledLicenseVersion(cwd = process.cwd()) {
23
+ try {
24
+ return packageVersion("@endo-ui/license", cwd);
25
+ }
26
+ catch {
27
+ throw new Error("Install @endo-ui/license in this project before activation: " +
28
+ "npm install --save-dev @endo-ui/license");
29
+ }
30
+ }
31
+ export function resolveLicensePackageVersion() {
32
+ const value = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
33
+ if (value.name !== "@endo-ui/license" || typeof value.version !== "string") {
34
+ throw new Error("@endo-ui/license has invalid package metadata.");
35
+ }
36
+ return value.version;
5
37
  }
6
- catch {
7
- throw new Error("Unable to find @endo-ui/core from the current project.");
8
- } const value = require(file); if (value.name !== "@endo-ui/core" || typeof value.version !== "string")
9
- throw new Error("@endo-ui/core has invalid package metadata."); return value.version; }
package/bin/public-key.js CHANGED
File without changes
@@ -1,4 +1,5 @@
1
1
  import { SchematicsException } from "@angular-devkit/schematics";
2
+ import { configureAngularWorkspaceObject } from "../../angular-config.js";
2
3
  export default function ngAdd() {
3
4
  return (tree) => {
4
5
  const workspaceFile = tree.get("angular.json");
@@ -6,26 +7,14 @@ export default function ngAdd() {
6
7
  throw new SchematicsException("This project does not contain angular.json.");
7
8
  }
8
9
  const workspace = JSON.parse(workspaceFile.content.toString("utf8"));
9
- let configured = false;
10
- for (const project of Object.values(workspace.projects ?? {})) {
11
- if (project.projectType !== "application")
12
- continue;
13
- const targets = project.architect ?? project.targets;
14
- if (!targets)
15
- continue;
16
- if (targets['build']?.builder === "@angular/build:application") {
17
- targets['build'].builder = "@endo-ui/license:application";
18
- configured = true;
19
- }
20
- if (targets['serve']?.builder === "@angular/build:dev-server") {
21
- targets['serve'].builder = "@endo-ui/license:dev-server";
22
- configured = true;
23
- }
10
+ try {
11
+ const changed = configureAngularWorkspaceObject(workspace);
12
+ if (changed)
13
+ tree.overwrite("angular.json", `${JSON.stringify(workspace, null, 2)}\n`);
24
14
  }
25
- if (!configured) {
26
- throw new SchematicsException("No Angular 22 application build or serve targets were found to configure.");
15
+ catch (error) {
16
+ throw new SchematicsException(error instanceof Error ? error.message : "Unable to configure Angular.");
27
17
  }
28
- tree.overwrite("angular.json", `${JSON.stringify(workspace, null, 2)}\n`);
29
18
  return tree;
30
19
  };
31
20
  }
package/bin/storage.js CHANGED
@@ -1,14 +1,27 @@
1
1
  import { chmod, mkdir, readFile, writeFile } from "node:fs/promises";
2
2
  import os from "node:os";
3
3
  import path from "node:path";
4
- function target(major) { const override = process.env['ENDOUI_LICENSE_CACHE']?.trim(); return override ? path.resolve(override) : path.join(os.homedir(), ".endoui", `endoui-v${major}.jwt`); }
5
- export async function readLease(major) { try {
6
- return (await readFile(target(major), "utf8")).trim();
4
+ export function leasePath(major, environment = process.env, homeDirectory = os.homedir()) {
5
+ const override = environment["ENDOUI_LICENSE_CACHE"]?.trim();
6
+ return override
7
+ ? path.resolve(override)
8
+ : path.join(homeDirectory, ".endoui", `endoui-v${major}.jwt`);
9
+ }
10
+ export async function readLease(major) {
11
+ try {
12
+ return (await readFile(leasePath(major), "utf8")).trim();
13
+ }
14
+ catch (error) {
15
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
16
+ throw new Error("EndoUI is not activated. Run `endoui-license activate`.");
17
+ }
18
+ throw error;
19
+ }
20
+ }
21
+ export async function writeLease(major, lease, environment = process.env, homeDirectory = os.homedir()) {
22
+ const file = leasePath(major, environment, homeDirectory);
23
+ await mkdir(path.dirname(file), { recursive: true, mode: 0o700 });
24
+ await writeFile(file, `${lease}\n`, { encoding: "utf8", mode: 0o600 });
25
+ if (process.platform !== "win32")
26
+ await chmod(file, 0o600);
7
27
  }
8
- catch (error) {
9
- if (error instanceof Error && "code" in error && error.code === "ENOENT")
10
- throw new Error("EndoUI is not activated. Run `endoui-license activate`.");
11
- throw error;
12
- } }
13
- export async function writeLease(major, lease) { const file = target(major); await mkdir(path.dirname(file), { recursive: true, mode: 0o700 }); await writeFile(file, `${lease}\n`, { encoding: "utf8", mode: 0o600 }); if (process.platform !== "win32")
14
- await chmod(file, 0o600); }
package/builders.json CHANGED
File without changes
package/collection.json CHANGED
File without changes
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@endo-ui/license",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Developer and CI license activation for EndoUI.",
5
5
  "license": "SEE LICENSE IN LICENSE.md",
6
6
  "publishConfig": {
7
- "access": "restricted"
7
+ "access": "public"
8
8
  },
9
9
  "type": "module",
10
10
  "builders": "./builders.json",
File without changes
File without changes