@1consent/cli 0.1.0-beta.1 → 0.1.0-beta.3

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/dist/cli.js CHANGED
@@ -20591,8 +20591,6 @@ var LOGO_ALIGNMENTS = ["left", "center"];
20591
20591
  var BUTTON_ORDERS = ["accept-first", "reject-first"];
20592
20592
  var BUTTON_ALIGNMENTS = ["left", "right", "center", "full"];
20593
20593
  var BUTTON_EMPHASIS = ["equal", "emphasize-accept"];
20594
- var NOTICE_POSITION_SET = new Set(NOTICE_POSITIONS);
20595
- var DETAILS_POSITION_SET = new Set(DETAILS_POSITIONS);
20596
20594
  var LOGO_ALIGNMENT_SET = new Set(LOGO_ALIGNMENTS);
20597
20595
  var BUTTON_ORDER_SET = new Set(BUTTON_ORDERS);
20598
20596
  var BUTTON_ALIGNMENT_SET = new Set(BUTTON_ALIGNMENTS);
@@ -21174,7 +21172,7 @@ function isOptionalBoolean(value) {
21174
21172
  return value === void 0 || typeof value === "boolean";
21175
21173
  }
21176
21174
  function isTypegenService(value) {
21177
- return isRecord(value) && typeof value.id === "string" && value.id.length > 0 && isOptionalString(value.name) && isOptionalString(value.tag) && (value.categoryId === null || isOptionalString(value.categoryId)) && isOptionalBoolean(value.essential) && isOptionalBoolean(value.usSensitive);
21175
+ return isRecord(value) && typeof value.id === "string" && value.id.length > 0 && isOptionalString(value.catalogServiceKey) && isOptionalString(value.name) && isOptionalString(value.tag) && (value.categoryId === null || isOptionalString(value.categoryId)) && isOptionalBoolean(value.essential) && isOptionalBoolean(value.usSensitive);
21178
21176
  }
