mcpField
S
script-include-patterns

script-include-patterns

github.com/serac-labs開発7758 回インストール9 週間前に更新
リポジトリ

script-include-patterns について

再利用可能なサーバーサイドScript Includeを作成するServiceNow開発者向けスキルです。各種Script Include、継承、GlideAjaxのES5パターンと実践方法を紹介します。

主な機能

  1. 01標準およびクライアント呼び出し可能な型のパターン
  2. 02継承とスコープ付きScript IncludeのES5例
  3. 03GlideAjaxのクライアント呼び出し例
  4. 04命名、責務分担、エラー処理の指針
  5. 05MCPツールによる作成、検索、更新、テスト

活用シーン

  1. 01再利用可能なサーバーサイドユーティリティの作成
  2. 02GlideAjaxを介したクライアントスクリプトへの公開
  3. 03既存Script Includeの検索と変更
  4. 04MCPツールを使ったScript Includeのテスト

MCP 対応

READMEにはScript Includeの作成、検索、更新、テストに使うMCPツールが記載されています。ツールを提供するMCPサーバーやクライアントは明記されていません。

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/serac-labs/serac --skill script-include-patterns

インストール後、Claude Code で/script-include-patternsを入力して読み込まれたか確認します。

詳細

ServiceNow のスクリプトインクルードパターン

スクリプトインクルードは、あらゆるサーバーサイドスクリプトから呼び出せる、再利用可能なサーバーサイド JavaScript ライブラリです。

スクリプトインクルードの種類

種類用途クライアントから呼び出し可能
標準サーバーサイドのユーティリティいいえ
クライアントから呼び出し可能クライアントからの GlideAjaxはい
オンデマンド遅延読み込みいいえ
AbstractAjaxProcessorクライアントとサーバー間の通信はい

標準スクリプトインクルード(ES5)

// Basic utility class
var IncidentUtils = Class.create()
IncidentUtils.prototype = {
  initialize: function () {
    this.LOG_PREFIX = "[IncidentUtils] "
  },

  /**
   * Get incident by number
   * @param {string} number - Incident number (INC0010001)
   * @returns {GlideRecord|null} - Incident record or null
   */
  getByNumber: function (number) {
    var gr = new GlideRecord("incident")
    gr.addQuery("number", number)
    gr.query()
    if (gr.next()) {
      return gr
    }
    return null
  },

  /**
   * Calculate priority based on impact and urgency
   * @param {number} impact - Impact value (1-3)
   * @param {number} urgency - Urgency value (1-3)
   * @returns {number} - Calculated priority (1-5)
   */
  calculatePriority: function (impact, urgency) {
    var matrix = {
      "1-1": 1,
      "1-2": 2,
      "1-3": 3,
      "2-1": 2,
      "2-2": 3,
      "2-3": 4,
      "3-1": 3,
      "3-2": 4,
      "3-3": 5,
    }
    var key = impact + "-" + urgency
    return matrix[key] || 4
  },

  /**
   * Get open incidents for user
   * @param {string} userSysId - User sys_id
   * @returns {Array} - Array of incident objects
   */
  getOpenIncidentsForUser: function (userSysId) {
    var incidents = []
    var gr = new GlideRecord("incident")
    gr.addQuery("caller_id", userSysId)
    gr.addQuery("active", true)
    gr.orderByDesc("opened_at")
    gr.query()

    while (gr.next()) {
      incidents.push({
        sys_id: gr.getUniqueValue(),
        number: gr.getValue("number"),
        short_description: gr.getValue("short_description"),
        state: gr.state.getDisplayValue(),
        priority: gr.priority.getDisplayValue(),
      })
    }
    return incidents
  },

  type: "IncidentUtils",
}

クライアントから呼び出し可能なスクリプトインクルード(ES5)

// Extends AbstractAjaxProcessor for GlideAjax calls
var IncidentAjax = Class.create()
IncidentAjax.prototype = Object.extendsObject(AbstractAjaxProcessor, {
  /**
   * Get incident details - callable from client
   * Client calls: new GlideAjax('IncidentAjax').addParam('sysparm_name', 'getIncidentDetails')
   */
  getIncidentDetails: function () {
    var incidentId = this.getParameter("sysparm_incident_id")
    var result = {}

    var gr = new GlideRecord("incident")
    if (gr.get(incidentId)) {
      result.success = true
      result.number = gr.getValue("number")
      result.short_description = gr.getValue("short_description")
      result.state = gr.state.getDisplayValue()
      result.priority = gr.priority.getDisplayValue()
      result.assigned_to = gr.assigned_to.getDisplayValue()
      result.assignment_group = gr.assignment_group.getDisplayValue()
    } else {
      result.success = false
      result.message = "Incident not found"
    }

    return JSON.stringify(result)
  },

  /**
   * Search incidents by keyword
   */
  searchIncidents: function () {
    var keyword = this.getParameter("sysparm_keyword")
    var limit = parseInt(this.getParameter("sysparm_limit"), 10) || 10
    var incidents = []

    var gr = new GlideRecord("incident")
    gr.addQuery("short_description", "CONTAINS", keyword)
    gr.addOrCondition("description", "CONTAINS", keyword)
    gr.addQuery("active", true)
    gr.setLimit(limit)
    gr.orderByDesc("opened_at")
    gr.query()

    while (gr.next()) {
      incidents.push({
        sys_id: gr.getUniqueValue(),
        number: gr.getValue("number"),
        short_description: gr.getValue("short_description"),
      })
    }

    return JSON.stringify(incidents)
  },

  /**
   * Check if user can update incident
   */
  canUserUpdate: function () {
    var incidentId = this.getParameter("sysparm_incident_id")
    var userId = gs.getUserID()

    var gr = new GlideRecord("incident")
    if (gr.get(incidentId)) {
      // Check if user is assigned or in assignment group
      var canUpdate =
        gr.getValue("assigned_to") === userId || this._isUserInGroup(userId, gr.getValue("assignment_group"))
      return JSON.stringify({ canUpdate: canUpdate })
    }

    return JSON.stringify({ canUpdate: false })
  },

  _isUserInGroup: function (userId, groupId) {
    var member = new GlideRecord("sys_user_grmember")
    member.addQuery("user", userId)
    member.addQuery("group", groupId)
    member.query()
    return member.hasNext()
  },

  type: "IncidentAjax",
})

