CodeSampleX

샘플

electron 43.4.1: app

검증된 샘플 — npm electron 43.4.1: app. node 22 · linux alpine/x64 · docker에서 contract를 실행해 통과했습니다: electron type definitions declare App interface with lifecycle…

sha256:58bb9716ddd4724547029f326dce9ffff2ce259e755e199537b0d55fbb010201

이 네트워크가 제공하는 것은 하나입니다. 빌드되는 샘플. 샌드박스에서 돌리고 서명된 영수증을 보관합니다. 등급을 매기지 않고 무엇도 보증하지 않습니다 — 같은 코드가 당신 환경에서 빌드되는지는 측정한 적이 없습니다. 통과한 계약 영수증을 낸 서로 다른 서명 키의 수입니다. 하나면 작성자 혼자이고, 둘 이상이면 다른 사람도 빌드했다는 뜻입니다. 키는 스스로 만드는 것이고 뒤에 등록된 신원이 없으므로, 세는 것은 사람이 아니라 키입니다. MIT-0

실행 증거

선언된 환경과 서명된 실행을 분리해 두었습니다. 이 샘플이 무엇을 어디서 실행했는지 그대로 볼 수 있습니다.

증거 기준
서명된 컨트랙트 통과
검증 영수증
1
빌드한 서명 키
1
선언된 환경 node 22.23 linux 24 · ubuntu · glibc 2.39 x64 node 22.23 javascript npm 10

검증 실행 환경

환경 컨트랙트 단계 실행일
node 22 · linux alpine/x64 · docker ed25519:c1973797be207ac4 PASS compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS
CONTAINER_RUN · node-typescript@1node:22-alpine@sha256:c610fcdfb1d5…
2026-08-28

케이스

HOW
목표
verify electron.app in pkg:npm/electron@43.4.1
패키지
심벌
  • electron.app
환경
node 22.23.2
생성일
2026-08-28T06:03:44Z

컨트랙트

  1. electron type definitions declare App interface with lifecycle methods, path resolution, and event emitters
  2. createAppController manages application ready state and executes whenReady lifecycle promise
  3. getPath and setPath manage standard Electron platform paths and reject invalid path names
  4. requestSingleInstanceLock and releaseSingleInstanceLock enforce single instance mutual exclusion
  5. quit and exit handle graceful shutdown and terminate lifecycle with exit codes

파일

  • PROMPT.md
  • csx.json
  • index.js
  • package-lock.json
  • package.json
  • spec.json
  • test/contract.cjs

소스 아티팩트 내려받기 (tar.gz)

소스

PROMPT.md
Clean-room public code sample — generation instructions

Write a brand-new, minimal, self-contained code sample in this clean-room directory.
Do not copy, paraphrase, or reference any existing project source. Work only from this spec.

A csx.json manifest scaffold already exists. Do not recreate it from memory. Preserve its case.goal, packages and symbols; fill its empty case.contract with exact assertions and correct its environment, commands and verifierAdapter for the files you generate.

Goal: verify electron.app in pkg:npm/electron@43.4.1
Kind: HOW

Use EXACTLY these public packages and versions:
  - pkg:npm/electron@43.4.1
Demonstrate these symbols/APIs:
  - electron.app
Constraints:
  - executionContext: node
Required runtime conditions:
  - ecosystem: npm
  - language: javascript
  - moduleSystem: cjs
  - packageManager: npm@10.9.8
  - runtime: node@22.23.2

Rules:
  - One focused purpose; the smallest project that proves the goal.
  - Include a contract test (test/contract.*) that runs OFFLINE and exits 0 exactly when the goal behavior works.
  - Pin every dependency with a lockfile so resolution is reproducible.
  - No secrets, credentials, or tokens. No real URLs (only example.com or localhost). No absolute paths.
  - No personal names, emails, company names, or project identifiers of any kind.
  - No binaries and no generated output (node_modules, dist, target, venv, .git, .env).
  - Keep it under 200 files and 256KB packed.
