@push.rocks/smartacme 9.5.0 → 9.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/.smartconfig.json +12 -7
  2. package/dist_ts/00_commitinfo_data.js +1 -1
  3. package/dist_ts/acme/acme.classes.client.d.ts +2 -0
  4. package/dist_ts/acme/acme.classes.client.js +5 -2
  5. package/dist_ts/acme/acme.classes.http-client.d.ts +2 -1
  6. package/dist_ts/acme/acme.classes.http-client.js +4 -2
  7. package/dist_ts/acme/acme.classes.order.d.ts +3 -0
  8. package/dist_ts/acme/acme.classes.order.js +11 -2
  9. package/dist_ts/classes.exact-certificate-issuer.d.ts +37 -0
  10. package/dist_ts/classes.exact-certificate-issuer.js +397 -0
  11. package/dist_ts/handlers/IChallengeHandler.d.ts +8 -3
  12. package/dist_ts/handlers/index.d.ts +1 -1
  13. package/dist_ts/index.d.ts +2 -0
  14. package/dist_ts/index.js +2 -1
  15. package/dist_ts/interfaces/exact-issuance.d.ts +79 -0
  16. package/dist_ts/interfaces/exact-issuance.js +2 -0
  17. package/dist_ts/plugins.d.ts +5 -1
  18. package/dist_ts/plugins.js +6 -2
  19. package/dist_ts/smartacme.classes.smartacme.d.ts +22 -0
  20. package/dist_ts/smartacme.classes.smartacme.js +141 -16
  21. package/package.json +11 -15
  22. package/readme.md +90 -1
  23. package/ts/00_commitinfo_data.ts +1 -1
  24. package/ts/acme/acme.classes.client.ts +6 -1
  25. package/ts/acme/acme.classes.http-client.ts +2 -1
  26. package/ts/acme/acme.classes.order.ts +12 -1
  27. package/ts/classes.exact-certificate-issuer.ts +408 -0
  28. package/ts/handlers/IChallengeHandler.ts +10 -4
  29. package/ts/handlers/index.ts +2 -2
  30. package/ts/index.ts +2 -0
  31. package/ts/interfaces/exact-issuance.ts +104 -0
  32. package/ts/plugins.ts +5 -1
  33. package/ts/smartacme.classes.smartacme.ts +151 -15
  34. package/readme.hints.md +0 -81
  35. package/readme.plan.md +0 -3
@@ -3,6 +3,12 @@ import type { ICertManager } from './interfaces/certmanager.js';
3
3
  import { SmartacmeCertMatcher } from './smartacme.classes.certmatcher.js';
4
4
  import { commitinfo } from './00_commitinfo_data.js';
5
5
  import { SmartacmeCert } from './smartacme.classes.cert.js';
6
+ import { ExactCertificateIssuer } from './classes.exact-certificate-issuer.js';
7
+ import type {
8
+ IExactCertificateIdentity,
9
+ IExactCertificateRequest, IExactIssuanceRecoveryRequest, IExactIssuanceStore,
10
+ IExactIssuanceInfo, TExactCertificateResult,
11
+ } from './interfaces/exact-issuance.js';
6
12
 
7
13
  // ── Types & constants for certificate issuance task ──────────────────────────
8
14
 
@@ -12,6 +18,7 @@ interface ICertIssuanceInput {
12
18
  isWildcardRequest: boolean;
13
19
  includeWildcard: boolean;
14
20
  forceRenew: boolean;
21
+ exact?: { request: IExactCertificateRequest; recovery?: IExactIssuanceRecoveryRequest };
15
22
  }
16
23
 
