├── .babelrc ├── .gitignore ├── LICENSE.md ├── README.md ├── lib └── util.js ├── one-off-scripts ├── README.md ├── compile-babel-files.js ├── open-async-save-issues.js ├── open-coffee-upgrade-issues.js ├── open-onDidChange-issues.js ├── print-grammar-scope-names.js └── search-lint-output.js ├── package.json └── script ├── clone-packages.js ├── download-metadata.js ├── download-package-json.js └── update.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "sourceMap": "no", 3 | "plugins": [ 4 | ["add-module-exports", {}], 5 | ["transform-async-to-generator", {}], 6 | ["transform-decorators-legacy", {}], 7 | ["transform-class-properties", {}], 8 | ["transform-es2015-modules-commonjs", {"strictMode": false}], 9 | ["transform-export-extensions", {}], 10 | ["transform-do-expressions", {}], 11 | ["transform-function-bind", {}], 12 | ["transform-object-rest-spread", {}], 13 | ["transform-flow-strip-types", {}], 14 | ["transform-react-jsx", {}] 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | package-json/ 2 | packages/ 3 | package-metadata.json 4 | node_modules/ 5 | .DS_Store 6 | npm-debug.log 7 | *.swp 8 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 GitHub Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ##### Atom and all repositories under Atom will be archived on December 15, 2022. Learn more in our [official announcement](https://github.blog/2022-06-08-sunsetting-atom/) 2 | # atom-package-downloader 3 | 4 | Downloads all Atom packages so we can search for patterns. 5 | 6 | ``` 7 | git clone https://github.com/atom/atom-package-downloader 8 | cd atom-package-downloader 9 | npm install 10 | script/update.js 11 | ``` 12 | 13 | * Packages will be downloaded to `./packages` in the repository directory. 14 | * Metadata will be updated every time. 15 | * Existing packages will be fetched when re-run. 16 | -------------------------------------------------------------------------------- /lib/util.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const url = require('url') 3 | const path = require('path') 4 | const querystring = require('querystring') 5 | 6 | const rootPath = path.join(__dirname, '..') 7 | const metadataPath = path.join(rootPath, 'package-metadata.json') 8 | const packagesDirPath = path.join(rootPath, 'packages') 9 | const packageJsonDirPath = path.join(rootPath, 'package-json') 10 | const dummyAuthString = 'some-user:some-password' 11 | 12 | function packageURL (packageName) { 13 | const gitConfigPath = path.join(packagesDirPath, packageName, '.git', 'config') 14 | const gitConfig = fs.readFileSync(gitConfigPath, 'utf8') 15 | const gitURL = gitConfig.match(/url = (.+)/)[1] 16 | return url.format(Object.assign(url.parse(gitURL), { 17 | auth: null 18 | })) 19 | } 20 | 21 | function newIssueURL (packageName, title, body) { 22 | return packageURL(packageName) + '/issues/new?' + querystring.stringify({ 23 | title: title, 24 | body: body 25 | }) 26 | } 27 | 28 | function getDefaultBranchSha (packageName) { 29 | const headsDir = path.join(packagesDirPath, packageName, '.git', 'refs', 'heads') 30 | const branchNames = fs.readdirSync(headsDir) 31 | const branchName = branchNames.includes('master') ? 'master' : branchNames[0] 32 | return fs.readFileSync(path.join(packagesDirPath, packageName, '.git', 'refs', 'heads', branchName), 'utf8').trim() 33 | } 34 | 35 | module.exports = { 36 | rootPath, 37 | metadataPath, 38 | packagesDirPath, 39 | packageJsonDirPath, 40 | getDefaultBranchSha, 41 | dummyAuthString, 42 | packageURL, 43 | newIssueURL 44 | } 45 | -------------------------------------------------------------------------------- /one-off-scripts/README.md: -------------------------------------------------------------------------------- 1 | # one-off scripts 2 | 3 | These are one-time-use scripts that may be useful as examples of how to automate future tasks related to helping the package community. 4 | -------------------------------------------------------------------------------- /one-off-scripts/compile-babel-files.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const fs = require('fs') 4 | const glob = require('glob') 5 | const path = require('path') 6 | const babel = require('babel-core') 7 | 8 | var PREFIXES = [ 9 | '/** @babel */', 10 | '"use babel"', 11 | '\'use babel\'', 12 | '/* @flow */' 13 | ] 14 | const packagesPath = path.resolve('.') 15 | const filePaths = glob.sync(path.join(packagesPath, 'packages', '**', '*.js')) 16 | const options = JSON.parse(fs.readFileSync(path.join(packagesPath, '.babelrc'))) 17 | const plugins = [] 18 | for (const [pluginName, pluginOptions] of options['plugins']) { 19 | plugins.push([require.resolve(`babel-plugin-${pluginName}`), pluginOptions]) 20 | } 21 | options['plugins'] = plugins 22 | 23 | for (const filePath of filePaths) { 24 | if (!filePath.includes('node_modules/')) { 25 | try { 26 | const fileContent = fs.readFileSync(filePath, 'utf8') 27 | if (PREFIXES.some((p) => fileContent.startsWith(p))) { 28 | babel.transform(fileContent, options) 29 | // console.log(filePath) 30 | } 31 | } catch (e) { 32 | if (!e.message.includes('EISDIR')) { 33 | console.warn('ERROR: ' + filePath) 34 | console.warn(e.message) 35 | // break 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /one-off-scripts/open-async-save-issues.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const fs = require('fs') 4 | const temp = require('temp') 5 | const dedent = require('dedent') 6 | const {execFile} = require('child_process') 7 | const {newIssueURL, packageURL} = require('../lib/util') 8 | 9 | process.on('unhandledRejection', console.error) 10 | 11 | function callPattern(variableName, methodName) { 12 | return `\\b\\w*${variableName}([A-Z]\\w+)?\\.${methodName}\\b` 13 | } 14 | 15 | const ASYNC_METHODS = { 16 | 'Pane.close': callPattern('pane', 'close'), 17 | 'TextBuffer.save': callPattern('buffer', 'save'), 18 | 'TextEditor.save': callPattern('editor', 'save'), 19 | 'Pane.saveItem' : callPattern('pane', 'saveItem'), 20 | 'Pane.saveItemAs': callPattern('pane', 'saveItemAs'), 21 | 'Pane.saveActiveItem': callPattern('pane', 'saveActiveItem'), 22 | 'Pane.saveActiveItemAs': callPattern('pane', 'saveActiveItemAs'), 23 | 'Pane.saveItems': callPattern('pane', 'saveItems'), 24 | 'Pane.close': callPattern('pane', 'close'), 25 | 'Workspace.saveActivePaneItem': callPattern('workspace', 'saveActivePaneItem'), 26 | 'Workspace.saveActivePaneItemAs': callPattern('workspace', 'saveActivePaneItemAs'), 27 | } 28 | 29 | let searchPromise 30 | let resultsPath = process.argv[2] 31 | if (resultsPath) { 32 | process.stderr.write(`reading search results from ${resultsPath}\n`); 33 | searchPromise = Promise.resolve(JSON.parse(fs.readFileSync(resultsPath))) 34 | } else { 35 | searchPromise = Promise.all(Object.keys(ASYNC_METHODS).map((method) => { 36 | return new Promise(resolve => { 37 | const pattern = ASYNC_METHODS[method] 38 | execFile( 39 | 'ag', 40 | [ 41 | pattern, 42 | 'packages', 43 | '--group', 44 | '--file-search-regex', 45 | '(js|coffee|ts)x?$', 46 | ], 47 | (error, stdout, stderr) => { 48 | const files = [] 49 | for (let chunk of stdout.split('\n\n')) { 50 | chunk = chunk.trim() 51 | if (chunk) { 52 | const lines = chunk.split('\n') 53 | files.push({ 54 | path: lines[0], 55 | method: method, 56 | matches: lines.slice(1).map(line => parseInt(line)).filter(Number.isFinite) 57 | }) 58 | } 59 | } 60 | process.stderr.write(`found ${files.length} files calling ${method}\n`); 61 | resolve(files) 62 | } 63 | ) 64 | }) 65 | })).then(resultArrays => { 66 | const results = resultArrays.concat.apply([], resultArrays) 67 | resultsPath = temp.openSync().path 68 | fs.writeFileSync(resultsPath, JSON.stringify(results, null, 2)) 69 | process.stderr.write(`wrote search results to ${resultsPath}\n`) 70 | return results 71 | }) 72 | } 73 | 74 | searchPromise.then(results => { 75 | const packageResults = {} 76 | const packageURLsByName = {} 77 | 78 | for (const result of results) { 79 | const pathComponents = result.path.split('/') 80 | const packageName = pathComponents[1] 81 | let packageResult = packageResults[packageName] 82 | if (!packageResult) { 83 | packageResult = packageResults[packageName] = { 84 | url: packageURL(packageName), 85 | methodCallSites: {} 86 | } 87 | } 88 | 89 | if (!packageResult.methodCallSites[result.method]) { 90 | packageResult.methodCallSites[result.method] = [] 91 | } 92 | 93 | for (let match of result.matches) { 94 | packageResult.methodCallSites[result.method].push({ 95 | path: pathComponents.slice(2).join('/'), 96 | line: match 97 | }) 98 | } 99 | } 100 | 101 | let i = 0 102 | 103 | for (const packageName in packageResults) { 104 | const result = packageResults[packageName] 105 | if (result.url.startsWith('https://github.com/atom/')) continue 106 | 107 | if (i <= 200) { 108 | i++ 109 | continue 110 | } 111 | 112 | console.log('open $\'' + newIssueURL( 113 | packageName, 114 | 'Changes required due to TextBuffer.save becoming async in Atom 1.19', 115 | ` 116 | Hi! Thanks for maintaining the ${packageName} package! 117 | 118 | In Atom v1.19, we will release a [major change](https://github.com/atom/atom/pull/14435) to Atom's core text buffer data structure. As part of this change, we have made \`TextBuffer.save\` asynchronous; rather than blocking until the save is complete, it now immediately returns a \`Promise\` that *resolves* when the save is complete. Because of this, a few other Atom APIs that *use* \`save\` have similarly become async: 119 | 120 | ${ 121 | Object.keys(ASYNC_METHODS) 122 | .map(method => '* `' + method + '`') 123 | .join('\n') 124 | } 125 | 126 | #### Effects on this package 127 | 128 | We think this package could be impacted by this upgrade because it calls the changed methods in the following places: 129 | 130 | ${ 131 | Object.keys(result.methodCallSites) 132 | .map(method => { 133 | const callSites = result.methodCallSites[method] 134 | return '* `' + method + '`\n' + callSites.map(callSite => { 135 | return ` * [here](${result.url}/tree/master/${callSite.path}#L${callSite.line})` 136 | }).join('\n') 137 | }).join('\n') 138 | } 139 | 140 | We found these calls using a regex search, so this list might be incomplete, and it might contain some false positives. 141 | 142 | #### What to do about the change 143 | 144 | It should be pretty easy to adjust your package code and/or tests to work with the new async behavior, and to simultaneously keep it working with older versions of Atom. Here are some examples of pull requests we opened on our bundled packages to cope with the change: 145 | 146 | * https://github.com/atom/autocomplete-plus/pull/852 147 | * https://github.com/atom/whitespace/pull/155 148 | * https://github.com/atom/symbols-view/pull/222 149 | * https://github.com/atom/status-bar/pull/193 150 | * https://github.com/atom/tabs/pull/448 151 | 152 | Please let me know if you have any questions. I would be happy to help! 153 | 154 | ` 155 | ).replace("'", "\\'") + '\';\n') 156 | 157 | i++ 158 | } 159 | }) -------------------------------------------------------------------------------- /one-off-scripts/open-coffee-upgrade-issues.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const fs = require('fs') 4 | const dedent = require('dedent') 5 | const {newIssueURL, packageURL} = require('../lib/util') 6 | 7 | const errorFilePath = process.argv[2] 8 | const errorDescriptions = fs.readFileSync(errorFilePath, 'utf8').split('\n\n') 9 | 10 | let lastPackageName = null 11 | let lastPackageErrors = [] 12 | 13 | for (const errorDescription of errorDescriptions) { 14 | const [filePath, variableName] = errorDescription.split('\n') 15 | const packageName = filePath.split('/')[1] 16 | const [relativePath, lineNumber] = filePath.split('/').slice(2).join('/').split(':') 17 | 18 | if (lastPackageName && packageName !== lastPackageName) { 19 | const baseURL = packageURL(lastPackageName) 20 | 21 | console.log('open \'' + newIssueURL( 22 | lastPackageName, 23 | 'Changes required for upcoming CoffeeScript upgrade', 24 | dedent ` 25 | Hi! Thanks for maintaining the ${lastPackageName} package! 26 | 27 | In Atom v1.12, we are going to [upgrade CoffeeScript](https://github.com/atom/atom/pull/12780) to the latest version. This upgrade entails one potentially breaking change to the language: 28 | 29 | > Changed strategy for the generation of internal compiler variable names. Note that this means that @example function parameters are no longer available as naked example variables within the function body. 30 | 31 | We think your package may be affected by this upgrade, in the following places: 32 | 33 | ${lastPackageErrors.map(error => { 34 | return `* The \`${error.variableName}\` variable [here](${baseURL}/tree/master/${error.filePath}#L${error.lineNumber})` 35 | }).join('\n')} 36 | 37 | These findings are based on linting packages with \`coffeescope\`. We could be wrong about some of them. When we release v1.12 beta, please test your package against it to make sure that it works. Let me know if you have any further questions; I will be happy to help! 38 | ` 39 | ) + '\'') 40 | 41 | lastPackageErrors = [] 42 | } 43 | 44 | lastPackageErrors.push({ 45 | filePath: relativePath, 46 | lineNumber: lineNumber, 47 | variableName: variableName 48 | }) 49 | 50 | lastPackageName = packageName 51 | } 52 | -------------------------------------------------------------------------------- /one-off-scripts/open-onDidChange-issues.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const fs = require('fs') 4 | const temp = require('temp') 5 | const dedent = require('dedent') 6 | const {execFile} = require('child_process') 7 | const {newIssueURL, packageURL, getDefaultBranchSha} = require('../lib/util') 8 | 9 | process.on('unhandledRejection', console.error) 10 | 11 | function callPattern(variableName, methodName) { 12 | return `\\b\\w*${variableName}([A-Z]\\w+)?(\(\))?\\.${methodName}\\b` 13 | } 14 | 15 | const METHODS = { 16 | 'TextBuffer.onDidChange': callPattern('buffer', 'onDidChange'), 17 | 'TextEditor.onDidChange': callPattern('editor', 'onDidChange'), 18 | } 19 | 20 | let searchPromise 21 | let resultsPath = process.argv[2] 22 | if (resultsPath) { 23 | process.stderr.write(`reading search results from ${resultsPath}\n`); 24 | searchPromise = Promise.resolve(JSON.parse(fs.readFileSync(resultsPath))) 25 | } else { 26 | searchPromise = Promise.all(Object.keys(METHODS).map((method) => { 27 | return new Promise(resolve => { 28 | const pattern = METHODS[method] 29 | execFile( 30 | 'ag', 31 | [ 32 | pattern, 33 | 'packages', 34 | '--group', 35 | '--file-search-regex', 36 | '(js|coffee|ts)x?$', 37 | ], 38 | (error, stdout, stderr) => { 39 | const files = [] 40 | for (let chunk of stdout.split('\n\n')) { 41 | chunk = chunk.trim() 42 | if (chunk) { 43 | const lines = chunk.split('\n') 44 | files.push({ 45 | path: lines[0], 46 | method: method, 47 | matches: lines.slice(1).map(line => parseInt(line)).filter(Number.isFinite) 48 | }) 49 | } 50 | } 51 | process.stderr.write(`found ${files.length} files calling ${method}\n`); 52 | resolve(files) 53 | } 54 | ) 55 | }) 56 | })).then(resultArrays => { 57 | const results = resultArrays.concat.apply([], resultArrays) 58 | resultsPath = temp.openSync().path 59 | fs.writeFileSync(resultsPath, JSON.stringify(results, null, 2)) 60 | process.stderr.write(`wrote search results to ${resultsPath}\n`) 61 | return results 62 | }) 63 | } 64 | 65 | searchPromise.then(results => { 66 | const packageResults = {} 67 | const packageURLsByName = {} 68 | 69 | for (const result of results) { 70 | const pathComponents = result.path.split('/') 71 | const packageName = pathComponents[1] 72 | let packageResult = packageResults[packageName] 73 | if (!packageResult) { 74 | packageResult = packageResults[packageName] = { 75 | url: packageURL(packageName), 76 | methodCallSites: {} 77 | } 78 | } 79 | 80 | if (!packageResult.methodCallSites[result.method]) { 81 | packageResult.methodCallSites[result.method] = [] 82 | } 83 | 84 | for (let match of result.matches) { 85 | packageResult.methodCallSites[result.method].push({ 86 | path: pathComponents.slice(2).join('/'), 87 | line: match 88 | }) 89 | } 90 | } 91 | 92 | let i = -1 93 | 94 | for (const packageName in packageResults) { 95 | const result = packageResults[packageName] 96 | if (result.url.startsWith('https://github.com/atom/')) continue 97 | i++ 98 | 99 | if (i <= 100) continue 100 | 101 | const defaultBranchSha = getDefaultBranchSha(packageName) 102 | 103 | console.log('open $\'' + newIssueURL( 104 | packageName, 105 | 'Changes to TextEditor.onDidChange and TextBuffer.onDidChange coming in Atom 1.23', 106 | ` 107 | Hi! Thanks for maintaining the ${packageName} package! 108 | 109 | In Atom v1.23, we will [some](https://github.com/atom/text-buffer/pull/273) [changes](https://github.com/atom/text-buffer/pull/274) that may affect your package. 110 | 111 | The methods \`TextEditor.onDidChange\` and \`TextBuffer.onDidChange\` will now call their callbacks *less frequently*. Previously, these callbacks would get called once for each individual change to the buffer. So if you had 5 cursors and typed a character, they would get called 5 times. Now, they will only get called once, and the event that is passed to them will contain information about *all 5* of the changes that have occurred. 112 | 113 | The same properties that have always existed on the \`TextBuffer.onDidChange\` events (\`oldRange\`, \`newRange\`, \`oldText\`, and \`newText\`) will still be there, and they will now reflect the sum of *all* changes that have occurred. But now there will be an additional property called \`changes\`, which will contain an array of more fine-grained objects describing the *individual* changes. We encourage you to use this property instead of the old ones. 114 | 115 | #### Effects on this package 116 | 117 | It looks like this package calls the changed methods in the following places: 118 | 119 | ${ 120 | Object.keys(result.methodCallSites) 121 | .map(method => { 122 | const callSites = result.methodCallSites[method] 123 | return '* `' + method + '`\n' + callSites.map(callSite => { 124 | return ` * [here](${result.url}/blob/${defaultBranchSha}/${callSite.path}#L${callSite.line})` 125 | }).join('\n') 126 | }).join('\n') 127 | } 128 | 129 | We found these calls using a regex search, so this list might be incomplete, and it might contain some false positives. 130 | 131 | #### What to do about the change 132 | 133 | It is likely that you do not need to do anything. The old event properties will continue to work. 134 | 135 | However, you may be able to handle changes more accurately and efficiently by using the \`changes\` field of the events rather than the old properties. The \`changes\` field does not exist in Atom 1.22 unless you use the \`TextBuffer.onDidChangeText\` method. In Atom 1.23 and above though, \`.onDidChange\` and \`.onDidChangeText\` will become identical, having both the old properties and the new \`changes\` property. 136 | 137 | Please let me know if you have any questions. I would be happy to help! 138 | ` 139 | ).replace("'", "\\'") + '\';\n') 140 | } 141 | }) 142 | -------------------------------------------------------------------------------- /one-off-scripts/print-grammar-scope-names.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const CSON = require('season') 4 | const fs = require('fs') 5 | const glob = require('glob') 6 | const path = require('path') 7 | 8 | console.log('Collecting grammar scope names... This might take a while.') 9 | const packagesPath = path.resolve('.') 10 | const grammarPaths = glob.sync(path.join(packagesPath, 'packages', '**', 'grammars', '*.{json,cson}')) 11 | let scopes = [] 12 | for (const grammarPath of grammarPaths) { 13 | try { 14 | const fileContent = fs.readFileSync(grammarPath) 15 | const content = grammarPath.endsWith('.cson') ? CSON.parse(fileContent) : JSON.parse(fileContent) 16 | scopes = scopes.concat(getScopes(content)) 17 | } catch (e) { 18 | console.error(`Skipping ${grammarPath}. Error: ${e}`) 19 | } 20 | } 21 | 22 | console.log('[') 23 | let currentRow = ' ' 24 | for (let scope of Array.from(new Set(scopes)).sort()) { 25 | if (` ${currentRow}'${scope}',`.length > 80) { 26 | console.log(currentRow) 27 | currentRow = ` '${scope}',` 28 | } else { 29 | currentRow += ` '${scope}',` 30 | } 31 | } 32 | console.log(currentRow.slice(0, -1)) 33 | console.log(']') 34 | 35 | function getScopes (object, isWithinPattern) { 36 | const CSSClassNameRegExp = /^[_a-zA-Z]+[_a-zA-Z0-9-]*$/ 37 | const keys = new Set(Object.keys(object)) 38 | let names = [] 39 | for (let key of keys) { 40 | const value = object[key] 41 | const type = Object.prototype.toString.apply(value) 42 | const isScopeName = key === 'scopeName' || ((isWithinPattern || keys.has('match')) && key === 'name') 43 | if (type === '[object String]' && isScopeName) { 44 | names = names.concat(value.split('.').filter(v => CSSClassNameRegExp.test(v))) 45 | } else if (type === '[object Array]' || type === '[object Object]') { 46 | names = names.concat(getScopes(value, isWithinPattern || key === 'beginCaptures' || key === 'endCaptures' || key === 'patterns')) 47 | } 48 | } 49 | return names 50 | } 51 | -------------------------------------------------------------------------------- /one-off-scripts/search-lint-output.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const fs = require('fs') 4 | const path = require('path') 5 | 6 | const coffeelintOutputPath = process.argv[2] 7 | const packagesPath = path.resolve('.') 8 | const lines = fs.readFileSync(coffeelintOutputPath, 'utf8').split('\n') 9 | for (let i = 0; i < lines.length; i++) { 10 | const line = lines[i] 11 | const match = line.match(/#(\d+).*Undefined identifier "(\w+)"./) 12 | if (match) { 13 | const lineNumber = match[1] 14 | const variableName = match[2] 15 | if (variableName !== 'emit') { 16 | let filePath 17 | for (let j = i - 1; j >= 0; j--) { 18 | const previousLine = lines[j] 19 | const index = previousLine.indexOf('packages/') 20 | if (index !== -1) { 21 | filePath = previousLine.substring(index) 22 | break 23 | } 24 | } 25 | 26 | try { 27 | const fileContent = fs.readFileSync(path.join(packagesPath, filePath), 'utf8') 28 | const hasUndefinedInstanceVariable = 29 | fileContent.includes(`@${variableName}`) || 30 | (fileContent.includes('class') && fileContent.includes(`${variableName}:`)) 31 | if (hasUndefinedInstanceVariable) { 32 | console.log(`${filePath}:${lineNumber}`) 33 | console.log(variableName) 34 | console.log() 35 | } 36 | } catch (e) { 37 | console.error(e) 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "atom-package-downloader", 3 | "version": "0.0.0", 4 | "description": "Downloads all Atom packages so we can search for patterns.", 5 | "main": "./lib/atom-package-downloader", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/atom/atom-package-downloader.git" 9 | }, 10 | "bugs": { 11 | "url": "https://github.com/atom/atom-package-downloader/issues" 12 | }, 13 | "licenses": [ 14 | { 15 | "type": "MIT", 16 | "url": "http://github.com/atom/atom-package-downloader/raw/master/LICENSE.md" 17 | } 18 | ], 19 | "dependencies": { 20 | "async": "^0.9.0", 21 | "colors": "^1.1.2", 22 | "glob": "^7.1.0", 23 | "mkdirp": "^0.5.1", 24 | "request": "^2.48.0", 25 | "resolve": "^1.1.7", 26 | "temp": "^0.8.3" 27 | }, 28 | "devDependencies": { 29 | "babel-core": "^6.23.1", 30 | "babel-plugin-add-module-exports": "^0.2.1", 31 | "babel-plugin-transform-async-to-generator": "^6.22.0", 32 | "babel-plugin-transform-class-properties": "^6.23.0", 33 | "babel-plugin-transform-decorators-legacy": "^1.3.4", 34 | "babel-plugin-transform-do-expressions": "^6.22.0", 35 | "babel-plugin-transform-es2015-modules-commonjs": "^6.23.0", 36 | "babel-plugin-transform-export-extensions": "^6.22.0", 37 | "babel-plugin-transform-flow-strip-types": "^6.22.0", 38 | "babel-plugin-transform-function-bind": "^6.22.0", 39 | "babel-plugin-transform-object-rest-spread": "^6.23.0", 40 | "babel-plugin-transform-react-jsx": "^6.23.0", 41 | "babel-plugin-transform-strict-mode": "^6.22.0", 42 | "standard": "^8.4.0" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /script/clone-packages.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const fs = require('fs') 4 | const url = require('url') 5 | const path = require('path') 6 | const async = require('async') 7 | const spawn = require('child_process').spawn 8 | const util = require('../lib/util') 9 | 10 | const allPackages = require(util.metadataPath) 11 | let completedPackageCount = 0 12 | 13 | async.eachLimit( 14 | allPackages, 15 | 10, 16 | (pack, done) => { 17 | const packageRepoPath = path.join(util.packagesDirPath, pack.name) 18 | 19 | fs.exists(packageRepoPath, (exists) => { 20 | if (exists) { 21 | let options = {cwd: packageRepoPath} 22 | runGit(['fetch', 'origin'], options, () => { 23 | runGit(['reset', '--hard', 'origin/HEAD'], options, succeed) 24 | }) 25 | } else if (pack.repository && pack.repository.url) { 26 | const packageURL = url.format(Object.assign(url.parse(pack.repository.url), { 27 | auth: util.dummyAuthString 28 | })) 29 | runGit(['clone', '--depth=1', packageURL, packageRepoPath], null, succeed) 30 | } else { 31 | fail({message: 'Missing repository URL'}) 32 | } 33 | }) 34 | 35 | function runGit (args, options, callback) { 36 | let child = spawn('git', args, Object.assign({stdio: 'ignore'}, options)) 37 | child.on('error', fail) 38 | child.on('exit', callback) 39 | } 40 | 41 | function succeed () { 42 | proceed() 43 | done() 44 | } 45 | 46 | function fail (error) { 47 | proceed() 48 | console.error(' ' + error.message) 49 | done() 50 | } 51 | 52 | function proceed () { 53 | completedPackageCount++ 54 | console.log(completedPackageCount + '/' + allPackages.length + ' - ' + pack.name) 55 | } 56 | }, 57 | 58 | (error) => { 59 | if (error) { 60 | console.error(error) 61 | return 62 | } 63 | 64 | console.log(`Cloned all packages to ${util.packagesDirPath}`) 65 | } 66 | ) 67 | -------------------------------------------------------------------------------- /script/download-metadata.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const fs = require('fs') 4 | const async = require('async') 5 | const request = require('request') 6 | const util = require('../lib/util') 7 | 8 | const NEXT_PAGE_URL_REGEX = /<([^>]+)>; rel="next"/ 9 | const LAST_PAGE_URL_REGEX = /<([^>]+)>; rel="last"/ 10 | 11 | const allPackages = [] 12 | const packageNames = new Set() 13 | let pageCount = null 14 | let nextPageURL = 'https://atom.io/api/packages?sort=downloads&order=desc&page=1' 15 | 16 | async.whilst( 17 | () => nextPageURL, 18 | 19 | (done) => { 20 | process.stdout.write(`Fetching ${nextPageURL}`) 21 | if (pageCount !== null) process.stdout.write(` (${pageCount} pages)`) 22 | process.stdout.write('\n') 23 | 24 | request({url: nextPageURL, json: true}, (error, response, packages) => { 25 | if (error) { 26 | done(error) 27 | return 28 | } 29 | 30 | nextPageURL = null 31 | 32 | if (response.headers.link) { 33 | const nextLinkMatch = response.headers.link.match(NEXT_PAGE_URL_REGEX) 34 | if (nextLinkMatch) nextPageURL = nextLinkMatch[1] 35 | 36 | const lastLinkMatch = response.headers.link.match(LAST_PAGE_URL_REGEX) 37 | if (lastLinkMatch) pageCount = lastLinkMatch[1].match(/page=(\d+)/)[1] 38 | } 39 | 40 | for (const pack of packages) { 41 | // atom.io seems to return some of the same packages multiple times 42 | if (packageNames.has(pack.name)) continue 43 | packageNames.add(pack.name) 44 | 45 | delete pack.versions 46 | allPackages.push(pack) 47 | } 48 | 49 | done() 50 | }) 51 | }, 52 | 53 | (error) => { 54 | if (error) { 55 | console.error(error) 56 | return 57 | } 58 | 59 | fs.writeFileSync(util.metadataPath, JSON.stringify(allPackages, null, 2)) 60 | console.log(`Wrote package metadata to ${util.metadataPath}`) 61 | } 62 | ) 63 | -------------------------------------------------------------------------------- /script/download-package-json.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const fs = require('fs') 4 | const path = require('path') 5 | const async = require('async') 6 | const mkdirp = require('mkdirp') 7 | const request = require('request') 8 | const util = require('../lib/util') 9 | 10 | const allPackages = require(util.metadataPath) 11 | let completedPackageCount = 0 12 | 13 | async.eachLimit( 14 | allPackages, 15 | 10, 16 | (pack, done) => { 17 | if (!pack || !pack.name || !pack.releases || !pack.releases.latest) { 18 | const message = pack ? pack.name + ' has no latest version' : 'empty package' 19 | fail({message: message}) 20 | return 21 | } 22 | const packagePath = path.join(util.packageJsonDirPath, pack.name) 23 | const packageJsonPath = path.join(packagePath, 'package.json') 24 | const latestURL = 'https://atom.io/api/packages/' + pack.name + '/versions/' + pack.releases.latest 25 | request({url: latestURL, json: true}, (error, response, metadata) => { 26 | if (error) { 27 | fail(error) 28 | return 29 | } 30 | mkdirp.sync(packagePath) 31 | fs.writeFileSync(packageJsonPath, JSON.stringify(metadata, null, 2)) 32 | succeed() 33 | }) 34 | 35 | function succeed () { 36 | proceed() 37 | done() 38 | } 39 | 40 | function fail (error) { 41 | proceed() 42 | console.error(' ' + error.message) 43 | done() 44 | } 45 | 46 | function proceed () { 47 | completedPackageCount++ 48 | console.log(completedPackageCount + '/' + allPackages.length + ' - ' + pack.name) 49 | } 50 | }, 51 | 52 | (error) => { 53 | if (error) { 54 | console.error(error) 55 | return 56 | } 57 | 58 | console.log(`Downloaded package.json for all packages to ${util.packageJsonDirPath}`) 59 | } 60 | ) 61 | -------------------------------------------------------------------------------- /script/update.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const path = require('path') 4 | const execFileSync = require('child_process').execFileSync 5 | 6 | console.log('Downloading metadata') 7 | execFileSync(process.execPath, [path.join(__dirname, 'download-metadata.js')], {stdio: 'inherit'}) 8 | 9 | console.log('Cloning packages') 10 | execFileSync(process.execPath, [path.join(__dirname, 'clone-packages.js')], {stdio: 'inherit'}) 11 | --------------------------------------------------------------------------------