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.cjs CHANGED
@@ -60484,6 +60484,71 @@ function lookup(path) {
60484
60484
  // src/server.ts
60485
60485
  init_runtime();
60486
60486
 
60487
+ // src/smtp-server.ts
60488
+ var import_smtp_server = require("smtp-server");
60489
+ var DEFAULT_SMTP_PORT = 2525;
60490
+ var DEFAULT_SMTP_HOST = "127.0.0.1";
60491
+ function resolveSmtpServerConfig(config = {}) {
60492
+ const { port = DEFAULT_SMTP_PORT, host = DEFAULT_SMTP_HOST, label, ...serverOptions } = config;
60493
+ return {
60494
+ ...serverOptions,
60495
+ port,
60496
+ host,
60497
+ label
60498
+ };
60499
+ }
60500
+ function normalizeSmtpServerConfigs(input) {
60501
+ const configs = Array.isArray(input) ? input : input ? [input] : [];
60502
+ return configs.map((config) => resolveSmtpServerConfig(config));
60503
+ }
60504
+ function closeSmtpServer(server) {
60505
+ return new Promise((resolve9, reject) => {
60506
+ let settled = false;
60507
+ const finish = (error) => {
60508
+ if (settled) {
60509
+ return;
60510
+ }
60511
+ settled = true;
60512
+ if (error) {
60513
+ reject(error);
60514
+ return;
60515
+ }
60516
+ resolve9();
60517
+ };
60518
+ const handleCloseError = (error) => {
60519
+ const errorCode = error.code;
60520
+ if (errorCode === "ERR_SERVER_NOT_RUNNING") {
60521
+ finish();
60522
+ return;
60523
+ }
60524
+ finish(error);
60525
+ };
60526
+ try {
60527
+ server.close(() => finish());
60528
+ } catch (error) {
60529
+ handleCloseError(error);
60530
+ }
60531
+ });
60532
+ }
60533
+ function createSmtpServer(config = {}) {
60534
+ const resolvedConfig = resolveSmtpServerConfig(config);
60535
+ const { port, host, label: _label, ...serverOptions } = resolvedConfig;
60536
+ const server = new import_smtp_server.SMTPServer(serverOptions);
60537
+ return {
60538
+ server,
60539
+ config: resolvedConfig,
60540
+ listen(callback) {
60541
+ return callback ? server.listen(port, host, callback) : server.listen(port, host);
60542
+ },
60543
+ address() {
60544
+ return server.server.address();
60545
+ },
60546
+ close() {
60547
+ return closeSmtpServer(server);
60548
+ }
60549
+ };
60550
+ }
60551
+
60487
60552
  // src/render-context.ts
60488
60553
  var RUNTIME_TARGET_KEY = "__ELIT_RUNTIME_TARGET__";
60489
60554
  var CAPTURED_RENDER_KEY = "__ELIT_CAPTURED_RENDER__";
@@ -60937,6 +61002,7 @@ var DomNode = class {
60937
61002
  html += `</${tagName}>${newLine}`;
60938
61003
  return html;
60939
61004
  }
61005
+ const isRawText = tagName === "script" || tagName === "style";
60940
61006
  if (children && children.length > 0) {
60941
61007
  const resolvedChildren = children.map((c) => {
60942
61008
  const resolved = this.resolveStateValue(c);
@@ -60952,11 +61018,11 @@ var DomNode = class {
60952
61018
  if (Array.isArray(child)) {
60953
61019
  for (const c of child) {
60954
61020
  if (!shouldSkipChild(c)) {
60955
- html += this.renderToString(c, { pretty, indent: indent5 + 1 });
61021
+ html += isRawText && typeof c === "string" ? c : this.renderToString(c, { pretty, indent: indent5 + 1 });
60956
61022
  }
60957
61023
  }
60958
61024
  } else {
60959
- html += this.renderToString(child, { pretty, indent: indent5 + 1 });
61025
+ html += isRawText && typeof child === "string" ? child : this.renderToString(child, { pretty, indent: indent5 + 1 });
60960
61026
  }
60961
61027
  }
60962
61028
  html += indentStr;
@@ -60966,11 +61032,11 @@ var DomNode = class {
60966
61032
  if (Array.isArray(child)) {
60967
61033
  for (const c of child) {
60968
61034
  if (!shouldSkipChild(c)) {
60969
- html += this.renderToString(c, { pretty: false, indent: 0 });
61035
+ html += isRawText && typeof c === "string" ? c : this.renderToString(c, { pretty: false, indent: 0 });
60970
61036
  }
60971
61037
  }
60972
61038
  } else {
60973
- html += this.renderToString(child, { pretty: false, indent: 0 });
61039
+ html += isRawText && typeof child === "string" ? child : this.renderToString(child, { pretty: false, indent: 0 });
60974
61040
  }
60975
61041
  }
60976
61042
  }
@@ -61898,6 +61964,56 @@ var defaultOptions = {
61898
61964
  worker: [],
61899
61965
  mode: "dev"
61900
61966
  };
61967
+ function createSmtpBindingKey(config) {
61968
+ return `${config.host}:${config.port}`;
61969
+ }
61970
+ function createSmtpServerLabel(config) {
61971
+ return config.label || createSmtpBindingKey(config);
61972
+ }
61973
+ function formatSmtpServerAddress(address, fallback) {
61974
+ if (typeof address === "string") {
61975
+ return address;
61976
+ }
61977
+ if (address) {
61978
+ return `${address.address}:${address.port}`;
61979
+ }
61980
+ return createSmtpBindingKey(fallback);
61981
+ }
61982
+ function collectSmtpServerConfigs(config, usesClientArray) {
61983
+ const modeLabel = config.mode || "dev";
61984
+ const smtpConfigs = normalizeSmtpServerConfigs(config.smtp).map((smtpConfig, index) => ({
61985
+ ...smtpConfig,
61986
+ label: smtpConfig.label || `${modeLabel}.smtp[${index}]`
61987
+ }));
61988
+ if (!usesClientArray || !config.clients) {
61989
+ return smtpConfigs;
61990
+ }
61991
+ for (let clientIndex = 0; clientIndex < config.clients.length; clientIndex += 1) {
61992
+ const client = config.clients[clientIndex];
61993
+ const clientDescriptor = client.basePath || client.root;
61994
+ const clientPrefix = clientDescriptor ? `${modeLabel}.clients[${clientIndex}] (${clientDescriptor})` : `${modeLabel}.clients[${clientIndex}]`;
61995
+ smtpConfigs.push(...normalizeSmtpServerConfigs(client.smtp).map((smtpConfig, smtpIndex) => ({
61996
+ ...smtpConfig,
61997
+ label: smtpConfig.label || `${clientPrefix}.smtp[${smtpIndex}]`
61998
+ })));
61999
+ }
62000
+ return smtpConfigs;
62001
+ }
62002
+ function assertUniqueSmtpServerBindings(configs) {
62003
+ const seenBindings = /* @__PURE__ */ new Map();
62004
+ for (const smtpConfig of configs) {
62005
+ if (smtpConfig.port === 0) {
62006
+ continue;
62007
+ }
62008
+ const bindingKey = createSmtpBindingKey(smtpConfig);
62009
+ const currentLabel = createSmtpServerLabel(smtpConfig);
62010
+ const previousLabel = seenBindings.get(bindingKey);
62011
+ if (previousLabel) {
62012
+ throw new Error(`Duplicate SMTP server binding "${bindingKey}" configured for ${previousLabel} and ${currentLabel}`);
62013
+ }
62014
+ seenBindings.set(bindingKey, currentLabel);
62015
+ }
62016
+ }
61901
62017
  function shouldUseClientFallbackRoot(primaryRoot, fallbackRoot, indexPath) {
61902
62018
  if (!fallbackRoot) {
61903
62019
  return false;
@@ -62032,6 +62148,7 @@ function createDevServer(options) {
62032
62148
  const globalWebSocketEndpoints = usesClientArray ? normalizeWebSocketEndpoints(config.ws) : [];
62033
62149
  const normalizedWebSocketEndpoints = [...normalizedClients.flatMap((client) => client.ws), ...globalWebSocketEndpoints];
62034
62150
  const seenWebSocketPaths = /* @__PURE__ */ new Set();
62151
+ const smtpServerConfigs = collectSmtpServerConfigs(config, usesClientArray);
62035
62152
  for (const endpoint of normalizedWebSocketEndpoints) {
62036
62153
  if (endpoint.path === ELIT_INTERNAL_WS_PATH) {
62037
62154
  throw new Error(`WebSocket path "${ELIT_INTERNAL_WS_PATH}" is reserved for Elit internals`);
@@ -62041,6 +62158,21 @@ function createDevServer(options) {
62041
62158
  }
62042
62159
  seenWebSocketPaths.add(endpoint.path);
62043
62160
  }
62161
+ assertUniqueSmtpServerBindings(smtpServerConfigs);
62162
+ const smtpServers = smtpServerConfigs.map((smtpConfig) => {
62163
+ const smtpServer = createSmtpServer(smtpConfig);
62164
+ const smtpLabel = createSmtpServerLabel(smtpServer.config);
62165
+ smtpServer.server.on("error", (error) => {
62166
+ console.error(`[SMTP] ${smtpLabel} error:`, error);
62167
+ });
62168
+ if (config.logging) {
62169
+ smtpServer.server.server.once("listening", () => {
62170
+ console.log(`[SMTP] ${smtpLabel} listening on ${formatSmtpServerAddress(smtpServer.address(), smtpServer.config)}`);
62171
+ });
62172
+ }
62173
+ smtpServer.listen();
62174
+ return smtpServer;
62175
+ });
62044
62176
  const globalProxyHandler = config.proxy ? createProxyHandler(config.proxy) : null;
62045
62177
  const server = createServer(async (req, res) => {
62046
62178
  const originalUrl = req.url || "/";
@@ -62581,6 +62713,15 @@ ${elitImportMap}`;
62581
62713
  if (config.logging) console.log("\n[Server] Shutting down...");
62582
62714
  transformCache.clear();
62583
62715
  if (watcher) await watcher.close();
62716
+ if (smtpServers.length > 0) {
62717
+ await Promise.all(smtpServers.map(async (smtpServer) => {
62718
+ try {
62719
+ await smtpServer.close();
62720
+ } catch (error) {
62721
+ console.error(`[SMTP] ${createSmtpServerLabel(smtpServer.config)} close error:`, error);
62722
+ }
62723
+ }));
62724
+ }
62584
62725
  if (webSocketServers.length > 0) {
62585
62726
  webSocketServers.forEach((wsServer) => wsServer.close());
62586
62727
  wsClients.clear();
@@ -62597,6 +62738,7 @@ ${elitImportMap}`;
62597
62738
  return {
62598
62739
  server,
62599
62740
  wss,
62741
+ smtpServers,
62600
62742
  url: primaryUrl,
62601
62743
  state: stateManager,
62602
62744
  close
@@ -74476,7 +74618,7 @@ var WAPK_AUTH_TAG_LENGTH = 16;
74476
74618
  var WAPK_SCRYPT_OPTIONS = { N: 16384, r: 8, p: 1 };
74477
74619
  var DEFAULT_GOOGLE_DRIVE_TOKEN_ENV = "GOOGLE_DRIVE_ACCESS_TOKEN";
74478
74620
  var DEFAULT_WAPK_ONLINE_URL_ENV = "ELIT_WAPK_ONLINE_URL";
74479
- var DEFAULT_WAPK_ONLINE_URLS = ["https://wapk.d-osc.com/"];
74621
+ var DEFAULT_WAPK_ONLINE_URLS = ["http://wapk.d-osc.com/"];
74480
74622
  var WAPK_ONLINE_CREATE_PATH = "/api/shared-session/create";
74481
74623
  var WAPK_ONLINE_READ_PATH = "/api/shared-session/read";
74482
74624
  var WAPK_ONLINE_CLOSE_PATH = "/api/shared-session/close";
@@ -74514,6 +74656,113 @@ function normalizeNonEmptyString(value) {
74514
74656
  const normalized = value.trim();
74515
74657
  return normalized.length > 0 ? normalized : void 0;
74516
74658
  }
74659
+ function normalizeGeneratedIdentifier(value) {
74660
+ const normalized = value.normalize("NFKD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
74661
+ return normalized.length > 0 ? normalized : void 0;
74662
+ }
74663
+ function joinGeneratedIdentifier(...segments) {
74664
+ const normalizedSegments = segments.map((segment) => segment ? normalizeGeneratedIdentifier(segment) : void 0).filter((segment) => Boolean(segment));
74665
+ return normalizedSegments.length > 0 ? normalizedSegments.join(".") : void 0;
74666
+ }
74667
+ function parseScopedPackageName(value) {
74668
+ const normalizedValue = normalizeNonEmptyString(value);
74669
+ if (!normalizedValue) {
74670
+ return {};
74671
+ }
74672
+ if (!normalizedValue.startsWith("@")) {
74673
+ return {
74674
+ packageName: normalizedValue
74675
+ };
74676
+ }
74677
+ const scopeSeparatorIndex = normalizedValue.indexOf("/");
74678
+ if (scopeSeparatorIndex === -1) {
74679
+ return {
74680
+ packageName: normalizedValue
74681
+ };
74682
+ }
74683
+ return {
74684
+ scope: normalizedValue.slice(1, scopeSeparatorIndex),
74685
+ packageName: normalizedValue.slice(scopeSeparatorIndex + 1)
74686
+ };
74687
+ }
74688
+ function readPackageAuthorMetadata(value) {
74689
+ if (typeof value === "string") {
74690
+ const normalizedValue = value.trim();
74691
+ if (!normalizedValue) {
74692
+ return {};
74693
+ }
74694
+ const email = normalizedValue.match(/<([^>]+)>/)?.[1];
74695
+ const url = normalizedValue.match(/\(([^)]+)\)/)?.[1];
74696
+ const name = normalizedValue.replace(/<[^>]+>/g, " ").replace(/\([^)]+\)/g, " ").replace(/\s+/g, " ").trim();
74697
+ return {
74698
+ name: normalizeNonEmptyString(name),
74699
+ email: normalizeNonEmptyString(email),
74700
+ url: normalizeNonEmptyString(url)
74701
+ };
74702
+ }
74703
+ if (!isRecord(value)) {
74704
+ return {};
74705
+ }
74706
+ return {
74707
+ name: normalizeNonEmptyString(value.name),
74708
+ email: normalizeNonEmptyString(value.email),
74709
+ url: normalizeNonEmptyString(value.url)
74710
+ };
74711
+ }
74712
+ function extractPublisherIdFromRepository(value) {
74713
+ const repositoryUrl = typeof value === "string" ? value : isRecord(value) ? normalizeNonEmptyString(value.url) : void 0;
74714
+ const normalizedRepositoryUrl = normalizeNonEmptyString(repositoryUrl);
74715
+ if (!normalizedRepositoryUrl) {
74716
+ return void 0;
74717
+ }
74718
+ const shorthandMatch = normalizedRepositoryUrl.match(/^(?:github|gitlab|bitbucket):([^/]+)\/.+$/i);
74719
+ if (shorthandMatch?.[1]) {
74720
+ return normalizeGeneratedIdentifier(shorthandMatch[1]);
74721
+ }
74722
+ const sshMatch = normalizedRepositoryUrl.match(/^[^@]+@[^:]+:([^/]+)\/.+$/i);
74723
+ if (sshMatch?.[1]) {
74724
+ return normalizeGeneratedIdentifier(sshMatch[1]);
74725
+ }
74726
+ try {
74727
+ const parsed = new URL(normalizedRepositoryUrl.replace(/^git\+/, ""));
74728
+ const firstPathSegment = parsed.pathname.replace(/\.git$/i, "").split("/").filter(Boolean)[0];
74729
+ return normalizeGeneratedIdentifier(firstPathSegment ?? parsed.hostname.replace(/^www\./i, ""));
74730
+ } catch {
74731
+ return void 0;
74732
+ }
74733
+ }
74734
+ function extractPublisherIdFromUrl(value) {
74735
+ const normalizedValue = normalizeNonEmptyString(value);
74736
+ if (!normalizedValue) {
74737
+ return void 0;
74738
+ }
74739
+ try {
74740
+ const parsed = new URL(normalizedValue);
74741
+ return normalizeGeneratedIdentifier(parsed.hostname.replace(/^www\./i, ""));
74742
+ } catch {
74743
+ return void 0;
74744
+ }
74745
+ }
74746
+ function extractPublisherIdFromEmail(value) {
74747
+ const normalizedValue = normalizeNonEmptyString(value);
74748
+ if (!normalizedValue) {
74749
+ return void 0;
74750
+ }
74751
+ const domain = normalizedValue.split("@")[1];
74752
+ return domain ? normalizeGeneratedIdentifier(domain.replace(/^www\./i, "")) : void 0;
74753
+ }
74754
+ function resolveAutoGeneratedWapkAppId(packageName, fallbackName) {
74755
+ const scopedPackage = parseScopedPackageName(packageName);
74756
+ return joinGeneratedIdentifier(scopedPackage.scope, scopedPackage.packageName ?? fallbackName);
74757
+ }
74758
+ function resolveAutoGeneratedWapkPublisherId(packageJson, fallbackName) {
74759
+ const scopedPackage = parseScopedPackageName(typeof packageJson?.name === "string" ? packageJson.name : void 0);
74760
+ if (scopedPackage.scope) {
74761
+ return normalizeGeneratedIdentifier(scopedPackage.scope);
74762
+ }
74763
+ const author = readPackageAuthorMetadata(packageJson?.author);
74764
+ return extractPublisherIdFromRepository(packageJson?.repository) ?? extractPublisherIdFromUrl(packageJson?.homepage) ?? extractPublisherIdFromUrl(author.url) ?? extractPublisherIdFromEmail(author.email) ?? normalizeGeneratedIdentifier(author.name ?? fallbackName);
74765
+ }
74517
74766
  function normalizeStringMap(value) {
74518
74767
  if (!isRecord(value)) {
74519
74768
  return void 0;
@@ -74553,6 +74802,8 @@ function normalizeWapkConfig(value) {
74553
74802
  runtime: normalizeRuntime(value.runtime ?? value.engine),
74554
74803
  entry: typeof value.entry === "string" ? value.entry : void 0,
74555
74804
  scripts: normalizeStringMap(value.scripts ?? value.script),
74805
+ appId: normalizeNonEmptyString(value.appId),
74806
+ publisherId: normalizeNonEmptyString(value.publisherId),
74556
74807
  port: normalizePort(value.port),
74557
74808
  env: normalizeStringMap(value.env),
74558
74809
  desktop: normalizeDesktopConfig(value.desktop),
@@ -74842,6 +75093,7 @@ async function readWapkProjectConfig(directory) {
74842
75093
  const elitConfig = await loadConfig(directory);
74843
75094
  const elitWapkConfig = normalizeWapkConfig(elitConfig?.wapk);
74844
75095
  const packageJson = readJsonFile(packageJsonPath);
75096
+ const packageJsonWapk = isRecord(packageJson?.wapk) ? packageJson.wapk : void 0;
74845
75097
  const packageScripts = normalizeStringMap(packageJson?.scripts) ?? {};
74846
75098
  const selectedScripts = elitWapkConfig.scripts ?? packageScripts;
74847
75099
  const inferred = inferRuntimeAndEntryFromScript(selectedScripts.start ?? packageScripts.start);
@@ -74865,12 +75117,16 @@ async function readWapkProjectConfig(directory) {
74865
75117
  if (!(0, import_node_fs2.existsSync)(entryPath) || !(0, import_node_fs2.statSync)(entryPath).isFile()) {
74866
75118
  throw new Error(`WAPK entry not found: ${entryPath}`);
74867
75119
  }
75120
+ const appId = elitWapkConfig.appId ?? normalizeNonEmptyString(packageJson?.appId) ?? normalizeNonEmptyString(packageJsonWapk?.appId) ?? resolveAutoGeneratedWapkAppId(typeof packageJson?.name === "string" ? packageJson.name : void 0, name);
75121
+ const publisherId = elitWapkConfig.publisherId ?? normalizeNonEmptyString(packageJson?.publisherId) ?? normalizeNonEmptyString(packageJsonWapk?.publisherId) ?? resolveAutoGeneratedWapkPublisherId(packageJson, name);
74868
75122
  return {
74869
75123
  name,
74870
75124
  version,
74871
75125
  runtime: runtime2,
74872
75126
  entry,
74873
75127
  scripts: selectedScripts,
75128
+ appId,
75129
+ publisherId,
74874
75130
  port: elitWapkConfig.port,
74875
75131
  env: elitWapkConfig.env,
74876
75132
  desktop: elitWapkConfig.desktop,
@@ -74880,11 +75136,14 @@ async function readWapkProjectConfig(directory) {
74880
75136
  function readIgnorePatterns(directory) {
74881
75137
  return readLineIgnorePatterns((0, import_node_path2.join)(directory, ".wapkignore"));
74882
75138
  }
75139
+ function parsePatternLines(content) {
75140
+ return content.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0 && (!line.startsWith("#") || line.startsWith("\\#")));
75141
+ }
74883
75142
  function readLineIgnorePatterns(filePath) {
74884
75143
  if (!(0, import_node_fs2.existsSync)(filePath)) {
74885
75144
  return [];
74886
75145
  }
74887
- return (0, import_node_fs2.readFileSync)(filePath, "utf8").split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0 && (!line.startsWith("#") || line.startsWith("\\#")));
75146
+ return parsePatternLines((0, import_node_fs2.readFileSync)(filePath, "utf8"));
74888
75147
  }
74889
75148
  function normalizePackageEntry(value) {
74890
75149
  const normalized = normalizeNonEmptyString(value)?.replace(/^[.][\\/]/, "").split("\\").join("/");
@@ -75163,6 +75422,91 @@ function shouldIgnore(relativePath2, ignorePatterns, isDirectory) {
75163
75422
  }
75164
75423
  return ignored;
75165
75424
  }
75425
+ function matchesPatchPattern(relativePath2, pattern) {
75426
+ const normalizedRule = normalizeIgnorePattern(pattern);
75427
+ if (!normalizedRule) {
75428
+ return false;
75429
+ }
75430
+ const normalizedPath = relativePath2.replace(/\\/g, "/");
75431
+ const subtreeSelector = normalizedRule.pattern.endsWith("/*") && !normalizedRule.pattern.slice(0, -2).includes("*") && !normalizedRule.pattern.slice(0, -2).includes("?");
75432
+ if (subtreeSelector) {
75433
+ const directoryPath = normalizedRule.pattern.slice(0, -2);
75434
+ return directoryPath.length > 0 && normalizedPath.startsWith(`${directoryPath}/`);
75435
+ }
75436
+ const hasGlob = /[*?]/.test(normalizedRule.pattern);
75437
+ if (!hasGlob) {
75438
+ if (normalizedRule.directoryOnly) {
75439
+ return normalizedPath === normalizedRule.pattern || normalizedPath.startsWith(`${normalizedRule.pattern}/`);
75440
+ }
75441
+ return normalizedPath === normalizedRule.pattern;
75442
+ }
75443
+ return globPatternToRegex(normalizedRule.pattern, {
75444
+ directoryOnly: normalizedRule.directoryOnly,
75445
+ matchSegmentsOnly: false
75446
+ }).test(normalizedPath);
75447
+ }
75448
+ function shouldPatchArchivePath(relativePath2, patchPatterns) {
75449
+ let selected = false;
75450
+ for (const pattern of patchPatterns) {
75451
+ const normalizedRule = normalizeIgnorePattern(pattern);
75452
+ if (!normalizedRule) {
75453
+ continue;
75454
+ }
75455
+ if (!matchesPatchPattern(relativePath2, pattern)) {
75456
+ continue;
75457
+ }
75458
+ selected = !normalizedRule.negate;
75459
+ }
75460
+ return selected;
75461
+ }
75462
+ function resolvePatchManifestPatterns(files) {
75463
+ const patchManifest = files.find((file) => file.path === ".wapkpatch");
75464
+ if (!patchManifest) {
75465
+ throw new Error("Patch archive must include a .wapkpatch manifest file.");
75466
+ }
75467
+ const patterns = parsePatternLines(patchManifest.content.toString("utf8"));
75468
+ if (patterns.length === 0) {
75469
+ throw new Error("Patch archive .wapkpatch must define at least one patch rule.");
75470
+ }
75471
+ return patterns;
75472
+ }
75473
+ function applyPatchEntriesToFiles(targetFiles, patchFiles) {
75474
+ const fileMap = new Map(targetFiles.map((file) => [file.path, file]));
75475
+ const fileOrder = targetFiles.map((file) => file.path);
75476
+ const addedPaths = [];
75477
+ const updatedPaths = [];
75478
+ const unchangedPaths = [];
75479
+ for (const patchFile of [...patchFiles].sort((left, right) => left.path.localeCompare(right.path))) {
75480
+ const existing = fileMap.get(patchFile.path);
75481
+ const nextEntry = {
75482
+ path: patchFile.path,
75483
+ content: Buffer.from(patchFile.content),
75484
+ mode: patchFile.mode
75485
+ };
75486
+ if (!existing) {
75487
+ addedPaths.push(patchFile.path);
75488
+ fileOrder.push(patchFile.path);
75489
+ } else if (existing.mode === patchFile.mode && existing.content.equals(patchFile.content)) {
75490
+ unchangedPaths.push(patchFile.path);
75491
+ } else {
75492
+ updatedPaths.push(patchFile.path);
75493
+ }
75494
+ fileMap.set(patchFile.path, nextEntry);
75495
+ }
75496
+ return {
75497
+ files: fileOrder.map((filePath) => {
75498
+ const file = fileMap.get(filePath);
75499
+ if (!file) {
75500
+ throw new Error(`Internal WAPK patch error: missing file entry for ${filePath}`);
75501
+ }
75502
+ return file;
75503
+ }),
75504
+ patchedPaths: [...updatedPaths, ...addedPaths],
75505
+ addedPaths,
75506
+ updatedPaths,
75507
+ unchangedPaths
75508
+ };
75509
+ }
75166
75510
  function collectFiles(directory, baseDirectory, ignorePatterns) {
75167
75511
  const files = [];
75168
75512
  const entries = (0, import_node_fs2.readdirSync)(directory, { withFileTypes: true });
@@ -75318,6 +75662,8 @@ function decodeWapkPayload(buffer) {
75318
75662
  runtime: normalizeRuntime(rawHeader.runtime ?? rawHeader.engine) ?? "node",
75319
75663
  entry: typeof rawHeader.entry === "string" ? rawHeader.entry : "index.js",
75320
75664
  scripts: normalizeStringMap(rawHeader.scripts) ?? {},
75665
+ appId: normalizeNonEmptyString(rawHeader.appId),
75666
+ publisherId: normalizeNonEmptyString(rawHeader.publisherId),
75321
75667
  port: normalizePort(rawHeader.port),
75322
75668
  env: normalizeStringMap(rawHeader.env),
75323
75669
  desktop: normalizeDesktopConfig(rawHeader.desktop),
@@ -75815,11 +76161,14 @@ function sanitizeOnlineArchiveFileName(label, fallback) {
75815
76161
  const fileName = sanitized.length > 0 ? sanitized : "app.wapk";
75816
76162
  return fileName.toLowerCase().endsWith(".wapk") ? fileName : `${fileName}.wapk`;
75817
76163
  }
76164
+ var WAPK_ONLINE_JOIN_SOURCE_QUERY_PARAM = "launchSource";
76165
+ var WAPK_ONLINE_JOIN_SOURCE_QUERY_VALUE = "elit-wapk-online";
75818
76166
  function buildOnlineJoinUrl(baseUrl, joinKey) {
75819
76167
  const joinUrl = new URL(baseUrl.toString());
75820
76168
  joinUrl.search = "";
75821
76169
  joinUrl.hash = "";
75822
76170
  joinUrl.searchParams.set("join", joinKey);
76171
+ joinUrl.searchParams.set(WAPK_ONLINE_JOIN_SOURCE_QUERY_PARAM, WAPK_ONLINE_JOIN_SOURCE_QUERY_VALUE);
75823
76172
  return joinUrl.toString();
75824
76173
  }
75825
76174
  async function probeOnlineLauncherUrl(url) {
@@ -76000,11 +76349,16 @@ async function closeWapkOnlineSharedSession(launcherUrl, session) {
76000
76349
  function isPmWapkOnlineShutdownEnabled() {
76001
76350
  return process.env[WAPK_ONLINE_PM_SHUTDOWN_ENV] === "1" && Boolean(process.stdin) && !process.stdin.isTTY;
76002
76351
  }
76003
- async function waitForWapkOnlineSessionShutdown(launcherUrl, session, archiveHandle, lock) {
76352
+ function getWapkOnlineProcessDetails() {
76353
+ return `pid ${process.pid}, ppid ${process.ppid}`;
76354
+ }
76355
+ async function waitForWapkOnlineSessionShutdown(launcherUrl, session, archiveHandle, lock, options = {}) {
76004
76356
  let snapshotRevision = 0;
76005
76357
  let snapshotSyncPending = false;
76006
76358
  let snapshotSyncPromise = Promise.resolve();
76007
76359
  let lastSnapshotSyncError = null;
76360
+ const allowSigtermClose = options.allowSigtermClose === true;
76361
+ const processDetails = getWapkOnlineProcessDetails();
76008
76362
  const syncGuestSnapshotUpdates = () => {
76009
76363
  if (snapshotSyncPending) {
76010
76364
  return snapshotSyncPromise;
@@ -76038,6 +76392,7 @@ async function waitForWapkOnlineSessionShutdown(launcherUrl, session, archiveHan
76038
76392
  void syncGuestSnapshotUpdates();
76039
76393
  }, WAPK_ONLINE_KEEPALIVE_INTERVAL_MS);
76040
76394
  const pmManaged = isPmWapkOnlineShutdownEnabled();
76395
+ let ignoredSigTermLogged = false;
76041
76396
  let stdinBuffer = "";
76042
76397
  const cleanup = () => {
76043
76398
  clearInterval(keepAlive);
@@ -76056,7 +76411,17 @@ async function waitForWapkOnlineSessionShutdown(launcherUrl, session, archiveHan
76056
76411
  finish({ kind: "signal", signal: "SIGINT" });
76057
76412
  };
76058
76413
  const onSigTerm = () => {
76059
- finish({ kind: "signal", signal: "SIGTERM" });
76414
+ if (allowSigtermClose) {
76415
+ finish({ kind: "signal", signal: "SIGTERM" });
76416
+ return;
76417
+ }
76418
+ if (ignoredSigTermLogged) {
76419
+ return;
76420
+ }
76421
+ ignoredSigTermLogged = true;
76422
+ console.warn(
76423
+ 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.`
76424
+ );
76060
76425
  };
76061
76426
  const onStdinData = (chunk) => {
76062
76427
  stdinBuffer += typeof chunk === "string" ? chunk : chunk.toString("utf8");
@@ -76081,9 +76446,12 @@ async function waitForWapkOnlineSessionShutdown(launcherUrl, session, archiveHan
76081
76446
  if (shutdownTrigger.kind === "pm") {
76082
76447
  console.log(`
76083
76448
  [wapk] PM requested shutdown for shared session ${session.joinKey}...`);
76449
+ } else if (shutdownTrigger.signal === "SIGTERM") {
76450
+ console.log(`
76451
+ [wapk] Received SIGTERM for shared session ${session.joinKey} (${processDetails}); closing because --allow-sigterm-close is enabled...`);
76084
76452
  } else {
76085
76453
  console.log(`
76086
- [wapk] Closing shared session ${session.joinKey}...`);
76454
+ [wapk] Received ${shutdownTrigger.signal}; closing shared session ${session.joinKey}...`);
76087
76455
  }
76088
76456
  try {
76089
76457
  await closeWapkOnlineSharedSession(launcherUrl, session);
@@ -76123,7 +76491,9 @@ async function runWapkOnline(archiveSpecifier, options) {
76123
76491
  process.exitCode = await waitForWapkOnlineSessionShutdown(launcherUrl, {
76124
76492
  joinKey: response.joinKey,
76125
76493
  adminToken: response.adminToken
76126
- }, archiveHandle, onlineArchiveLock);
76494
+ }, archiveHandle, onlineArchiveLock, {
76495
+ allowSigtermClose: options.allowSigtermClose
76496
+ });
76127
76497
  }
76128
76498
  async function writeWapkArchiveFromMemory(archiveHandle, header, files, lock) {
76129
76499
  const updatedHeader = {
@@ -76380,6 +76750,8 @@ async function packWapkDirectory(directory, options = {}) {
76380
76750
  runtime: config.runtime,
76381
76751
  entry: config.entry,
76382
76752
  scripts: config.scripts,
76753
+ appId: config.appId,
76754
+ publisherId: config.publisherId,
76383
76755
  port: config.port,
76384
76756
  env: config.env,
76385
76757
  desktop: config.desktop,
@@ -76411,6 +76783,56 @@ function extractWapkArchive(wapkPath, outputDir = ".", options = {}) {
76411
76783
  console.log(`Extracted ${archive.files.length} files to: ${extractDirectory}`);
76412
76784
  return extractDirectory;
76413
76785
  }
76786
+ async function patchWapkArchive(wapkPath, options) {
76787
+ const targetHandle = resolveArchiveHandle(wapkPath);
76788
+ const targetSnapshot = await targetHandle.readSnapshot();
76789
+ const targetEnvelope = parseWapkEnvelope(targetSnapshot.buffer);
76790
+ const targetArchive = decodeWapk(targetSnapshot.buffer, options);
76791
+ const targetLock = targetEnvelope.version === WAPK_LOCKED_VERSION ? resolveArchiveCredentials(options) : void 0;
76792
+ const patchArchive = readWapkArchive(options.from, {
76793
+ password: options.fromPassword ?? options.password
76794
+ });
76795
+ const patchPatterns = resolvePatchManifestPatterns(patchArchive.files);
76796
+ const selectedPatchFiles = patchArchive.files.filter((file) => file.path !== ".wapkpatch").filter((file) => shouldPatchArchivePath(file.path, patchPatterns));
76797
+ const patchResult = applyPatchEntriesToFiles(targetArchive.files, selectedPatchFiles);
76798
+ console.log(`[wapk] Target: ${targetSnapshot.label ?? targetHandle.label}`);
76799
+ console.log(`[wapk] Patch: ${options.from}`);
76800
+ console.log(`[wapk] Rules: ${patchPatterns.length}`);
76801
+ if (selectedPatchFiles.length === 0) {
76802
+ console.log("[wapk] No files matched .wapkpatch. Archive was not modified.");
76803
+ return {
76804
+ archiveLabel: targetSnapshot.label ?? targetHandle.label,
76805
+ patchedPaths: [],
76806
+ addedPaths: [],
76807
+ updatedPaths: [],
76808
+ unchangedPaths: []
76809
+ };
76810
+ }
76811
+ if (patchResult.patchedPaths.length === 0) {
76812
+ console.log("[wapk] Matching patch files were already up to date. Archive was not modified.");
76813
+ return {
76814
+ archiveLabel: targetSnapshot.label ?? targetHandle.label,
76815
+ patchedPaths: [],
76816
+ addedPaths: [],
76817
+ updatedPaths: [],
76818
+ unchangedPaths: patchResult.unchangedPaths
76819
+ };
76820
+ }
76821
+ const writeResult = await writeWapkArchiveFromMemory(
76822
+ targetHandle,
76823
+ targetArchive.header,
76824
+ patchResult.files,
76825
+ targetLock
76826
+ );
76827
+ console.log(`[wapk] Applied ${patchResult.patchedPaths.length} patch file${patchResult.patchedPaths.length === 1 ? "" : "s"}.`);
76828
+ return {
76829
+ archiveLabel: writeResult.label,
76830
+ patchedPaths: patchResult.patchedPaths,
76831
+ addedPaths: patchResult.addedPaths,
76832
+ updatedPaths: patchResult.updatedPaths,
76833
+ unchangedPaths: patchResult.unchangedPaths
76834
+ };
76835
+ }
76414
76836
  async function prepareWapkApp(wapkPath, options = {}) {
76415
76837
  const archiveHandle = resolveArchiveHandle(wapkPath, options.googleDrive);
76416
76838
  const archivePath = archiveHandle.identifier;
@@ -76519,6 +76941,8 @@ function inspectWapkArchive(wapkPath, options = {}) {
76519
76941
  console.log(`App: ${decoded.header.version}`);
76520
76942
  console.log(`Runtime: ${decoded.header.runtime}`);
76521
76943
  console.log(`Entry: ${decoded.header.entry}`);
76944
+ console.log(`App ID: ${decoded.header.appId ?? "n/a"}`);
76945
+ console.log(`Publisher:${decoded.header.publisherId ? ` ${decoded.header.publisherId}` : " n/a"}`);
76522
76946
  console.log(`Port: ${decoded.header.port ?? "default"}`);
76523
76947
  console.log(`Created: ${decoded.header.createdAt}`);
76524
76948
  if (decoded.header.env && Object.keys(decoded.header.env).length > 0) {
@@ -76550,6 +76974,8 @@ function printWapkHelp() {
76550
76974
  " elit wapk gdrive://<fileId> --online",
76551
76975
  " elit wapk pack [directory]",
76552
76976
  " elit wapk pack [directory] --password secret-123",
76977
+ " elit wapk patch <file.wapk> --from <patch.wapk>",
76978
+ " elit wapk patch <file.wapk> --use <patch.wapk>",
76553
76979
  " elit wapk inspect <file.wapk>",
76554
76980
  " elit wapk extract <file.wapk>",
76555
76981
  "",
@@ -76561,24 +76987,32 @@ function printWapkHelp() {
76561
76987
  " --archive-watch Pull external archive changes back into the temp workdir",
76562
76988
  " --no-archive-watch Disable external archive read sync",
76563
76989
  " --online Create an Elit Run share session, stay alive, and close on Ctrl+C",
76990
+ " --allow-sigterm-close Allow SIGTERM to close an online shared session",
76564
76991
  " --online-url <url> Elit Run URL (default: auto-detect localhost:4177 or localhost:4179)",
76565
76992
  " --google-drive-file-id <id> Run a remote .wapk directly from Google Drive",
76566
76993
  " --google-drive-token-env <name> Env var containing the Google Drive OAuth token",
76567
76994
  " --google-drive-access-token <value> OAuth token for Google Drive API calls",
76568
76995
  " --google-drive-shared-drive Include supportsAllDrives=true for shared drives",
76996
+ " --from <file.wapk> Patch source archive for elit wapk patch",
76997
+ " --use <file.wapk> Alias for --from",
76998
+ " --from-password <value> Password for unlocking the patch archive",
76569
76999
  " --include-deps Legacy compatibility flag; node_modules are packed by default",
76570
77000
  " --password <value> Password for locking or unlocking the archive",
76571
77001
  " -h, --help Show this help",
76572
77002
  "",
76573
77003
  "Notes:",
76574
77004
  " - Pack reads wapk from elit.config.* and falls back to package.json.",
77005
+ " - If appId or publisherId is not configured, pack auto-generates stable defaults from package metadata.",
76575
77006
  " - Pack includes node_modules by default; use .wapkignore if you need to exclude them, and !pattern to re-include later matches.",
77007
+ " - Patch reads .wapkpatch from the patch archive and applies only matching archive-relative paths.",
77008
+ " - Patch keeps the target archive metadata and lock mode; use --from-password when the patch archive uses a different password.",
76576
77009
  " - Run never installs dependencies automatically; archives must include the runtime dependencies they need.",
76577
77010
  " - Run mode can read config.wapk.run for default file/runtime/live-sync options.",
76578
77011
  " - Browser-style archives with scripts.start or wapk.script.start run that start script automatically.",
76579
77012
  " - Run mode keeps files in RAM and syncs changes both to and from the archive source.",
76580
77013
  " - Google Drive mode talks to the Drive API directly; no local archive file is required.",
76581
77014
  " - Online mode creates a shared session on Elit Run directly, keeps the CLI alive, and closes it on Ctrl+C.",
77015
+ " - Online mode ignores SIGTERM by default; pass --allow-sigterm-close if an external supervisor should close the shared session with SIGTERM.",
76582
77016
  " - Locked archives in online mode must provide --password so the CLI can build the shared snapshot.",
76583
77017
  " - Locked archives require the same password for run/extract/inspect.",
76584
77018
  " - Archives stay unlocked by default unless a password is provided.",
@@ -76628,6 +77062,7 @@ function parseRunArgs(args) {
76628
77062
  let archiveSyncInterval;
76629
77063
  let online;
76630
77064
  let onlineUrl;
77065
+ let allowSigtermClose;
76631
77066
  let password;
76632
77067
  for (let index = 0; index < args.length; index++) {
76633
77068
  const arg = args[index];
@@ -76679,6 +77114,10 @@ function parseRunArgs(args) {
76679
77114
  onlineUrl = readRequiredOptionValue(args, ++index, "--online-url");
76680
77115
  break;
76681
77116
  }
77117
+ case "--allow-sigterm-close": {
77118
+ allowSigtermClose = true;
77119
+ break;
77120
+ }
76682
77121
  case "--google-drive-file-id": {
76683
77122
  googleDrive = {
76684
77123
  ...googleDrive,
@@ -76721,7 +77160,7 @@ function parseRunArgs(args) {
76721
77160
  break;
76722
77161
  }
76723
77162
  }
76724
- return { file, googleDrive, runtime: runtime2, syncInterval, useWatcher, watchArchive, archiveSyncInterval, online, onlineUrl, password };
77163
+ return { file, googleDrive, runtime: runtime2, syncInterval, useWatcher, watchArchive, archiveSyncInterval, online, onlineUrl, allowSigtermClose, password };
76725
77164
  }
76726
77165
  function parsePackArgs(args) {
76727
77166
  let directory = ".";
@@ -76747,6 +77186,46 @@ function parsePackArgs(args) {
76747
77186
  }
76748
77187
  return { directory, includeDeps, password };
76749
77188
  }
77189
+ function parsePatchArgs(args) {
77190
+ let file;
77191
+ let from;
77192
+ let password;
77193
+ let fromPassword;
77194
+ for (let index = 0; index < args.length; index++) {
77195
+ const arg = args[index];
77196
+ switch (arg) {
77197
+ case "--from":
77198
+ case "--use": {
77199
+ if (from) {
77200
+ throw new Error("WAPK patch accepts exactly one patch archive via --from or --use.");
77201
+ }
77202
+ from = readRequiredOptionValue(args, ++index, arg);
77203
+ break;
77204
+ }
77205
+ case "--password": {
77206
+ password = readRequiredOptionValue(args, ++index, "--password");
77207
+ break;
77208
+ }
77209
+ case "--from-password": {
77210
+ fromPassword = readRequiredOptionValue(args, ++index, "--from-password");
77211
+ break;
77212
+ }
77213
+ default:
77214
+ if (arg.startsWith("-")) {
77215
+ throw new Error(`Unknown WAPK option: ${arg}`);
77216
+ }
77217
+ if (file) {
77218
+ throw new Error("Usage: elit wapk patch <file.wapk> --from <patch.wapk>");
77219
+ }
77220
+ file = arg;
77221
+ break;
77222
+ }
77223
+ }
77224
+ if (!file || !from) {
77225
+ throw new Error("Usage: elit wapk patch <file.wapk> --from <patch.wapk>");
77226
+ }
77227
+ return { file, from, password, fromPassword };
77228
+ }
76750
77229
  async function readConfiguredWapkRunDefaults(cwd) {
76751
77230
  const config = await loadConfig(cwd);
76752
77231
  const runConfig = normalizeWapkRunConfig(config?.wapk?.run);
@@ -76786,6 +77265,7 @@ function resolveConfiguredWapkRunOptions(options, defaults) {
76786
77265
  archiveSyncInterval: options.archiveSyncInterval ?? defaults?.archiveSyncInterval,
76787
77266
  online: options.online ?? defaults?.online ?? Boolean(onlineUrl),
76788
77267
  onlineUrl,
77268
+ allowSigtermClose: options.allowSigtermClose === true,
76789
77269
  password: options.password ?? defaults?.password
76790
77270
  };
76791
77271
  }
@@ -76818,6 +77298,15 @@ async function runWapkCommand(args, cwd = process.cwd()) {
76818
77298
  });
76819
77299
  return;
76820
77300
  }
77301
+ if (args[0] === "patch") {
77302
+ const options = parsePatchArgs(args.slice(1));
77303
+ await patchWapkArchive(options.file, {
77304
+ from: options.from,
77305
+ password: options.password,
77306
+ fromPassword: options.fromPassword
77307
+ });
77308
+ return;
77309
+ }
76821
77310
  if (args[0] === "inspect") {
76822
77311
  const options = parseArchiveAccessArgs(args.slice(1), "Usage: elit wapk inspect <file.wapk>");
76823
77312
  inspectWapkArchive(options.file, options);
@@ -76846,6 +77335,7 @@ async function runWapkCommand(args, cwd = process.cwd()) {
76846
77335
  await runWapkOnline(archiveSpecifier, {
76847
77336
  googleDrive: runOptions.googleDrive,
76848
77337
  onlineUrl: runOptions.onlineUrl,
77338
+ allowSigtermClose: runOptions.allowSigtermClose,
76849
77339
  password: runOptions.password
76850
77340
  });
76851
77341
  return;
@@ -83169,6 +83659,7 @@ WAPK Options:
83169
83659
  elit wapk run [file.wapk] Run a packaged app or the configured default archive
83170
83660
  elit wapk run --google-drive-file-id <id> Run a packaged app directly from Google Drive
83171
83661
  elit wapk pack [directory] Pack a directory into a .wapk archive
83662
+ elit wapk patch <file.wapk> --from <patch.wapk> Apply a manifest-driven patch archive
83172
83663
  elit wapk inspect <file.wapk> Inspect a .wapk archive
83173
83664
  elit wapk extract <file.wapk> Extract a .wapk archive
83174
83665
  elit wapk --runtime node|bun|deno [file] Override the packaged runtime