17
24
  const CERT_ISSUANCE_STEPS = [
@@ -33,6 +40,14 @@ export interface ISmartAcmeOptions {
33
40
  certManager: ICertManager;
34
41
  // Removed legacy setChallenge/removeChallenge in favor of `challengeHandlers`
35
42
  environment: 'production' | 'integration';
43
+ /** Optional explicit ACME directory, for private CAs and isolated integration. */
44
+ directoryUrl?: string;
45
+ /** Enables isolated explicit-identifier issuance with durable recovery. */
46
+ exactIssuanceStore?: IExactIssuanceStore;
47
+ /** Bounded number of admitted exact-identity operations (default 64). */
48
+ maxPendingExactRequests?: number;
49
+ /** DNS propagation grace period for explicit issuance; defaults to 60 seconds. */
50
+ exactDnsPropagationDelayMs?: number;
36
51
  /**
37
52
  * Optional retry/backoff configuration for transient failures
38
53
  */
@@ -92,6 +107,10 @@ export class SmartAcme {
92
107
 
93
108
  // the account private key
94
109
  private privateKey!: string;
110
+ private exactIssuer?: ExactCertificateIssuer;
111
+ private exactRequests = new Map<string, Promise<TExactCertificateResult>>();
112
+ private lifecycleAbort = new AbortController();
113
+ private started = false;
95
114
 
96
115
 
97
116
  // certificate manager for persistence (implements ICertManager)
@@ -104,7 +123,7 @@ export class SmartAcme {
104
123
  // retry/backoff configuration (resolved with defaults)
105
124
  private retryOptions: { retries: number; factor: number; minTimeoutMs: number; maxTimeoutMs: number };
106
125
  // track pending DNS challenges for graceful shutdown
107
- private pendingChallenges: plugins.tsclass.network.IDnsChallenge[] = [];
126
+ private pendingChallenges: { input: any; context: plugins.handlers.IChallengeContext }[] = [];
108
127
  // priority order of challenge types
109
128
  private challengePriority: string[];
110
129
  // TaskManager for coordinating concurrent certificate requests
@@ -124,6 +143,12 @@ export class SmartAcme {
124
143
 
125
144
  constructor(optionsArg: ISmartAcmeOptions) {
126
145
  this.options = optionsArg;
146
+ if (!Number.isSafeInteger(optionsArg.maxPendingExactRequests ?? 64)
147
+ || (optionsArg.maxPendingExactRequests ?? 64) < 1
148
+ || !Number.isSafeInteger(optionsArg.exactDnsPropagationDelayMs ?? 60000)
149
+ || (optionsArg.exactDnsPropagationDelayMs ?? 60000) < 0) {
150
+ throw new Error('Invalid explicit issuance admission or propagation limits');
151
+ }
127
152
  this.logger = plugins.smartlog.Smartlog.createForCommitinfo(commitinfo);
128
153
  // enable console output for structured logging
129
154
  this.logger.enableConsole();
@@ -156,9 +181,11 @@ export class SmartAcme {
156
181
  maxConcurrent: 1,
157
182
  resultSharingMode: 'share-latest',
158
183
  constraintKeyForExecution: (_task, input?: ICertIssuanceInput) => {
184
+ if (input?.exact) return null;
159
185
  return input?.certDomainName ?? null;
160
186
  },
161
187
  shouldExecute: async (_task, input?: ICertIssuanceInput) => {
188
+ if (input?.exact) return true;
162
189
  if (!input?.certDomainName || !this.certmanager) return true;
163
190
  if (input.forceRenew) return true;
164
191
  // Safety net: if a valid cert is already cached, skip re-issuance
@@ -188,6 +215,12 @@ export class SmartAcme {
188
215
  });
189
216
 
190
217
  this.taskManager.addConstraintGroup(certDomainMutex);
218
+ // One fixed key reserves a bounded share of global capacity for testing.
219
+ // Exact results contain private keys and must never enter result-sharing caches.
220
+ this.taskManager.addConstraintGroup(new plugins.taskbuffer.TaskConstraintGroup({
221
+ name: 'exact-issuance-concurrency', maxConcurrent: 2,
222
+ constraintKeyForExecution: (_task, input?: ICertIssuanceInput) => input?.exact ? 'exact' : null,
223
+ }));
191
224
  this.taskManager.addConstraintGroup(acmeGlobalConcurrency);
192
225
  this.taskManager.addConstraintGroup(acmeAccountRateLimit);
193
226
 
@@ -196,6 +229,12 @@ export class SmartAcme {
196
229
  name: 'cert-issuance',
197
230
  steps: CERT_ISSUANCE_STEPS,
198
231
  taskFunction: async (input: ICertIssuanceInput) => {
232
+ if (input.exact) {
233
+ const issuer = this.requireExactIssuer();
234
+ return input.exact.recovery
235
+ ? issuer.recover(input.exact.recovery)
236
+ : issuer.run(input.exact.request);
237
+ }
199
238
  return this.performCertificateIssuance(input);
200
239
  },
201
240
  });
@@ -210,8 +249,31 @@ export class SmartAcme {
210
249
  * ```
211
250
  */
212
251
  public async start() {
252
+ if (this.started) throw new Error('SmartAcme is already started');
253
+ this.lifecycleAbort = new AbortController();
213
254
  this.privateKey =
214
255
  this.options.accountPrivateKey || plugins.acme.AcmeCrypto.createRsaPrivateKey();
256
+ const directoryUrl = this.options.directoryUrl ?? (this.options.environment === 'production'
257
+ ? plugins.acme.ACME_DIRECTORY_URLS.letsencrypt.production
258
+ : plugins.acme.ACME_DIRECTORY_URLS.letsencrypt.staging);
259
+ const directory = new URL(directoryUrl);
260
+ if (directory.username || directory.password || directory.hash
261
+ || (directory.protocol !== 'https:' && !(directory.protocol === 'http:'
262
+ && ['localhost', '127.0.0.1', '[::1]'].includes(directory.hostname)))) {
263
+ throw new Error('An HTTPS ACME directory or an explicit loopback test directory is required');
264
+ }
265
+ if (this.options.exactIssuanceStore && !this.options.accountPrivateKey) {
266
+ const accountId = plugins.crypto.createHash('sha256')
267
+ .update(JSON.stringify([directoryUrl, this.options.accountEmail.trim().toLowerCase()])).digest('hex');
268
+ const store = this.options.exactIssuanceStore;
269
+ let storedKey = await store.getAccountKey(accountId);
270
+ if (!storedKey) {
271
+ await store.createAccountKey(accountId, this.privateKey);
272
+ storedKey = await store.getAccountKey(accountId);
273
+ }
274
+ if (!storedKey) throw new Error('ACME account key could not be retained durably');
275
+ this.privateKey = storedKey;
276
+ }
215
277
 
216
278
  // Initialize certificate manager
217
279
  if (!this.options.certManager) {
@@ -225,14 +287,9 @@ export class SmartAcme {
225
287
 
226
288
  // ACME Client
227
289
  this.client = new plugins.acme.AcmeClient({
228
- directoryUrl: (() => {
229
- if (this.options.environment === 'production') {
230
- return plugins.acme.ACME_DIRECTORY_URLS.letsencrypt.production;
231
- } else {
232
- return plugins.acme.ACME_DIRECTORY_URLS.letsencrypt.staging;
233
- }
234
- })(),
290
+ directoryUrl,
235
291
  accountKeyPem: this.privateKey,
292
+ signal: this.lifecycleAbort.signal,
236
293
  logger: (level, message, data) => {
237
294
  this.logger.log(level as any, message, data);
238
295
  },
@@ -246,6 +303,15 @@ export class SmartAcme {
246
303
 
247
304
  // Start the task manager
248
305
  await this.taskManager.start();
306
+ if (this.options.exactIssuanceStore) {
307
+ this.exactIssuer = new ExactCertificateIssuer({
308
+ store: this.options.exactIssuanceStore, client: this.client, directoryUrl,
309
+ accountThumbprint: plugins.acme.AcmeCrypto.getJwkThumbprint(plugins.acme.AcmeCrypto.getJwk(this.privateKey)),
310
+ dns: this.smartdns, handlers: this.challengeHandlers, signal: this.lifecycleAbort.signal,
311
+ dnsPropagationDelayMs: this.options.exactDnsPropagationDelayMs ?? 60000,
312
+ });
313
+ }
314
+ this.started = true;
249
315
 
250
316
  // Setup graceful shutdown handlers (store references for removal in stop())
251
317
  this.boundSigintHandler = () => this.handleSignal('SIGINT');
@@ -258,6 +324,8 @@ export class SmartAcme {
258
324
  * Stops the SmartAcme instance and closes certificate store connections.
259
325
  */
260
326
  public async stop() {
327
+ this.started = false;
328
+ this.lifecycleAbort.abort();
261
329
  // Remove signal handlers so the process can exit cleanly
262
330
  if (this.boundSigintHandler) {
263
331
  process.removeListener('SIGINT', this.boundSigintHandler);
@@ -267,8 +335,13 @@ export class SmartAcme {
267
335
  process.removeListener('SIGTERM', this.boundSigtermHandler);
268
336
  this.boundSigtermHandler = null;
269
337
  }
270
- // Stop the task manager
338
+ // Observe active requests before rejecting queued work. The task manager
339
+ // must close its queue before we drain callers waiting on that queue.
340
+ const pendingExactRequests = Promise.allSettled([...this.exactRequests.values()]);
271
341
  await this.taskManager.stop();
342
+ await pendingExactRequests;
343
+ this.exactRequests.clear();
344
+ this.exactIssuer = undefined;
272
345
  // Destroy ACME HTTP transport (closes keep-alive sockets)
273
346
  if (this.client) {
274
347
  this.client.destroy();
@@ -281,6 +354,64 @@ export class SmartAcme {
281
354
  await (this.certmanager as any).close();
282
355
  }
283
356
  }
357
+
358
+ /** Bind durable work to the current account and issuer without creating an order. */
359
+ public getCertificateIdentity(request: IExactCertificateRequest): IExactCertificateIdentity {
360
+ return this.requireExactIssuer().identity(request);
361
+ }
362
+
363
+ /** Ensure a certificate whose SANs exactly match the explicit identifiers. */
364
+ public async getCertificateForIdentifiers(request: IExactCertificateRequest): Promise<TExactCertificateResult> {
365
+ return await this.runExact(request);
366
+ }
367
+
368
+ public async getCertificateIssuanceStatus(request: IExactCertificateRequest): Promise<IExactIssuanceInfo | null> {
369
+ return await this.requireExactIssuer().status(request);
370
+ }
371
+
372
+ /** Administrative recovery; embedding services must authorize the caller. */
373
+ public async recoverCertificateIssuance(request: IExactIssuanceRecoveryRequest): Promise<TExactCertificateResult> {
374
+ return await this.runExact(request, request);
375
+ }
376
+
377
+ private requireExactIssuer(): ExactCertificateIssuer {
378
+ if (!this.started || !this.exactIssuer) throw new Error('SmartAcme explicit issuance is not running or configured');
379
+ return this.exactIssuer;
380
+ }
381
+
382
+ private async runExact(request: IExactCertificateRequest, recovery?: IExactIssuanceRecoveryRequest): Promise<TExactCertificateResult> {
383
+ const issuer = this.requireExactIssuer();
384
+ const identity = issuer.identity(request);
385
+ const recoverySnapshot = recovery ? {
386
+ namespace: identity.namespace, identifiers: [...identity.identifiers],
387
+ issuanceId: recovery.issuanceId, expectedRevision: recovery.expectedRevision, action: recovery.action,
388
+ } : undefined;
389
+ const existing = this.exactRequests.get(identity.certificateKey);
390
+ if (existing) {
391
+ if (recovery) throw new Error('Issuance is running; recover after its current operation completes');
392
+ return await existing;
393
+ }
394
+ if (this.exactRequests.size >= (this.options.maxPendingExactRequests ?? 64)) {
395
+ throw new Error('Explicit issuance admission limit reached');
396
+ }
397
+ const operation = (async (): Promise<TExactCertificateResult> => {
398
+ if (!recovery) {
399
+ const cached = await issuer.cached(identity);
400
+ if (cached) return cached;
401
+ }
402
+ this.lifecycleAbort.signal.throwIfAborted();
403
+ const result = await this.taskManager.triggerTaskConstrained(this.certIssuanceTask, {
404
+ certDomainName: identity.certificateKey, domainArg: identity.identifiers[0],
405
+ isWildcardRequest: false, includeWildcard: false, forceRenew: false,
406
+ exact: { request: { namespace: identity.namespace, identifiers: identity.identifiers }, recovery: recoverySnapshot },
407
+ } satisfies ICertIssuanceInput) as TExactCertificateResult | undefined;
408
+ if (!result) throw new Error('Explicit issuance completed without a result');
409
+ return result;
410
+ })();
411
+ this.exactRequests.set(identity.certificateKey, operation);
412
+ try { return await operation; }
413
+ finally { this.exactRequests.delete(identity.certificateKey); }
414
+ }
284
415
  /** Retry helper with exponential backoff and AcmeError awareness */
285
416
  private async retry<T>(operation: () => Promise<T>, operationName: string = 'operation'): Promise<T> {
286
417
  let attempt = 0;
@@ -315,12 +446,12 @@ export class SmartAcme {
315
446
  }
316
447
  /** Clean up pending challenges and shut down */
317
448
  private async handleShutdown(): Promise<void> {
318
- for (const input of [...this.pendingChallenges]) {
449
+ for (const { input, context } of [...this.pendingChallenges]) {
319
450
  const type: string = (input as any).type;
320
451
  const handler = this.challengeHandlers.find((h) => h.getSupportedTypes().includes(type));
321
452
  if (handler) {
322
453
  try {
323
- await handler.cleanup(input);
454
+ await handler.cleanup(input, { ...context });
324
455
  await this.logger.log('info', `Removed pending ${type} challenge during shutdown`, input);
325
456
  } catch (err) {
326
457
  await this.logger.log('error', `Failed to remove pending ${type} challenge during shutdown`, err);
@@ -457,6 +588,11 @@ export class SmartAcme {
457
588
  identifiers,
458
589
  }), 'createOrder');
459
590
 
591
+ const challengeContext: plugins.handlers.IChallengeContext = {
592
+ operationId: plugins.crypto.createHash('sha256')
593
+ .update(JSON.stringify(['smartacme-legacy-challenge-v1', order.url])).digest('hex'),
594
+ };
595
+
460
596
  // ── Step: authorize ───────────────────────────────────────────────────
461
597
  this.certIssuanceTask.notifyStep('authorize');
462
598
 
@@ -498,9 +634,9 @@ export class SmartAcme {
498
634
  } else {
499
635
  challengeInput = { type, keyAuthorization: keyAuth, ...selectedChallengeArg };
500
636
  }
501
- this.pendingChallenges.push(challengeInput);
637
+ this.pendingChallenges.push({ input: challengeInput, context: challengeContext });
502
638
  try {
503
- await this.retry(() => handler.prepare(challengeInput), `${type}.prepare`);
639
+ await this.retry(() => handler.prepare(challengeInput, { ...challengeContext }), `${type}.prepare`);
504
640
  if (type === 'dns-01') {
505
641
  const dnsInput = challengeInput as { hostName: string; challenge: string };
506
642
  await this.retry(
@@ -528,11 +664,11 @@ export class SmartAcme {
528
664
  }
529
665
  } finally {
530
666
  try {
531
- await this.retry(() => handler.cleanup(challengeInput), `${type}.cleanup`);
667
+ await this.retry(() => handler.cleanup(challengeInput, { ...challengeContext }), `${type}.cleanup`);
532
668
  } catch (err) {
533
669
  await this.logger.log('error', `Error during ${type}.cleanup`, err);
534
670
  } finally {
535
- this.pendingChallenges = this.pendingChallenges.filter((c) => c !== challengeInput);
671
+ this.pendingChallenges = this.pendingChallenges.filter((c) => c.input !== challengeInput);
536
672
  }
537
673
  }
538
674
  }
package/readme.hints.md DELETED
@@ -1,81 +0,0 @@
1
- - this repo is dependent on letsencrypt and its limits
2
- - to simpify the outside API, smartacme is stateful, meaning it works with a mongodb and a collection called 'SmartacmeCert'.
3
-
4
- ## Certificate Request Behavior
5
-
6
- As of v7.4.0, SmartAcme no longer automatically requests wildcard certificates for all domain requests. This change was made to fix issues with HTTP-01 only configurations which cannot validate wildcard domains.
7
-
8
- - By default, `getCertificateForDomain('example.com')` only requests a certificate for `example.com`
9
- - To request both regular and wildcard certificates, use `getCertificateForDomain('example.com', { includeWildcard: true })`
10
- - Wildcard certificates require a DNS-01 challenge handler to be configured
11
- - Direct wildcard requests like `getCertificateForDomain('*.example.com')` only request the wildcard certificate
12
-
13
- This change ensures HTTP-01 only configurations work properly while still allowing wildcard certificates when needed and supported.
14
-
15
- ## ACME Protocol Implementation
16
-
17
- As of v8.1.0, the `acme-client` npm package has been replaced with a custom OOP implementation under `ts/acme/`. This uses `node:crypto` for all cryptographic operations and `@peculiar/x509` solely for CSR generation. The implementation follows RFC 8555.
18
-
19
- Key files:
20
- - `ts/acme/acme.classes.client.ts` — Top-level facade (`AcmeClient`), accepts optional `logger` callback
21
- - `ts/acme/acme.classes.crypto.ts` — Key gen, JWK, JWS signing, CSR (`AcmeCrypto`)
22
- - `ts/acme/acme.classes.http-client.ts` — JWS-signed HTTP transport with nonce management and logging
23
- - `ts/acme/acme.classes.error.ts` — Structured `AcmeError` with type URN, subproblems, Retry-After, `isRetryable`/`isRateLimited`
24
- - `ts/acme/acme.classes.account.ts` — Account registration
25
- - `ts/acme/acme.classes.order.ts` — Order lifecycle + polling
26
- - `ts/acme/acme.classes.challenge.ts` — Key authorization + challenge completion
27
- - `ts/acme/acme.classes.directory.ts` — CA directory URL constants (`ACME_DIRECTORY_URLS`)
28
-
29
- Usage in `ts/plugins.ts`: `import * as acme from './acme/index.js'` (replaces `acme-client`)
30
-
31
- ## Concurrency & Rate Limiting (taskbuffer integration)
32
-
33
- As of v9.1.0, `@push.rocks/lik.InterestMap` was replaced with `@push.rocks/taskbuffer.TaskManager` for coordinating concurrent certificate requests. This provides:
34
-
35
- - **Per-domain mutex** (`cert-domain-mutex`): Only one ACME issuance per TLD at a time, with `resultSharingMode: 'share-latest'` so queued callers get the same result without re-issuing.
36
- - **Global concurrency cap** (`acme-global-concurrency`): Limits total parallel ACME operations (default 5, configurable via `maxConcurrentIssuances`).
37
- - **Account-level rate limiting** (`acme-account-rate-limit`): Sliding-window rate limit (default 250 orders per 3 hours, configurable via `maxOrdersPerWindow`/`orderWindowMs`) to stay under Let's Encrypt limits.
38
- - **Step-based progress**: The cert issuance task uses `notifyStep()` for prepare/authorize/finalize/store phases, observable via `smartAcme.certIssuanceEvents`.
39
-
40
- Key implementation details:
41
- - A single reusable `Task` named `cert-issuance` handles all domains via `triggerTaskConstrained()` with different inputs.
42
- - The `shouldExecute` callback on the domain mutex checks the certmanager cache as a safety net.
43
- - `TaskManager.start()` is called in `SmartAcme.start()` and `TaskManager.stop()` in `SmartAcme.stop()`.
44
- - The "no cronjobs specified" log messages during tests come from taskbuffer's internal CronManager polling — harmless noise when no cron tasks are scheduled.
45
-
46
- ## ACME Directory Server (ts_server/)
47
-
48
- As of v9.2.0, a built-in ACME Directory Server lives under `ts_server/`. This is a full RFC 8555-compliant CA server that allows running your own Certificate Authority.
49
-
50
- Key files:
51
- - `ts_server/server.classes.acmeserver.ts` — Top-level `AcmeServer` facade (start/stop/config)
52
- - `ts_server/server.classes.ca.ts` — Self-signed root CA generation + certificate signing via `@peculiar/x509`
53
- - `ts_server/server.classes.jws.verifier.ts` — JWS signature verification (inverse of `AcmeCrypto.createJws`)
54
- - `ts_server/server.classes.router.ts` — Minimal HTTP router with `:param` support using raw `node:http`
55
- - `ts_server/server.classes.nonce.ts` — Single-use replay nonce management
56
- - `ts_server/server.classes.challenge.verifier.ts` — HTTP-01/DNS-01 verification (with bypass mode)
57
- - `ts_server/server.classes.account.store.ts` — In-memory account storage
58
- - `ts_server/server.classes.order.store.ts` — In-memory order/authz/challenge/cert storage
59
- - `ts_server/server.handlers.*.ts` — Route handlers for each ACME endpoint
60
-
61
- Design decisions:
62
- - Uses raw `node:http` (no framework dependency — `@api.global/typedserver` was explicitly removed in v8.1.0)
63
- - Zero new dependencies: uses `node:crypto`, `@peculiar/x509`, and existing project deps
64
- - Reuses `AcmeCrypto` for JWK thumbprint/base64url, ACME interfaces for response types, `AcmeError` patterns
65
- - `AcmeCrypto.getAlg()` was made public (was private) for use by the JWS verifier
66
- - Storage interfaces (`IServerAccountStore`, `IServerOrderStore`) are pluggable, with in-memory defaults
67
- - `challengeVerification: false` option auto-approves challenges for testing
68
- - `tsbuild tsfolders` automatically compiles `ts_server/` to `dist_ts_server/`
69
-
70
- ## Dependency Notes
71
-
72
- - `acme-client` was replaced with custom implementation in `ts/acme/` + `@peculiar/x509` for CSR generation
73
- - `@push.rocks/smartfile`, `@api.global/typedserver`, `@push.rocks/smartrequest`, `@push.rocks/smartpromise` were removed as unused dependencies in v8.1.0
74
- - The `@apiclient.xyz/cloudflare` `convenience` namespace is deprecated but still functional. The `Dns01Handler` accepts an `IConvenientDnsProvider` interface which remains stable.
75
- - Test imports use `@git.zone/tstest/tapbundle` (not `@push.rocks/tapbundle`)
76
- - Build uses `tsbuild tsfolders` (v4.4.0+) — auto-discovers and compiles `ts/` and `ts_server/` directories
77
- - `@peculiar/x509` v2.0.0 removed `reflect-metadata` from its dependencies. Since `tsyringe` (used internally by `@peculiar/x509`) requires the Reflect polyfill, `reflect-metadata` is now a direct dependency and imported in `ts/acme/acme.classes.crypto.ts` and `ts_server/server.classes.ca.ts`.
78
- - `@push.rocks/taskbuffer` upgraded from v6 to v8 (required by smartdata 7.1.3). API surface is backward-compatible.
79
- - TypeScript 6 (via tsbuild 4.4.0) requires `"types": ["node"]` in tsconfig.json for `ts_server/` compilation to resolve `@types/node`.
80
- - TypeScript 6 deprecated `baseUrl` in tsconfig — removed it since `paths` was empty.
81
- - Config file renamed from `npmextra.json` to `.smartconfig.json` (ecosystem convention change).
package/readme.plan.md DELETED
@@ -1,3 +0,0 @@
1
- ## Plan
2
-
3
- Move the