seo 0.0.15 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/LICENSE +201 -0
  2. package/PRIVACY.md +95 -0
  3. package/README.md +431 -35
  4. package/SECURITY.md +55 -0
  5. package/TERMS.md +94 -0
  6. package/TRADEMARKS.md +31 -0
  7. package/dist/cli.js +141 -0
  8. package/dist/cli.js.map +1 -0
  9. package/dist/dist-6bK_8r5M.js +713 -0
  10. package/dist/dist-6bK_8r5M.js.map +1 -0
  11. package/dist/dist-vRmqfhR4.js +3 -0
  12. package/dist/index.d.ts +18013 -0
  13. package/dist/index.js +27197 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/mcp.d.ts +74 -0
  16. package/dist/mcp.js +28611 -0
  17. package/dist/mcp.js.map +1 -0
  18. package/dist/package-7aT8_Xp0.js +23818 -0
  19. package/dist/package-7aT8_Xp0.js.map +1 -0
  20. package/evals/README.md +117 -0
  21. package/evals/affected-urls.json +57 -0
  22. package/evals/ai-readiness.json +84 -0
  23. package/evals/ai-referrals.json +71 -0
  24. package/evals/ai-search-scorecard.json +70 -0
  25. package/evals/audit-page.json +57 -0
  26. package/evals/audit-urls.json +58 -0
  27. package/evals/cannibalisation.json +57 -0
  28. package/evals/community-intent.json +57 -0
  29. package/evals/compare-crawls.json +56 -0
  30. package/evals/content-optimization.json +57 -0
  31. package/evals/crawl-diff.json +58 -0
  32. package/evals/crawl-history.json +44 -0
  33. package/evals/crawl-report.json +44 -0
  34. package/evals/crawler-rules.json +44 -0
  35. package/evals/ctr-underperformers.json +57 -0
  36. package/evals/decaying-pages.json +57 -0
  37. package/evals/entity-readiness.json +57 -0
  38. package/evals/explain-crawl-issue.json +44 -0
  39. package/evals/generate-llms-txt.json +44 -0
  40. package/evals/geo-gaps.json +57 -0
  41. package/evals/index-coverage-plan.json +58 -0
  42. package/evals/index-coverage.json +70 -0
  43. package/evals/index-monitor.json +58 -0
  44. package/evals/index-watch.json +68 -0
  45. package/evals/internal-links.json +57 -0
  46. package/evals/link-recovery.json +58 -0
  47. package/evals/llms-txt-audit.json +44 -0
  48. package/evals/measure-change.json +58 -0
  49. package/evals/monthly-action-plan.json +56 -0
  50. package/evals/monthly-report.json +58 -0
  51. package/evals/narrative-report.json +58 -0
  52. package/evals/okf-build.json +44 -0
  53. package/evals/okf-validate.json +44 -0
  54. package/evals/page-opportunities.json +57 -0
  55. package/evals/performance-audit.json +57 -0
  56. package/evals/pseo-audit.json +84 -0
  57. package/evals/query-clusters.json +57 -0
  58. package/evals/quick-wins.json +86 -0
  59. package/evals/redirect-trace.json +57 -0
  60. package/evals/refresh-priorities.json +57 -0
  61. package/evals/search-performance-overview.json +86 -0
  62. package/evals/second-page.json +58 -0
  63. package/evals/segment-impact.json +57 -0
  64. package/evals/seo-fix-queue.json +85 -0
  65. package/evals/seo-geo-readiness.json +84 -0
  66. package/evals/seo-site-audit.json +85 -0
  67. package/evals/seo-to-ai-query.json +57 -0
  68. package/evals/seo.json +87 -0
  69. package/evals/setup-check.json +56 -0
  70. package/evals/site-crawl.json +44 -0
  71. package/evals/striking-distance.json +85 -0
  72. package/evals/technical-watch.json +57 -0
  73. package/evals/top-fixes.json +44 -0
  74. package/evals/traffic-anomaly.json +57 -0
  75. package/evals/update-correlation.json +57 -0
  76. package/evals/update-postmortem.json +57 -0
  77. package/package.json +103 -23
  78. package/skills/README.md +68 -0
  79. package/skills/seo/SKILL.md +99 -0
  80. package/skills/seo/agents/openai.yaml +4 -0
  81. package/.jshintrc +0 -18
  82. package/.npmignore +0 -3
  83. package/.travis.yml +0 -3
  84. package/Makefile +0 -10
  85. package/index.js +0 -2
  86. package/lib/middleware.js +0 -159
  87. package/lib/seo.js +0 -32
  88. package/test/init.js +0 -43
  89. package/test/middleware.test.js +0 -111
  90. package/test/seo-page-result.html +0 -9
  91. package/test/seo-page.html +0 -9
  92. package/test/seo-routes.js +0 -5
  93. package/test/vendor/jquery-1.11.1.min.js +0 -4