クライアントサイドの GlideAjax 呼び出し(ES5)

// Client script calling Script Include
function getIncidentDetails(incidentSysId, callback) {
  var ga = new GlideAjax("IncidentAjax")
  ga.addParam("sysparm_name", "getIncidentDetails")
  ga.addParam("sysparm_incident_id", incidentSysId)
  ga.getXMLAnswer(function (answer) {
    var result = JSON.parse(answer)
    callback(result)
  })
}

// Usage in client script
getIncidentDetails(g_form.getUniqueValue(), function (incident) {
  if (incident.success) {
    g_form.addInfoMessage("Incident: " + incident.number)
  } else {
    g_form.addErrorMessage(incident.message)
  }
})

継承パターン(ES5)

// Base class
var TaskUtils = Class.create()
TaskUtils.prototype = {
  initialize: function (tableName) {
    this.tableName = tableName || "task"
  },

  getByState: function (state) {
    var records = []
    var gr = new GlideRecord(this.tableName)
    gr.addQuery("state", state)
    gr.query()
    while (gr.next()) {
      records.push(this._toObject(gr))
    }
    return records
  },

  _toObject: function (gr) {
    return {
      sys_id: gr.getUniqueValue(),
      number: gr.getValue("number"),
      short_description: gr.getValue("short_description"),
      state: gr.getValue("state"),
    }
  },

  type: "TaskUtils",
}

// Derived class
var IncidentUtilsExtended = Class.create()
IncidentUtilsExtended.prototype = Object.extendsObject(TaskUtils, {
  initialize: function () {
    TaskUtils.prototype.initialize.call(this, "incident")
  },

  getP1Incidents: function () {
    var incidents = []
    var gr = new GlideRecord("incident")
    gr.addQuery("priority", 1)
    gr.addQuery("active", true)
    gr.query()
    while (gr.next()) {
      var obj = this._toObject(gr)
      obj.caller = gr.caller_id.getDisplayValue()
      incidents.push(obj)
    }
    return incidents
  },

  type: "IncidentUtilsExtended",
})

スコープ付きスクリプトインクルード(ES5)

// In scoped application: x_myapp
var MyAppUtils = Class.create()
MyAppUtils.prototype = {
  initialize: function () {
    this.APP_SCOPE = "x_myapp"
  },

  /**
   * Get application property
   * @param {string} name - Property name (without scope prefix)
   */
  getAppProperty: function (name) {
    return gs.getProperty(this.APP_SCOPE + "." + name)
  },

  /**
   * Log with application prefix
   */
  log: function (message, source) {
    gs.info("[" + this.APP_SCOPE + "][" + (source || "MyAppUtils") + "] " + message)
  },

  /**
   * Access cross-scope table safely
   */
  getGlobalUser: function (userId) {
    var gr = new GlideRecord("sys_user")
    if (gr.get(userId)) {
      return {
        name: gr.getValue("name"),
        email: gr.getValue("email"),
      }
    }
    return null
  },

  type: "MyAppUtils",
}

MCP ツールとの連携

利用可能なスクリプトインクルード用ツール
ツール目的
snow_create_script_include新しいスクリプトインクルードを作成
snow_artifact_manage (action='find')既存のスクリプトインクルードを検索
snow_artifact_manage (action='update')スクリプトインクルードのコードを変更
snow_execute_scriptスクリプトインクルードをテスト
ワークフローの例
// 1. Create Script Include
await snow_create_script_include({
  name: "IncidentUtils",
  script: "/* Script Include code */",
  client_callable: false,
  description: "Incident utility functions",
})

// 2. Test the Script Include
await snow_execute_script({
  script: `
        var utils = new IncidentUtils();
        var incident = utils.getByNumber('INC0010001');
        gs.info('Found: ' + (incident ? incident.number : 'null'));
    `,
})

ベストプラクティス

  1. 単一責任 - 1 つのクラスに 1 つの目的を持たせます
  2. 意味のある名前 - Utils ではなく IncidentUtils を使います
  3. メソッドを文書化する - JSDoc コメントを使います
  4. エラー処理 - ロギングを伴う try-catch を使います
  5. プライベートメソッド - 先頭にアンダースコアを付けます
  6. 副作用を避ける - 初期化時にデータを変更しないようにします
  7. テスト可能にする - 単体テスト可能なメソッドを記述します
  8. ES5 のみ - const、let、アロー関数、テンプレートリテラルは使いません

コメント