csx.json
{"case":{"caseId":"case:sha256:44179462155f8c34a30b2c0e05e249e5d9aa840a1f853e117c06efe1b5e3a142","constraints":{"executionContext":"node"},"contract":["electron type definitions declare App interface with lifecycle methods, path resolution, and event emitters","createAppController manages application ready state and executes whenReady lifecycle promise","getPath and setPath manage standard Electron platform paths and reject invalid path names","requestSingleInstanceLock and releaseSingleInstanceLock enforce single instance mutual exclusion","quit and exit handle graceful shutdown and terminate lifecycle with exit codes"],"goal":"verify electron.app in pkg:npm/electron@43.4.1","kind":"HOW","packages":["pkg:npm/electron@43.4.1"],"schemaVersion":1,"symbols":["electron.app"]},"contractCommand":["node","test/contract.cjs"],"environment":{"arch":"x64","distro":"ubuntu","ecosystem":"npm","executionContext":"node","language":"javascript","libc":"glibc","libcVersion":"2.39","moduleSystem":"cjs","os":"linux","osVersionBucket":"24","packageManager":"npm","packageManagerVersion":"10.9.8","runtime":"node","runtimeVersion":"22.23.2","schemaVersion":1},"license":"MIT-0","packages":["pkg:npm/electron@43.4.1"],"schemaVersion":1,"subject":"pkg:npm/electron@43.4.1","symbols":["electron.app"],"verifierAdapter":"node-typescript@1"}
index.js
'use strict';

const fs = require('node:fs');
const path = require('node:path');
const { EventEmitter } = require('node:events');

/**
 * Reads and verifies electron.app TypeScript definitions from the electron package.
 * @returns {{
 *   hasAppInterface: boolean,
 *   isMainExported: boolean,
 *   isCrossProcessExported: boolean,
 *   methods: string[]
 * }}
 */
