diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..15374c9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +build +triggers \ No newline at end of file diff --git a/deno.lock b/deno.lock index 4d9d111..779e44a 100644 --- a/deno.lock +++ b/deno.lock @@ -1,12 +1,110 @@ { "version": "3", + "packages": { + "specifiers": { + "npm:dedent": "npm:dedent@1.5.1" + }, + "npm": { + "dedent@1.5.1": { + "integrity": "sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==", + "dependencies": {} + } + } + }, + "redirects": { + "https://deno.land/x/import/mod.ts": "https://deno.land/x/import@0.2.1/mod.ts" + }, "remote": { + "https://deno.land/std@0.140.0/_util/assert.ts": "e94f2eb37cebd7f199952e242c77654e43333c1ac4c5c700e929ea3aa5489f74", + "https://deno.land/std@0.140.0/_util/os.ts": "3b4c6e27febd119d36a416d7a97bd3b0251b77c88942c8f16ee5953ea13e2e49", + "https://deno.land/std@0.140.0/bytes/bytes_list.ts": "67eb118e0b7891d2f389dad4add35856f4ad5faab46318ff99653456c23b025d", + "https://deno.land/std@0.140.0/bytes/equals.ts": "fc16dff2090cced02497f16483de123dfa91e591029f985029193dfaa9d894c9", + "https://deno.land/std@0.140.0/bytes/mod.ts": "763f97d33051cc3f28af1a688dfe2830841192a9fea0cbaa55f927b49d49d0bf", + "https://deno.land/std@0.140.0/fmt/colors.ts": "30455035d6d728394781c10755351742dd731e3db6771b1843f9b9e490104d37", + "https://deno.land/std@0.140.0/fs/_util.ts": "0fb24eb4bfebc2c194fb1afdb42b9c3dda12e368f43e8f2321f84fc77d42cb0f", + "https://deno.land/std@0.140.0/fs/ensure_dir.ts": "9dc109c27df4098b9fc12d949612ae5c9c7169507660dcf9ad90631833209d9d", + "https://deno.land/std@0.140.0/hash/sha256.ts": "803846c7a5a8a5a97f31defeb37d72f519086c880837129934f5d6f72102a8e8", + "https://deno.land/std@0.140.0/io/buffer.ts": "bd0c4bf53db4b4be916ca5963e454bddfd3fcd45039041ea161dbf826817822b", + "https://deno.land/std@0.140.0/path/_constants.ts": "df1db3ffa6dd6d1252cc9617e5d72165cd2483df90e93833e13580687b6083c3", + "https://deno.land/std@0.140.0/path/_interface.ts": "ee3b431a336b80cf445441109d089b70d87d5e248f4f90ff906820889ecf8d09", + "https://deno.land/std@0.140.0/path/_util.ts": "c1e9686d0164e29f7d880b2158971d805b6e0efc3110d0b3e24e4b8af2190d2b", + "https://deno.land/std@0.140.0/path/common.ts": "bee563630abd2d97f99d83c96c2fa0cca7cee103e8cb4e7699ec4d5db7bd2633", + "https://deno.land/std@0.140.0/path/glob.ts": "cb5255638de1048973c3e69e420c77dc04f75755524cb3b2e160fe9277d939ee", + "https://deno.land/std@0.140.0/path/mod.ts": "d3e68d0abb393fb0bf94a6d07c46ec31dc755b544b13144dee931d8d5f06a52d", + "https://deno.land/std@0.140.0/path/posix.ts": "293cdaec3ecccec0a9cc2b534302dfe308adb6f10861fa183275d6695faace44", + "https://deno.land/std@0.140.0/path/separator.ts": "fe1816cb765a8068afb3e8f13ad272351c85cbc739af56dacfc7d93d710fe0f9", + "https://deno.land/std@0.140.0/path/win32.ts": "31811536855e19ba37a999cd8d1b62078235548d67902ece4aa6b814596dd757", + "https://deno.land/std@0.140.0/streams/conversion.ts": "712585bfa0172a97fb68dd46e784ae8ad59d11b88079d6a4ab098ff42e697d21", + "https://deno.land/std@0.173.0/_util/asserts.ts": "178dfc49a464aee693a7e285567b3d0b555dc805ff490505a8aae34f9cfb1462", + "https://deno.land/std@0.173.0/_util/os.ts": "d932f56d41e4f6a6093d56044e29ce637f8dcc43c5a90af43504a889cf1775e3", + "https://deno.land/std@0.173.0/encoding/base32.ts": "cb15f16e53c580d2491637280302bc6cfeb48f8911521ea9c7895ba513a2bcc5", + "https://deno.land/std@0.173.0/encoding/jsonc.ts": "02b86115d2b812f26789481ebcf4748171e8ece6514b60243b3733e2c200876a", + "https://deno.land/std@0.173.0/fs/_util.ts": "65381f341af1ff7f40198cee15c20f59951ac26e51ddc651c5293e24f9ce6f32", + "https://deno.land/std@0.173.0/fs/copy.ts": "14214efd94fc3aa6db1e4af2b4b9578e50f7362b7f3725d5a14ad259a5df26c8", + "https://deno.land/std@0.173.0/fs/empty_dir.ts": "c3d2da4c7352fab1cf144a1ecfef58090769e8af633678e0f3fabaef98594688", + "https://deno.land/std@0.173.0/fs/ensure_dir.ts": "724209875497a6b4628dfb256116e5651c4f7816741368d6c44aab2531a1e603", + "https://deno.land/std@0.173.0/fs/ensure_file.ts": "c38602670bfaf259d86ca824a94e6cb9e5eb73757fefa4ebf43a90dd017d53d9", + "https://deno.land/std@0.173.0/fs/ensure_link.ts": "c0f5b2f0ec094ed52b9128eccb1ee23362a617457aa0f699b145d4883f5b2fb4", + "https://deno.land/std@0.173.0/fs/ensure_symlink.ts": "2955cc8332aeca9bdfefd05d8d3976b94e282b0f353392a71684808ed2ffdd41", + "https://deno.land/std@0.173.0/fs/eol.ts": "f1f2eb348a750c34500741987b21d65607f352cf7205f48f4319d417fff42842", + "https://deno.land/std@0.173.0/fs/exists.ts": "b8c8a457b71e9d7f29b9d2f87aad8dba2739cbe637e8926d6ba6e92567875f8e", + "https://deno.land/std@0.173.0/fs/expand_glob.ts": "45d17e89796a24bd6002e4354eda67b4301bb8ba67d2cac8453cdabccf1d9ab0", + "https://deno.land/std@0.173.0/fs/mod.ts": "bc3d0acd488cc7b42627044caf47d72019846d459279544e1934418955ba4898", + "https://deno.land/std@0.173.0/fs/move.ts": "4cb47f880e3f0582c55e71c9f8b1e5e8cfaacb5e84f7390781dd563b7298ec19", + "https://deno.land/std@0.173.0/fs/walk.ts": "ea95ffa6500c1eda6b365be488c056edc7c883a1db41ef46ec3bf057b1c0fe32", + "https://deno.land/std@0.173.0/path/_constants.ts": "e49961f6f4f48039c0dfed3c3f93e963ca3d92791c9d478ac5b43183413136e0", + "https://deno.land/std@0.173.0/path/_interface.ts": "6471159dfbbc357e03882c2266d21ef9afdb1e4aa771b0545e90db58a0ba314b", + "https://deno.land/std@0.173.0/path/_util.ts": "86c2375a996c1931b2f2ac71fefd5ddf0cf0e579fa4ab12d3e4c552d4223b8d8", + "https://deno.land/std@0.173.0/path/common.ts": "ee7505ab01fd22de3963b64e46cff31f40de34f9f8de1fff6a1bd2fe79380000", + "https://deno.land/std@0.173.0/path/glob.ts": "d479e0a695621c94d3fd7fe7abd4f9499caf32a8de13f25073451c6ef420a4e1", + "https://deno.land/std@0.173.0/path/mod.ts": "4b83694ac500d7d31b0cdafc927080a53dc0c3027eb2895790fb155082b0d232", + "https://deno.land/std@0.173.0/path/posix.ts": "0874b341c2c6968ca38d323338b8b295ea1dae10fa872a768d812e2e7d634789", + "https://deno.land/std@0.173.0/path/separator.ts": "0fb679739d0d1d7bf45b68dacfb4ec7563597a902edbaf3c59b50d5bcadd93b1", + "https://deno.land/std@0.173.0/path/win32.ts": "672942919dd66ce1b8c224e77d3447e2ad8254eaff13fe6946420a9f78fa141e", "https://deno.land/std@0.196.0/assert/assert.ts": "9a97dad6d98c238938e7540736b826440ad8c1c1e54430ca4c4e623e585607ee", "https://deno.land/std@0.196.0/assert/assertion_error.ts": "4d0bde9b374dfbcbe8ac23f54f567b77024fb67dbb1906a852d67fe050d42f56", "https://deno.land/std@0.196.0/console/_data.json": "cf2cc9d039a192b3adbfe64627167c7e6212704c888c25c769fc8f1709e1e1b8", "https://deno.land/std@0.196.0/console/_rle.ts": "56668d5c44f964f1b4ff93f21c9896df42d6ee4394e814db52d6d13f5bb247c7", "https://deno.land/std@0.196.0/console/unicode_width.ts": "10661c0f2eeab802d16b8b85ed8825bbc573991bbfb6affed32dc1ff994f54f9", "https://deno.land/std@0.196.0/fmt/colors.ts": "a7eecffdf3d1d54db890723b303847b6e0a1ab4b528ba6958b8f2e754cf1b3bc", + "https://deno.land/std@0.198.0/_util/os.ts": "d932f56d41e4f6a6093d56044e29ce637f8dcc43c5a90af43504a889cf1775e3", + "https://deno.land/std@0.198.0/assert/assert.ts": "9a97dad6d98c238938e7540736b826440ad8c1c1e54430ca4c4e623e585607ee", + "https://deno.land/std@0.198.0/assert/assertion_error.ts": "4d0bde9b374dfbcbe8ac23f54f567b77024fb67dbb1906a852d67fe050d42f56", + "https://deno.land/std@0.198.0/path/_basename.ts": "057d420c9049821f983f784fd87fa73ac471901fb628920b67972b0f44319343", + "https://deno.land/std@0.198.0/path/_constants.ts": "e49961f6f4f48039c0dfed3c3f93e963ca3d92791c9d478ac5b43183413136e0", + "https://deno.land/std@0.198.0/path/_dirname.ts": "355e297236b2218600aee7a5301b937204c62e12da9db4b0b044993d9e658395", + "https://deno.land/std@0.198.0/path/_extname.ts": "eaaa5aae1acf1f03254d681bd6a8ce42a9cb5b7ff2213a9d4740e8ab31283664", + "https://deno.land/std@0.198.0/path/_format.ts": "4a99270d6810f082e614309164fad75d6f1a483b68eed97c830a506cc589f8b4", + "https://deno.land/std@0.198.0/path/_from_file_url.ts": "7e4e5626089785adddb061f1b9f4932d6b21c7df778e7449531a11e32048245c", + "https://deno.land/std@0.198.0/path/_interface.ts": "6471159dfbbc357e03882c2266d21ef9afdb1e4aa771b0545e90db58a0ba314b", + "https://deno.land/std@0.198.0/path/_is_absolute.ts": "05dac10b5e93c63198b92e3687baa2be178df5321c527dc555266c0f4f51558c", + "https://deno.land/std@0.198.0/path/_join.ts": "fd78555bc34d5f188918fc7018dfe8fe2df5bbad94a3b30a433666c03934d77f", + "https://deno.land/std@0.198.0/path/_normalize.ts": "a19ec8706b2707f9dd974662a5cd89fad438e62ab1857e08b314a8eb49a34d81", + "https://deno.land/std@0.198.0/path/_parse.ts": "0f9b0ff43682dd9964eb1c4398610c4e165d8db9d3ac9d594220217adf480cfa", + "https://deno.land/std@0.198.0/path/_relative.ts": "27bdeffb5311a47d85be26d37ad1969979359f7636c5cd9fcf05dcd0d5099dc5", + "https://deno.land/std@0.198.0/path/_resolve.ts": "3bf0287d62488cad08c3c219a9708c4a4c658c65d7b4400fd99afdc3ba10a64d", + "https://deno.land/std@0.198.0/path/_to_file_url.ts": "739bfda583598790b2e77ce227f2bb618f6ebdb939788cea47555b43970ec58c", + "https://deno.land/std@0.198.0/path/_to_namespaced_path.ts": "0d5f4caa2ed98ef7a8786286df6af804b50e38859ae897b5b5b4c8c5930a75c8", + "https://deno.land/std@0.198.0/path/_util.ts": "4e191b1bac6b3bf0c31aab42e5ca2e01a86ab5a0d2e08b75acf8585047a86221", + "https://deno.land/std@0.198.0/path/basename.ts": "6f08fbb90dbfcf320765b3abb01f995b1723f75e2534acfd5380e202c802a3aa", + "https://deno.land/std@0.198.0/path/common.ts": "ee7505ab01fd22de3963b64e46cff31f40de34f9f8de1fff6a1bd2fe79380000", + "https://deno.land/std@0.198.0/path/dirname.ts": "098996822a31b4c46e1eb52a19540d3c6f9f54b772fc8a197939eeabc29fca2f", + "https://deno.land/std@0.198.0/path/extname.ts": "9b83c62fd16505739541f7a3ab447d8972da39dbf668d47af2f93206c2480893", + "https://deno.land/std@0.198.0/path/format.ts": "cb22f95cc7853d590b87708cc9441785e760d711188facff3d225305a8213aca", + "https://deno.land/std@0.198.0/path/from_file_url.ts": "a6221cfc928928ec4d9786d767dfac98fa2ab746af0786446c9834a07b98817e", + "https://deno.land/std@0.198.0/path/glob.ts": "d479e0a695621c94d3fd7fe7abd4f9499caf32a8de13f25073451c6ef420a4e1", + "https://deno.land/std@0.198.0/path/is_absolute.ts": "6b3d36352eb7fa29edb53f9e7b09b1aeb022a3c5465764f6cc5b8c41f9736197", + "https://deno.land/std@0.198.0/path/join.ts": "4a2867ff2f3c81ffc9eb3d56dade16db6f8bd3854f269306d23dad4115089c84", + "https://deno.land/std@0.198.0/path/mod.ts": "7765507696cb321994cdacfc19ee3ba61e8e3ebf4bd98fa75a276cf5dc18ce2a", + "https://deno.land/std@0.198.0/path/normalize.ts": "7d992cd262b2deefa842d93a8ba2ed51f3949ba595b1d07f627ac2cddbc74808", + "https://deno.land/std@0.198.0/path/parse.ts": "031fe488b3497fb8312fc1dc3c3d6c2d80707edd9c661e18ee9fd20f95edf322", + "https://deno.land/std@0.198.0/path/posix.ts": "0a1c1952d132323a88736d03e92bd236f3ed5f9f079e5823fae07c8d978ee61b", + "https://deno.land/std@0.198.0/path/relative.ts": "7db80c5035016174267da16321a742d76e875215c317859a383b12f413c6f5d6", + "https://deno.land/std@0.198.0/path/resolve.ts": "103b62207726a27f28177f397008545804ecb20aaf00623af1f622b18cd80b9f", + "https://deno.land/std@0.198.0/path/separator.ts": "0fb679739d0d1d7bf45b68dacfb4ec7563597a902edbaf3c59b50d5bcadd93b1", + "https://deno.land/std@0.198.0/path/to_file_url.ts": "dd32f7a01bbf3b15b5df46796659984b372973d9b2d7d59bcf0eb990763a0cb5", + "https://deno.land/std@0.198.0/path/to_namespaced_path.ts": "4e643ab729bf49ccdc166ad48d2de262ff462938fcf2a44a4425588f4a0bd690", + "https://deno.land/std@0.198.0/path/win32.ts": "8b3f80ef7a462511d5e8020ff490edcaa0a0d118f1b1e9da50e2916bdd73f9dd", "https://deno.land/std@0.220.1/assert/assert.ts": "bec068b2fccdd434c138a555b19a2c2393b71dfaada02b7d568a01541e67cdc5", "https://deno.land/std@0.220.1/assert/assertion_error.ts": "9f689a101ee586c4ce92f52fa7ddd362e86434ffdf1f848e45987dc7689976b8", "https://deno.land/std@0.220.1/dotenv/mod.ts": "0180eaeedaaf88647318811cdaa418cc64dc51fb08354f91f5f480d0a1309f7d", @@ -167,6 +265,32 @@ "https://deno.land/x/cliffy@v1.0.0-rc.3/table/deps.ts": "1226c4d39d53edc81d7c3e661fb8a79f2e704937c276c60355cd4947a0fe9153", "https://deno.land/x/cliffy@v1.0.0-rc.3/table/mod.ts": "40f148428acbfffa270f7077c403b3893797d9e454a74ccb41a8434367351326", "https://deno.land/x/cliffy@v1.0.0-rc.3/table/row.ts": "79eb1468aafdd951e5963898cdafe0752d4ab4c519d5f847f3d8ecb8fe857d4f", - "https://deno.land/x/cliffy@v1.0.0-rc.3/table/table.ts": "298671e72e61f1ab18b42ae36643181993f79e29b39dc411fdc6ffd53aa04684" + "https://deno.land/x/cliffy@v1.0.0-rc.3/table/table.ts": "298671e72e61f1ab18b42ae36643181993f79e29b39dc411fdc6ffd53aa04684", + "https://deno.land/x/deno_cache@0.4.1/auth_tokens.ts": "5fee7e9155e78cedf3f6ff3efacffdb76ac1a76c86978658d9066d4fb0f7326e", + "https://deno.land/x/deno_cache@0.4.1/cache.ts": "51f72f4299411193d780faac8c09d4e8cbee951f541121ef75fcc0e94e64c195", + "https://deno.land/x/deno_cache@0.4.1/deno_dir.ts": "f2a9044ce8c7fe1109004cda6be96bf98b08f478ce77e7a07f866eff1bdd933f", + "https://deno.land/x/deno_cache@0.4.1/deps.ts": "8974097d6c17e65d9a82d39377ae8af7d94d74c25c0cbb5855d2920e063f2343", + "https://deno.land/x/deno_cache@0.4.1/dirs.ts": "d2fa473ef490a74f2dcb5abb4b9ab92a48d2b5b6320875df2dee64851fa64aa9", + "https://deno.land/x/deno_cache@0.4.1/disk_cache.ts": "1f3f5232cba4c56412d93bdb324c624e95d5dd179d0578d2121e3ccdf55539f9", + "https://deno.land/x/deno_cache@0.4.1/file_fetcher.ts": "07a6c5f8fd94bf50a116278cc6012b4921c70d2251d98ce1c9f3c352135c39f7", + "https://deno.land/x/deno_cache@0.4.1/http_cache.ts": "f632e0d6ec4a5d61ae3987737a72caf5fcdb93670d21032ddb78df41131360cd", + "https://deno.land/x/deno_cache@0.4.1/mod.ts": "ef1cda9235a93b89cb175fe648372fc0f785add2a43aa29126567a05e3e36195", + "https://deno.land/x/deno_cache@0.4.1/util.ts": "8cb686526f4be5205b92c819ca2ce82220aa0a8dd3613ef0913f6dc269dbbcfe", + "https://deno.land/x/denoflate@1.2.1/mod.ts": "f5628e44b80b3d80ed525afa2ba0f12408e3849db817d47a883b801f9ce69dd6", + "https://deno.land/x/denoflate@1.2.1/pkg/denoflate.js": "b9f9ad9457d3f12f28b1fb35c555f57443427f74decb403113d67364e4f2caf4", + "https://deno.land/x/denoflate@1.2.1/pkg/denoflate_bg.wasm.js": "d581956245407a2115a3d7e8d85a9641c032940a8e810acbd59ca86afd34d44d", + "https://deno.land/x/esbuild@v0.19.1/mod.js": "37937134e2c363c2afd42bf83640b6cf8296403a4711b27318fb45a7c9f12c31", + "https://deno.land/x/esbuild@v0.19.1/wasm.js": "6f2b5d809456db4ebb9f8fbfd8120b5b00d095cad760eb1041572e521157a69b", + "https://deno.land/x/esbuild_deno_loader@0.8.1/deps.ts": "987b50a1a921fcc84ddfcf1a1256cb955f6b16acac28a3fc77901c12c0752173", + "https://deno.land/x/esbuild_deno_loader@0.8.1/mod.ts": "28524460bef46d487221b01ade6ed913d2e127de7eeee025ab75b34b491283da", + "https://deno.land/x/esbuild_deno_loader@0.8.1/src/deno.ts": "b0af3e430c068f18c6fa48c2083a1b4354b6c303e16fb37855e02fcafb95f36d", + "https://deno.land/x/esbuild_deno_loader@0.8.1/src/loader_native.ts": "0289d8708f47c876d6a4280592a8a12bb2d29676fedf25ddf222ecd6a1bb0bd8", + "https://deno.land/x/esbuild_deno_loader@0.8.1/src/loader_portable.ts": "d999f452ef3d8ec2dd3c8443f542adf57efc8a2cd59b29cc41f5b3d7dff512e5", + "https://deno.land/x/esbuild_deno_loader@0.8.1/src/plugin_deno_loader.ts": "166356133ee63d80e5559a10c18e10b625da96e39a4518b8c7adfef718bb4e32", + "https://deno.land/x/esbuild_deno_loader@0.8.1/src/plugin_deno_resolver.ts": "0449ed23ae93db1ec74d015a46934aefd7ba7a8f719f7a4980b616cb3f5bbee4", + "https://deno.land/x/esbuild_deno_loader@0.8.1/src/shared.ts": "33052684aeb542ebd24da372816bbbf885cd090a7ab0fde7770801f7f5b49572", + "https://deno.land/x/import@0.2.1/mod.ts": "574a255ac7f454c99df6497b28a85197929a91772048c9c0eae59f5227502a0d", + "https://deno.land/x/importmap@0.2.1/_util.ts": "ada9a9618b537e6c0316c048a898352396c882b9f2de38aba18fd3f2950ede89", + "https://deno.land/x/importmap@0.2.1/mod.ts": "ae3d1cd7eabd18c01a4960d57db471126b020f23b37ef14e1359bbb949227ade" } } diff --git a/src/deps.ts b/src/deps.ts index bbf649b..e9a3594 100644 --- a/src/deps.ts +++ b/src/deps.ts @@ -6,3 +6,4 @@ export { mergeReadableStreams } from 'std/streams/merge_readable_streams.ts' export { Command, EnumType, ValidationError } from 'https://deno.land/x/cliffy@v1.0.0-rc.3/command/mod.ts' export { Cell, Row, Table } from 'https://deno.land/x/cliffy@v1.0.0-rc.3/table/mod.ts' +export { dynamicImport } from 'https://deno.land/x/import/mod.ts' diff --git a/src/index.ts b/src/index.ts index 37a638c..f783bac 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,9 @@ import { VERSION } from './version.ts' -import { Command } from '/deps.ts' -import { Row, Table } from '/deps.ts' -import { PerforceClient } from './lib/p4.ts' +import { Command, dotenv, dynamicImport, path, Row, Table } from '/deps.ts' +import { logger } from './lib/logger.ts' +import { P4Client } from './lib/p4.ts' +import { template } from './lib/template.ts' +import { P4Trigger, TriggerConfig, TriggerContext, TriggerFn } from './lib/types.ts' await new Command() .name('triggerr') @@ -10,7 +12,7 @@ await new Command() .globalOption('-d, --debug', 'Enable debug output.') .command('list', 'list current triggers') .action(async () => { - const p4 = new PerforceClient() + const p4 = new P4Client() const cmd = await p4.runCommandZ(`triggers`, ['-o']) const triggers = p4.parseTriggersOutput(cmd.output) @@ -22,4 +24,134 @@ await new Command() .border(true) .render() }) + .command('init', 'initialize a new triggerr project') + .option('-p, --path ', 'Path to initialize', { default: path.join(Deno.cwd(), 'triggers/') }) + .action(async ({ path }) => { + // create the directory + await Deno.mkdir(path, { recursive: true }) + // create the .env file + await Deno.writeTextFile(`${path}/.env`, '') + // copy types.ts + await Deno.copyFile('./src/lib/types.ts', `${path}/types.ts`) + // create trigger from template + await Deno.writeTextFile(`${path}/example-trigger.ts`, template.trim()) + }) + .command('install', 'install a trigger') + .arguments('') + .option('-e, --executable', 'setup the trigger as an executable') + .option('-p, --platform ', 'platform to setup the executable as', { default: Deno.build.os }) + .option('-d, --deno-binary ', 'path to the deno binary', { default: 'deno' }) + .option('-u, --update', 'update the trigger if it already exists') + .action(async ({ executable, platform, denoBinary, update }, script) => { + const scriptPath = import.meta.resolve(script) + const { config } = await dynamicImport(scriptPath) + + let triggerCommand = '' + // TODO(warman): setup step to actually compile the binary if it doesn't exist + if (executable) { + if (platform === 'windows') { + triggerCommand = `./triggerr.exe run ${scriptPath}` + } else { + triggerCommand = `./triggerr run ${scriptPath}` + } + } else { + // We want to run this cli as the entry point + const cliPath = path.fromFileUrl(import.meta.url) + triggerCommand = `${denoBinary} run -A ${cliPath} exec ${scriptPath}` + } + const trigger: P4Trigger = { + name: config.name, + type: config.type, + path: config.path, + command: triggerCommand, + } + + const p4 = new P4Client() + const cmd = await p4.runCommandZ(`triggers`, ['-o']) + const triggers = p4.parseTriggersOutput(cmd.output) + const exists = triggers.find((t) => t.name === config.name) + if (exists) { + if (!update) { + console.error('trigger already exists') + return + } else { + const index = triggers.findIndex((t) => t.name === config.name) + triggers[index] = { + ...triggers[index], + ...trigger, + } + } + } else { + triggers.push(trigger) + } + + const newTriggers = await p4.saveTriggerTable(triggers) + new Table() + .header(Row.from(['Name', 'Type', 'Path', 'Command']).border()) + .body( + newTriggers.map((trigger) => new Row(trigger.name, trigger.type, trigger.path, trigger.command)), + ) + .border(true) + .render() + }) + .command('rm', 'remove a trigger') + .arguments('') + .action(async (_, triggerName) => { + const p4 = new P4Client() + const cmd = await p4.runCommandZ(`triggers`, ['-o']) + const triggers = p4.parseTriggersOutput(cmd.output) + const exists = triggers.find((t) => t.name === triggerName) + if (!exists) { + console.log(`${triggerName} not found`) + return + } else { + const index = triggers.findIndex((t) => t.name === triggerName) + triggers.splice(index, 1) + } + + const newTriggers = await p4.saveTriggerTable(triggers) + new Table() + .header(Row.from(['Name', 'Type', 'Path', 'Command']).border()) + .body( + newTriggers.map((trigger) => new Row(trigger.name, trigger.type, trigger.path, trigger.command)), + ) + .border(true) + .render() + }) + .command('exec', 'execute a trigger') + .arguments(' [...args]') + .stopEarly() + .action(async (_, script, ...args: Array) => { + // import.meta.resolve will return a file:/// url needed for dynamic import + const scriptPath = import.meta.resolve(script) + const { main, config }: { main: TriggerFn; config: TriggerConfig } = await dynamicImport(scriptPath) + + const envPath = path.fromFileUrl(`${path.dirname(scriptPath)}/.env`) + await dotenv.load({ + envPath, + examplePath: `${envPath}.example`, + export: true, + }) + + logger.setContext(config.name) + + const ctx: TriggerContext = { + config, + log: logger, + p4: new P4Client(), + } + + try { + const { result, error } = await main(args, ctx) + if (error) { + logger.error(error.message) + } + logger.info(`trigger executed successfully`) + if (result) { + logger.info('trigger result:', result) + } + } catch (e) { + logger.error(e.message) + } + }) .parse(Deno.args) diff --git a/src/lib/logger.ts b/src/lib/logger.ts new file mode 100644 index 0000000..aaca2b4 --- /dev/null +++ b/src/lib/logger.ts @@ -0,0 +1,126 @@ +import { fmt } from '/deps.ts' +import { createConfigDirSync, DefaultMap, getRandomInt } from '/lib/utils.ts' + +export enum LogLevel { + DEBUG = 'DEBUG', + INFO = 'INFO', + ERROR = 'ERROR', +} + +interface Rgb { + r: number + g: number + b: number +} + +const randomRgb24 = (): Rgb => ({ + r: getRandomInt(256), + g: getRandomInt(256), + b: getRandomInt(256), +}) + +const randomStrColors = new DefaultMap(randomRgb24) + +const randomColorForStr = (str: string): Rgb => { + return randomStrColors.get(str) +} + +class Logger { + private commandContext = null as string | null + private sessionId = null as string | null + private logToFile = true + private logLevel = LogLevel.INFO + private logDir = createConfigDirSync() + private writeQueue: Promise[] = [] + + setContext(context: string) { + this.commandContext = context + } + + setSessionId(id: string) { + this.sessionId = id + } + + enableFileLogging(enable: boolean) { + this.logToFile = enable + } + + setLogLevel(level: LogLevel) { + this.logLevel = level + } + + private formatMessage(level: LogLevel, args: any[]) { + const timestamp = new Date().toISOString() + const messageStr = args.map((arg) => typeof arg === 'object' ? `${Deno.inspect(arg, { colors: true })}` : arg).join( + ' ', + ) + let levelStr + switch (level) { + case LogLevel.INFO: + levelStr = fmt.bold(fmt.green(level)) + break + case LogLevel.ERROR: + levelStr = fmt.bold(fmt.red(level)) + break + case LogLevel.DEBUG: + levelStr = fmt.bold(fmt.yellow(level)) + break + default: + levelStr = level + } + let str = `[${timestamp}] [${levelStr}]` + if (this.commandContext) { + const color = randomColorForStr(this.commandContext) + str += ` [${fmt.rgb24(this.commandContext.toUpperCase(), color)}]` + } + str += ` ${messageStr}` + return str + } + + private writeToFile(message: string) { + if (this.logToFile && this.sessionId) { + const file = `${this.logDir}/${this.sessionId}.log` + const msg = `${fmt.stripAnsiCode(message)}\n` + const p = Deno.writeTextFile(file, msg, { append: true }).catch((e) => {}) + this.writeQueue.push(p) + } + } + + async drainWriteQueue() { + await Promise.all(this.writeQueue) + this.writeQueue = [] + } + + private shouldLog(level: LogLevel) { + return level >= this.logLevel + } + + info(...args: any[]) { + if (!this.shouldLog(LogLevel.INFO)) return + const formatted = this.formatMessage(LogLevel.INFO, args) + console.log(formatted) + this.writeToFile(formatted) + } + + error(...args: any[]) { + if (!this.shouldLog(LogLevel.ERROR)) return + const formatted = this.formatMessage(LogLevel.ERROR, args) + console.error(formatted) + this.writeToFile(formatted) + } + + debug(...args: any[]) { + if (!this.shouldLog(LogLevel.DEBUG)) return + const formatted = this.formatMessage(LogLevel.DEBUG, args) + console.log(formatted) + this.writeToFile(formatted) + } +} + +export const logger = new Logger() + +// Before the program exits, ensure all logs are written to the file. +Deno.addSignalListener('SIGINT', async () => { + await logger.drainWriteQueue() + Deno.exit() +}) diff --git a/src/lib/p4.ts b/src/lib/p4.ts index 7059a32..66fb744 100644 --- a/src/lib/p4.ts +++ b/src/lib/p4.ts @@ -1,15 +1,8 @@ -import { exec, ExecResult } from './utils.ts' +import { exec } from './utils.ts' import { fs } from '../deps.ts' +import { ExecResult, P4ClientInterface, P4Trigger } from './types.ts' -type P4Trigger = { - index: number - name: string - type: string - path: string - command: string -} - -export class PerforceClient { +export class P4Client implements P4ClientInterface { private p4Path: string private cwd: string | URL private config: Record = {} @@ -74,6 +67,16 @@ export class PerforceClient { }) } + openPipe(command: string, args: string[] = []) { + const fullArgs = [command, ...args] + const pipe = new Deno.Command(this.p4Path, { + args: fullArgs, + env: this.config, + stdin: 'piped', + }) + return pipe.spawn() + } + parseTriggersOutput(output: string): P4Trigger[] { const triggers: P4Trigger[] = [] const lines = output.split('\n') @@ -94,4 +97,33 @@ export class PerforceClient { return triggers } + + serializeTriggers(triggers: P4Trigger[]): string { + return triggers.map((trigger) => { + return `${trigger.name} ${trigger.type} ${trigger.path} "${trigger.command}"` + }).join('\n\t') + } + + buildTriggerTable(triggers: P4Trigger[]): string { + return ` +Triggers: + ${this.serializeTriggers(triggers)} +`.trim() + } + + async saveTriggerTable(triggers: P4Trigger[]): Promise { + // build new the trigger table + const table = this.buildTriggerTable(triggers) + + // write the new table back to p4 + const pipe = this.openPipe('triggers', ['-i']) + const writer = pipe.stdin.getWriter() + await writer.write(new TextEncoder().encode(table)) + await writer.close() + + // read the updated triggers back + const cmd = await this.runCommandZ(`triggers`, ['-o']) + const newTriggers = this.parseTriggersOutput(cmd.output) + return newTriggers + } } diff --git a/src/lib/template.ts b/src/lib/template.ts new file mode 100644 index 0000000..13e7a3b --- /dev/null +++ b/src/lib/template.ts @@ -0,0 +1,19 @@ +import dedent from 'npm:dedent' + +export const template = dedent` + import { TriggerConfig, TriggerContext, TriggerFn } from '/lib/types.ts' + + // https://www.perforce.com/manuals/p4sag/Content/P4SAG/scripting.trigger.table.fields.html + export const config: TriggerConfig = { + name: 'example-trigger', // unique name for the trigger + type: 'change-commit', // type of trigger to apply + path: '//Engine/...', // path to apply the trigger to + args: ['--debug'] // arguments to pass to the trigger + } + + export const main: TriggerFn = async (args: string[], ctx: TriggerContext) => { + ctx.log.debug('execution args', args) + ctx.log.debug('context', ctx) + return { result: {} } + } +` diff --git a/src/lib/test.ts b/src/lib/test.ts deleted file mode 100644 index 11929b3..0000000 --- a/src/lib/test.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { Cell, Row, Table } from '../deps.ts' - -type P4Trigger = { - index: number - name: string - type: string - path: string - command: string -} - -function parseTriggersOutput(output: string): P4Trigger[] { - const triggers: P4Trigger[] = [] - const lines = output.split('\n') - - lines.forEach((line) => { - // Adjusted regex to match the new input format - const match = line.match(/\.\.\. Triggers(\d+) (\S+) (\S+) (\S+) "(.*)"/) - if (match) { - const [, index, name, type, path, command] = match - triggers.push({ - index: parseInt(index), - name, - type, - path, - command, - }) - } - }) - - return triggers -} - -// Example usage -const output = ` -... Triggers0 slack-notification1 change-commit //Engine/... "C:/Users/ck-user/.deno/bin/deno run --allow-all D:/P4TRIGGERS/p4-notify.ts %user% %changelist%" -... Triggers1 slack-notification2 change-commit //ProjectLyra/... "C:/Users/ck-user/.deno/bin/deno run --allow-all D:/P4TRIGGERS/p4-notify.ts %user% %changelist%" -... Triggers2 buildkite-trigger3 change-commit //Engine/... "C:/Users/ck-user/.deno/bin/deno run --allow-all D:/P4TRIGGERS/bk-trigger.ts buildkite-perforce-test %user% %changelist%" -... Triggers3 buildkite-trigger4 change-commit //ProjectLyra/... "C:/Users/ck-user/.deno/bin/deno run --allow-all D:/P4TRIGGERS/bk-trigger.ts buildkite-perforce-test %user% %changelist%" -`.trim() - -const output1 = ` -... Triggers0 slack-notification1 change-commit //Engine/... "C:/Users/ck-user/.deno/bin/deno run --allow-all D:/P4TRIGGERS/p4-notify.ts %user% %changelist%" -`.trim() - -const triggers = parseTriggersOutput(output) - -// console.log(JSON.stringify(triggers, null, 2)); - -new Table() - .header(Row.from(['Name', 'Type', 'Path', 'Command']).border()) - .body( - triggers.map((trigger) => new Row(trigger.name, trigger.type, trigger.path, trigger.command)), - ) - .border(true) - .render() diff --git a/src/lib/types.ts b/src/lib/types.ts new file mode 100644 index 0000000..fcc32db --- /dev/null +++ b/src/lib/types.ts @@ -0,0 +1,49 @@ +export interface ExecResult { + success: boolean + code: number + signal: Deno.Signal | null + output: string +} + +export type P4Trigger = { + index?: number + name: string + type: string + path: string + command: string +} + +export interface P4ClientInterface { + runCommand: (command: string, args: string[], options: { quiet: boolean }) => Promise + runCommandZ: (command: string, args: string[], options: { quiet: boolean }) => Promise + openPipe: (command: string, args: string[]) => Deno.ChildProcess + parseTriggersOutput: (output: string) => P4Trigger[] + serializeTriggers: (triggers: P4Trigger[]) => string + buildTriggerTable: (triggers: P4Trigger[]) => string +} + +export interface TriggerConfig { + name: string + type: string + path: string + args: string[] +} + +export interface TriggerContext { + config: TriggerConfig + log: { + info: (...args: any[]) => void + error: (...args: any[]) => void + debug: (...args: any[]) => void + } + p4: P4ClientInterface +} + +export type TriggerResult = { + error?: Error + result: any +} + +export interface TriggerFn { + (args: string[], ctx: TriggerContext): Promise +} diff --git a/src/lib/utils.ts b/src/lib/utils.ts index e09d6a4..d8523c6 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -1,11 +1,5 @@ import { mergeReadableStreams } from '/deps.ts' - -export interface ExecResult { - success: boolean - code: number - signal: Deno.Signal | null - output: string -} +import { ExecResult } from '/lib/types.ts' export async function exec( cmd: string, @@ -75,3 +69,25 @@ export function execSync( const { success, code, signal } = process return { success, code, signal, output } } + +export function createConfigDirSync(): string { + const homeDir = Deno.build.os === 'windows' ? Deno.env.get('USERPROFILE') : Deno.env.get('HOME') + const configDir = `${homeDir}/.triggerr` + Deno.mkdirSync(configDir, { recursive: true }) + return configDir +} + +export class DefaultMap extends Map { + constructor(private defaultFn: (key: K) => V, entries?: readonly (readonly [K, V])[] | null) { + super(entries) + } + + get(key: K): V { + if (!super.has(key)) { + super.set(key, this.defaultFn(key)) + } + return super.get(key)! + } +} + +export const getRandomInt = (max: number) => Math.floor(Math.random() * max)