elit 3.6.4 → 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.
Files changed (52) hide show
  1. package/Cargo.lock +1 -1
  2. package/Cargo.toml +1 -1
  3. package/README.md +14 -1
  4. package/dist/build.d.ts +4 -1
  5. package/dist/cli.cjs +746 -166
  6. package/dist/cli.mjs +746 -166
  7. package/dist/config.d.ts +8 -1
  8. package/dist/coverage.d.ts +4 -1
  9. package/dist/desktop-auto-render.cjs +5 -4
  10. package/dist/desktop-auto-render.d.ts +4 -1
  11. package/dist/desktop-auto-render.js +5 -4
  12. package/dist/desktop-auto-render.mjs +5 -4
  13. package/dist/dom.cjs +5 -4
  14. package/dist/dom.d.ts +2 -0
  15. package/dist/dom.js +5 -4
  16. package/dist/dom.mjs +5 -4
  17. package/dist/el.d.ts +2 -0
  18. package/dist/index.cjs +5 -4
  19. package/dist/index.d.ts +2 -0
  20. package/dist/index.js +5 -4
  21. package/dist/index.mjs +5 -4
  22. package/dist/native.cjs +5 -4
  23. package/dist/native.d.ts +2 -0
  24. package/dist/native.js +5 -4
  25. package/dist/native.mjs +5 -4
  26. package/dist/render-context.d.ts +4 -1
  27. package/dist/router.cjs +5 -4
  28. package/dist/router.d.ts +2 -0
  29. package/dist/router.js +5 -4
  30. package/dist/router.mjs +5 -4
  31. package/dist/{server-CcBFc2F5.d.ts → server-uMQvZAll.d.ts} +9 -0
  32. package/dist/server.cjs +146 -4
  33. package/dist/server.d.ts +4 -1
  34. package/dist/server.js +4494 -285
  35. package/dist/server.mjs +146 -4
  36. package/dist/smtp-server.cjs +115 -0
  37. package/dist/smtp-server.d.ts +41 -0
  38. package/dist/smtp-server.js +4186 -0
  39. package/dist/smtp-server.mjs +87 -0
  40. package/dist/state.cjs +5 -4
  41. package/dist/state.d.ts +2 -0
  42. package/dist/state.js +5 -4
  43. package/dist/state.mjs +5 -4
  44. package/dist/test-runtime.cjs +184 -141
  45. package/dist/test-runtime.js +193 -150
  46. package/dist/test-runtime.mjs +184 -141
  47. package/dist/test.cjs +143 -134
  48. package/dist/test.js +152 -143
  49. package/dist/test.mjs +143 -134
  50. package/dist/types.d.ts +34 -2
  51. package/dist/universal.d.ts +2 -0
  52. package/package.json +9 -1
package/dist/cli.cjs CHANGED
@@ -1992,8 +1992,8 @@ var require_chance = __commonJS({
1992
1992
  return i;
1993
1993
  });
1994
1994
  }
