elit 3.6.5 → 3.6.6

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.mjs CHANGED
@@ -60469,6 +60469,71 @@ function lookup(path) {
60469
60469
  // src/server.ts
60470
60470
  init_runtime();
60471
60471
 
60472
+ // src/smtp-server.ts
60473
+ import { SMTPServer } from "smtp-server";
60474
+ var DEFAULT_SMTP_PORT = 2525;
60475
+ var DEFAULT_SMTP_HOST = "127.0.0.1";
60476
+ function resolveSmtpServerConfig(config = {}) {
60477
+ const { port = DEFAULT_SMTP_PORT, host = DEFAULT_SMTP_HOST, label, ...serverOptions } = config;
60478
+ return {
60479
+ ...serverOptions,
60480
+ port,
60481
+ host,
60482
+ label
60483
+ };
60484
+ }
60485
+ function normalizeSmtpServerConfigs(input) {
60486
+ const configs = Array.isArray(input) ? input : input ? [input] : [];
60487
+ return configs.map((config) => resolveSmtpServerConfig(config));
60488
+ }
60489
+ function closeSmtpServer(server) {
60490
+ return new Promise((resolve9, reject) => {
60491
+ let settled = false;
60492
+ const finish = (error) => {
60493
+ if (settled) {
60494
+ return;
60495
+ }
60496
+ settled = true;
60497
+ if (error) {
60498
+ reject(error);
60499
+ return;
60500
+ }
60501
+ resolve9();
60502
+ };
60503
+ const handleCloseError = (error) => {
60504
+ const errorCode = error.code;
60505
+ if (errorCode === "ERR_SERVER_NOT_RUNNING") {
60506
+ finish();
60507
+ return;
60508
+ }
60509
+ finish(error);
60510
+ };
60511
+ try {
60512
+ server.close(() => finish());
60513
+ } catch (error) {
60514
+ handleCloseError(error);
60515
+ }
60516
+ });
60517
+ }
60518
+ function createSmtpServer(config = {}) {
60519
+ const resolvedConfig = resolveSmtpServerConfig(config);
60520
+ const { port, host, label: _label, ...serverOptions } = resolvedConfig;
60521
+ const server = new SMTPServer(serverOptions);
60522
+ return {
60523
+ server,
60524
+ config: resolvedConfig,
60525
+ listen(callback) {
60526
+ return callback ? server.listen(port, host, callback) : server.listen(port, host);
60527
+ },
60528
+ address() {
60529
+ return server.server.address();
60530
+ },
60531
+ close() {
60532
+ return closeSmtpServer(server);
60533
+ }
60534
+ };
60535
+ }
60536
+
60472
60537
  // src/render-context.ts
60473
60538
  var RUNTIME_TARGET_KEY = "__ELIT_RUNTIME_TARGET__";
60474
60539
  var CAPTURED_RENDER_KEY = "__ELIT_CAPTURED_RENDER__";
@@ -60922,6 +60987,7 @@ var DomNode = class {
60922
60987
  html += `</${tagName}>${newLine}`;
60923
60988
  return html;
60924
60989
  }
60990
+ const isRawText = tagName === "script" || tagName === "style";
60925
60991
  if (children && children.length > 0) {
60926
60992
  const resolvedChildren = children.map((c) => {
60927
60993
  const resolved = this.resolveStateValue(c);
@@ -60937,11 +61003,11 @@ var DomNode = class {
60937
61003
  if (Array.isArray(child)) {
60938
61004
  for (const c of child) {
60939
61005
  if (!shouldSkipChild(c)) {
60940
- html += this.renderToString(c, { pretty, indent: indent5 + 1 });
61006
+ html += isRawText && typeof c === "string" ? c : this.renderToString(c, { pretty, indent: indent5 + 1 });
60941
61007
  }
60942
61008
  }
60943
61009
  } else {
60944
- html += this.renderToString(child, { pretty, indent: indent5 + 1 });
61010
+ html += isRawText && typeof child === "string" ? child : this.renderToString(child, { pretty, indent: indent5 + 1 });
60945
61011
  }
60946
61012
  }
60947
61013
  html += indentStr;
@@ -60951,11 +61017,11 @@ var DomNode = class {
60951
61017
  if (Array.isArray(child)) {
60952
61018
  for (const c of child) {
60953
61019
  if (!shouldSkipChild(c)) {
60954
- html += this.renderToString(c, { pretty: false, indent: 0 });
61020
+ html += isRawText && typeof c === "string" ? c : this.renderToString(c, { pretty: false, indent: 0 });
60955
61021
  }
60956
61022
  }
60957
61023
  } else {
60958
- html += this.renderToString(child, { pretty: false, indent: 0 });
61024
+ html += isRawText && typeof child === "string" ? child : this.renderToString(child, { pretty: false, indent: 0 });
60959
61025
  }
60960
61026
  }
60961
61027
  }
@@ -61883,6 +61949,56 @@ var defaultOptions = {
61883
61949
  worker: [],
61884
61950
  mode: "dev"
61885
61951
  };
61952
+ function createSmtpBindingKey(config) {
61953
+ return `${config.host}:${config.port}`;
61954
+ }
61955
+ function createSmtpServerLabel(config) {
61956
+ return config.label || createSmtpBindingKey(config);
61957
+ }
61958
+ function formatSmtpServerAddress(address, fallback) {
61959
+ if (typeof address === "string") {
61960
+ return address;
61961
+ }
61962
+ if (address) {
61963
+ return `${address.address}:${address.port}`;
61964
+ }
61965
+ return createSmtpBindingKey(fallback);
61966
+ }
61967
+ function collectSmtpServerConfigs(config, usesClientArray) {
61968
+ const modeLabel = config.mode || "dev";
61969
+ const smtpConfigs = normalizeSmtpServerConfigs(config.smtp).map((smtpConfig, index) => ({
61970
+ ...smtpConfig,
61971
+ label: smtpConfig.label || `${modeLabel}.smtp[${index}]`
61972
+ }));
61973
+ if (!usesClientArray || !config.clients) {
61974
+ return smtpConfigs;
61975
+ }
61976
+ for (let clientIndex = 0; clientIndex < config.clients.length; clientIndex += 1) {
61977
+ const client = config.clients[clientIndex];
61978
+ const clientDescriptor = client.basePath || client.root;
61979
+ const clientPrefix = clientDescriptor ? `${modeLabel}.clients[${clientIndex}] (${clientDescriptor})` : `${modeLabel}.clients[${clientIndex}]`;
61980
+ smtpConfigs.push(...normalizeSmtpServerConfigs(client.smtp).map((smtpConfig, smtpIndex) => ({
61981
+ ...smtpConfig,
61982
+ label: smtpConfig.label || `${clientPrefix}.smtp[${smtpIndex}]`
61983
+ })));
61984
+ }
61985
+ return smtpConfigs;
61986
+ }
61987
+ function assertUniqueSmtpServerBindings(configs) {
61988
+ const seenBindings = /* @__PURE__ */ new Map();
61989
+ for (const smtpConfig of configs) {
61990
+ if (smtpConfig.port === 0) {
61991
+ continue;
61992
+ }
61993
+ const bindingKey = createSmtpBindingKey(smtpConfig);
61994
+ const currentLabel = createSmtpServerLabel(smtpConfig);
61995
+ const previousLabel = seenBindings.get(bindingKey);
61996
+ if (previousLabel) {
61997
+ throw new Error(`Duplicate SMTP server binding "${bindingKey}" configured for ${previousLabel} and ${currentLabel}`);
61998
+ }
61999
+ seenBindings.set(bindingKey, currentLabel);
62000
+ }
62001
+ }
61886
62002
  function shouldUseClientFallbackRoot(primaryRoot, fallbackRoot, indexPath) {
61887
62003
  if (!fallbackRoot) {
61888
62004
  return false;
@@ -62017,6 +62133,7 @@ function createDevServer(options) {
62017
62133
  const globalWebSocketEndpoints = usesClientArray ? normalizeWebSocketEndpoints(config.ws) : [];
62018
62134
  const normalizedWebSocketEndpoints = [...normalizedClients.flatMap((client) => client.ws), ...globalWebSocketEndpoints];
62019
62135
  const seenWebSocketPaths = /* @__PURE__ */ new Set();
62136
+ const smtpServerConfigs = collectSmtpServerConfigs(config, usesClientArray);
62020
62137
  for (const endpoint of normalizedWebSocketEndpoints) {
62021
62138
  if (endpoint.path === ELIT_INTERNAL_WS_PATH) {
62022
62139
  throw new Error(`WebSocket path "${ELIT_INTERNAL_WS_PATH}" is reserved for Elit internals`);
@@ -62026,6 +62143,21 @@ function createDevServer(options) {
62026
62143
  }
62027
62144
  seenWebSocketPaths.add(endpoint.path);
62028
62145
  }
62146
+ assertUniqueSmtpServerBindings(smtpServerConfigs);
62147
+ const smtpServers = smtpServerConfigs.map((smtpConfig) => {
62148
+ const smtpServer = createSmtpServer(smtpConfig);
62149
+ const smtpLabel = createSmtpServerLabel(smtpServer.config);
62150
+ smtpServer.server.on("error", (error) => {
62151
+ console.error(`[SMTP] ${smtpLabel} error:`, error);
62152
+ });
62153
+ if (config.logging) {
62154
+ smtpServer.server.server.once("listening", () => {
62155
+ console.log(`[SMTP] ${smtpLabel} listening on ${formatSmtpServerAddress(smtpServer.address(), smtpServer.config)}`);
62156
+ });
62157
+ }
62158
+ smtpServer.listen();
62159
+ return smtpServer;
62160
+ });
62029
62161
  const globalProxyHandler = config.proxy ? createProxyHandler(config.proxy) : null;
62030
62162
  const server = createServer(async (req, res) => {
62031
62163
  const originalUrl = req.url || "/";
@@ -62566,6 +62698,15 @@ ${elitImportMap}`;
62566
62698
  if (config.logging) console.log("\n[Server] Shutting down...");
62567
62699
  transformCache.clear();
62568
62700
  if (watcher) await watcher.close();
62701
+ if (smtpServers.length > 0) {
62702
+ await Promise.all(smtpServers.map(async (smtpServer) => {
62703
+ try {
62704
+ await smtpServer.close();
62705
+ } catch (error) {
62706
+ console.error(`[SMTP] ${createSmtpServerLabel(smtpServer.config)} close error:`, error);
62707
+ }
62708
+ }));
62709
+ }
62569
62710
  if (webSocketServers.length > 0) {
62570
62711
  webSocketServers.forEach((wsServer) => wsServer.close());
62571
62712
  wsClients.clear();
@@ -62582,6 +62723,7 @@ ${elitImportMap}`;
62582
62723
  return {
62583
62724
  server,
62584
62725
  wss,
62726
+ smtpServers,
62585
62727
  url: primaryUrl,
62586
62728
  state: stateManager,
62587
62729
  close
@@ -74461,7 +74603,7 @@ var WAPK_AUTH_TAG_LENGTH = 16;
74461
74603
  var WAPK_SCRYPT_OPTIONS = { N: 16384, r: 8, p: 1 };
74462
74604
  var DEFAULT_GOOGLE_DRIVE_TOKEN_ENV = "GOOGLE_DRIVE_ACCESS_TOKEN";
74463
74605
  var DEFAULT_WAPK_ONLINE_URL_ENV = "ELIT_WAPK_ONLINE_URL";
74464
- var DEFAULT_WAPK_ONLINE_URLS = ["https://wapk.d-osc.com/"];
74606
+ var DEFAULT_WAPK_ONLINE_URLS = ["http://wapk.d-osc.com/"];
74465
74607
  var WAPK_ONLINE_CREATE_PATH = "/api/shared-session/create";
74466
74608
  var WAPK_ONLINE_READ_PATH = "/api/shared-session/read";
74467
74609
  var WAPK_ONLINE_CLOSE_PATH = "/api/shared-session/close";
@@ -74499,6 +74641,113 @@ function normalizeNonEmptyString(value) {
74499
74641
  const normalized = value.trim();
74500
74642
  return normalized.length > 0 ? normalized : void 0;
74501
74643
  }
74644
+ function normalizeGeneratedIdentifier(value) {
74645
+ const normalized = value.normalize("NFKD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
74646
+ return normalized.length > 0 ? normalized : void 0;
74647
+ }
74648
+ function joinGeneratedIdentifier(...segments) {
74649
+ const normalizedSegments = segments.map((segment) => segment ? normalizeGeneratedIdentifier(segment) : void 0).filter((segment) => Boolean(segment));
74650
+ return normalizedSegments.length > 0 ? normalizedSegments.join(".") : void 0;
74651
+ }
74652
+ function parseScopedPackageName(value) {
74653
+ const normalizedValue = normalizeNonEmptyString(value);
74654
+ if (!normalizedValue) {
74655
+ return {};
74656
+ }
74657
+ if (!normalizedValue.startsWith("@")) {
74658
+ return {
74659
+ packageName: normalizedValue
74660
+ };
74661
+ }
74662
+ const scopeSeparatorIndex = normalizedValue.indexOf("/");
74663
+ if (scopeSeparatorIndex === -1) {
74664
+ return {
74665
+ packageName: normalizedValue
74666
+ };
74667
+ }
74668
+ return {
74669
+ scope: normalizedValue.slice(1, scopeSeparatorIndex),
74670
+ packageName: normalizedValue.slice(scopeSeparatorIndex + 1)
74671
+ };
74672
+ }
74673
+ function readPackageAuthorMetadata(value) {
74674
+ if (typeof value === "string") {
74675
+ const normalizedValue = value.trim();
74676
+ if (!normalizedValue) {
74677
+ return {};
74678
+ }
74679
+ const email = normalizedValue.match(/<([^>]+)>/)?.[1];
74680
+ const url = normalizedValue.match(/\(([^)]+)\)/)?.[1];
74681
+ const name = normalizedValue.replace(/<[^>]+>/g, " ").replace(/\([^)]+\)/g, " ").replace(/\s+/g, " ").trim();
74682
+ return {
74683
+ name: normalizeNonEmptyString(name),
74684
+ email: normalizeNonEmptyString(email),
74685
+ url: normalizeNonEmptyString(url)
74686
+ };
74687
+ }
74688
+ if (!isRecord(value)) {
74689
+ return {};
74690
+ }
74691
+ return {
74692
+ name: normalizeNonEmptyString(value.name),
74693
+ email: normalizeNonEmptyString(value.email),
74694
+ url: normalizeNonEmptyString(value.url)
74695
+ };
74696
+ }
74697
+ function extractPublisherIdFromRepository(value) {
74698
+ const repositoryUrl = typeof value === "string" ? value : isRecord(value) ? normalizeNonEmptyString(value.url) : void 0;
74699
+ const normalizedRepositoryUrl = normalizeNonEmptyString(repositoryUrl);
74700
+ if (!normalizedRepositoryUrl) {
74701
+ return void 0;
74702
+ }
74703
+ const shorthandMatch = normalizedRepositoryUrl.match(/^(?:github|gitlab|bitbucket):([^/]+)\/.+$/i);
74704
+ if (shorthandMatch?.[1]) {
74705
+ return normalizeGeneratedIdentifier(shorthandMatch[1]);
74706
+ }
74707
+ const sshMatch = normalizedRepositoryUrl.match(/^[^@]+@[^:]+:([^/]+)\/.+$/i);
74708
+ if (sshMatch?.[1]) {
74709
+ return normalizeGeneratedIdentifier(sshMatch[1]);
74710
+ }
74711
+ try {
74712
+ const parsed = new URL(normalizedRepositoryUrl.replace(/^git\+/, ""));
74713
+ const firstPathSegment = parsed.pathname.replace(/\.git$/i, "").split("/").filter(Boolean)[0];
74714
+ return normalizeGeneratedIdentifier(firstPathSegment ?? parsed.hostname.replace(/^www\./i, ""));
74715
+ } catch {
74716
+ return void 0;
74717
+ }
74718
+ }
74719
+ function extractPublisherIdFromUrl(value) {
74720
+ const normalizedValue = normalizeNonEmptyString(value);
74721
+ if (!normalizedValue) {
74722
+ return void 0;
74723
+ }
74724
+ try {
74725
+ const parsed = new URL(normalizedValue);
74726
+ return normalizeGeneratedIdentifier(parsed.hostname.replace(/^www\./i, ""));
74727
+ } catch {
74728
+ return void 0;
74729
+ }
74730
+ }
74731
+ function extractPublisherIdFromEmail(value) {
74732
+ const normalizedValue = normalizeNonEmptyString(value);
74733
+ if (!normalizedValue) {
74734
+ return void 0;
74735
+ }
74736
+ const domain = normalizedValue.split("@")[1];
74737
+ return domain ? normalizeGeneratedIdentifier(domain.replace(/^www\./i, "")) : void 0;
74738
+ }
74739
+ function resolveAutoGeneratedWapkAppId(packageName, fallbackName) {
74740
+ const scopedPackage = parseScopedPackageName(packageName);
74741
+ return joinGeneratedIdentifier(scopedPackage.scope, scopedPackage.packageName ?? fallbackName);
74742
+ }
74743
+ function resolveAutoGeneratedWapkPublisherId(packageJson, fallbackName) {
74744
+ const scopedPackage = parseScopedPackageName(typeof packageJson?.name === "string" ? packageJson.name : void 0);
74745
+ if (scopedPackage.scope) {
74746
+ return normalizeGeneratedIdentifier(scopedPackage.scope);
74747
+ }
74748
+ const author = readPackageAuthorMetadata(packageJson?.author);
74749
+ return extractPublisherIdFromRepository(packageJson?.repository) ?? extractPublisherIdFromUrl(packageJson?.homepage) ?? extractPublisherIdFromUrl(author.url) ?? extractPublisherIdFromEmail(author.email) ?? normalizeGeneratedIdentifier(author.name ?? fallbackName);
74750
+ }
74502
74751
  function normalizeStringMap(value) {
74503
74752
  if (!isRecord(value)) {
74504
74753
  return void 0;
@@ -74538,6 +74787,8 @@ function normalizeWapkConfig(value) {
74538
74787
  runtime: normalizeRuntime(value.runtime ?? value.engine),
74539
74788
  entry: typeof value.entry === "string" ? value.entry : void 0,
74540
74789
  scripts: normalizeStringMap(value.scripts ?? value.script),
74790
+ appId: normalizeNonEmptyString(value.appId),
74791
+ publisherId: normalizeNonEmptyString(value.publisherId),
74541
74792
  port: normalizePort(value.port),
74542
74793
  env: normalizeStringMap(value.env),
74543
74794
  desktop: normalizeDesktopConfig(value.desktop),
@@ -74827,6 +75078,7 @@ async function readWapkProjectConfig(directory) {
74827
75078
  const elitConfig = await loadConfig(directory);
74828
75079
  const elitWapkConfig = normalizeWapkConfig(elitConfig?.wapk);
74829
75080
  const packageJson = readJsonFile(packageJsonPath);
75081
+ const packageJsonWapk = isRecord(packageJson?.wapk) ? packageJson.wapk : void 0;
74830
75082
  const packageScripts = normalizeStringMap(packageJson?.scripts) ?? {};
74831
75083
  const selectedScripts = elitWapkConfig.scripts ?? packageScripts;
74832
75084
  const inferred = inferRuntimeAndEntryFromScript(selectedScripts.start ?? packageScripts.start);
@@ -74850,12 +75102,16 @@ async function readWapkProjectConfig(directory) {
74850
75102
  if (!existsSync2(entryPath) || !statSync2(entryPath).isFile()) {
74851
75103
  throw new Error(`WAPK entry not found: ${entryPath}`);
74852
75104
  }
75105
+ const appId = elitWapkConfig.appId ?? normalizeNonEmptyString(packageJson?.appId) ?? normalizeNonEmptyString(packageJsonWapk?.appId) ?? resolveAutoGeneratedWapkAppId(typeof packageJson?.name === "string" ? packageJson.name : void 0, name);
75106
+ const publisherId = elitWapkConfig.publisherId ?? normalizeNonEmptyString(packageJson?.publisherId) ?? normalizeNonEmptyString(packageJsonWapk?.publisherId) ?? resolveAutoGeneratedWapkPublisherId(packageJson, name);
74853
75107
  return {
74854
75108
  name,
74855
75109
  version,
74856
75110
  runtime: runtime2,
74857
75111
  entry,
74858
75112
  scripts: selectedScripts,
75113
+ appId,
75114
+ publisherId,
74859
75115
  port: elitWapkConfig.port,
74860
75116
  env: elitWapkConfig.env,
74861
75117
  desktop: elitWapkConfig.desktop,
@@ -74865,11 +75121,14 @@ async function readWapkProjectConfig(directory) {
74865
75121
  function readIgnorePatterns(directory) {
74866
75122
  return readLineIgnorePatterns(join2(directory, ".wapkignore"));
74867
75123
  }
75124
+ function parsePatternLines(content) {
75125
+ return content.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0 && (!line.startsWith("#") || line.startsWith("\\#")));
75126
+ }
74868
75127
  function readLineIgnorePatterns(filePath) {
74869
75128
  if (!existsSync2(filePath)) {
74870
75129
  return [];
74871
75130
  }
74872
- return readFileSync2(filePath, "utf8").split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0 && (!line.startsWith("#") || line.startsWith("\\#")));
75131
+ return parsePatternLines(readFileSync2(filePath, "utf8"));
74873
75132
  }
74874
75133
  function normalizePackageEntry(value) {
74875
75134
  const normalized = normalizeNonEmptyString(value)?.replace(/^[.][\\/]/, "").split("\\").join("/");
@@ -75148,6 +75407,91 @@ function shouldIgnore(relativePath2, ignorePatterns, isDirectory) {
75148
75407
  }
75149
75408
  return ignored;
75150
75409
  }
75410
+ function matchesPatchPattern(relativePath2, pattern) {
75411
+ const normalizedRule = normalizeIgnorePattern(pattern);
75412
+ if (!normalizedRule) {
75413
+ return false;
75414
+ }
75415
+ const normalizedPath = relativePath2.replace(/\\/g, "/");
75416
+ const subtreeSelector = normalizedRule.pattern.endsWith("/*") && !normalizedRule.pattern.slice(0, -2).includes("*") && !normalizedRule.pattern.slice(0, -2).includes("?");
75417
+ if (subtreeSelector) {
75418
+ const directoryPath = normalizedRule.pattern.slice(0, -2);
75419
+ return directoryPath.length > 0 && normalizedPath.startsWith(`${directoryPath}/`);
75420
+ }
75421
+ const hasGlob = /[*?]/.test(normalizedRule.pattern);
75422
+ if (!hasGlob) {
75423
+ if (normalizedRule.directoryOnly) {
75424
+ return normalizedPath === normalizedRule.pattern || normalizedPath.startsWith(`${normalizedRule.pattern}/`);
75425
+ }
75426
+ return normalizedPath === normalizedRule.pattern;
75427
+ }
75428
+ return globPatternToRegex(normalizedRule.pattern, {
75429
+ directoryOnly: normalizedRule.directoryOnly,
75430
+ matchSegmentsOnly: false
75431
+ }).test(normalizedPath);
75432
+ }
75433
+ function shouldPatchArchivePath(relativePath2, patchPatterns) {
75434
+ let selected = false;
75435
+ for (const pattern of patchPatterns) {
75436
+ const normalizedRule = normalizeIgnorePattern(pattern);
75437
+ if (!normalizedRule) {
75438
+ continue;
75439
+ }
75440
+ if (!matchesPatchPattern(relativePath2, pattern)) {
75441
+ continue;
75442
+ }
75443
+ selected = !normalizedRule.negate;
75444
+ }
75445
+ return selected;
75446
+ }
75447
+ function resolvePatchManifestPatterns(files) {
75448
+ const patchManifest = files.find((file) => file.path === ".wapkpatch");
75449
+ if (!patchManifest) {
75450
+ throw new Error("Patch archive must include a .wapkpatch manifest file.");
75451
+ }
75452
+ const patterns = parsePatternLines(patchManifest.content.toString("utf8"));
75453
+ if (patterns.length === 0) {
75454
+ throw new Error("Patch archive .wapkpatch must define at least one patch rule.");
75455
+ }
75456
+ return patterns;
75457
+ }
75458
+ function applyPatchEntriesToFiles(targetFiles, patchFiles) {
75459
+ const fileMap = new Map(targetFiles.map((file) => [file.path, file]));
75460
+ const fileOrder = targetFiles.map((file) => file.path);
75461
+ const addedPaths = [];
75462
+ const updatedPaths = [];
75463
+ const unchangedPaths = [];
75464
+ for (const patchFile of [...patchFiles].sort((left, right) => left.path.localeCompare(right.path))) {
75465
+ const existing = fileMap.get(patchFile.path);
75466
+ const nextEntry = {
75467
+ path: patchFile.path,
75468
+ content: Buffer.from(patchFile.content),
75469
+ mode: patchFile.mode
75470
+ };
75471
+ if (!existing) {
75472
+ addedPaths.push(patchFile.path);
75473
+ fileOrder.push(patchFile.path);
75474
+ } else if (existing.mode === patchFile.mode && existing.content.equals(patchFile.content)) {
75475
+ unchangedPaths.push(patchFile.path);
75476
+ } else {
75477
+ updatedPaths.push(patchFile.path);
75478
+ }
75479
+ fileMap.set(patchFile.path, nextEntry);
75480
+ }
75481
+ return {
75482
+ files: fileOrder.map((filePath) => {
75483
+ const file = fileMap.get(filePath);
75484
+ if (!file) {
75485
+ throw new Error(`Internal WAPK patch error: missing file entry for ${filePath}`);
75486
+ }
75487
+ return file;
75488
+ }),
75489
+ patchedPaths: [...updatedPaths, ...addedPaths],
75490
+ addedPaths,
75491
+ updatedPaths,
75492
+ unchangedPaths
75493
+ };
75494
+ }
75151
75495
  function collectFiles(directory, baseDirectory, ignorePatterns) {
75152
75496
  const files = [];
75153
75497
  const entries = readdirSync2(directory, { withFileTypes: true });
@@ -75303,6 +75647,8 @@ function decodeWapkPayload(buffer) {
75303
75647
  runtime: normalizeRuntime(rawHeader.runtime ?? rawHeader.engine) ?? "node",
75304
75648
  entry: typeof rawHeader.entry === "string" ? rawHeader.entry : "index.js",
75305
75649
  scripts: normalizeStringMap(rawHeader.scripts) ?? {},
75650
+ appId: normalizeNonEmptyString(rawHeader.appId),
75651
+ publisherId: normalizeNonEmptyString(rawHeader.publisherId),
75306
75652
  port: normalizePort(rawHeader.port),
75307
75653
  env: normalizeStringMap(rawHeader.env),
75308
75654
  desktop: normalizeDesktopConfig(rawHeader.desktop),
@@ -75800,11 +76146,14 @@ function sanitizeOnlineArchiveFileName(label, fallback) {
75800
76146
  const fileName = sanitized.length > 0 ? sanitized : "app.wapk";
75801
76147
  return fileName.toLowerCase().endsWith(".wapk") ? fileName : `${fileName}.wapk`;
75802
76148
  }
76149
+ var WAPK_ONLINE_JOIN_SOURCE_QUERY_PARAM = "launchSource";
76150
+ var WAPK_ONLINE_JOIN_SOURCE_QUERY_VALUE = "elit-wapk-online";
75803
76151
  function buildOnlineJoinUrl(baseUrl, joinKey) {
75804
76152
  const joinUrl = new URL(baseUrl.toString());
75805
76153
  joinUrl.search = "";
75806
76154
  joinUrl.hash = "";
75807
76155
  joinUrl.searchParams.set("join", joinKey);
76156
+ joinUrl.searchParams.set(WAPK_ONLINE_JOIN_SOURCE_QUERY_PARAM, WAPK_ONLINE_JOIN_SOURCE_QUERY_VALUE);
75808
76157
  return joinUrl.toString();
75809
76158
  }
75810
76159
  async function probeOnlineLauncherUrl(url) {
@@ -75985,11 +76334,16 @@ async function closeWapkOnlineSharedSession(launcherUrl, session) {
75985
76334
  function isPmWapkOnlineShutdownEnabled() {
75986
76335
  return process.env[WAPK_ONLINE_PM_SHUTDOWN_ENV] === "1" && Boolean(process.stdin) && !process.stdin.isTTY;
75987
76336
  }
75988
- async function waitForWapkOnlineSessionShutdown(launcherUrl, session, archiveHandle, lock) {
76337
+ function getWapkOnlineProcessDetails() {
76338
+ return `pid ${process.pid}, ppid ${process.ppid}`;
76339
+ }
76340
+ async function waitForWapkOnlineSessionShutdown(launcherUrl, session, archiveHandle, lock, options = {}) {
75989
76341
  let snapshotRevision = 0;
75990
76342
  let snapshotSyncPending = false;
75991
76343
  let snapshotSyncPromise = Promise.resolve();
75992
76344
  let lastSnapshotSyncError = null;
76345
+ const allowSigtermClose = options.allowSigtermClose === true;
76346
+ const processDetails = getWapkOnlineProcessDetails();
75993
76347
  const syncGuestSnapshotUpdates = () => {
75994
76348
  if (snapshotSyncPending) {
75995
76349
  return snapshotSyncPromise;
@@ -76023,6 +76377,7 @@ async function waitForWapkOnlineSessionShutdown(launcherUrl, session, archiveHan
76023
76377
  void syncGuestSnapshotUpdates();
76024
76378
  }, WAPK_ONLINE_KEEPALIVE_INTERVAL_MS);
76025
76379
  const pmManaged = isPmWapkOnlineShutdownEnabled();
76380
+ let ignoredSigTermLogged = false;
76026
76381
  let stdinBuffer = "";
76027
76382
  const cleanup = () => {
76028
76383
  clearInterval(keepAlive);
@@ -76041,7 +76396,17 @@ async function waitForWapkOnlineSessionShutdown(launcherUrl, session, archiveHan
76041
76396
  finish({ kind: "signal", signal: "SIGINT" });
76042
76397
  };
76043
76398
  const onSigTerm = () => {
76044
- finish({ kind: "signal", signal: "SIGTERM" });
76399
+ if (allowSigtermClose) {
76400
+ finish({ kind: "signal", signal: "SIGTERM" });
76401
+ return;
76402
+ }
76403
+ if (ignoredSigTermLogged) {
76404
+ return;
76405
+ }
76406
+ ignoredSigTermLogged = true;
76407
+ console.warn(
76408
+ pmManaged ? `[wapk] Ignoring SIGTERM while shared session ${session.joinKey} is active (${processDetails}). Use elit pm stop, restart, or delete to close the session.` : `[wapk] Ignoring SIGTERM while shared session ${session.joinKey} is active (${processDetails}). Press Ctrl+C to stop sharing, or pass --allow-sigterm-close to close on SIGTERM.`
76409
+ );
76045
76410
  };
76046
76411
  const onStdinData = (chunk) => {
76047
76412
  stdinBuffer += typeof chunk === "string" ? chunk : chunk.toString("utf8");
@@ -76066,9 +76431,12 @@ async function waitForWapkOnlineSessionShutdown(launcherUrl, session, archiveHan
76066
76431
  if (shutdownTrigger.kind === "pm") {
76067
76432
  console.log(`
76068
76433
  [wapk] PM requested shutdown for shared session ${session.joinKey}...`);
76434
+ } else if (shutdownTrigger.signal === "SIGTERM") {
76435
+ console.log(`
76436
+ [wapk] Received SIGTERM for shared session ${session.joinKey} (${processDetails}); closing because --allow-sigterm-close is enabled...`);
76069
76437
  } else {
76070
76438
  console.log(`
76071
- [wapk] Closing shared session ${session.joinKey}...`);
76439
+ [wapk] Received ${shutdownTrigger.signal}; closing shared session ${session.joinKey}...`);
76072
76440
  }
76073
76441
  try {
76074
76442
  await closeWapkOnlineSharedSession(launcherUrl, session);
@@ -76108,7 +76476,9 @@ async function runWapkOnline(archiveSpecifier, options) {
76108
76476
  process.exitCode = await waitForWapkOnlineSessionShutdown(launcherUrl, {
76109
76477
  joinKey: response.joinKey,
76110
76478
  adminToken: response.adminToken
76111
- }, archiveHandle, onlineArchiveLock);
76479
+ }, archiveHandle, onlineArchiveLock, {
76480
+ allowSigtermClose: options.allowSigtermClose
76481
+ });
76112
76482
  }
76113
76483
  async function writeWapkArchiveFromMemory(archiveHandle, header, files, lock) {
76114
76484
  const updatedHeader = {
@@ -76365,6 +76735,8 @@ async function packWapkDirectory(directory, options = {}) {
76365
76735
  runtime: config.runtime,
76366
76736
  entry: config.entry,
76367
76737
  scripts: config.scripts,
76738
+ appId: config.appId,
76739
+ publisherId: config.publisherId,
76368
76740
  port: config.port,
76369
76741
  env: config.env,
76370
76742
  desktop: config.desktop,
@@ -76396,6 +76768,56 @@ function extractWapkArchive(wapkPath, outputDir = ".", options = {}) {
76396
76768
  console.log(`Extracted ${archive.files.length} files to: ${extractDirectory}`);
76397
76769
  return extractDirectory;
76398
76770
  }
76771
+ async function patchWapkArchive(wapkPath, options) {
76772
+ const targetHandle = resolveArchiveHandle(wapkPath);
76773
+ const targetSnapshot = await targetHandle.readSnapshot();
76774
+ const targetEnvelope = parseWapkEnvelope(targetSnapshot.buffer);
76775
+ const targetArchive = decodeWapk(targetSnapshot.buffer, options);
76776
+ const targetLock = targetEnvelope.version === WAPK_LOCKED_VERSION ? resolveArchiveCredentials(options) : void 0;
76777
+ const patchArchive = readWapkArchive(options.from, {
76778
+ password: options.fromPassword ?? options.password
76779
+ });
76780
+ const patchPatterns = resolvePatchManifestPatterns(patchArchive.files);
76781
+ const selectedPatchFiles = patchArchive.files.filter((file) => file.path !== ".wapkpatch").filter((file) => shouldPatchArchivePath(file.path, patchPatterns));
76782
+ const patchResult = applyPatchEntriesToFiles(targetArchive.files, selectedPatchFiles);
76783
+ console.log(`[wapk] Target: ${targetSnapshot.label ?? targetHandle.label}`);
76784
+ console.log(`[wapk] Patch: ${options.from}`);
76785
+ console.log(`[wapk] Rules: ${patchPatterns.length}`);
76786
+ if (selectedPatchFiles.length === 0) {
76787
+ console.log("[wapk] No files matched .wapkpatch. Archive was not modified.");
76788
+ return {
76789
+ archiveLabel: targetSnapshot.label ?? targetHandle.label,
76790
+ patchedPaths: [],
76791
+ addedPaths: [],
76792
+ updatedPaths: [],
76793
+ unchangedPaths: []
76794
+ };
76795
+ }
76796
+ if (patchResult.patchedPaths.length === 0) {
76797
+ console.log("[wapk] Matching patch files were already up to date. Archive was not modified.");
76798
+ return {
76799
+ archiveLabel: targetSnapshot.label ?? targetHandle.label,
76800
+ patchedPaths: [],
76801
+ addedPaths: [],
76802
+ updatedPaths: [],
76803
+ unchangedPaths: patchResult.unchangedPaths
76804
+ };
76805
+ }
76806
+ const writeResult = await writeWapkArchiveFromMemory(
76807
+ targetHandle,
76808
+ targetArchive.header,
76809
+ patchResult.files,
76810
+ targetLock
76811
+ );
76812
+ console.log(`[wapk] Applied ${patchResult.patchedPaths.length} patch file${patchResult.patchedPaths.length === 1 ? "" : "s"}.`);
76813
+ return {
76814
+ archiveLabel: writeResult.label,
76815
+ patchedPaths: patchResult.patchedPaths,
76816
+ addedPaths: patchResult.addedPaths,
76817
+ updatedPaths: patchResult.updatedPaths,
76818
+ unchangedPaths: patchResult.unchangedPaths
76819
+ };
76820
+ }
76399
76821
  async function prepareWapkApp(wapkPath, options = {}) {
76400
76822
  const archiveHandle = resolveArchiveHandle(wapkPath, options.googleDrive);
76401
76823
  const archivePath = archiveHandle.identifier;
@@ -76504,6 +76926,8 @@ function inspectWapkArchive(wapkPath, options = {}) {
76504
76926
  console.log(`App: ${decoded.header.version}`);
76505
76927
  console.log(`Runtime: ${decoded.header.runtime}`);
76506
76928
  console.log(`Entry: ${decoded.header.entry}`);
76929
+ console.log(`App ID: ${decoded.header.appId ?? "n/a"}`);
76930
+ console.log(`Publisher:${decoded.header.publisherId ? ` ${decoded.header.publisherId}` : " n/a"}`);
76507
76931
  console.log(`Port: ${decoded.header.port ?? "default"}`);
76508
76932
  console.log(`Created: ${decoded.header.createdAt}`);
76509
76933
  if (decoded.header.env && Object.keys(decoded.header.env).length > 0) {
@@ -76535,6 +76959,8 @@ function printWapkHelp() {
76535
76959
  " elit wapk gdrive://<fileId> --online",
76536
76960
  " elit wapk pack [directory]",
76537
76961
  " elit wapk pack [directory] --password secret-123",
76962
+ " elit wapk patch <file.wapk> --from <patch.wapk>",
76963
+ " elit wapk patch <file.wapk> --use <patch.wapk>",
76538
76964
  " elit wapk inspect <file.wapk>",
76539
76965
  " elit wapk extract <file.wapk>",
76540
76966
  "",
@@ -76546,24 +76972,32 @@ function printWapkHelp() {
76546
76972
  " --archive-watch Pull external archive changes back into the temp workdir",
76547
76973
  " --no-archive-watch Disable external archive read sync",
76548
76974
  " --online Create an Elit Run share session, stay alive, and close on Ctrl+C",
76975
+ " --allow-sigterm-close Allow SIGTERM to close an online shared session",
76549
76976
  " --online-url <url> Elit Run URL (default: auto-detect localhost:4177 or localhost:4179)",
76550
76977
  " --google-drive-file-id <id> Run a remote .wapk directly from Google Drive",
76551
76978
  " --google-drive-token-env <name> Env var containing the Google Drive OAuth token",
76552
76979
  " --google-drive-access-token <value> OAuth token for Google Drive API calls",
76553
76980
  " --google-drive-shared-drive Include supportsAllDrives=true for shared drives",
76981
+ " --from <file.wapk> Patch source archive for elit wapk patch",
76982
+ " --use <file.wapk> Alias for --from",
76983
+ " --from-password <value> Password for unlocking the patch archive",
76554
76984
  " --include-deps Legacy compatibility flag; node_modules are packed by default",
76555
76985
  " --password <value> Password for locking or unlocking the archive",
76556
76986
  " -h, --help Show this help",
76557
76987
  "",
76558
76988
  "Notes:",
76559
76989
  " - Pack reads wapk from elit.config.* and falls back to package.json.",
76990
+ " - If appId or publisherId is not configured, pack auto-generates stable defaults from package metadata.",
76560
76991
  " - Pack includes node_modules by default; use .wapkignore if you need to exclude them, and !pattern to re-include later matches.",
76992
+ " - Patch reads .wapkpatch from the patch archive and applies only matching archive-relative paths.",
76993
+ " - Patch keeps the target archive metadata and lock mode; use --from-password when the patch archive uses a different password.",
76561
76994
  " - Run never installs dependencies automatically; archives must include the runtime dependencies they need.",
76562
76995
  " - Run mode can read config.wapk.run for default file/runtime/live-sync options.",
76563
76996
  " - Browser-style archives with scripts.start or wapk.script.start run that start script automatically.",
76564
76997
  " - Run mode keeps files in RAM and syncs changes both to and from the archive source.",
76565
76998
  " - Google Drive mode talks to the Drive API directly; no local archive file is required.",
76566
76999
  " - Online mode creates a shared session on Elit Run directly, keeps the CLI alive, and closes it on Ctrl+C.",
77000
+ " - Online mode ignores SIGTERM by default; pass --allow-sigterm-close if an external supervisor should close the shared session with SIGTERM.",
76567
77001
  " - Locked archives in online mode must provide --password so the CLI can build the shared snapshot.",
76568
77002
  " - Locked archives require the same password for run/extract/inspect.",
76569
77003
  " - Archives stay unlocked by default unless a password is provided.",
@@ -76613,6 +77047,7 @@ function parseRunArgs(args) {
76613
77047
  let archiveSyncInterval;
76614
77048
  let online;
76615
77049
  let onlineUrl;
77050
+ let allowSigtermClose;
76616
77051
  let password;
76617
77052
  for (let index = 0; index < args.length; index++) {
76618
77053
  const arg = args[index];
@@ -76664,6 +77099,10 @@ function parseRunArgs(args) {
76664
77099
  onlineUrl = readRequiredOptionValue(args, ++index, "--online-url");
76665
77100
  break;
76666
77101
  }
77102
+ case "--allow-sigterm-close": {
77103
+ allowSigtermClose = true;
77104
+ break;
77105
+ }
76667
77106
  case "--google-drive-file-id": {
76668
77107
  googleDrive = {
76669
77108
  ...googleDrive,
@@ -76706,7 +77145,7 @@ function parseRunArgs(args) {
76706
77145
  break;
76707
77146
  }
76708
77147
  }
76709
- return { file, googleDrive, runtime: runtime2, syncInterval, useWatcher, watchArchive, archiveSyncInterval, online, onlineUrl, password };
77148
+ return { file, googleDrive, runtime: runtime2, syncInterval, useWatcher, watchArchive, archiveSyncInterval, online, onlineUrl, allowSigtermClose, password };
76710
77149
  }
76711
77150
  function parsePackArgs(args) {
76712
77151
  let directory = ".";
@@ -76732,6 +77171,46 @@ function parsePackArgs(args) {
76732
77171
  }
76733
77172
  return { directory, includeDeps, password };
76734
77173
  }
77174
+ function parsePatchArgs(args) {
77175
+ let file;
77176
+ let from;
77177
+ let password;
77178
+ let fromPassword;
77179
+ for (let index = 0; index < args.length; index++) {
77180
+ const arg = args[index];
77181
+ switch (arg) {
77182
+ case "--from":
77183
+ case "--use": {
77184
+ if (from) {
77185
+ throw new Error("WAPK patch accepts exactly one patch archive via --from or --use.");
77186
+ }
77187
+ from = readRequiredOptionValue(args, ++index, arg);
77188
+ break;
77189
+ }
77190
+ case "--password": {
77191
+ password = readRequiredOptionValue(args, ++index, "--password");
77192
+ break;
77193
+ }
77194
+ case "--from-password": {
77195
+ fromPassword = readRequiredOptionValue(args, ++index, "--from-password");
77196
+ break;
77197
+ }
77198
+ default:
77199
+ if (arg.startsWith("-")) {
77200
+ throw new Error(`Unknown WAPK option: ${arg}`);
77201
+ }
77202
+ if (file) {
77203
+ throw new Error("Usage: elit wapk patch <file.wapk> --from <patch.wapk>");
77204
+ }
77205
+ file = arg;
77206
+ break;
77207
+ }
77208
+ }
77209
+ if (!file || !from) {
77210
+ throw new Error("Usage: elit wapk patch <file.wapk> --from <patch.wapk>");
77211
+ }
77212
+ return { file, from, password, fromPassword };
77213
+ }
76735
77214
  async function readConfiguredWapkRunDefaults(cwd) {
76736
77215
  const config = await loadConfig(cwd);
76737
77216
  const runConfig = normalizeWapkRunConfig(config?.wapk?.run);
@@ -76771,6 +77250,7 @@ function resolveConfiguredWapkRunOptions(options, defaults) {
76771
77250
  archiveSyncInterval: options.archiveSyncInterval ?? defaults?.archiveSyncInterval,
76772
77251
  online: options.online ?? defaults?.online ?? Boolean(onlineUrl),
76773
77252
  onlineUrl,
77253
+ allowSigtermClose: options.allowSigtermClose === true,
76774
77254
  password: options.password ?? defaults?.password
76775
77255
  };
76776
77256
  }
@@ -76803,6 +77283,15 @@ async function runWapkCommand(args, cwd = process.cwd()) {
76803
77283
  });
76804
77284
  return;
76805
77285
  }
77286
+ if (args[0] === "patch") {
77287
+ const options = parsePatchArgs(args.slice(1));
77288
+ await patchWapkArchive(options.file, {
77289
+ from: options.from,
77290
+ password: options.password,
77291
+ fromPassword: options.fromPassword
77292
+ });
77293
+ return;
77294
+ }
76806
77295
  if (args[0] === "inspect") {
76807
77296
  const options = parseArchiveAccessArgs(args.slice(1), "Usage: elit wapk inspect <file.wapk>");
76808
77297
  inspectWapkArchive(options.file, options);
@@ -76831,6 +77320,7 @@ async function runWapkCommand(args, cwd = process.cwd()) {
76831
77320
  await runWapkOnline(archiveSpecifier, {
76832
77321
  googleDrive: runOptions.googleDrive,
76833
77322
  onlineUrl: runOptions.onlineUrl,
77323
+ allowSigtermClose: runOptions.allowSigtermClose,
76834
77324
  password: runOptions.password
76835
77325
  });
76836
77326
  return;
@@ -83163,6 +83653,7 @@ WAPK Options:
83163
83653
  elit wapk run [file.wapk] Run a packaged app or the configured default archive
83164
83654
  elit wapk run --google-drive-file-id <id> Run a packaged app directly from Google Drive
83165
83655
  elit wapk pack [directory] Pack a directory into a .wapk archive
83656
+ elit wapk patch <file.wapk> --from <patch.wapk> Apply a manifest-driven patch archive
83166
83657
  elit wapk inspect <file.wapk> Inspect a .wapk archive
83167
83658
  elit wapk extract <file.wapk> Extract a .wapk archive
83168
83659
  elit wapk --runtime node|bun|deno [file] Override the packaged runtime