package/package.json CHANGED
@@ -1,36 +1,116 @@
1
1
  {
2
2
  "name": "seo",
3
- "version": "0.0.15",
4
- "description": "A connect middleware that serves html snapshots of single page apps",
5
- "main": "index.js",
6
- "scripts": {
7
- "test": "make test"
8
- },
3
+ "version": "0.2.0",
4
+ "description": "The only SEO skill your agent needs. 50+ SEO audit tools through a local CLI and MCP server, using your own crawl, Search Console, and GA4 data.",
5
+ "type": "module",
6
+ "license": "Apache-2.0",
7
+ "author": "Ian Nuttall",
8
+ "homepage": "https://seoskill.dev",
9
9
  "repository": {
10
10
  "type": "git",
11
- "url": "git://github.com/saschagehlich/node-seo.git"
11
+ "url": "git+https://github.com/iannuttall/seo.git"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/iannuttall/seo/issues"
12
15
  },
16
+ "funding": "https://github.com/iannuttall/seo",
13
17
  "keywords": [
14
- "snapshot",
15
- "phantom",
16
- "search",
17
- "bots"
18
+ "seo",
19
+ "search-console",
20
+ "google-analytics",
21
+ "crawler",
22
+ "cli",
23
+ "mcp"
18
24
  ],
19
- "author": "Sascha Gehlich <sascha@gehlich.us>",
20
- "license": "MIT",
21
- "bugs": {
22
- "url": "https://github.com/saschagehlich/node-seo/issues"
25
+ "main": "./dist/index.js",
26
+ "module": "./dist/index.js",
27
+ "types": "./dist/index.d.ts",
28
+ "exports": {
29
+ ".": {
30
+ "types": "./dist/index.d.ts",
31
+ "import": "./dist/index.js"
32
+ },
33
+ "./mcp": {
34
+ "types": "./dist/mcp.d.ts",
35
+ "import": "./dist/mcp.js"
36
+ },
37
+ "./package.json": "./package.json"
38
+ },
39
+ "bin": {
40
+ "seo": "./dist/cli.js"
41
+ },
42
+ "files": [
43
+ "dist",
44
+ "skills",
45
+ "evals",
46
+ "PRIVACY.md",
47
+ "SECURITY.md",
48
+ "TERMS.md",
49
+ "TRADEMARKS.md"
50
+ ],
51
+ "publishConfig": {
52
+ "access": "public",
53
+ "provenance": true
54
+ },
55
+ "packageManager": "pnpm@11.11.0",
56
+ "engines": {
57
+ "node": ">=22.19.0"
58
+ },
59
+ "scripts": {
60
+ "auth:inject-shared-client": "node scripts/inject-shared-oauth-client.mjs",
61
+ "build": "turbo run build && pnpm run build:package",
62
+ "build:package": "tsdown --config tsdown.config.ts",
63
+ "clean": "turbo run clean && rm -rf dist",
64
+ "dev": "turbo run dev",
65
+ "lint": "biome check . && node scripts/check-emdashes.mjs && node scripts/check-report-parity.mjs && pnpm run skills:validate && turbo run lint",
66
+ "lint:fix": "biome check . --write",
67
+ "loc:audit": "node scripts/loc-audit.mjs",
68
+ "pack:check": "pnpm pack --dry-run",
69
+ "prepack": "pnpm run skills:validate && pnpm run build:package && node --test scripts/package.test.mjs",
70
+ "security:audit": "pnpm audit --audit-level low",
71
+ "security:check": "pnpm run security:audit && pnpm run security:secrets",
72
+ "security:secrets": "node scripts/security-secrets.mjs",
73
+ "skills:validate": "node scripts/validate-skills.mjs",
74
+ "test": "turbo run test && pnpm run build:package && node --test scripts/package.test.mjs && pnpm run skills:validate",
75
+ "test:package-install": "pnpm run build:package && node --test scripts/installed-package.test.mjs",
76
+ "typecheck": "turbo run typecheck"
23
77
  },
24
- "homepage": "https://github.com/saschagehlich/node-seo",
25
78
  "dependencies": {
26
- "underscore": "~1.6.0",
27
- "phantom": "~0.6.1",
28
- "q": "~1.0.1"
79
+ "@clack/prompts": "^1.7.0",
80
+ "@modelcontextprotocol/sdk": "^1.29.0",
81
+ "@mozilla/readability": "^0.6.0",
82
+ "@napi-rs/keyring": "^1.3.0",
83
+ "@types/better-sqlite3": "^7.6.13",
84
+ "@types/node": "^22.20.1",
85
+ "better-sqlite3": "^12.11.1",
86
+ "citty": "^0.2.2",
87
+ "cli-table3": "^0.6.5",
88
+ "consola": "^3.4.2",
89
+ "debug": "^4.4.3",
90
+ "defuddle": "^0.19.1",
91
+ "jsonc-parser": "^3.3.1",
92
+ "lighthouse": "13.4.0",
93
+ "linkedom": "^0.18.13",
94
+ "open": "^11.0.0",
95
+ "p-queue": "^9.3.1",
96
+ "p-retry": "^8.0.0",
97
+ "picocolors": "^1.1.1",
98
+ "pino": "^10.3.1",
99
+ "pino-pretty": "^13.1.3",
100
+ "playwright-core": "1.61.1",
101
+ "proper-lockfile": "^4.1.2",
102
+ "robots-parser": "^3.0.1",
103
+ "schema-dts": "^2.0.0",
104
+ "undici": "^8.7.0",
105
+ "update-notifier": "^7.3.1",
106
+ "user-agents": "2.1.117",
107
+ "zod": "^4.4.3"
29
108
  },
30
109
  "devDependencies": {
31
- "should": "~3.3.1",
32
- "mocha": "~1.18.2",
33
- "supertest": "~0.12.0",
34
- "express": "~4.1.2"
110
+ "@biomejs/biome": "^2.5.3",
111
+ "@changesets/cli": "^2.31.0",
112
+ "tsdown": "^0.22.4",
113
+ "turbo": "^2.10.4",
114
+ "typescript": "^6.0.3"
35
115
  }
36
116
  }
@@ -0,0 +1,68 @@
1
+ # Agent skills
2
+
3
+ This package ships one skill: `seo`. It teaches an agent how to use `seo`
4
+ without reading the whole repo.
5
+
6
+ The skill is a router, not a manual. It explains the discover, describe, run
7
+ flow, names the report ids for common jobs, and states the evidence rules that
8
+ apply to every report. It does not memorise each report. Per-report depth, when
9
+ to use a report, when to avoid it, its schema, the order to read its output,
10
+ what its evidence cannot support, and one verification step, is fetched at
11
+ runtime with `describe`. This keeps the skill small and always in sync with the
12
+ registry.
13
+
14
+ ## Install
15
+
16
+ The skill works best with the MCP server installed. This command is an
17
+ interactive setup flow for a human:
18
+
19
+ ```bash
20
+ seo mcp install
21
+ ```
22
+
23
+ The skill also works by calling the CLI directly when MCP tools are not
24
+ available. The npm package and repository contain the same skill. Install it
25
+ with the standard agent skills installer:
26
+
27
+ ```bash
28
+ npx skills add iannuttall/seo
29
+ ```
30
+
31
+ Use `seo skill list` or `seo skill path seo` to inspect the copy bundled with
32
+ the npm package.
33
+
34
+ ## Discover, describe, run
35
+
36
+ The MCP path is deliberately small: discover ids with `seo_list_reports`, load
37
+ one report's full depth with `seo_describe_report`, then execute it with
38
+ `seo_run_report`. The same catalog is available without MCP:
39
+
40
+ ```bash
41
+ seo reports list --json
42
+ seo reports describe <report-id> --json
43
+ seo reports run <report-id> --params '<json>' --json
44
+ ```
45
+
46
+ Always describe a report before its first run. The describe response is the
47
+ per-report manual: its `readOrder`, `doNotClaim` limits, `verify` step, and
48
+ `related` ids come straight from the registry.
49
+
50
+ ## Adding a report
51
+
52
+ Register the report once in `packages/mcp/src/report-registry.ts`, including its
53
+ depth guidance in `packages/mcp/src/report-guidance.ts` and
54
+ `packages/mcp/src/report-depth.ts` (`readOrder`, `doNotClaim`, `verify`,
55
+ `related`). CLI discovery, MCP discovery, and the skill then share its id,
56
+ description, input schema, handler, and depth automatically. No new skill file
57
+ is needed. The router only lists report ids for common jobs; a report earns a
58
+ mention there when it belongs in one of those chains.
59
+
60
+ Run `pnpm skills:validate` after changing the skill or the registry. The
61
+ validator checks the skill frontmatter and word count, that every command and
62
+ report id the skill names resolves, that the MCP tool names are real, and that
63
+ every `evals/*.json` is well formed.
64
+
65
+ ## Evals
66
+
67
+ Behaviour tests for the skill live in the top-level `evals/` directory. See
68
+ [../evals/README.md](../evals/README.md) for the file shape and how to run them.
@@ -0,0 +1,99 @@
1
+ ---
2
+ name: seo
3
+ description: Use and read this skill immediately if the user request is in any way related to SEO or a site's organic search or AI search presence. That includes site audits, rankings, click or traffic changes, indexing problems, crawling, redirects, sitemaps, metadata, structured data, Core Web Vitals, internal links, content opportunities, Search Console or GA4 questions, Google update impact, llms.txt, AI search visibility in ChatGPT, Claude, Perplexity, or Google AI Overviews, and client SEO reporting. Routes to 52 evidence-backed local reports through the SEO CLI and MCP server.
4
+ ---
5
+
6
+ # seo
7
+
8
+ `seo` is a local CLI, MCP server, and report engine. It answers SEO questions
9
+ with evidence from three sources: its own crawl of the site, Google Search
10
+ Console, and GA4. Every report returns structured JSON with the observed
11
+ evidence, derived findings, caveats, and provenance kept separate. Nothing is
12
+ hosted; data stays on the machine.
13
+
14
+ You do not need to memorise reports. Discover them at runtime.
15
+
16
+ ## Discover, describe, run
17
+
18
+ With the MCP server (preferred):
19
+
20
+ 1. `seo_list_reports` returns every report id with a one-line purpose,
21
+ optionally by category.
22
+ 2. `seo_describe_report` with one id returns when to use it, when to avoid it,
23
+ its exact JSON Schema, the order to read its output in, what its evidence
24
+ cannot support, one verification step, and related report ids.
25
+ 3. `seo_run_report` with the id and a bounded `params` object. Read
26
+ `structuredContent`, not the display text.
27
+
28
+ The same catalog exists without MCP:
29
+
30
+ ```bash
31
+ seo reports list --json
32
+ seo reports describe <report-id> --json
33
+ seo reports run <report-id> --params '<json>' --json
34
+ ```
35
+
36
+ Always describe a report before its first run in a session. The describe
37
+ response is the per-report manual: follow its `readOrder`, respect its
38
+ `doNotClaim` limits, and use its `related` ids to decide what to run next.
39
+ Reuse the schema for repeated runs. Do not guess parameters.
40
+
41
+ ## Setup and selection
42
+
43
+ Run the `setup-check` report (or `seo doctor`) if auth state is unknown.
44
+ Saved project profiles are selected with `--project <id>`; list them with
45
+ `seo projects list --json`. Without a profile, pass `--site sc-domain:example.com`
46
+ for Search Console reports or `--url https://example.com` for crawl-only
47
+ reports. Crawl and page audits work with no Google connection at all. Always
48
+ pass `--json`; JSON mode never prompts.
49
+
50
+ ## Common jobs
51
+
52
+ Each job lists report ids in a sensible starting order. Run the first, read
53
+ its evidence, then decide; do not run a whole chain blindly.
54
+
55
+ | Job | Reports |
56
+ |---|---|
57
+ | Page not indexed or missing from Google | `index-coverage`, `index-monitor` (URL Inspection), `audit-page`, `redirect-trace` |
58
+ | Traffic or clicks dropped | `search-performance-overview`, `traffic-anomaly`, `update-correlation`, `segment-impact`, `decaying-pages`, `link-recovery` |
59
+ | Audit a whole site | `report` command (main report), `site-crawl`, `top-fixes`, `ai-search-scorecard` |
60
+ | More clicks from existing pages | `quick-wins`, `ctr-underperformers`, `striking-distance`, `second-page`, `internal-links` |
61
+ | AI search visibility and readiness | `ai-search-scorecard`, `ai-readiness`, `geo-gaps`, `entity-readiness`, `llms-txt-audit`, `ai-referrals` |
62
+ | Plan content from real demand | `query-clusters`, `page-opportunities`, `content-optimization`, `cannibalisation` |
63
+ | Catch regressions over time | `technical-watch`, `crawl-diff`, `index-watch`, `measure-change` after a fix ships |
64
+ | Client-ready reporting | `monthly-report`, `narrative-report`, `monthly-action-plan` |
65
+ | Turn crawl findings into tickets | `top-fixes`, `affected-urls`, `explain-crawl-issue` |
66
+
67
+ A single page complaint usually needs both angles: the index evidence for that
68
+ URL and an `audit-page` pass for content or technical problems on the page
69
+ itself.
70
+
71
+ The main human report is the `seo report` command. It is the right first move
72
+ when the user asks a broad "how is my site doing" question; the focused
73
+ reports above are for specific symptoms.
74
+
75
+ ## Evidence rules
76
+
77
+ - Check `dataStatus`, selection counts, `caveats`, and `warnings` before
78
+ summarising any report. Name skipped or incomplete evidence first.
79
+ - Partial, capped, filtered, or sampled sources never support a zero or an
80
+ all-clear. Grouped Search Console totals undercount because anonymised query
81
+ rows are withheld.
82
+ - Values marked heuristic are prioritisation aids, not forecasts. Never
83
+ promise clicks, rankings, indexing, or AI citations from any report.
84
+ - Quote `principle` and `evidenceRef` when explaining a recommendation, and
85
+ give the user the report's verification step alongside any suggested change.
86
+ - If a report returns no rows, say so plainly.
87
+ - Intentional controls such as `noindex`, canonicals, and robots rules are
88
+ observations until the user confirms they are unintended.
89
+
90
+ ## Beyond the report catalog
91
+
92
+ The CLI also has direct commands for raw provider access and administration:
93
+ `seo gsc-query`, `seo url-inspect`, `seo ga4-report`, `seo updates`,
94
+ `seo crawl`, `seo export`, and `seo projects`. Use `seo help all` to list
95
+ everything. Prefer a registered report when one covers the question, because
96
+ reports carry provenance and caveats that raw queries do not.
97
+
98
+ Report output is cached locally. Pass `refresh: true` (or `--refresh`) only
99
+ when the user explicitly wants a fresh fetch.
@@ -0,0 +1,4 @@
1
+ interface:
2
+ display_name: "seo"
3
+ short_description: "Use immediately for anything touching SEO, organic search, or AI search: audits, rankings, traffic changes, indexing, crawling, Search Console, GA4, or AI visibility"
4
+ default_prompt: "Use $seo to discover, describe, and run the right local SEO report and explain its evidence."
package/.jshintrc DELETED
@@ -1,18 +0,0 @@
1
- {
2
- "globals" : {
3
- /* MOCHA */
4
- "describe" : false,
5
- "it" : false,
6
- "before" : false,
7
- "beforeEach" : false,
8
- "after" : false,
9
- "afterEach" : false,
10
-
11
- /* COMMONJS / NODE */
12
- "module" : false,
13
- "require" : false,
14
- "console" : false,
15
- "process" : false,
16
- "global" : false
17
- }
18
- }
package/.npmignore DELETED
@@ -1,3 +0,0 @@
1
- .DS_Store
2
- node_modules
3
- .seo-cache
package/.travis.yml DELETED
@@ -1,3 +0,0 @@
1
- language: node_js
2
- node_js:
3
- - "0.10"
package/Makefile DELETED
@@ -1,10 +0,0 @@
1
- REPORTER = spec
2
-
3
- test:
4
- @./node_modules/.bin/mocha \
5
- --require ./test/init.js \
6
- --require should \
7
- --reporter $(REPORTER) \
8
- test/*.test.js
9
-
10
- .PHONY: all test
package/index.js DELETED
@@ -1,2 +0,0 @@
1
- 'use strict';
2
- module.exports = require('./lib/seo');
package/lib/middleware.js DELETED
@@ -1,159 +0,0 @@
1
- /* global document */
2
- 'use strict';
3
-
4
- var fs = require('fs');
5
- var path = require('path');
6
- var crypto = require('crypto');
7
- var phantom = require('phantom');
8
- var Q = require('q');
9
-
10
- function Middleware(options) {
11
- this.options = options;
12
- }
13
-
14
- /**
15
- * Handles the given request
16
- * @param {Request} req
17
- * @param {Response} res
18
- * @param {Function} next [description]
19
- * @public
20
- */
21
- Middleware.prototype.request = function (req, res, next) {
22
- var self = this;
23
- var shouldTakeSnapshot = this.options.routes(req);
24
- if (!shouldTakeSnapshot) return next();
25
-
26
- var botRegex = /(google|yahoo|bing|baidu|jeeves|facebook|twitter|linkedin|xovibot)/i;
27
- var isBot = botRegex.test(req.headers['user-agent']);
28
- if (!isBot) return next();
29
-
30
- this._checkForCache(req)
31
- .then(function (inCache) {
32
- if (!inCache) {
33
- return self._createSnapshot(req);
34
- }
35
- })
36
- .then(function () {
37
- return self._serveSnapshot(req, res);
38
- });
39
- };
40
-
41
- Middleware.prototype._serveSnapshot = function(request, response) {
42
- var cachePath = this._getCachePathForRequest(request);
43
- fs.readFile(cachePath, function (err, data) {
44
- if(err) throw err;
45
- response.send(data.toString());
46
- });
47
- };
48
-
49
- Middleware.prototype._createSnapshot = function(request) {
50
- var self = this;
51
- var deferred = Q.defer();
52
- var cachePath = this._getCachePathForRequest(request);
53
-
54
- var url = request.protocol + '://' + request.get('host') + request.originalUrl;
55
- this._ensureCacheDirectoryExists()
56
- .then(function () { return self._getContentForURL(url); })
57
- .then(function (content) {
58
- fs.writeFile(cachePath, content, function (err) {
59
- if (err) return deferred.reject(err);
60
- deferred.resolve();
61
- });
62
- });
63
- return deferred.promise;
64
- };
65
-
66
- Middleware.prototype._checkForCache = function (request) {
67
- var deferred = Q.defer();
68
-
69
- // Does a cache file exist for this hash?
70
- var cachePath = this._getCachePathForRequest(request);
71
- fs.exists(cachePath, function (exists) {
72
- deferred.resolve(exists);
73
- });
74
-
75
- return deferred.promise;
76
- };
77
-
78
- Middleware.prototype._getCachePathForRequest = function(request) {
79
- var requestedPath = request.path;
80
- var requestedHost = request.host;
81
- var hash = this._getHashForUrl(requestedHost, requestedPath);
82
-
83
- return path.resolve(this.options.cacheDirectory, hash);
84
- };
85
-
86
- Middleware.prototype._ensureCacheDirectoryExists = function() {
87
- var self = this;
88
- var deferred = Q.defer();
89
- fs.exists(this.options.cacheDirectory, function (exists) {
90
- if (!exists) {
91
- fs.mkdir(self.options.cacheDirectory, function (err) {
92
- if (err) return deferred.reject(err);
93
- deferred.resolve();
94
- });
95
- } else {
96
- deferred.resolve();
97
- }
98
- });
99
- return deferred.promise;
100
- };
101
-
102
- /**
103
- * Returns an MD5 hash for the given path
104
- * @param {String} host
105
- * @param {String} path
106
- * @return {String}
107
- * @private
108
- */
109
- Middleware.prototype._getHashForUrl = function (host, path) {
110
- return crypto.createHash('md5').update(host + '/' + path).digest('hex');
111
- };
112
-
113
- /**
114
- * Creates a PhantomJS instance and returns the computed DOM
115
- * for the given URL
116
- * @param {String} url
117
- * @return {Promise}
118
- * @private
119
- */
120
- Middleware.prototype._getContentForURL = function (url) {
121
- var deferred = Q.defer();
122
- var self = this;
123
- phantom.create(function (ph) {
124
- ph.onError = function (err) {
125
- ph.exit();
126
- return deferred.reject(err);
127
- };
128
- ph.createPage(function (page) {
129
- page.onError = function (err) {
130
- ph.exit();
131
- return deferred.reject(err);
132
- };
133
-
134
- page.open(url, function (status) {
135
- if (status !== 'success') {
136
- return deferred.reject(new Error('PhantomJS status for ' + url + ': ' + status));
137
- }
138
-
139
- var generateHTML = function () {
140
- page.evaluate(function() { return document.documentElement.outerHTML; }, function (result) {
141
- ph.exit();
142
- deferred.resolve(result);
143
- });
144
- };
145
-
146
- if (typeof self.options.pageModifier === 'function') {
147
- self.options.pageModifier(page, function () {
148
- generateHTML();
149
- });
150
- } else {
151
- generateHTML();
152
- }
153
- });
154
- });
155
- });
156
- return deferred.promise;
157
- };
158
-
159
- module.exports = Middleware;
package/lib/seo.js DELETED
@@ -1,32 +0,0 @@
1
- 'use strict';
2
-
3
- var path = require('path');
4
- var _ = require('underscore');
5
- var Middleware = require('./middleware');
6
-
7
- function SEO(options) {
8
- this.options = options;
9
- this.options = _.defaults(this.options, {
10
- cacheDirectory: path.resolve(process.cwd(), '.seo-cache')
11
- });
12
-
13
- if (!this.options.routes) {
14
- this.options.routes = require(path.resolve(process.cwd(), 'seo-routes'));
15
- }
16
-
17
- this.middleware = new Middleware(this.options);
18
- }
19
-
20
- /**
21
- * Returns the middleware
22
- * @return {Function}
23
- * @public
24
- */
25
- SEO.prototype.init = function() {
26
- var middleware = this.middleware;
27
- return function() {
28
- middleware.request.apply(middleware, arguments);
29
- };
30
- };
31
-
32
- module.exports = SEO;
package/test/init.js DELETED
@@ -1,43 +0,0 @@
1
- 'use strict';
2
- process.env.NODE_ENV = 'test';
3
-
4
- var path = require('path');
5
- var fs = require('fs');
6
- var _ = require('underscore');
7
- var express = require('express');
8
- var seo = require('../index');
9
-
10
- global._after = function (t, f) { return setTimeout(f, t); };
11
- global._every = function (t, f) { return setInterval(f, t); };
12
-
13
- global.createServer = function(options) {
14
- var app = {};
15
- var _seo = new seo(options);
16
-
17
- app.server = express();
18
- app.seo = _seo;
19
- app.server.use(_seo.init());
20
-
21
- app.server.get('/seo-route', function (req, res) {
22
- var seoPagePath = path.resolve(process.cwd(), 'test/seo-page.html');
23
- res.end(fs.readFileSync(seoPagePath));
24
- });
25
-
26
- app.server = app.server.listen(8899);
27
-
28
- return app;
29
- };
30
-
31
- global.clearCache = function() {
32
- var directory = path.resolve(process.cwd(), '.seo-cache');
33
-
34
- if (!fs.existsSync(directory)) return;
35
-
36
- var files = fs.readdirSync(directory);
37
- files.forEach(function (file) {
38
- var filePath = directory + '/' + file;
39
- if (!fs.statSync(filePath).isDirectory()) {
40
- fs.unlinkSync(filePath);
41
- }
42
- });
43
- };