1995
- function testRange(test2, errorMessage) {
1996
- if (test2) {
1995
+ function testRange(test, errorMessage) {
1996
+ if (test) {
1997
1997
  throw new RangeError(errorMessage);
1998
1998
  }
1999
1999
  }
@@ -54898,12 +54898,126 @@ ${k.ValidationErrorsFormatter.format(i2)}`);
54898
54898
  });
54899
54899
 
54900
54900
  // src/test-runtime.ts
54901
- function escapeRegex(str) {
54901
+ function escapeRegex2(str) {
54902
54902
  return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
54903
54903
  }
54904
+ function resolveTestLoader(filePath) {
54905
+ return /\.(?:ts|tsx|mts|cts)$/i.test(filePath) ? "ts" : "js";
54906
+ }
54907
+ function createTestTransformOptions(filePath, format2, sourcemap) {
54908
+ return {
54909
+ loader: resolveTestLoader(filePath),
54910
+ format: format2,
54911
+ sourcemap,
54912
+ sourcefile: filePath,
54913
+ target: "es2020",
54914
+ tsconfigRaw: {
54915
+ compilerOptions: {
54916
+ jsx: "react",
54917
+ jsxFactory: "h",
54918
+ jsxFragmentFactory: "Fragment"
54919
+ }
54920
+ }
54921
+ };
54922
+ }
54923
+ function resolveExistingTestModulePath(basePath) {
54924
+ const nodePath = require("path");
54925
+ if (existsSync(basePath) && statSync(basePath).isFile()) {
54926
+ return basePath;
54927
+ }
54928
+ for (const extension of TEST_MODULE_EXTENSIONS) {
54929
+ const candidatePath = `${basePath}${extension}`;
54930
+ if (existsSync(candidatePath) && statSync(candidatePath).isFile()) {
54931
+ return candidatePath;
54932
+ }
54933
+ }
54934
+ if (existsSync(basePath) && statSync(basePath).isDirectory()) {
54935
+ const packageJsonPath = nodePath.join(basePath, "package.json");
54936
+ if (existsSync(packageJsonPath) && statSync(packageJsonPath).isFile()) {
54937
+ try {
54938
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
54939
+ for (const candidateEntry of [packageJson.main, packageJson.module]) {
54940
+ if (typeof candidateEntry !== "string" || candidateEntry.trim().length === 0) {
54941
+ continue;
54942
+ }
54943
+ try {
54944
+ return resolveExistingTestModulePath(nodePath.resolve(basePath, candidateEntry));
54945
+ } catch {
54946
+ continue;
54947
+ }
54948
+ }
54949
+ } catch {
54950
+ }
54951
+ }
54952
+ for (const extension of TEST_MODULE_EXTENSIONS) {
54953
+ const candidatePath = nodePath.join(basePath, `index${extension}`);
54954
+ if (existsSync(candidatePath) && statSync(candidatePath).isFile()) {
54955
+ return candidatePath;
54956
+ }
54957
+ }
54958
+ }
54959
+ return basePath;
54960
+ }
54961
+ function resolveTestModulePath(fromFilePath, specifier) {
54962
+ if (!specifier.startsWith(".") && !specifier.startsWith("/")) {
54963
+ return specifier;
54964
+ }
54965
+ const nodePath = require("path");
54966
+ const basePath = specifier.startsWith(".") ? nodePath.resolve(dirname(fromFilePath), specifier) : specifier;
54967
+ return resolveExistingTestModulePath(basePath);
54968
+ }
54969
+ function shouldTranspileTestModule(filePath) {
54970
+ return /\.(?:ts|tsx|mts|cts|js|jsx|mjs|cjs)$/i.test(filePath);
54971
+ }
54972
+ function createTestModuleRequire(fromFilePath, moduleCache) {
54973
+ return (specifier) => {
54974
+ if (specifier.startsWith("elit/") || specifier === "elit") {
54975
+ return require(specifier);
54976
+ }
54977
+ const resolvedPath = resolveTestModulePath(fromFilePath, specifier);
54978
+ if (resolvedPath === specifier) {
54979
+ return require(specifier);
54980
+ }
54981
+ if (!existsSync(resolvedPath) || !statSync(resolvedPath).isFile()) {
54982
+ return require(resolvedPath);
54983
+ }
54984
+ if (!shouldTranspileTestModule(resolvedPath)) {
54985
+ return require(resolvedPath);
54986
+ }
54987
+ return loadTranspiledTestModule(resolvedPath, moduleCache);
54988
+ };
54989
+ }
54990
+ function loadTranspiledTestModule(modulePath, moduleCache) {
54991
+ const cached = moduleCache.get(modulePath);
54992
+ if (cached) {
54993
+ return cached.exports;
54994
+ }
54995
+ const source = readFileSync(modulePath, "utf-8");
54996
+ let transpiled;
54997
+ try {
54998
+ transpiled = (0, import_esbuild3.transformSync)(source, createTestTransformOptions(modulePath, "cjs", false));
54999
+ } catch (error) {
55000
+ throw new Error(`Failed to transpile test dependency ${modulePath}: ${error instanceof Error ? error.message : String(error)}`);
55001
+ }
55002
+ const moduleRecord = { exports: {} };
55003
+ const moduleObj = { exports: moduleRecord.exports };
55004
+ moduleCache.set(modulePath, moduleRecord);
55005
+ try {
55006
+ const fn = new Function("module", "exports", "require", "__filename", "__dirname", transpiled.code);
55007
+ const requireFn = createTestModuleRequire(modulePath, moduleCache);
55008
+ fn(moduleObj, moduleObj.exports, requireFn, modulePath, dirname(modulePath));
55009
+ } catch (error) {
55010
+ throw new Error(`Failed to execute test dependency ${modulePath}: ${error instanceof Error ? error.message : String(error)}`);
55011
+ }
55012
+ moduleRecord.exports = moduleObj.exports;
55013
+ if (!modulePath.includes(".test.") && !modulePath.includes(".spec.")) {
55014
+ coveredFiles.add(modulePath);
55015
+ }
55016
+ return moduleRecord.exports;
55017
+ }
54904
55018
  function createTestFunction(defaultTimeout = 5e3) {
54905
55019
  const testFn = function(name, fn, timeout) {
54906
- const test2 = {
55020
+ const test = {
54907
55021
  name,
54908
55022
  fn,
54909
55023
  skip: currentSuite.skip,
@@ -54912,10 +55026,10 @@ function createTestFunction(defaultTimeout = 5e3) {
54912
55026
  timeout: timeout ?? defaultTimeout,
54913
55027
  suite: currentSuite
54914
55028
  };
54915
- currentSuite.tests.push(test2);
55029
+ currentSuite.tests.push(test);
54916
55030
  };
54917
55031
  testFn.skip = (name, fn, timeout) => {
54918
- const test2 = {
55032
+ const test = {
54919
55033
  name,
54920
55034
  fn,
54921
55035
  skip: true,
@@ -54924,11 +55038,11 @@ function createTestFunction(defaultTimeout = 5e3) {
54924
55038
  timeout: timeout ?? defaultTimeout,
54925
55039
  suite: currentSuite
54926
55040
  };
54927
- currentSuite.tests.push(test2);
55041
+ currentSuite.tests.push(test);
54928
55042
  };
54929
55043
  testFn.only = (name, fn, timeout) => {
54930
55044
  hasOnly = true;
54931
- const test2 = {
55045
+ const test = {
54932
55046
  name,
54933
55047
  fn,
54934
55048
  skip: false,
@@ -54937,10 +55051,10 @@ function createTestFunction(defaultTimeout = 5e3) {
54937
55051
  timeout: timeout ?? defaultTimeout,
54938
55052
  suite: currentSuite
54939
55053
  };
54940
- currentSuite.tests.push(test2);
55054
+ currentSuite.tests.push(test);
54941
55055
  };
54942
55056
  testFn.todo = (name, fn, timeout) => {
54943
- const test2 = {
55057
+ const test = {
54944
55058
  name,
54945
55059
  fn,
54946
55060
  skip: false,
@@ -54949,7 +55063,7 @@ function createTestFunction(defaultTimeout = 5e3) {
54949
55063
  timeout: timeout ?? defaultTimeout,
54950
55064
  suite: currentSuite
54951
55065
  };
54952
- currentSuite.tests.push(test2);
55066
+ currentSuite.tests.push(test);
54953
55067
  };
54954
55068
  return testFn;
54955
55069
  }
@@ -55059,29 +55173,7 @@ async function runTests(options) {
55059
55173
  try {
55060
55174
  const source = await readFile(file, "utf-8");
55061
55175
  const testFileDir = dirname(file);
55062
- const importRegex = /import\s+{\s*([^}]+)\s*}\s+from\s+['"]([^'"]+)['"]/g;
55063
- const imports = {};
55064
- let importIndex = 0;
55065
- let codeWithoutImports = source.replace(importRegex, (_, named, path) => {
55066
- const varName = `__import_${importIndex++}`;
55067
- const trimmedNamed = named.trim();
55068
- imports[varName] = { path, named: trimmedNamed };
55069
- return `// ${trimmedNamed} import injected later
55070
- `;
55071
- });
55072
- const result2 = (0, import_esbuild3.transformSync)(codeWithoutImports, {
55073
- loader: file.endsWith(".ts") || file.endsWith(".tsx") ? "ts" : "js",
55074
- format: "iife",
55075
- sourcemap: "inline",
55076
- target: "es2020",
55077
- tsconfigRaw: {
55078
- compilerOptions: {
55079
- jsx: "react",
55080
- jsxFactory: "h",
55081
- jsxFragmentFactory: "Fragment"
55082
- }
55083
- }
55084
- });
55176
+ const result2 = (0, import_esbuild3.transformSync)(source, createTestTransformOptions(file, "cjs", "inline"));
55085
55177
  let code = result2.code;
55086
55178
  const sourceMapMatch = code.match(/\/\/# sourceMappingURL=data:application\/json;base64,(.+)/);
55087
55179
  if (sourceMapMatch) {
@@ -55092,99 +55184,15 @@ async function runTests(options) {
55092
55184
  } else {
55093
55185
  currentSourceMapConsumer = void 0;
55094
55186
  }
55095
- const importedValues = {};
55096
- const importParamNames = [];
55097
- const importAssignments = [];
55098
- if (Object.keys(imports).length > 0) {
55099
- for (const [, { path, named }] of Object.entries(imports)) {
55100
- let resolvedPath = path;
55101
- if (path.startsWith(".")) {
55102
- const nodePath = require("path");
55103
- resolvedPath = nodePath.resolve(testFileDir, path);
55104
- }
55105
- if (!resolvedPath.endsWith(".ts") && !resolvedPath.endsWith(".js") && !resolvedPath.endsWith(".mjs") && !resolvedPath.endsWith(".cjs")) {
55106
- resolvedPath += ".ts";
55107
- }
55108
- if (resolvedPath.endsWith(".ts")) {
55109
- try {
55110
- const importSource = await readFile(resolvedPath, "utf-8");
55111
- const transpiled = (0, import_esbuild3.transformSync)(importSource, {
55112
- loader: "ts",
55113
- format: "cjs",
55114
- target: "es2020",
55115
- tsconfigRaw: {
55116
- compilerOptions: {
55117
- jsx: "react",
55118
- jsxFactory: "h",
55119
- jsxFragmentFactory: "Fragment"
55120
- }
55121
- }
55122
- });
55123
- const moduleExports = {};
55124
- const moduleObj = { exports: moduleExports };
55125
- const fn2 = new Function("module", "exports", "require", "__filename", "__dirname", transpiled.code);
55126
- const requireFn = (id) => {
55127
- if (id.startsWith("elit/") || id === "elit") {
55128
- return require(id);
55129
- }
55130
- if (id.startsWith(".")) {
55131
- const nodePath = require("path");
55132
- const absPath = nodePath.resolve(dirname(resolvedPath), id);
55133
- return require(absPath);
55134
- }
55135
- return require(id);
55136
- };
55137
- fn2(moduleObj, moduleExports, requireFn, resolvedPath, dirname(resolvedPath));
55138
- if (!resolvedPath.includes(".test.") && !resolvedPath.includes(".spec.")) {
55139
- coveredFiles.add(resolvedPath);
55140
- }
55141
- let exportedValue = moduleObj.exports[named];
55142
- if (exportedValue === void 0 && moduleObj.exports.default) {
55143
- exportedValue = moduleObj.exports.default[named];
55144
- }
55145
- if (exportedValue === void 0 && typeof moduleObj.exports === "object") {
55146
- exportedValue = moduleObj.exports[named];
55147
- }
55148
- const paramKey = `__import_${Math.random().toString(36).substring(2, 11)}`;
55149
- importedValues[paramKey] = exportedValue;
55150
- importParamNames.push(paramKey);
55151
- importAssignments.push(`const ${named} = ${paramKey};`);
55152
- } catch (err) {
55153
- const paramKey = `__import_${Math.random().toString(36).substring(2, 11)}`;
55154
- importedValues[paramKey] = null;
55155
- importParamNames.push(paramKey);
55156
- importAssignments.push(`const ${named} = ${paramKey}; /* Error importing ${resolvedPath}: ${err} */`);
55157
- }
55158
- } else {
55159
- const requiredModule = require(resolvedPath);
55160
- const exportedValue = requiredModule[named];
55161
- const paramKey = `__import_${Math.random().toString(36).substring(2, 11)}`;
55162
- importedValues[paramKey] = exportedValue;
55163
- importParamNames.push(paramKey);
55164
- importAssignments.push(`const ${named} = ${paramKey};`);
55165
- }
55166
- }
55167
- }
55168
- let preamble = "";
55169
- if (Object.keys(imports).length > 0) {
55170
- const iifeStartMatch = code.match(/^(\s*(?:var\s+\w+\s*=\s*)?\(\(\)\s*=>\s*\{\n)/);
55171
- if (iifeStartMatch) {
55172
- const iifePrefix = iifeStartMatch[1];
55173
- const assignments = `${importAssignments.join("\n")}
55174
- `;
55175
- preamble = iifePrefix;
55176
- code = iifePrefix + assignments + code.slice(iifeStartMatch[1].length);
55177
- } else {
55178
- preamble = importAssignments.join("\n") + "\n";
55179
- code = preamble + code;
55180
- }
55181
- }
55182
- wrapperLineOffset = preamble.split("\n").length;
55187
+ wrapperLineOffset = 0;
55183
55188
  setupGlobals();
55184
- const allParams = ["describe", "it", "test", "expect", "beforeAll", "afterAll", "beforeEach", "afterEach", "vi", "require", "module", "__filename", "__dirname", ...importParamNames];
55185
- const allArgs = [describe, it, test, expect, beforeAll, afterAll, beforeEach, afterEach, vi, require, module, file, testFileDir, ...importParamNames.map((p) => importedValues[p])];
55186
- const fn = new Function(...allParams, code);
55187
- await fn(...allArgs);
55189
+ const moduleCache = /* @__PURE__ */ new Map();
55190
+ const moduleRecord = { exports: {} };
55191
+ const moduleObj = { exports: moduleRecord.exports };
55192
+ moduleCache.set(file, moduleRecord);
55193
+ const fn = new Function("module", "exports", "require", "__filename", "__dirname", code);
55194
+ const requireFn = createTestModuleRequire(file, moduleCache);
55195
+ await fn(moduleObj, moduleObj.exports, requireFn, file, testFileDir);
55188
55196
  await executeSuite(currentSuite, timeout, bail);
55189
55197
  if (currentSourceMapConsumer) {
55190
55198
  currentSourceMapConsumer.destroy();
@@ -55219,13 +55227,13 @@ async function runTests(options) {
55219
55227
  async function executeSuite(suite, timeout, bail, parentMatched = false) {
55220
55228
  let directMatch = false;
55221
55229
  if (describePattern) {
55222
- const escapedPattern = escapeRegex(describePattern);
55230
+ const escapedPattern = escapeRegex2(describePattern);
55223
55231
  const regex = new RegExp(escapedPattern, "i");
55224
55232
  directMatch = regex.test(suite.name);
55225
55233
  }
55226
55234
  function suiteOrDescendantMatches(s) {
55227
55235
  if (!describePattern) return true;
55228
- const escapedPattern = escapeRegex(describePattern);
55236
+ const escapedPattern = escapeRegex2(describePattern);
55229
55237
  const regex = new RegExp(escapedPattern, "i");
55230
55238
  if (regex.test(s.name)) return true;
55231
55239
  for (const child of s.suites) {
@@ -55249,22 +55257,22 @@ async function executeSuite(suite, timeout, bail, parentMatched = false) {
55249
55257
  for (const hook of beforeAllHooks) {
55250
55258
  await hook();
55251
55259
  }
55252
- for (const test2 of suite.tests) {
55253
- if (hasOnly && !test2.only && !suite.only) {
55260
+ for (const test of suite.tests) {
55261
+ if (hasOnly && !test.only && !suite.only) {
55254
55262
  continue;
55255
55263
  }
55256
55264
  let testMatches = true;
55257
55265
  if (testPattern) {
55258
- const escapedPattern = escapeRegex(testPattern);
55266
+ const escapedPattern = escapeRegex2(testPattern);
55259
55267
  const regex = new RegExp(escapedPattern, "i");
55260
- testMatches = regex.test(test2.name);
55268
+ testMatches = regex.test(test.name);
55261
55269
  }
55262
55270
  if (!testMatches) {
55263
55271
  continue;
55264
55272
  }
55265
- if (test2.skip || suite.skip) {
55273
+ if (test.skip || suite.skip) {
55266
55274
  testResults.push({
55267
- name: test2.name,
55275
+ name: test.name,
55268
55276
  status: "skip",
55269
55277
  duration: 0,
55270
55278
  suite: suite.name,
@@ -55272,9 +55280,9 @@ async function executeSuite(suite, timeout, bail, parentMatched = false) {
55272
55280
  });
55273
55281
  continue;
55274
55282
  }
55275
- if (test2.todo) {
55283
+ if (test.todo) {
55276
55284
  testResults.push({
55277
- name: test2.name,
55285
+ name: test.name,
55278
55286
  status: "todo",
55279
55287
  duration: 0,
55280
55288
  suite: suite.name,
@@ -55288,13 +55296,13 @@ async function executeSuite(suite, timeout, bail, parentMatched = false) {
55288
55296
  const startTime = Date.now();
55289
55297
  try {
55290
55298
  await Promise.race([
55291
- test2.fn(),
55299
+ test.fn(),
55292
55300
  new Promise(
55293
- (_, reject) => setTimeout(() => reject(new Error(`Test timed out after ${test2.timeout}ms`)), test2.timeout)
55301
+ (_, reject) => setTimeout(() => reject(new Error(`Test timed out after ${test.timeout}ms`)), test.timeout)
55294
55302
  )
55295
55303
  ]);
55296
55304
  testResults.push({
55297
- name: test2.name,
55305
+ name: test.name,
55298
55306
  status: "pass",
55299
55307
  duration: Date.now() - startTime,
55300
55308
  suite: suite.name,
@@ -55308,7 +55316,7 @@ async function executeSuite(suite, timeout, bail, parentMatched = false) {
55308
55316
  codeSnippet = error.codeSnippet;
55309
55317
  }
55310
55318
  testResults.push({
55311
- name: test2.name,
55319
+ name: test.name,
55312
55320
  status: "fail",
55313
55321
  duration: Date.now() - startTime,
55314
55322
  error,
@@ -55357,7 +55365,7 @@ function getCoveredFiles() {
55357
55365
  function resetCoveredFiles() {
55358
55366
  coveredFiles.clear();
55359
55367
  }
55360
- var import_esbuild3, import_source_map, AssertionError, currentSuite, testResults, hasOnly, coveredFiles, describePattern, testPattern, currentTestFile, currentSourceMapConsumer, wrapperLineOffset, Expect, vi, beforeAllHooks, afterAllHooks, beforeEachHooks, afterEachHooks, beforeAll, afterAll, beforeEach, afterEach, globals;
55368
+ var import_esbuild3, import_source_map, AssertionError, currentSuite, testResults, hasOnly, coveredFiles, describePattern, testPattern, currentTestFile, currentSourceMapConsumer, wrapperLineOffset, TEST_MODULE_EXTENSIONS, Expect, vi, beforeAllHooks, afterAllHooks, beforeEachHooks, afterEachHooks, beforeAll, afterAll, beforeEach, afterEach, globals;
55361
55369
  var init_test_runtime = __esm({
55362
55370
  "src/test-runtime.ts"() {
55363
55371
  "use strict";
@@ -55390,6 +55398,7 @@ var init_test_runtime = __esm({
55390
55398
  currentTestFile = void 0;
55391
55399
  currentSourceMapConsumer = void 0;
55392
55400
  wrapperLineOffset = 0;
55401
+ TEST_MODULE_EXTENSIONS = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs", ".json"];
55393
55402
  Expect = class _Expect {
55394
55403
  constructor(actual, isNot = false, isAsync = false) {
55395
55404
  this.actual = actual;
@@ -60475,6 +60484,71 @@ function lookup(path) {
60475
60484
  // src/server.ts
60476
60485
  init_runtime();
60477
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
+
60478
60552
  // src/render-context.ts
60479
60553
  var RUNTIME_TARGET_KEY = "__ELIT_RUNTIME_TARGET__";
60480
60554
  var CAPTURED_RENDER_KEY = "__ELIT_CAPTURED_RENDER__";
@@ -60928,6 +61002,7 @@ var DomNode = class {
60928
61002
  html += `</${tagName}>${newLine}`;
60929
61003
  return html;
60930
61004
  }
61005
+ const isRawText = tagName === "script" || tagName === "style";
60931
61006
  if (children && children.length > 0) {
60932
61007
  const resolvedChildren = children.map((c) => {
60933
61008
  const resolved = this.resolveStateValue(c);
@@ -60943,11 +61018,11 @@ var DomNode = class {
60943
61018
  if (Array.isArray(child)) {
60944
61019
  for (const c of child) {
60945
61020
  if (!shouldSkipChild(c)) {
60946
- html += this.renderToString(c, { pretty, indent: indent5 + 1 });
61021
+ html += isRawText && typeof c === "string" ? c : this.renderToString(c, { pretty, indent: indent5 + 1 });
60947
61022
  }
60948
61023
  }
60949
61024
  } else {
60950
- html += this.renderToString(child, { pretty, indent: indent5 + 1 });
61025
+ html += isRawText && typeof child === "string" ? child : this.renderToString(child, { pretty, indent: indent5 + 1 });
60951
61026
  }
60952
61027
  }
60953
61028
  html += indentStr;
@@ -60957,11 +61032,11 @@ var DomNode = class {
60957
61032
  if (Array.isArray(child)) {
60958
61033
  for (const c of child) {
60959
61034
  if (!shouldSkipChild(c)) {
60960
- html += this.renderToString(c, { pretty: false, indent: 0 });
61035
+ html += isRawText && typeof c === "string" ? c : this.renderToString(c, { pretty: false, indent: 0 });
60961
61036
  }
60962
61037
  }
60963
61038
  } else {
60964
- html += this.renderToString(child, { pretty: false, indent: 0 });
61039
+ html += isRawText && typeof child === "string" ? child : this.renderToString(child, { pretty: false, indent: 0 });
60965
61040
  }
60966
61041
  }
60967
61042
  }
@@ -61889,6 +61964,56 @@ var defaultOptions = {
61889
61964
  worker: [],
61890
61965
  mode: "dev"
61891
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
+ }
61892
62017
  function shouldUseClientFallbackRoot(primaryRoot, fallbackRoot, indexPath) {
61893
62018
  if (!fallbackRoot) {
61894
62019
  return false;
@@ -62023,6 +62148,7 @@ function createDevServer(options) {
62023
62148
  const globalWebSocketEndpoints = usesClientArray ? normalizeWebSocketEndpoints(config.ws) : [];
62024
62149
  const normalizedWebSocketEndpoints = [...normalizedClients.flatMap((client) => client.ws), ...globalWebSocketEndpoints];
62025
62150
  const seenWebSocketPaths = /* @__PURE__ */ new Set();
62151
+ const smtpServerConfigs = collectSmtpServerConfigs(config, usesClientArray);
62026
62152
  for (const endpoint of normalizedWebSocketEndpoints) {
62027
62153
  if (endpoint.path === ELIT_INTERNAL_WS_PATH) {
62028
62154
  throw new Error(`WebSocket path "${ELIT_INTERNAL_WS_PATH}" is reserved for Elit internals`);
@@ -62032,6 +62158,21 @@ function createDevServer(options) {
62032
62158
  }
62033
62159
  seenWebSocketPaths.add(endpoint.path);
62034
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
+ });
62035
62176
  const globalProxyHandler = config.proxy ? createProxyHandler(config.proxy) : null;
62036
62177
  const server = createServer(async (req, res) => {
62037
62178
  const originalUrl = req.url || "/";
@@ -62572,6 +62713,15 @@ ${elitImportMap}`;
62572
62713
  if (config.logging) console.log("\n[Server] Shutting down...");
62573
62714
  transformCache.clear();
62574
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
+ }
62575
62725
  if (webSocketServers.length > 0) {
62576
62726
  webSocketServers.forEach((wsServer) => wsServer.close());
62577
62727
  wsClients.clear();
@@ -62588,6 +62738,7 @@ ${elitImportMap}`;
62588
62738
  return {
62589
62739
  server,
62590
62740
  wss,
62741
+ smtpServers,
62591
62742
  url: primaryUrl,
62592
62743
  state: stateManager,
62593
62744
  close
@@ -74467,7 +74618,7 @@ var WAPK_AUTH_TAG_LENGTH = 16;
74467
74618
  var WAPK_SCRYPT_OPTIONS = { N: 16384, r: 8, p: 1 };
74468
74619
  var DEFAULT_GOOGLE_DRIVE_TOKEN_ENV = "GOOGLE_DRIVE_ACCESS_TOKEN";
74469
74620
  var DEFAULT_WAPK_ONLINE_URL_ENV = "ELIT_WAPK_ONLINE_URL";
74470
- var DEFAULT_WAPK_ONLINE_URLS = ["https://wapk.d-osc.com/"];
74621
+ var DEFAULT_WAPK_ONLINE_URLS = ["http://wapk.d-osc.com/"];
74471
74622
  var WAPK_ONLINE_CREATE_PATH = "/api/shared-session/create";
74472
74623
  var WAPK_ONLINE_READ_PATH = "/api/shared-session/read";
74473
74624
  var WAPK_ONLINE_CLOSE_PATH = "/api/shared-session/close";
@@ -74505,6 +74656,113 @@ function normalizeNonEmptyString(value) {
74505
74656
  const normalized = value.trim();
74506
74657
  return normalized.length > 0 ? normalized : void 0;
74507
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
+ }
74508
74766
  function normalizeStringMap(value) {
74509
74767
  if (!isRecord(value)) {
74510
74768
  return void 0;
@@ -74544,6 +74802,8 @@ function normalizeWapkConfig(value) {
74544
74802
  runtime: normalizeRuntime(value.runtime ?? value.engine),
74545
74803
  entry: typeof value.entry === "string" ? value.entry : void 0,
74546
74804
  scripts: normalizeStringMap(value.scripts ?? value.script),
74805
+ appId: normalizeNonEmptyString(value.appId),
74806
+ publisherId: normalizeNonEmptyString(value.publisherId),
74547
74807
  port: normalizePort(value.port),
74548
74808
  env: normalizeStringMap(value.env),
74549
74809
  desktop: normalizeDesktopConfig(value.desktop),
@@ -74833,6 +75093,7 @@ async function readWapkProjectConfig(directory) {
74833
75093
  const elitConfig = await loadConfig(directory);
74834
75094
  const elitWapkConfig = normalizeWapkConfig(elitConfig?.wapk);
74835
75095
  const packageJson = readJsonFile(packageJsonPath);
75096
+ const packageJsonWapk = isRecord(packageJson?.wapk) ? packageJson.wapk : void 0;
74836
75097
  const packageScripts = normalizeStringMap(packageJson?.scripts) ?? {};
74837
75098
  const selectedScripts = elitWapkConfig.scripts ?? packageScripts;
74838
75099
  const inferred = inferRuntimeAndEntryFromScript(selectedScripts.start ?? packageScripts.start);
@@ -74856,12 +75117,16 @@ async function readWapkProjectConfig(directory) {
74856
75117
  if (!(0, import_node_fs2.existsSync)(entryPath) || !(0, import_node_fs2.statSync)(entryPath).isFile()) {
74857
75118
  throw new Error(`WAPK entry not found: ${entryPath}`);
74858
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);
74859
75122
  return {
74860
75123
  name,
74861
75124
  version,
74862
75125
  runtime: runtime2,
74863
75126
  entry,
74864
75127
  scripts: selectedScripts,
75128
+ appId,
75129
+ publisherId,
74865
75130
  port: elitWapkConfig.port,
74866
75131
  env: elitWapkConfig.env,
74867
75132
  desktop: elitWapkConfig.desktop,
@@ -74871,11 +75136,14 @@ async function readWapkProjectConfig(directory) {
74871
75136
  function readIgnorePatterns(directory) {
74872
75137
  return readLineIgnorePatterns((0, import_node_path2.join)(directory, ".wapkignore"));
74873
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
+ }
74874
75142
  function readLineIgnorePatterns(filePath) {
74875
75143
  if (!(0, import_node_fs2.existsSync)(filePath)) {
74876
75144
  return [];
74877
75145
  }
74878
- return (0, import_node_fs2.readFileSync)(filePath, "utf8").split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
75146
+ return parsePatternLines((0, import_node_fs2.readFileSync)(filePath, "utf8"));
74879
75147
  }
74880
75148
  function normalizePackageEntry(value) {
74881
75149
  const normalized = normalizeNonEmptyString(value)?.replace(/^[.][\\/]/, "").split("\\").join("/");
@@ -75056,23 +75324,188 @@ function resolveLinkedDependencyArchivePrefix(archivePrefix, dependencyName) {
75056
75324
  }
75057
75325
  return `${normalizedPrefix.slice(0, markerIndex + marker.length)}${dependencyName}`;
75058
75326
  }
75059
- function shouldIgnore(relativePath2, ignorePatterns) {
75060
- const pathParts = relativePath2.split("/");
75327
+ function escapeRegex(text) {
75328
+ return text.replace(/[|\\{}()[\]^$+?.]/g, "\\$&");
75329
+ }
75330
+ function globPatternToRegex(pattern, options = {}) {
75331
+ let regex = "";
75332
+ for (let index = 0; index < pattern.length; index++) {
75333
+ const char = pattern[index];
75334
+ if (char === "*") {
75335
+ const nextChar = pattern[index + 1];
75336
+ const nextNextChar = pattern[index + 2];
75337
+ if (nextChar === "*") {
75338
+ if (nextNextChar === "/") {
75339
+ regex += "(?:.*?/)?";
75340
+ index += 2;
75341
+ continue;
75342
+ }
75343
+ regex += ".*";
75344
+ index += 1;
75345
+ continue;
75346
+ }
75347
+ regex += "[^/]*";
75348
+ continue;
75349
+ }
75350
+ if (char === "?") {
75351
+ regex += "[^/]";
75352
+ continue;
75353
+ }
75354
+ regex += escapeRegex(char);
75355
+ }
75356
+ const suffix = options.directoryOnly ? "(?:$|/.*)" : "$";
75357
+ const prefix = options.matchSegmentsOnly ? "^(?:.*?/)?" : "^";
75358
+ return new RegExp(`${prefix}${regex}${suffix}`);
75359
+ }
75360
+ function normalizeIgnorePattern(pattern) {
75361
+ let normalizedPattern = pattern.trim();
75362
+ if (!normalizedPattern) {
75363
+ return void 0;
75364
+ }
75365
+ let negate = false;
75366
+ if (normalizedPattern.startsWith("\\!") || normalizedPattern.startsWith("\\#")) {
75367
+ normalizedPattern = normalizedPattern.slice(1);
75368
+ } else if (normalizedPattern.startsWith("!")) {
75369
+ negate = true;
75370
+ normalizedPattern = normalizedPattern.slice(1).trim();
75371
+ }
75372
+ if (!normalizedPattern) {
75373
+ return void 0;
75374
+ }
75375
+ const directoryOnly = normalizedPattern.endsWith("/");
75376
+ if (directoryOnly) {
75377
+ normalizedPattern = normalizedPattern.replace(/\/+$/, "");
75378
+ }
75379
+ normalizedPattern = normalizedPattern.replace(/^\.\//, "").replace(/^\//, "").replace(/\\/g, "/");
75380
+ if (!normalizedPattern) {
75381
+ return void 0;
75382
+ }
75383
+ return {
75384
+ directoryOnly,
75385
+ matchSegmentsOnly: !normalizedPattern.includes("/"),
75386
+ negate,
75387
+ pattern: normalizedPattern
75388
+ };
75389
+ }
75390
+ function matchesIgnorePattern(relativePath2, pattern, isDirectory) {
75391
+ const normalizedRule = normalizeIgnorePattern(pattern);
75392
+ if (!normalizedRule) {
75393
+ return false;
75394
+ }
75395
+ if (normalizedRule.directoryOnly && !isDirectory) {
75396
+ return false;
75397
+ }
75398
+ const hasGlob = /[*?]/.test(normalizedRule.pattern);
75399
+ if (!hasGlob) {
75400
+ if (normalizedRule.matchSegmentsOnly) {
75401
+ return relativePath2 === normalizedRule.pattern || relativePath2.split("/").includes(normalizedRule.pattern);
75402
+ }
75403
+ return relativePath2 === normalizedRule.pattern;
75404
+ }
75405
+ if (normalizedRule.matchSegmentsOnly) {
75406
+ const segmentRegex = globPatternToRegex(normalizedRule.pattern);
75407
+ return relativePath2.split("/").some((segment) => segmentRegex.test(segment));
75408
+ }
75409
+ return globPatternToRegex(normalizedRule.pattern, { directoryOnly: normalizedRule.directoryOnly }).test(relativePath2);
75410
+ }
75411
+ function shouldIgnore(relativePath2, ignorePatterns, isDirectory) {
75412
+ let ignored = false;
75061
75413
  for (const pattern of ignorePatterns) {
75062
- if (relativePath2 === pattern || pathParts.includes(pattern)) {
75063
- return true;
75414
+ const normalizedRule = normalizeIgnorePattern(pattern);
75415
+ if (!normalizedRule) {
75416
+ continue;
75064
75417
  }
75065
- if (pattern.endsWith("*")) {
75066
- const prefix = pattern.slice(0, -1);
75067
- if (relativePath2.startsWith(prefix) || pathParts.some((part) => part.startsWith(prefix))) {
75068
- return true;
75069
- }
75418
+ if (!matchesIgnorePattern(relativePath2, pattern, isDirectory)) {
75419
+ continue;
75070
75420
  }
75071
- if (pattern.startsWith("*.") && relativePath2.endsWith(pattern.slice(1))) {
75072
- return true;
75421
+ ignored = !normalizedRule.negate;
75422
+ }
75423
+ return ignored;
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;
75073
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);
75074
75495
  }
75075
- return false;
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
+ };
75076
75509
  }
75077
75510
  function collectFiles(directory, baseDirectory, ignorePatterns) {
75078
75511
  const files = [];
@@ -75080,7 +75513,7 @@ function collectFiles(directory, baseDirectory, ignorePatterns) {
75080
75513
  for (const entry of entries) {
75081
75514
  const fullPath = (0, import_node_path2.join)(directory, entry.name);
75082
75515
  const relativePath2 = (0, import_node_path2.relative)(baseDirectory, fullPath).split("\\").join("/");
75083
- if (shouldIgnore(relativePath2, ignorePatterns)) {
75516
+ if (shouldIgnore(relativePath2, ignorePatterns, entry.isDirectory())) {
75084
75517
  continue;
75085
75518
  }
75086
75519
  if (entry.isSymbolicLink()) {
@@ -75229,6 +75662,8 @@ function decodeWapkPayload(buffer) {
75229
75662
  runtime: normalizeRuntime(rawHeader.runtime ?? rawHeader.engine) ?? "node",
75230
75663
  entry: typeof rawHeader.entry === "string" ? rawHeader.entry : "index.js",
75231
75664
  scripts: normalizeStringMap(rawHeader.scripts) ?? {},
75665
+ appId: normalizeNonEmptyString(rawHeader.appId),
75666
+ publisherId: normalizeNonEmptyString(rawHeader.publisherId),
75232
75667
  port: normalizePort(rawHeader.port),
75233
75668
  env: normalizeStringMap(rawHeader.env),
75234
75669
  desktop: normalizeDesktopConfig(rawHeader.desktop),
@@ -75726,11 +76161,14 @@ function sanitizeOnlineArchiveFileName(label, fallback) {
75726
76161
  const fileName = sanitized.length > 0 ? sanitized : "app.wapk";
75727
76162
  return fileName.toLowerCase().endsWith(".wapk") ? fileName : `${fileName}.wapk`;
75728
76163
  }
76164
+ var WAPK_ONLINE_JOIN_SOURCE_QUERY_PARAM = "launchSource";
76165
+ var WAPK_ONLINE_JOIN_SOURCE_QUERY_VALUE = "elit-wapk-online";
75729
76166
  function buildOnlineJoinUrl(baseUrl, joinKey) {
75730
76167
  const joinUrl = new URL(baseUrl.toString());
75731
76168
  joinUrl.search = "";
75732
76169
  joinUrl.hash = "";
75733
76170
  joinUrl.searchParams.set("join", joinKey);
76171
+ joinUrl.searchParams.set(WAPK_ONLINE_JOIN_SOURCE_QUERY_PARAM, WAPK_ONLINE_JOIN_SOURCE_QUERY_VALUE);
75734
76172
  return joinUrl.toString();
75735
76173
  }
75736
76174
  async function probeOnlineLauncherUrl(url) {
@@ -75911,11 +76349,16 @@ async function closeWapkOnlineSharedSession(launcherUrl, session) {
75911
76349
  function isPmWapkOnlineShutdownEnabled() {
75912
76350
  return process.env[WAPK_ONLINE_PM_SHUTDOWN_ENV] === "1" && Boolean(process.stdin) && !process.stdin.isTTY;
75913
76351
  }
75914
- 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 = {}) {
75915
76356
  let snapshotRevision = 0;
75916
76357
  let snapshotSyncPending = false;
75917
76358
  let snapshotSyncPromise = Promise.resolve();
75918
76359
  let lastSnapshotSyncError = null;
76360
+ const allowSigtermClose = options.allowSigtermClose === true;
76361
+ const processDetails = getWapkOnlineProcessDetails();
75919
76362
  const syncGuestSnapshotUpdates = () => {
75920
76363
  if (snapshotSyncPending) {
75921
76364
  return snapshotSyncPromise;
@@ -75949,6 +76392,7 @@ async function waitForWapkOnlineSessionShutdown(launcherUrl, session, archiveHan
75949
76392
  void syncGuestSnapshotUpdates();
75950
76393
  }, WAPK_ONLINE_KEEPALIVE_INTERVAL_MS);
75951
76394
  const pmManaged = isPmWapkOnlineShutdownEnabled();
76395
+ let ignoredSigTermLogged = false;
75952
76396
  let stdinBuffer = "";
75953
76397
  const cleanup = () => {
75954
76398
  clearInterval(keepAlive);
@@ -75967,7 +76411,17 @@ async function waitForWapkOnlineSessionShutdown(launcherUrl, session, archiveHan
75967
76411
  finish({ kind: "signal", signal: "SIGINT" });
75968
76412
  };
75969
76413
  const onSigTerm = () => {
75970
- 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
+ );
75971
76425
  };
75972
76426
  const onStdinData = (chunk) => {
75973
76427
  stdinBuffer += typeof chunk === "string" ? chunk : chunk.toString("utf8");
@@ -75992,9 +76446,12 @@ async function waitForWapkOnlineSessionShutdown(launcherUrl, session, archiveHan
75992
76446
  if (shutdownTrigger.kind === "pm") {
75993
76447
  console.log(`
75994
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...`);
75995
76452
  } else {
75996
76453
  console.log(`
75997
- [wapk] Closing shared session ${session.joinKey}...`);
76454
+ [wapk] Received ${shutdownTrigger.signal}; closing shared session ${session.joinKey}...`);
75998
76455
  }