function getAppDeclarations() {
  const electronDir = path.dirname(require.resolve('electron/package.json'));
  const dtsPath = path.join(electronDir, 'electron.d.ts');
  const dtsContent = fs.readFileSync(dtsPath, 'utf-8');

  const interfaceMatch = dtsContent.match(/interface\s+App\s+extends\s+NodeJS\.EventEmitter\s*\{([\s\S]*?)\n {2}\}/);
  const body = interfaceMatch ? interfaceMatch[1] : '';

  const checkedMethods = [
    'quit',
    'exit',
    'relaunch',
    'isReady',
    'whenReady',
    'focus',
    'getName',
    'setName',
    'getVersion',
    'getAppPath',
    'getPath',
    'setPath',
    'requestSingleInstanceLock',
    'hasSingleInstanceLock',
    'releaseSingleInstanceLock',
  ];

  const foundMethods = checkedMethods.filter((method) =>
    new RegExp(`\\b${method}\\s*\\(`).test(body)
  );

  const isMainExported = /namespace\s+Main\s*\{[\s\S]*?app:\s*App/m.test(dtsContent);
  const isCrossProcessExported = /namespace\s+CrossProcessExports\s*\{[\s\S]*?app:\s*App/m.test(dtsContent);

  return {
    hasAppInterface: Boolean(interfaceMatch),
    isMainExported,
    isCrossProcessExported,
    methods: foundMethods,
  };
}

/**
 * Valid path names supported by Electron app.getPath() / app.setPath()
 */
const SUPPORTED_PATH_NAMES = new Set([
  'home',
  'appData',
  'userData',
  'sessionData',
  'temp',
  'exe',
  'module',
  'desktop',
  'documents',
  'downloads',
  'music',
  'pictures',
  'videos',
  'recent',
  'logs',
  'crashDumps',
]);

/**
 * Creates an in-memory Electron App controller adhering to Electron app API contracts.
 * @param {{ name?: string, version?: string, appPath?: string }} [options={}]
 * @returns {any}
 */
function createAppController(options = {}) {
  let appName = options.name || 'SampleApp';
  const appVersion = options.version || '1.0.0';
  const appPath = options.appPath || '/mock/app';
  let readyState = false;
  let readyPromise = null;
  let readyResolver = null;
  let hasSingleLock = false;
  let singleLockData = null;
  let exitStatus = null;

  const standardPaths = new Map([
    ['home', '/mock/home/user'],
    ['appData', '/mock/home/user/.config'],
    ['userData', `/mock/home/user/.config/${appName}`],
    ['sessionData', `/mock/home/user/.config/${appName}/Session`],
    ['temp', '/mock/tmp'],
    ['exe', '/mock/usr/lib/electron/electron'],
    ['module', '/mock/usr/lib/electron/electron'],
    ['desktop', '/mock/home/user/Desktop'],
    ['documents', '/mock/home/user/Documents'],
    ['downloads', '/mock/home/user/Downloads'],
    ['music', '/mock/home/user/Music'],
    ['pictures', '/mock/home/user/Pictures'],
    ['videos', '/mock/home/user/Videos'],
    ['recent', '/mock/home/user/Recent'],
    ['logs', `/mock/home/user/.config/${appName}/logs`],
    ['crashDumps', `/mock/tmp/${appName} Crashes`],
  ]);

  const emitter = new EventEmitter();

  function getName() {
    return appName;
  }

  function setName(name) {
    if (typeof name !== 'string' || name.trim().length === 0) {
      throw new TypeError('App name must be a non-empty string');
    }
    appName = name;
  }

  function getVersion() {
    return appVersion;
  }

  function getAppPath() {
    return appPath;
  }

  function isReady() {
    return readyState;
  }

  function whenReady() {
    if (readyState) {
      return Promise.resolve();
    }
    if (!readyPromise) {
      readyPromise = new Promise((resolve) => {
        readyResolver = resolve;
      });
    }
    return readyPromise;
  }

  function markReady() {
    if (readyState) return;
    emitter.emit('will-finish-launching');
    readyState = true;
    emitter.emit('ready');
    if (readyResolver) {
      readyResolver();
      readyResolver = null;
    }
  }

  function getPath(name) {
    if (!SUPPORTED_PATH_NAMES.has(name)) {
      throw new Error(`Invalid path name: ${name}`);
    }
    return standardPaths.get(name);
  }

  function setPath(name, customPath) {
    if (!SUPPORTED_PATH_NAMES.has(name)) {
      throw new Error(`Invalid path name: ${name}`);
    }
    if (typeof customPath !== 'string' || customPath.trim().length === 0) {
      throw new TypeError('Custom path must be a non-empty string');
    }
    standardPaths.set(name, customPath);
  }

  function requestSingleInstanceLock(additionalData = null) {
    if (hasSingleLock) {
      return false;
    }
    hasSingleLock = true;
    singleLockData = additionalData;
    return true;
  }

  function hasSingleInstanceLock() {
    return hasSingleLock;
  }

  function releaseSingleInstanceLock() {
    hasSingleLock = false;
    singleLockData = null;
  }

  function notifySecondInstance(argv = [], workingDirectory = '', additionalData = null) {
    if (!hasSingleLock) return;
    const event = { defaultPrevented: false, preventDefault: () => { event.defaultPrevented = true; } };
    emitter.emit('second-instance', event, argv, workingDirectory, additionalData);
  }

  function quit() {
    const beforeQuitEvent = {
      defaultPrevented: false,
      preventDefault() { this.defaultPrevented = true; }
    };
    emitter.emit('before-quit', beforeQuitEvent);
    if (beforeQuitEvent.defaultPrevented) {
      return false;
    }

    const willQuitEvent = {
      defaultPrevented: false,
      preventDefault() { this.defaultPrevented = true; }
    };
    emitter.emit('will-quit', willQuitEvent);
    if (willQuitEvent.defaultPrevented) {
      return false;
    }

    emitter.emit('quit', {}, 0);
    exitStatus = { exited: true, exitCode: 0, graceful: true };
    return true;
  }

  function exit(exitCode = 0) {
    exitStatus = { exited: true, exitCode, graceful: false };
    return exitStatus;
  }

  function getExitStatus() {
    return exitStatus;
  }

  return {
    on: emitter.on.bind(emitter),
    once: emitter.once.bind(emitter),
    emit: emitter.emit.bind(emitter),
    removeListener: emitter.removeListener.bind(emitter),
    getName,
    setName,
    getVersion,
    getAppPath,
    isReady,
    whenReady,
    markReady,
    getPath,
    setPath,
    requestSingleInstanceLock,
    hasSingleInstanceLock,
    releaseSingleInstanceLock,
    notifySecondInstance,
    quit,
    exit,
    getExitStatus,
  };
}

module.exports = {
  getAppDeclarations,
  SUPPORTED_PATH_NAMES: Array.from(SUPPORTED_PATH_NAMES),
  createAppController,
};
package-lock.json
{
  "name": "sample-electron-app",
  "version": "1.0.0",
  "lockfileVersion": 3,
  "requires": true,
  "packages": {
    "": {
      "name": "sample-electron-app",
      "version": "1.0.0",
      "license": "MIT-0",
      "dependencies": {
        "electron": "43.4.1"
      }
    },
    "node_modules/@electron-internal/extract-zip": {
      "version": "1.0.5",
      "resolved": "https://registry.npmjs.org/@electron-internal/extract-zip/-/extract-zip-1.0.5.tgz",
      "integrity": "sha512-+bqFCP98pLI0Tt0XQo1TmlXtwjWchISndDOxCkEcIuUgXWpBnLyRI+2DU+mesvnMMX6L1XDqYNA0lXNDHd/yiA==",
      "license": "BSD-2-Clause",
      "engines": {
        "node": ">=22.12.0"
      }
    },
    "node_modules/@electron/get": {
      "version": "5.1.0",
      "resolved": "https://registry.npmjs.org/@electron/get/-/get-5.1.0.tgz",
      "integrity": "sha512-3kSBtG8ObcTVfXanm5vVJ6UnBLEVmVsRk1M+vGqCuMBV+XLCbJYuWQful+yIy0GQDsSlK0kHEriEHn7SPk4EnA==",
      "license": "MIT",
      "dependencies": {
        "debug": "^4.1.1",
        "env-paths": "^3.0.0",
        "graceful-fs": "^4.2.11",
        "progress": "^2.0.3",
        "semver": "^7.6.3",
        "sumchecker": "^3.0.1"
      },
      "engines": {
        "node": ">=22.12.0"
      },
      "optionalDependencies": {
        "undici": "^7.24.4"
      }
    },
    "node_modules/@types/node": {
      "version": "24.13.3",
      "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.3.tgz",
      "integrity": "sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==",
      "license": "MIT",
      "dependencies": {
        "undici-types": "~7.18.0"
      }
    },
    "node_modules/debug": {
      "version": "4.4.3",
      "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
      "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
      "license": "MIT",
      "dependencies": {
        "ms": "^2.1.3"
      },
      "engines": {
        "node": ">=6.0"
      },
      "peerDependenciesMeta": {
        "supports-color": {
          "optional": true
        }
      }
    },
    "node_modules/electron": {
      "version": "43.4.1",
      "resolved": "https://registry.npmjs.org/electron/-/electron-43.4.1.tgz",
      "integrity": "sha512-5b+EuiwkgG5iRcsEL34rimgRpkYp15SsfZOa0pC5kXs0Tb82TH4n95rpQzTZa7yRCbA7tm0WoEbuBL6NaAhAcA==",
      "license": "MIT",
      "dependencies": {
        "@electron-internal/extract-zip": "^1.0.1",
        "@electron/get": "^5.0.0",
        "@types/node": "^24.9.0"
      },
      "bin": {
        "electron": "cli.js",
        "install-electron": "install.js"
      },
      "engines": {
        "node": ">= 22.12.0"
      }
    },
    "node_modules/env-paths": {
      "version": "3.0.0",
      "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz",
      "integrity": "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==",
      "license": "MIT",
      "engines": {
        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/graceful-fs": {
      "version": "4.2.11",
      "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
      "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
      "license": "ISC"
    },
    "node_modules/ms": {
      "version": "2.1.3",
      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
      "license": "MIT"
    },
    "node_modules/progress": {
      "version": "2.0.3",
      "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
      "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
      "license": "MIT",
      "engines": {
        "node": ">=0.4.0"
      }
    },
    "node_modules/semver": {
      "version": "7.8.5",
      "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
      "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
      "license": "ISC",
      "bin": {
        "semver": "bin/semver.js"
      },
      "engines": {
        "node": ">=10"
      }
    },
    "node_modules/sumchecker": {
      "version": "3.0.1",
      "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz",
      "integrity": "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==",
      "license": "Apache-2.0",
      "dependencies": {
        "debug": "^4.1.0"
      },
      "engines": {
        "node": ">= 8.0"
      }
    },
    "node_modules/undici": {
      "version": "7.29.0",
      "resolved": "https://registry.npmjs.org/undici/-/undici-7.29.0.tgz",
      "integrity": "sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==",
      "license": "MIT",
      "optional": true,
      "engines": {
        "node": ">=20.18.1"
      }
    },
    "node_modules/undici-types": {
      "version": "7.18.2",
      "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
      "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
      "license": "MIT"
    }
  }
}
package.json
{
  "name": "sample-electron-app",
  "version": "1.0.0",
  "description": "verify electron.app in pkg:npm/electron@43.4.1",
  "main": "index.js",
  "license": "MIT-0",
  "dependencies": {
    "electron": "43.4.1"
  }
}
spec.json
{
  "schemaVersion": 1,
  "goal": "verify electron.app in pkg:npm/electron@43.4.1",
  "kind": "HOW",
  "packages": [
    "pkg:npm/electron@43.4.1"
  ],
  "symbols": [
    "electron.app"
  ],
  "constraints": {
    "executionContext": "node"
  },
  "runtimeConditions": {
    "ecosystem": "npm",
    "language": "javascript",
    "moduleSystem": "cjs",
    "packageManager": "npm@10.9.8",
    "runtime": "node@22.23.2"
  }
}
test/contract.cjs
const assert = require('node:assert');
const {
  getAppDeclarations,
  SUPPORTED_PATH_NAMES,
  createAppController,
} = require('../index.js');

async function runTests() {
  // Contract 1: electron type definitions declare App interface with lifecycle methods, path resolution, and event emitters
  {
    const decl = getAppDeclarations();
    assert.strictEqual(decl.hasAppInterface, true, 'App interface must exist in electron.d.ts');
    assert.strictEqual(decl.isMainExported, true, 'app must be exported in Main namespace');
    assert.strictEqual(decl.isCrossProcessExported, true, 'app must be exported in CrossProcessExports namespace');

    const expectedMethods = [
      'quit',
      'exit',
      'relaunch',
      'isReady',
      'whenReady',
      'focus',
      'getName',
      'setName',
      'getVersion',
      'getAppPath',
      'getPath',
      'setPath',
      'requestSingleInstanceLock',
      'hasSingleInstanceLock',
      'releaseSingleInstanceLock',
    ];

    for (const method of expectedMethods) {
      assert.ok(
        decl.methods.includes(method),
        `App declaration must contain '${method}' method`
      );
    }
  }

  // Contract 2: createAppController manages application ready state and executes whenReady lifecycle promise
  {
    const app = createAppController({ name: 'ContractTestApp', version: '2.4.0', appPath: '/mock/project' });

    assert.strictEqual(app.getName(), 'ContractTestApp');
    assert.strictEqual(app.getVersion(), '2.4.0');
    assert.strictEqual(app.getAppPath(), '/mock/project');
    assert.strictEqual(app.isReady(), false, 'App should not be ready initially');

    let willFinishEmitted = false;
    let readyEmitted = false;

    app.on('will-finish-launching', () => {
      willFinishEmitted = true;
    });

    app.on('ready', () => {
      readyEmitted = true;
    });

    let whenReadyResolved = false;
    const promise = app.whenReady().then(() => {
      whenReadyResolved = true;
    });

    assert.strictEqual(whenReadyResolved, false, 'whenReady promise should remain pending before markReady');

    app.markReady();

    await promise;

    assert.strictEqual(willFinishEmitted, true, 'will-finish-launching event must fire');
    assert.strictEqual(readyEmitted, true, 'ready event must fire');
    assert.strictEqual(whenReadyResolved, true, 'whenReady promise must resolve');
    assert.strictEqual(app.isReady(), true, 'isReady() must return true after markReady');

    // Calling whenReady again on ready app resolves immediately
    let subsequentResolved = false;
    await app.whenReady().then(() => {
      subsequentResolved = true;
    });
    assert.strictEqual(subsequentResolved, true, 'whenReady must resolve immediately once already ready');
  }

  // Contract 3: getPath and setPath manage standard Electron platform paths and reject invalid path names
  {
    const app = createAppController({ name: 'PathApp' });

    assert.ok(SUPPORTED_PATH_NAMES.length >= 15, 'Supported paths must cover Electron standard path set');
    assert.ok(SUPPORTED_PATH_NAMES.includes('userData'), 'userData path must be supported');
    assert.ok(SUPPORTED_PATH_NAMES.includes('temp'), 'temp path must be supported');
    assert.ok(SUPPORTED_PATH_NAMES.includes('home'), 'home path must be supported');

    const userDataPath = app.getPath('userData');
    assert.strictEqual(typeof userDataPath, 'string', 'userData path must be string');
    assert.ok(userDataPath.includes('PathApp'), 'userData path must reflect app name');

    // Override custom path
    app.setPath('userData', '/custom/path/userData');
    assert.strictEqual(app.getPath('userData'), '/custom/path/userData', 'setPath must override path');

    // Rejects invalid path names
    assert.throws(() => {
      app.getPath('invalidPathName');
    }, /Invalid path name/);

    assert.throws(() => {
      app.setPath('invalidPathName', '/some/path');
    }, /Invalid path name/);

    // Rejects empty path
    assert.throws(() => {
      app.setPath('userData', '');
    }, /non-empty string/);
  }

  // Contract 4: requestSingleInstanceLock and releaseSingleInstanceLock enforce single instance mutual exclusion
  {
    const app = createAppController();

    assert.strictEqual(app.hasSingleInstanceLock(), false, 'Single instance lock not held initially');

    const firstLock = app.requestSingleInstanceLock({ instanceId: 1 });
    assert.strictEqual(firstLock, true, 'First lock request must succeed');
    assert.strictEqual(app.hasSingleInstanceLock(), true, 'Lock status must be true');

    // Second lock attempt must fail
    const secondLock = app.requestSingleInstanceLock({ instanceId: 2 });
    assert.strictEqual(secondLock, false, 'Second lock request must fail');

    let secondInstanceNotified = false;
    let receivedArgv = null;
    let receivedAdditionalData = null;

    app.on('second-instance', (event, argv, cwd, additionalData) => {
      secondInstanceNotified = true;
      receivedArgv = argv;
      receivedAdditionalData = additionalData;
    });

    app.notifySecondInstance(['--flag', 'arg'], '/app/dir', { secondary: true });
    assert.strictEqual(secondInstanceNotified, true, 'second-instance event must fire');
    assert.deepStrictEqual(receivedArgv, ['--flag', 'arg']);
    assert.deepStrictEqual(receivedAdditionalData, { secondary: true });

    // Release lock
    app.releaseSingleInstanceLock();
    assert.strictEqual(app.hasSingleInstanceLock(), false, 'Lock status must be false after release');

    // Now lock can be re-acquired
    const reacquired = app.requestSingleInstanceLock({ instanceId: 3 });
    assert.strictEqual(reacquired, true, 'Lock can be acquired after release');
  }

  // Contract 5: quit and exit handle graceful shutdown and terminate lifecycle with exit codes
  {
    // Graceful quit with lifecycle cancellation support
    const gracefulApp = createAppController();

    let beforeQuitFired = false;
    let willQuitFired = false;
    let quitFired = false;

    gracefulApp.on('before-quit', (e) => {
      beforeQuitFired = true;
      // Prevent quit once
      e.preventDefault();
    });

    const quitResult1 = gracefulApp.quit();
    assert.strictEqual(quitResult1, false, 'Quit should be cancelled by before-quit preventDefault');
    assert.strictEqual(beforeQuitFired, true);
    assert.strictEqual(gracefulApp.getExitStatus(), null);

    // Now allowed quit
    const cleanApp = createAppController();
    cleanApp.on('before-quit', () => { beforeQuitFired = true; });
    cleanApp.on('will-quit', () => { willQuitFired = true; });
    cleanApp.on('quit', () => { quitFired = true; });

    const quitResult2 = cleanApp.quit();
    assert.strictEqual(quitResult2, true, 'Quit should succeed');
    const exitStatus = cleanApp.getExitStatus();
    assert.deepStrictEqual(exitStatus, { exited: true, exitCode: 0, graceful: true });

    // Immediate exit
    const exitApp = createAppController();
    exitApp.exit(42);
    assert.deepStrictEqual(exitApp.getExitStatus(), { exited: true, exitCode: 42, graceful: false });
  }

  console.log('All contracts passed successfully.');
}

runTests().catch((err) => {
  console.error(err);
  process.exit(1);
});

오리진 시더

익명