21179
21177
  function isTypegenCategory(value) {
21180
21178
  return isRecord(value) && typeof value.id === "string" && value.id.length > 0 && typeof value.name === "string" && isOptionalBoolean(value.essential);
@@ -21833,7 +21831,7 @@ function getWorkspaceRoot(cwd = process.cwd()) {
21833
21831
 
21834
21832
  // src/package-identity.ts
21835
21833
  var CLI_PACKAGE_NAME = "@1consent/cli";
21836
- var CLI_VERSION = "0.1.0-beta.1";
21834
+ var CLI_VERSION = "0.1.0-beta.3";
21837
21835
 
21838
21836
  // src/commands/init.ts
21839
21837
  var ENDPOINTS = {
@@ -23288,7 +23286,7 @@ function findDuplicateKeys(input) {
23288
23286
  }
23289
23287
  return [...idsByKey.entries()].filter(([, ids]) => ids.size > 1).map(([key]) => key).sort();
23290
23288
  }
23291
- function generateTypesModule(input) {
23289
+ function generateTypesModule(input, regenerationCommand) {
23292
23290
  const frameworks = [...input.frameworks].sort((a, b) => a.id.localeCompare(b.id));
23293
23291
  const services = frameworks.flatMap((framework) => framework.services);
23294
23292
  const categories = frameworks.flatMap((framework) => framework.categories);
@@ -23301,7 +23299,7 @@ function generateTypesModule(input) {
23301
23299
  const serviceKeys = servicePairs.map(([key]) => key);
23302
23300
  const categoryKeys = categoryPairs.map(([key]) => key);
23303
23301
  return `// AUTO-GENERATED by \`1c typegen\` \u2014 do not edit.
23304
- // Regenerate: npx ${CLI_PACKAGE_NAME}@${CLI_VERSION} typegen --app ${input.appId}
23302
+ // Regenerate: ${regenerationCommand}
23305
23303
  // App: ${input.appId}
23306
23304
  // Frameworks: ${frameworks.map((framework) => framework.id).join(", ")}
23307
23305
 
@@ -23332,6 +23330,26 @@ declare module "@1consent/react" {
23332
23330
 
23333
23331
  // src/commands/typegen.ts
23334
23332
  var ENVIRONMENTS = /* @__PURE__ */ new Set(["local", "develop", "production"]);
23333
+ function quoteRegenerationArgument(value) {
23334
+ if (/[\p{Cc}\p{Zl}\p{Zp}]/u.test(value)) {
23335
+ throw new Error(
23336
+ "[typegen] Regeneration arguments must not contain control characters or line breaks."
23337
+ );
23338
+ }
23339
+ return /^[A-Za-z0-9_./:@%+=,-]+$/.test(value) ? value : `'${value.replaceAll("'", `'"'"'`)}'`;
23340
+ }
23341
+ function buildTypegenRegenerationCommand(options) {
23342
+ const args = [
23343
+ "npx",
23344
+ `${CLI_PACKAGE_NAME}@${CLI_VERSION}`,
23345
+ "typegen",
23346
+ `--app=${quoteRegenerationArgument(options.app)}`,
23347
+ `--env=${quoteRegenerationArgument(options.env)}`,
23348
+ `--out=${quoteRegenerationArgument(options.out)}`
23349
+ ];
23350
+ if (options.url !== void 0) args.push(`--url=${quoteRegenerationArgument(options.url)}`);
23351
+ return args.join(" ");
23352
+ }
23335
23353
  function defaultOutFile() {
23336
23354
  const srcDir = path9.join(process.cwd(), "src");
23337
23355
  const hasSrc = import_fs_extra7.default.existsSync(srcDir) && import_fs_extra7.default.statSync(srcDir).isDirectory();
@@ -23367,8 +23385,15 @@ async function typegenCommand(options) {
23367
23385
  process.exit(1);
23368
23386
  }
23369
23387
  const baseUrl = options.url ?? getPublicPackageServiceUrls(env2).cmpApi;
23370
- const outFile = path9.resolve(options.out ?? defaultOutFile());
23388
+ const output = options.out ?? defaultOutFile();
23389
+ const outFile = path9.resolve(output);
23371
23390
  try {
23391
+ const regenerationCommand = buildTypegenRegenerationCommand({
23392
+ app: appId,
23393
+ env: env2,
23394
+ out: output,
23395
+ url: options.url
23396
+ });
23372
23397
  const input = await fetchAppConfig(appId, baseUrl);
23373
23398
  for (const duplicate of findDuplicateKeys(input)) {
23374
23399
  console.log(
@@ -23377,7 +23402,7 @@ async function typegenCommand(options) {
23377
23402
  )
23378
23403
  );
23379
23404
  }
23380
- const generated = generateTypesModule(input);
23405
+ const generated = generateTypesModule(input, regenerationCommand);
23381
23406
  if (options.check) {
23382
23407
  const existing = await import_fs_extra7.default.pathExists(outFile) ? await import_fs_extra7.default.readFile(outFile, "utf8") : null;
23383
23408
  if (existing === generated) {
@@ -23386,7 +23411,7 @@ async function typegenCommand(options) {
23386
23411
  }
23387
23412
  console.log(
23388
23413
  source_default.red(
23389
- `\u274C ${path9.basename(outFile)} is ${existing === null ? "missing" : "stale"}. Run '1c typegen --app ${appId}' to regenerate.`
23414
+ `\u274C ${path9.basename(outFile)} is ${existing === null ? "missing" : "stale"}. Run ${regenerationCommand} to regenerate.`
23390
23415
  )
23391
23416
  );
23392
23417
  process.exit(1);
@@ -87,18 +87,24 @@ var ANALYTICS_MAX_TIME_TO_CONSENT_MS = 24 * 60 * 60 * 1e3;
87
87
  function isPlainRecord(value) {
88
88
  return typeof value === "object" && value !== null && !Array.isArray(value);
89
89
  }
90
+ function isPublicId(value) {
91
+ return typeof value === "string" && /^[A-Za-z0-9_-]{1,64}$/.test(value) && !/^[\da-f]{8}(-[\da-f]{4}){3}-[\da-f]{12}$/i.test(value);
92
+ }
93
+ function isPublicRuntimeIdentity(value) {
94
+ return isPlainRecord(value) && [value.appId, value.frameworkId].every(isPublicId);
95
+ }
90
96
  function isOneConsentConfig(config2) {
91
97
  if (!isPlainRecord(config2))
92
98
  return false;
93
99
  const c = config2;
94
- const hasBaseFields = typeof c.appId === "string" && typeof c.version === "number" && typeof c.machine === "string" && isPlainRecord(c.languages) && typeof c.languages.primary === "string" && Array.isArray(c.languages.supported) && c.languages.supported.length > 0 && isCanonicalSupportedLocale(c.languages.primary) && c.languages.supported.every(isCanonicalSupportedLocale) && new Set(c.languages.supported).size === c.languages.supported.length && c.languages.supported.includes(c.languages.primary) && isPlainRecord(c.cmp) && typeof c.cmp.main === "string" && // uiData is REQUIRED (1CO-180): a pre-1CO-180 snapshot (cmp.i18n, no
100
+ const hasBaseFields = typeof c.appId === "string" && (c.publicIdentity === void 0 || isPublicRuntimeIdentity(c.publicIdentity)) && typeof c.version === "number" && isPlainRecord(c.languages) && Array.isArray(c.languages.supported) && isCanonicalSupportedLocale(c.languages.primary) && c.languages.supported.every(isCanonicalSupportedLocale) && new Set(c.languages.supported).size === c.languages.supported.length && c.languages.supported.includes(c.languages.primary) && isPlainRecord(c.cmp) && typeof c.cmp.main === "string" && // uiData is REQUIRED (1CO-180): a pre-1CO-180 snapshot (cmp.i18n, no
95
101
  // uiData) would mount blank-label banners on the pinned SDK — it must
96
102
  // fail this gate so the worker degrades to the CMP-API fallback and
97
103
  // self-heal regenerates the snapshot on first hit.
98
- typeof c.cmp.uiData === "string" && typeof c.cmp.serviceDetails === "string" && Array.isArray(c.categories) && Array.isArray(c.services) && Array.isArray(c.scripts) && typeof c.appearance === "object" && c.appearance !== null && typeof c.framework === "object" && c.framework !== null;
104
+ typeof c.cmp.uiData === "string" && typeof c.cmp.serviceDetails === "string" && Array.isArray(c.categories) && Array.isArray(c.services) && Array.isArray(c.scripts) && isPlainRecord(c.appearance) && isPlainRecord(c.framework);
99
105
  if (!hasBaseFields)
100
106
  return false;
101
- if (!["P", "S", "D"].includes(c.machine))
107
+ if (c.machine !== "P" && c.machine !== "S" && c.machine !== "D")
102
108
  return false;
103
109
  return true;
104
110
  }
@@ -14634,8 +14640,6 @@ var LOGO_ALIGNMENTS = ["left", "center"];
14634
14640
  var BUTTON_ORDERS = ["accept-first", "reject-first"];
14635
14641
  var BUTTON_ALIGNMENTS = ["left", "right", "center", "full"];
14636
14642
  var BUTTON_EMPHASIS = ["equal", "emphasize-accept"];
14637
- var NOTICE_POSITION_SET = new Set(NOTICE_POSITIONS);
14638
- var DETAILS_POSITION_SET = new Set(DETAILS_POSITIONS);
14639
14643
  var LOGO_ALIGNMENT_SET = new Set(LOGO_ALIGNMENTS);
14640
14644
  var BUTTON_ORDER_SET = new Set(BUTTON_ORDERS);
14641
14645
  var BUTTON_ALIGNMENT_SET = new Set(BUTTON_ALIGNMENTS);
@@ -18720,12 +18724,117 @@ ${lines.join("\n")}
18720
18724
  }
18721
18725
 
18722
18726
  // src/frame-check.ts
18727
+ import { request as httpRequest2 } from "node:http";
18728
+ import { request as httpsRequest2 } from "node:https";
18729
+
18730
+ // ../network-safety/dist/index.js
18731
+ import { promises as dns } from "node:dns";
18732
+ import { BlockList, isIP as isIP2 } from "node:net";
18733
+
18734
+ // ../network-safety/dist/public-text.js
18723
18735
  import { request as httpRequest } from "node:http";
18724
18736
  import { request as httpsRequest } from "node:https";
18737
+ import { isIP } from "node:net";
18738
+ var ERROR_MESSAGES = {
18739
+ timeout: "Public document request exceeded its time limit",
18740
+ response_too_large: "Public document response exceeds its byte limit",
18741
+ redirect_limit: "Public document exceeded its redirect limit",
18742
+ redirect_missing: "Public document redirect has no destination",
18743
+ http_status: "Public document returned an unsuccessful HTTP status",
18744
+ unsupported_encoding: "Public document returned an unsupported content encoding",
18745
+ transport_failure: "Public document transport failed"
18746
+ };
18747
+ var PublicTextError = class extends Error {
18748
+ code;
18749
+ constructor(code) {
18750
+ super(ERROR_MESSAGES[code]);
18751
+ this.code = code;
18752
+ this.name = "PublicTextError";
18753
+ }
18754
+ };
18755
+ function publicResponseHeader(headers, name) {
18756
+ const value = headers[name];
18757
+ return Array.isArray(value) ? value[0] ?? "" : value ?? "";
18758
+ }
18759
+ function readBody(response, maxBodyBytes) {
18760
+ return new Promise((resolve3, reject) => {
18761
+ const encoding = publicResponseHeader(response.headers, "content-encoding");
18762
+ if (encoding && encoding.toLowerCase() !== "identity") {
18763
+ response.destroy();
18764
+ reject(new PublicTextError("unsupported_encoding"));
18765
+ return;
18766
+ }
18767
+ const length = Number(response.headers["content-length"]);
18768
+ if (Number.isFinite(length) && length > maxBodyBytes) {
18769
+ response.destroy();
18770
+ reject(new PublicTextError("response_too_large"));
18771
+ return;
18772
+ }
18773
+ const chunks = [];
18774
+ let bytes = 0;
18775
+ response.on("data", (chunk) => {
18776
+ bytes += chunk.byteLength;
18777
+ if (bytes > maxBodyBytes) {
18778
+ response.destroy(new PublicTextError("response_too_large"));
18779
+ return;
18780
+ }
18781
+ chunks.push(chunk);
18782
+ });
18783
+ response.once("end", () => {
18784
+ try {
18785
+ resolve3(new TextDecoder("utf-8", { fatal: true }).decode(Buffer.concat(chunks)));
18786
+ } catch {
18787
+ reject(new PublicTextError("unsupported_encoding"));
18788
+ }
18789
+ });
18790
+ response.once("error", reject);
18791
+ response.once("aborted", () => reject(new PublicTextError("transport_failure")));
18792
+ });
18793
+ }
18794
+ function createPublicTextRequester(options = {}) {
18795
+ return (target, signal, maxBodyBytes) => {
18796
+ const address = target.addresses[0];
18797
+ if (!address)
18798
+ return Promise.reject(new PublicTextError("transport_failure"));
18799
+ const url2 = target.url;
18800
+ const originHost = url2.hostname.replace(/^\[|\]$/gu, "");
18801
+ const requestOptions = {
18802
+ family: address.family === 6 ? 6 : 4,
18803
+ hostname: address.address,
18804
+ port: Number(url2.port || (url2.protocol === "https:" ? "443" : "80")),
18805
+ path: `${url2.pathname}${url2.search}`,
18806
+ method: "GET",
18807
+ signal,
18808
+ headers: {
18809
+ Accept: options.accept ?? "text/html, text/plain;q=0.9",
18810
+ "Accept-Encoding": "identity",
18811
+ Host: url2.host,
18812
+ "User-Agent": options.userAgent ?? "1Consent-Public-Document/1.0"
18813
+ }
18814
+ };
18815
+ return new Promise((resolve3, reject) => {
18816
+ const onResponse = (response) => {
18817
+ const status = response.statusCode ?? 0;
18818
+ if (status < 200 || status >= 300) {
18819
+ const headers = response.headers;
18820
+ response.destroy();
18821
+ resolve3({ status, headers, body: "" });
18822
+ return;
18823
+ }
18824
+ readBody(response, maxBodyBytes).then((body) => resolve3({ status, headers: response.headers, body })).catch(reject);
18825
+ };
18826
+ const request = url2.protocol === "https:" ? httpsRequest({
18827
+ ...requestOptions,
18828
+ servername: isIP(originHost) ? "" : originHost,
18829
+ rejectUnauthorized: true
18830
+ }, onResponse) : httpRequest(requestOptions, onResponse);
18831
+ request.once("error", reject);
18832
+ request.end();
18833
+ });
18834
+ };
18835
+ }
18725
18836
 
18726
18837
  // ../network-safety/dist/index.js
18727
- import { promises as dns } from "node:dns";
18728
- import { BlockList, isIP } from "node:net";
18729
18838
  var PublicNetworkTargetError = class extends Error {
18730
18839
  code;
18731
18840
  constructor(code, message) {
@@ -18780,7 +18889,7 @@ var mappedIpv4 = (address) => {
18780
18889
  if (!normalized.startsWith("::ffff:"))
18781
18890
  return null;
18782
18891
  const suffix = normalized.slice("::ffff:".length);
18783
- if (isIP(suffix) === 4)
18892
+ if (isIP2(suffix) === 4)
18784
18893
  return suffix;
18785
18894
  const words = suffix.split(":");
18786
18895
  if (words.length !== 2)
@@ -18794,7 +18903,7 @@ var mappedIpv4 = (address) => {
18794
18903
  };
18795
18904
  function isPublicInternetAddress(address) {
18796
18905
  const normalized = stripIpv6Syntax(address);
18797
- const family = isIP(normalized);
18906
+ const family = isIP2(normalized);
18798
18907
  if (family === 4)
18799
18908
  return !blockedAddresses.check(normalized, "ipv4");
18800
18909
  if (family !== 6)
@@ -18878,7 +18987,7 @@ async function resolvePublicWebUrl(input, resolve3 = resolveAllAddresses, option
18878
18987
  throw new PublicNetworkTargetError("non-public-literal", "Target must not be a loopback or local hostname");
18879
18988
  }
18880
18989
  let addresses;
18881
- const literalFamily = isIP(hostname3);
18990
+ const literalFamily = isIP2(hostname3);
18882
18991
  if (literalFamily > 0) {
18883
18992
  addresses = [{ address: hostname3, family: literalFamily }];
18884
18993
  } else {
@@ -18893,7 +19002,7 @@ async function resolvePublicWebUrl(input, resolve3 = resolveAllAddresses, option
18893
19002
  }
18894
19003
  const normalizedAddresses = addresses.map(({ address }) => {
18895
19004
  const normalized = stripIpv6Syntax(address);
18896
- return { address: normalized, family: isIP(normalized) };
19005
+ return { address: normalized, family: isIP2(normalized) };
18897
19006
  });
18898
19007
  if (normalizedAddresses.some(({ address, family }) => family === 0 || !isPublicInternetAddress(address))) {
18899
19008
  throw literalFamily > 0 ? new PublicNetworkTargetError(
@@ -18937,7 +19046,7 @@ function requestResolvedHeaders(target, signal) {
18937
19046
  response.destroy();
18938
19047
  resolve3(result);
18939
19048
  };
18940
- const request = url2.protocol === "https:" ? httpsRequest({ ...options, servername: url2.hostname }, onResponse) : httpRequest(options, onResponse);
19049
+ const request = url2.protocol === "https:" ? httpsRequest2({ ...options, servername: url2.hostname }, onResponse) : httpRequest2(options, onResponse);
18941
19050
  request.once("error", reject);
18942
19051
  request.end();
18943
19052
  });
@@ -19142,70 +19251,9 @@ function readdirSafe(dir) {
19142
19251
  }
19143
19252
 
19144
19253
  // src/vendor-disclosure.ts
19145
- import { request as httpRequest2 } from "node:http";
19146
- import { request as httpsRequest2 } from "node:https";
19147
19254
  var DEFAULT_MAX_BODY_BYTES = 1e6;
19148
19255
  var DEFAULT_MAX_REDIRECTS2 = 3;
19149
19256
  var DEFAULT_TIMEOUT_MS2 = 5e3;
19150
- function responseHeader(headers, name) {
19151
- const value = headers[name];
19152
- return Array.isArray(value) ? value[0] ?? "" : value ?? "";
19153
- }
19154
- function readResponseBody(response, maxBodyBytes) {
19155
- return new Promise((resolve3, reject) => {
19156
- const contentLength = Number(response.headers["content-length"]);
19157
- if (Number.isFinite(contentLength) && contentLength > maxBodyBytes) {
19158
- response.destroy();
19159
- reject(new Error("Vendor disclosure response exceeds the size limit"));
19160
- return;
19161
- }
19162
- const chunks = [];
19163
- let receivedBytes = 0;
19164
- response.on("data", (chunk) => {
19165
- receivedBytes += chunk.byteLength;
19166
- if (receivedBytes > maxBodyBytes) {
19167
- response.destroy(new Error("Vendor disclosure response exceeds the size limit"));
19168
- return;
19169
- }
19170
- chunks.push(chunk);
19171
- });
19172
- response.once("end", () => resolve3(Buffer.concat(chunks).toString("utf8")));
19173
- response.once("error", reject);
19174
- });
19175
- }
19176
- function requestResolvedJson(target, signal, maxBodyBytes) {
19177
- const address = target.addresses[0];
19178
- if (!address) return Promise.reject(new Error("Target has no validated public address"));
19179
- const url2 = target.url;
19180
- const options = {
19181
- family: address.family === 6 ? 6 : 4,
19182
- headers: {
19183
- Accept: "application/json",
19184
- Host: url2.host,
19185
- "User-Agent": "1Consent-Design-Kit/1.0"
19186
- },
19187
- hostname: address.address,
19188
- method: "GET",
19189
- path: `${url2.pathname}${url2.search}`,
19190
- port: Number(url2.port || (url2.protocol === "https:" ? "443" : "80")),
19191
- signal
19192
- };
19193
- return new Promise((resolve3, reject) => {
19194
- const onResponse = (response) => {
19195
- const status = response.statusCode ?? 0;
19196
- if (status >= 300 && status < 400) {
19197
- const headers = response.headers;
19198
- response.destroy();
19199
- resolve3({ body: "", headers, status });
19200
- return;
19201
- }
19202
- readResponseBody(response, maxBodyBytes).then((body) => resolve3({ body, headers: response.headers, status })).catch(reject);
19203
- };
19204
- const request = url2.protocol === "https:" ? httpsRequest2({ ...options, servername: url2.hostname }, onResponse) : httpRequest2(options, onResponse);
19205
- request.once("error", reject);
19206
- request.end();
19207
- });
19208
- }
19209
19257
  async function requestWithTimeout2(target, request, timeoutMs, maxBodyBytes) {
19210
19258
  const controller = new AbortController();
19211
19259
  const timeout = setTimeout(() => controller.abort(), timeoutMs);
@@ -19233,7 +19281,10 @@ async function loadVendorDisclosure(module, vendorId, dependencies = {}) {
19233
19281
  if (!disclosureUrl) return null;
19234
19282
  const maxBodyBytes = dependencies.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES;
19235
19283
  const maxRedirects = dependencies.maxRedirects ?? DEFAULT_MAX_REDIRECTS2;
19236
- const request = dependencies.request ?? requestResolvedJson;
19284
+ const request = dependencies.request ?? createPublicTextRequester({
19285
+ accept: "application/json",
19286
+ userAgent: "1Consent-Design-Kit/1.0"
19287
+ });
19237
19288
  const resolve3 = dependencies.resolve ?? resolveAllAddresses;
19238
19289
  const timeoutMs = dependencies.timeoutMs ?? DEFAULT_TIMEOUT_MS2;
19239
19290
  let current = disclosureUrl;
@@ -19242,7 +19293,7 @@ async function loadVendorDisclosure(module, vendorId, dependencies = {}) {
19242
19293
  const target = await resolvePublicWebUrl(current, resolve3);
19243
19294
  const response = await requestWithTimeout2(target, request, timeoutMs, maxBodyBytes);
19244
19295
  if (response.status >= 300 && response.status < 400) {
19245
- const location = responseHeader(response.headers, "location");
19296
+ const location = publicResponseHeader(response.headers, "location");
19246
19297
  if (!location || redirectCount === maxRedirects) return null;
19247
19298
  current = new URL(location, target.url).toString();
19248
19299
  continue;