@grainulation/barn 1.0.0 → 1.0.1
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.
- package/CODE_OF_CONDUCT.md +25 -0
- package/CONTRIBUTING.md +97 -0
- package/README.md +46 -29
- package/bin/barn.js +30 -23
- package/lib/index.js +41 -25
- package/lib/server.js +211 -114
- package/package.json +8 -4
- package/public/grainulation-icons.svg +47 -0
- package/public/grainulation-tokens.css +78 -18
- package/public/status-icons.svg +40 -0
- package/templates/README.md +4 -0
- package/templates/adr.json +1 -3
- package/templates/brief.json +1 -4
- package/templates/certificate.json +7 -1
- package/templates/comparison.json +1 -3
- package/templates/dashboard.json +1 -4
- package/templates/email-digest.html +1 -1
- package/templates/email-digest.json +1 -3
- package/templates/evidence-matrix.json +1 -3
- package/templates/explainer.json +1 -4
- package/templates/handoff.json +1 -3
- package/templates/rfc.json +1 -4
- package/templates/risk-register.json +1 -4
- package/templates/slide-deck.json +1 -3
- package/templates/template.schema.json +20 -4
- package/tools/README.md +5 -2
- package/tools/build-pdf.js +6 -6
- package/tools/detect-sprints.js +78 -54
- package/tools/generate-manifest.js +78 -43
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Code of Conduct
|
|
2
|
+
|
|
3
|
+
## Our standards
|
|
4
|
+
|
|
5
|
+
We are committed to providing a welcoming and productive environment for everyone. We expect participants to:
|
|
6
|
+
|
|
7
|
+
- Use welcoming and inclusive language
|
|
8
|
+
- Respect differing viewpoints and experiences
|
|
9
|
+
- Accept constructive criticism gracefully
|
|
10
|
+
- Focus on what is best for the community and the project
|
|
11
|
+
- Show empathy toward other participants
|
|
12
|
+
|
|
13
|
+
Unacceptable behavior includes harassment, trolling, personal attacks, and publishing others' private information without permission.
|
|
14
|
+
|
|
15
|
+
## Scope
|
|
16
|
+
|
|
17
|
+
This code of conduct applies to all project spaces -- issues, pull requests, discussions, and any public channel where someone represents the project.
|
|
18
|
+
|
|
19
|
+
## Enforcement
|
|
20
|
+
|
|
21
|
+
Instances of unacceptable behavior may be reported to the project maintainers. All complaints will be reviewed and investigated, and will result in a response deemed necessary and appropriate.
|
|
22
|
+
|
|
23
|
+
## Attribution
|
|
24
|
+
|
|
25
|
+
Adapted from the [Contributor Covenant](https://www.contributor-covenant.org/), version 2.1.
|
package/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# Contributing to Barn
|
|
2
|
+
|
|
3
|
+
Thanks for considering contributing. Barn is the template engine for the grainulation ecosystem -- it turns structured data into polished HTML artifacts.
|
|
4
|
+
|
|
5
|
+
## Quick setup
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
git clone https://github.com/grainulation/barn.git
|
|
9
|
+
cd barn
|
|
10
|
+
node bin/barn.js --help
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
No `npm install` needed -- barn has zero dependencies.
|
|
14
|
+
|
|
15
|
+
## How to contribute
|
|
16
|
+
|
|
17
|
+
### Report a bug
|
|
18
|
+
|
|
19
|
+
Open an issue with:
|
|
20
|
+
|
|
21
|
+
- What you expected
|
|
22
|
+
- What happened instead
|
|
23
|
+
- Your Node version (`node --version`)
|
|
24
|
+
- Steps to reproduce
|
|
25
|
+
|
|
26
|
+
### Suggest a feature
|
|
27
|
+
|
|
28
|
+
Open an issue describing the use case, not just the solution. "I need X because Y" is more useful than "add X."
|
|
29
|
+
|
|
30
|
+
### Submit a PR
|
|
31
|
+
|
|
32
|
+
1. Fork the repo
|
|
33
|
+
2. Create a branch (`git checkout -b fix/description`)
|
|
34
|
+
3. Make your changes
|
|
35
|
+
4. Run the tests: `node test/basic.test.js`
|
|
36
|
+
5. Commit with a clear message
|
|
37
|
+
6. Open a PR
|
|
38
|
+
|
|
39
|
+
### Add a template
|
|
40
|
+
|
|
41
|
+
Templates live in `templates/`. Each template is a pair: an HTML file and a JSON schema file. To add one:
|
|
42
|
+
|
|
43
|
+
1. Create `templates/your-template.html` and `templates/your-template.json`
|
|
44
|
+
2. Follow the pattern of existing templates (use `template.schema.json` as the base schema)
|
|
45
|
+
3. Ensure the HTML is self-contained (inline CSS/JS, no external dependencies)
|
|
46
|
+
4. Add it to the README templates table
|
|
47
|
+
|
|
48
|
+
## Architecture
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
bin/barn.js CLI entrypoint -- dispatches subcommands
|
|
52
|
+
lib/index.js Core library -- template resolution and rendering
|
|
53
|
+
lib/server.js Local preview server (SSE, zero deps)
|
|
54
|
+
templates/ HTML + JSON schema pairs for each artifact type
|
|
55
|
+
public/ Web UI -- two-column template nav + preview
|
|
56
|
+
site/ Public website (barn.grainulation.com)
|
|
57
|
+
tools/ Utility scripts
|
|
58
|
+
test/ Node built-in test runner tests
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
The key architectural principle: **templates are self-contained HTML with inline CSS/JS.** No external dependencies, no build step, no CDN links.
|
|
62
|
+
|
|
63
|
+
## Code style
|
|
64
|
+
|
|
65
|
+
- Zero dependencies. If you need something, write it or use Node built-ins.
|
|
66
|
+
- No transpilation. Ship what you write.
|
|
67
|
+
- ESM imports (`import`/`export`). Node 18+ required.
|
|
68
|
+
- Keep functions small. If a function needs a scroll, split it.
|
|
69
|
+
- No emojis in code, CLI output, or templates.
|
|
70
|
+
|
|
71
|
+
## Testing
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
node test/basic.test.js
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Tests use Node's built-in test runner. No test framework dependencies.
|
|
78
|
+
|
|
79
|
+
## Commit messages
|
|
80
|
+
|
|
81
|
+
Follow the existing pattern:
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
barn: <what changed>
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Examples:
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
barn: add postmortem template
|
|
91
|
+
barn: fix server SSE reconnection
|
|
92
|
+
barn: update schema validation for nested fields
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## License
|
|
96
|
+
|
|
97
|
+
MIT. See LICENSE for details.
|
package/README.md
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="site/wordmark.svg" alt="Barn" width="400">
|
|
3
|
+
</p>
|
|
2
4
|
|
|
3
|
-
|
|
5
|
+
<p align="center">
|
|
6
|
+
<a href="https://www.npmjs.com/package/@grainulation/barn"><img src="https://img.shields.io/npm/v/@grainulation/barn" alt="npm version"></a> <a href="https://www.npmjs.com/package/@grainulation/barn"><img src="https://img.shields.io/npm/dm/@grainulation/barn" alt="npm downloads"></a> <a href="https://github.com/grainulation/barn/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-green" alt="license"></a> <a href="https://nodejs.org"><img src="https://img.shields.io/node/v/@grainulation/barn" alt="node"></a> <a href="https://github.com/grainulation/barn/actions"><img src="https://github.com/grainulation/barn/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
|
|
7
|
+
<a href="https://deepwiki.com/grainulation/barn"><img src="https://deepwiki.com/badge.svg" alt="Explore on DeepWiki"></a>
|
|
8
|
+
</p>
|
|
4
9
|
|
|
5
|
-
|
|
10
|
+
<p align="center"><strong>Shared tools for the grainulation ecosystem.</strong></p>
|
|
11
|
+
|
|
12
|
+
Barn extracts the reusable utilities from [wheat](https://github.com/grainulation/wheat) into a standalone package. Sprint detection, manifest generation, PDF builds, and 17 HTML templates for research artifacts.
|
|
6
13
|
|
|
7
14
|
## Install
|
|
8
15
|
|
|
@@ -49,38 +56,48 @@ barn build-pdf output/brief.md
|
|
|
49
56
|
|
|
50
57
|
## Templates
|
|
51
58
|
|
|
52
|
-
HTML templates for sprint artifacts.
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
-
|
|
64
|
-
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
59
|
+
17 self-contained HTML templates for sprint artifacts. Dark theme, inline CSS/JS, no external deps, mobile responsive.
|
|
60
|
+
|
|
61
|
+
| Template | Purpose |
|
|
62
|
+
| ---------------------- | -------------------------------------- |
|
|
63
|
+
| `adr.html` | Architecture Decision Record |
|
|
64
|
+
| `brief.html` | Sprint brief / recommendation document |
|
|
65
|
+
| `certificate.html` | Compilation certificate |
|
|
66
|
+
| `changelog.html` | Sprint changelog |
|
|
67
|
+
| `comparison.html` | Side-by-side comparison dashboard |
|
|
68
|
+
| `conflict-map.html` | Claim conflict visualization |
|
|
69
|
+
| `dashboard.html` | Sprint status dashboard |
|
|
70
|
+
| `email-digest.html` | Email digest summary |
|
|
71
|
+
| `evidence-matrix.html` | Evidence tier matrix |
|
|
72
|
+
| `explainer.html` | Full-screen scroll-snap presentation |
|
|
73
|
+
| `handoff.html` | Knowledge transfer document |
|
|
74
|
+
| `one-pager.html` | Single-page executive summary |
|
|
75
|
+
| `postmortem.html` | Sprint postmortem |
|
|
76
|
+
| `rfc.html` | Request for Comments |
|
|
77
|
+
| `risk-register.html` | Risk tracking register |
|
|
78
|
+
| `slide-deck.html` | Slide deck presentation |
|
|
79
|
+
| `wiki-page.html` | Wiki-style documentation page |
|
|
73
80
|
|
|
74
81
|
```bash
|
|
75
82
|
cp node_modules/@grainulation/barn/templates/explainer.html ./output/
|
|
76
83
|
```
|
|
77
84
|
|
|
78
|
-
##
|
|
85
|
+
## Zero dependencies
|
|
86
|
+
|
|
87
|
+
Node built-in modules only. No npm install waterfall.
|
|
88
|
+
|
|
89
|
+
## Part of the grainulation ecosystem
|
|
79
90
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
91
|
+
| Tool | Role |
|
|
92
|
+
| ------------------------------------------------------------ | ----------------------------------------------------------- |
|
|
93
|
+
| [wheat](https://github.com/grainulation/wheat) | Research engine -- grow structured evidence |
|
|
94
|
+
| [farmer](https://github.com/grainulation/farmer) | Permission dashboard -- approve AI actions in real time |
|
|
95
|
+
| **barn** | Shared tools -- templates, validators, sprint detection |
|
|
96
|
+
| [mill](https://github.com/grainulation/mill) | Format conversion -- export to PDF, CSV, slides, 24 formats |
|
|
97
|
+
| [silo](https://github.com/grainulation/silo) | Knowledge storage -- reusable claim libraries and packs |
|
|
98
|
+
| [harvest](https://github.com/grainulation/harvest) | Analytics -- cross-sprint patterns and prediction scoring |
|
|
99
|
+
| [orchard](https://github.com/grainulation/orchard) | Orchestration -- multi-sprint coordination and dependencies |
|
|
100
|
+
| [grainulation](https://github.com/grainulation/grainulation) | Unified CLI -- single entry point to the ecosystem |
|
|
84
101
|
|
|
85
102
|
## License
|
|
86
103
|
|
package/bin/barn.js
CHANGED
|
@@ -14,44 +14,51 @@
|
|
|
14
14
|
* Zero npm dependencies (Node built-in only).
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import { fileURLToPath } from
|
|
18
|
-
import { dirname, join } from
|
|
19
|
-
import { fork } from
|
|
17
|
+
import { fileURLToPath } from "node:url";
|
|
18
|
+
import { dirname, join } from "node:path";
|
|
19
|
+
import { fork } from "node:child_process";
|
|
20
20
|
|
|
21
21
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
22
|
-
const TOOLS_DIR = join(__dirname,
|
|
22
|
+
const TOOLS_DIR = join(__dirname, "..", "tools");
|
|
23
23
|
|
|
24
|
-
const LIB_DIR = join(__dirname,
|
|
24
|
+
const LIB_DIR = join(__dirname, "..", "lib");
|
|
25
25
|
|
|
26
26
|
// ── --version / -v ───────────────────────────────────────────────────────────
|
|
27
|
-
import { readFileSync } from
|
|
27
|
+
import { readFileSync } from "node:fs";
|
|
28
28
|
|
|
29
29
|
const args = process.argv.slice(2);
|
|
30
30
|
const command = args[0];
|
|
31
31
|
|
|
32
|
-
if (command ===
|
|
33
|
-
const pkg = JSON.parse(
|
|
32
|
+
if (command === "--version" || command === "-v") {
|
|
33
|
+
const pkg = JSON.parse(
|
|
34
|
+
readFileSync(join(__dirname, "..", "package.json"), "utf8"),
|
|
35
|
+
);
|
|
34
36
|
console.log(pkg.version);
|
|
35
37
|
process.exit(0);
|
|
36
38
|
}
|
|
37
39
|
|
|
38
|
-
const verbose = process.argv.includes(
|
|
40
|
+
const verbose = process.argv.includes("--verbose");
|
|
39
41
|
function vlog(...a) {
|
|
40
42
|
if (!verbose) return;
|
|
41
43
|
const ts = new Date().toISOString();
|
|
42
|
-
process.stderr.write(`[${ts}] barn: ${a.join(
|
|
44
|
+
process.stderr.write(`[${ts}] barn: ${a.join(" ")}\n`);
|
|
43
45
|
}
|
|
44
46
|
export { vlog, verbose };
|
|
45
47
|
|
|
46
48
|
const commands = {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
49
|
+
"detect-sprints": "detect-sprints.js",
|
|
50
|
+
"generate-manifest": "generate-manifest.js",
|
|
51
|
+
"build-pdf": "build-pdf.js",
|
|
50
52
|
};
|
|
51
53
|
|
|
52
|
-
vlog(
|
|
54
|
+
vlog("startup", `command=${command || "(none)"}`, `cwd=${process.cwd()}`);
|
|
53
55
|
|
|
54
|
-
if (
|
|
56
|
+
if (
|
|
57
|
+
!command ||
|
|
58
|
+
command === "help" ||
|
|
59
|
+
command === "--help" ||
|
|
60
|
+
command === "-h"
|
|
61
|
+
) {
|
|
55
62
|
console.log(`barn — open tools for structured research
|
|
56
63
|
|
|
57
64
|
Usage:
|
|
@@ -81,16 +88,16 @@ https://github.com/grainulation/barn`);
|
|
|
81
88
|
}
|
|
82
89
|
|
|
83
90
|
// ── serve command (lib/server.js) ──
|
|
84
|
-
if (command ===
|
|
85
|
-
const serverPath = join(LIB_DIR,
|
|
86
|
-
const child = fork(serverPath, args.slice(1), { stdio:
|
|
87
|
-
child.on(
|
|
88
|
-
process.on(
|
|
89
|
-
process.on(
|
|
91
|
+
if (command === "serve") {
|
|
92
|
+
const serverPath = join(LIB_DIR, "server.js");
|
|
93
|
+
const child = fork(serverPath, args.slice(1), { stdio: "inherit" });
|
|
94
|
+
child.on("exit", (code) => process.exit(code ?? 0));
|
|
95
|
+
process.on("SIGTERM", () => child.kill("SIGTERM"));
|
|
96
|
+
process.on("SIGINT", () => child.kill("SIGINT"));
|
|
90
97
|
} else if (commands[command]) {
|
|
91
98
|
const toolPath = join(TOOLS_DIR, commands[command]);
|
|
92
|
-
const child = fork(toolPath, args.slice(1), { stdio:
|
|
93
|
-
child.on(
|
|
99
|
+
const child = fork(toolPath, args.slice(1), { stdio: "inherit" });
|
|
100
|
+
child.on("exit", (code) => process.exit(code ?? 0));
|
|
94
101
|
} else {
|
|
95
102
|
console.error(`barn: unknown command: ${command}`);
|
|
96
103
|
console.error(`Run "barn help" for available commands.`);
|
package/lib/index.js
CHANGED
|
@@ -8,12 +8,14 @@
|
|
|
8
8
|
* generateManifest(opts) — build wheat-manifest.json topic map (re-export)
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import { readFileSync, existsSync, readdirSync, statSync } from
|
|
12
|
-
import { join, dirname } from
|
|
13
|
-
import { fileURLToPath } from
|
|
11
|
+
import { readFileSync, existsSync, readdirSync, statSync } from "node:fs";
|
|
12
|
+
import { join, dirname } from "node:path";
|
|
13
|
+
import { fileURLToPath } from "node:url";
|
|
14
14
|
|
|
15
15
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
16
|
-
const pkg = JSON.parse(
|
|
16
|
+
const pkg = JSON.parse(
|
|
17
|
+
readFileSync(join(__dirname, "..", "package.json"), "utf8"),
|
|
18
|
+
);
|
|
17
19
|
|
|
18
20
|
export const name = pkg.name;
|
|
19
21
|
export const version = pkg.version;
|
|
@@ -30,64 +32,78 @@ export function loadTemplates(templatesDir) {
|
|
|
30
32
|
if (!existsSync(templatesDir)) return templates;
|
|
31
33
|
|
|
32
34
|
for (const file of readdirSync(templatesDir)) {
|
|
33
|
-
if (!file.endsWith(
|
|
35
|
+
if (!file.endsWith(".html")) continue;
|
|
34
36
|
const filePath = join(templatesDir, file);
|
|
35
|
-
const content = readFileSync(filePath,
|
|
36
|
-
const tplName = file.replace(
|
|
37
|
+
const content = readFileSync(filePath, "utf8");
|
|
38
|
+
const tplName = file.replace(".html", "");
|
|
37
39
|
|
|
38
40
|
// Extract placeholders
|
|
39
41
|
const placeholders = [...new Set(content.match(/\{\{[A-Z_]+\}\}/g) || [])];
|
|
40
42
|
|
|
41
43
|
// Extract description from first comment
|
|
42
44
|
const commentMatch = content.match(/<!--\s*(.*?)\s*-->/);
|
|
43
|
-
let description = commentMatch ? commentMatch[1] :
|
|
45
|
+
let description = commentMatch ? commentMatch[1] : "";
|
|
44
46
|
|
|
45
47
|
// Count lines and size
|
|
46
|
-
const lines = content.split(
|
|
48
|
+
const lines = content.split("\n").length;
|
|
47
49
|
const size = statSync(filePath).size;
|
|
48
50
|
|
|
49
51
|
// Detect features
|
|
50
52
|
const features = [];
|
|
51
|
-
if (content.includes(
|
|
52
|
-
if (content.includes(
|
|
53
|
-
if (content.includes(
|
|
54
|
-
if (content.includes(
|
|
55
|
-
if (content.includes(
|
|
56
|
-
if (content.includes(
|
|
53
|
+
if (content.includes("scroll-snap")) features.push("scroll-snap");
|
|
54
|
+
if (content.includes("@media")) features.push("responsive");
|
|
55
|
+
if (content.includes("var(--")) features.push("css-variables");
|
|
56
|
+
if (content.includes("<table")) features.push("tables");
|
|
57
|
+
if (content.includes(".card")) features.push("cards");
|
|
58
|
+
if (content.includes(".slide")) features.push("slides");
|
|
57
59
|
|
|
58
60
|
// Merge optional template.json metadata
|
|
59
|
-
const metaPath = join(templatesDir, tplName +
|
|
61
|
+
const metaPath = join(templatesDir, tplName + ".json");
|
|
60
62
|
let title = tplName;
|
|
61
63
|
let tags = [];
|
|
62
|
-
let author =
|
|
63
|
-
let tplVersion =
|
|
64
|
+
let author = "";
|
|
65
|
+
let tplVersion = "";
|
|
64
66
|
let exportPresets = [];
|
|
65
67
|
let seedPacks = [];
|
|
66
68
|
let scaffoldConfig = null;
|
|
67
69
|
if (existsSync(metaPath)) {
|
|
68
70
|
try {
|
|
69
|
-
const meta = JSON.parse(readFileSync(metaPath,
|
|
71
|
+
const meta = JSON.parse(readFileSync(metaPath, "utf8"));
|
|
70
72
|
if (meta.title) title = meta.title;
|
|
71
73
|
if (meta.description) description = meta.description;
|
|
72
74
|
if (Array.isArray(meta.tags)) tags = meta.tags;
|
|
73
75
|
if (meta.author) author = meta.author;
|
|
74
76
|
if (meta.version) tplVersion = meta.version;
|
|
75
|
-
if (Array.isArray(meta.exportPresets))
|
|
77
|
+
if (Array.isArray(meta.exportPresets))
|
|
78
|
+
exportPresets = meta.exportPresets;
|
|
76
79
|
if (Array.isArray(meta.seedPacks)) seedPacks = meta.seedPacks;
|
|
77
|
-
if (meta.scaffoldConfig && typeof meta.scaffoldConfig ===
|
|
80
|
+
if (meta.scaffoldConfig && typeof meta.scaffoldConfig === "object")
|
|
81
|
+
scaffoldConfig = meta.scaffoldConfig;
|
|
78
82
|
} catch {
|
|
79
83
|
// skip malformed sidecar
|
|
80
84
|
}
|
|
81
85
|
}
|
|
82
86
|
|
|
83
87
|
templates.push({
|
|
84
|
-
name: tplName,
|
|
85
|
-
|
|
88
|
+
name: tplName,
|
|
89
|
+
file,
|
|
90
|
+
title,
|
|
91
|
+
placeholders,
|
|
92
|
+
description,
|
|
93
|
+
lines,
|
|
94
|
+
size,
|
|
95
|
+
features,
|
|
96
|
+
tags,
|
|
97
|
+
author,
|
|
98
|
+
version: tplVersion,
|
|
99
|
+
exportPresets,
|
|
100
|
+
seedPacks,
|
|
101
|
+
scaffoldConfig,
|
|
86
102
|
});
|
|
87
103
|
}
|
|
88
104
|
return templates;
|
|
89
105
|
}
|
|
90
106
|
|
|
91
107
|
// Re-export tools
|
|
92
|
-
export { detectSprints } from
|
|
93
|
-
export { generateManifest } from
|
|
108
|
+
export { detectSprints } from "../tools/detect-sprints.js";
|
|
109
|
+
export { generateManifest } from "../tools/generate-manifest.js";
|