75999
76456
  try {
76000
76457
  await closeWapkOnlineSharedSession(launcherUrl, session);
@@ -76034,7 +76491,9 @@ async function runWapkOnline(archiveSpecifier, options) {
76034
76491
  process.exitCode = await waitForWapkOnlineSessionShutdown(launcherUrl, {
76035
76492
  joinKey: response.joinKey,
76036
76493
  adminToken: response.adminToken
76037
- }, archiveHandle, onlineArchiveLock);
76494
+ }, archiveHandle, onlineArchiveLock, {
76495
+ allowSigtermClose: options.allowSigtermClose
76496
+ });
76038
76497
  }
76039
76498
  async function writeWapkArchiveFromMemory(archiveHandle, header, files, lock) {
76040
76499
  const updatedHeader = {
@@ -76291,6 +76750,8 @@ async function packWapkDirectory(directory, options = {}) {
76291
76750
  runtime: config.runtime,
76292
76751
  entry: config.entry,
76293
76752
  scripts: config.scripts,
76753
+ appId: config.appId,
76754
+ publisherId: config.publisherId,
76294
76755
  port: config.port,
76295
76756
  env: config.env,
76296
76757
  desktop: config.desktop,
@@ -76322,6 +76783,56 @@ function extractWapkArchive(wapkPath, outputDir = ".", options = {}) {
76322
76783
  console.log(`Extracted ${archive.files.length} files to: ${extractDirectory}`);
76323
76784
  return extractDirectory;
76324
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
+ }
76325
76836
  async function prepareWapkApp(wapkPath, options = {}) {
76326
76837
  const archiveHandle = resolveArchiveHandle(wapkPath, options.googleDrive);
76327
76838
  const archivePath = archiveHandle.identifier;
@@ -76430,6 +76941,8 @@ function inspectWapkArchive(wapkPath, options = {}) {
76430
76941
  console.log(`App: ${decoded.header.version}`);
76431
76942
  console.log(`Runtime: ${decoded.header.runtime}`);
76432
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"}`);
76433
76946
  console.log(`Port: ${decoded.header.port ?? "default"}`);
76434
76947
  console.log(`Created: ${decoded.header.createdAt}`);
76435
76948
  if (decoded.header.env && Object.keys(decoded.header.env).length > 0) {
@@ -76461,6 +76974,8 @@ function printWapkHelp() {
76461
76974
  " elit wapk gdrive://<fileId> --online",
76462
76975
  " elit wapk pack [directory]",
76463
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>",
76464
76979
  " elit wapk inspect <file.wapk>",
76465
76980
  " elit wapk extract <file.wapk>",
76466
76981
  "",
@@ -76472,24 +76987,32 @@ function printWapkHelp() {
76472
76987
  " --archive-watch Pull external archive changes back into the temp workdir",
76473
76988
  " --no-archive-watch Disable external archive read sync",
76474
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",
76475
76991
  " --online-url <url> Elit Run URL (default: auto-detect localhost:4177 or localhost:4179)",
76476
76992
  " --google-drive-file-id <id> Run a remote .wapk directly from Google Drive",
76477
76993
  " --google-drive-token-env <name> Env var containing the Google Drive OAuth token",
76478
76994
  " --google-drive-access-token <value> OAuth token for Google Drive API calls",
76479
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",
76480
76999
  " --include-deps Legacy compatibility flag; node_modules are packed by default",
76481
77000
  " --password <value> Password for locking or unlocking the archive",
76482
77001
  " -h, --help Show this help",
76483
77002
  "",
76484
77003
  "Notes:",
76485
77004
  " - Pack reads wapk from elit.config.* and falls back to package.json.",
76486
- " - Pack includes node_modules by default; use .wapkignore if you need to exclude them.",
77005
+ " - If appId or publisherId is not configured, pack auto-generates stable defaults from package metadata.",
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.",
76487
77009
  " - Run never installs dependencies automatically; archives must include the runtime dependencies they need.",
76488
77010
  " - Run mode can read config.wapk.run for default file/runtime/live-sync options.",
76489
77011
  " - Browser-style archives with scripts.start or wapk.script.start run that start script automatically.",
76490
77012
  " - Run mode keeps files in RAM and syncs changes both to and from the archive source.",
76491
77013
  " - Google Drive mode talks to the Drive API directly; no local archive file is required.",
76492
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.",
76493
77016
  " - Locked archives in online mode must provide --password so the CLI can build the shared snapshot.",
76494
77017
  " - Locked archives require the same password for run/extract/inspect.",
76495
77018
  " - Archives stay unlocked by default unless a password is provided.",
@@ -76539,6 +77062,7 @@ function parseRunArgs(args) {
76539
77062
  let archiveSyncInterval;
76540
77063
  let online;
76541
77064
  let onlineUrl;
77065
+ let allowSigtermClose;
76542
77066
  let password;
76543
77067
  for (let index = 0; index < args.length; index++) {
76544
77068
  const arg = args[index];
@@ -76590,6 +77114,10 @@ function parseRunArgs(args) {
76590
77114
  onlineUrl = readRequiredOptionValue(args, ++index, "--online-url");
76591
77115
  break;
76592
77116
  }
77117
+ case "--allow-sigterm-close": {
77118
+ allowSigtermClose = true;
77119
+ break;
77120
+ }
76593
77121
  case "--google-drive-file-id": {
76594
77122
  googleDrive = {
76595
77123
  ...googleDrive,
@@ -76632,7 +77160,7 @@ function parseRunArgs(args) {
76632
77160
  break;
76633
77161
  }
76634
77162
  }
76635
- 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 };
76636
77164
  }
76637
77165
  function parsePackArgs(args) {
76638
77166
  let directory = ".";
@@ -76658,6 +77186,46 @@ function parsePackArgs(args) {
76658
77186
  }
76659
77187
  return { directory, includeDeps, password };
76660
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
+ }
76661
77229
  async function readConfiguredWapkRunDefaults(cwd) {
76662
77230
  const config = await loadConfig(cwd);
76663
77231
  const runConfig = normalizeWapkRunConfig(config?.wapk?.run);
@@ -76697,6 +77265,7 @@ function resolveConfiguredWapkRunOptions(options, defaults) {
76697
77265
  archiveSyncInterval: options.archiveSyncInterval ?? defaults?.archiveSyncInterval,
76698
77266
  online: options.online ?? defaults?.online ?? Boolean(onlineUrl),
76699
77267
  onlineUrl,
77268
+ allowSigtermClose: options.allowSigtermClose === true,
76700
77269
  password: options.password ?? defaults?.password
76701
77270
  };
76702
77271
  }
@@ -76729,6 +77298,15 @@ async function runWapkCommand(args, cwd = process.cwd()) {
76729
77298
  });
76730
77299
  return;
76731
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
+ }
76732
77310
  if (args[0] === "inspect") {
76733
77311
  const options = parseArchiveAccessArgs(args.slice(1), "Usage: elit wapk inspect <file.wapk>");
76734
77312
  inspectWapkArchive(options.file, options);
@@ -76757,6 +77335,7 @@ async function runWapkCommand(args, cwd = process.cwd()) {
76757
77335
  await runWapkOnline(archiveSpecifier, {
76758
77336
  googleDrive: runOptions.googleDrive,
76759
77337
  onlineUrl: runOptions.onlineUrl,
77338
+ allowSigtermClose: runOptions.allowSigtermClose,
76760
77339
  password: runOptions.password
76761
77340
  });
76762
77341
  return;
@@ -83080,6 +83659,7 @@ WAPK Options:
83080
83659
  elit wapk run [file.wapk] Run a packaged app or the configured default archive
83081
83660
  elit wapk run --google-drive-file-id <id> Run a packaged app directly from Google Drive
83082
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
83083
83663
  elit wapk inspect <file.wapk> Inspect a .wapk archive
83084
83664
  elit wapk extract <file.wapk> Extract a .wapk archive
83085
83665
  elit wapk --runtime node|bun|deno [file] Override the packaged runtime