@mutka-explorer/create 1.0.0-rc.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/README.md +48 -0
- package/bin/cli.mjs +8 -0
- package/lib/main.mjs +109 -0
- package/lib/prompt.mjs +23 -0
- package/lib/scaffold.mjs +44 -0
- package/lib/templates.mjs +159 -0
- package/package.json +35 -0
package/README.md
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# @mutka-explorer/create
|
|
2
|
+
|
|
3
|
+
Scaffold a new [Mutka](https://github.com/ilianAZZ/mutka) module — a typed
|
|
4
|
+
TypeScript project wired to [`@mutka-explorer/module`](https://www.npmjs.com/package/@mutka-explorer/module)
|
|
5
|
+
that builds to a single self-contained ESM file (what Mutka loads).
|
|
6
|
+
|
|
7
|
+
## Usage
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm create @mutka-explorer@latest my-module
|
|
11
|
+
# or
|
|
12
|
+
npx @mutka-explorer/create my-module
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
It prompts for the module id (`author.name`), display name, GitHub username, and
|
|
16
|
+
permissions, then generates:
|
|
17
|
+
|
|
18
|
+
```text
|
|
19
|
+
my-module/
|
|
20
|
+
src/index.ts ← typed skeleton with a working command
|
|
21
|
+
package.json ← pins @mutka-explorer/module + a tsup build
|
|
22
|
+
tsconfig.json
|
|
23
|
+
mutka.config.json ← points GitHub discovery at dist/index.js
|
|
24
|
+
scripts/dev-install.mjs
|
|
25
|
+
README.md
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Non-interactive
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npm create @mutka-explorer@latest my-module -- \
|
|
32
|
+
--yes --author you --name "My Module" --permissions "fs:read,ui" --no-install
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Flags: `--id`, `--name`, `--description`, `--author`, `--permissions` (comma-separated),
|
|
36
|
+
`--yes` (accept defaults), `--no-install`, `--pm <npm|pnpm|yarn>`.
|
|
37
|
+
|
|
38
|
+
## After scaffolding
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
cd my-module
|
|
42
|
+
npm install
|
|
43
|
+
npm run install:local # build + copy into ~/.mutka/modules/<id>/, then reload Mutka
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Edit `src/index.ts` — `host` is fully typed. The module runs isolated in a Web
|
|
47
|
+
Worker (no DOM, no native network; use `host.net`). See the
|
|
48
|
+
[developer guide](https://github.com/ilianAZZ/mutka/blob/main/COMMUNITY_MODULES.md).
|
package/bin/cli.mjs
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Entry point for `npm create @mutka-explorer` / `npx @mutka-explorer/create`.
|
|
3
|
+
import { main } from "../lib/main.mjs";
|
|
4
|
+
|
|
5
|
+
main(process.argv.slice(2)).catch((err) => {
|
|
6
|
+
console.error(`\n✖ ${err instanceof Error ? err.message : String(err)}`);
|
|
7
|
+
process.exit(1);
|
|
8
|
+
});
|
package/lib/main.mjs
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// Orchestrates the scaffolder: parse args, fill gaps interactively, validate,
|
|
2
|
+
// then write the project and install. Kept dependency-free.
|
|
3
|
+
import { parseArgs } from "node:util";
|
|
4
|
+
import { readFileSync } from "node:fs";
|
|
5
|
+
import { resolve, basename, dirname, join } from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { ask, confirm } from "./prompt.mjs";
|
|
8
|
+
import { PERMISSIONS } from "./templates.mjs";
|
|
9
|
+
import { writeProject, installDeps, detectPackageManager } from "./scaffold.mjs";
|
|
10
|
+
|
|
11
|
+
const ID_RE = /^[a-z0-9][a-z0-9-]*\.[a-z0-9][a-z0-9-]*$/;
|
|
12
|
+
const slug = (s) => s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
13
|
+
|
|
14
|
+
/** This CLI's own version → the @mutka-explorer/module version we pin (lockstep). */
|
|
15
|
+
function typesVersion() {
|
|
16
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
17
|
+
const v = JSON.parse(readFileSync(join(here, "..", "package.json"), "utf8")).version;
|
|
18
|
+
return !v || v === "0.0.0" ? "latest" : `^${v}`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function main(argv) {
|
|
22
|
+
// `--no-install` isn't native to parseArgs; pull it out before parsing.
|
|
23
|
+
const skipInstall = argv.includes("--no-install");
|
|
24
|
+
const args = argv.filter((a) => a !== "--no-install");
|
|
25
|
+
|
|
26
|
+
const { values, positionals } = parseArgs({
|
|
27
|
+
args,
|
|
28
|
+
allowPositionals: true,
|
|
29
|
+
options: {
|
|
30
|
+
id: { type: "string" },
|
|
31
|
+
name: { type: "string" },
|
|
32
|
+
description: { type: "string" },
|
|
33
|
+
author: { type: "string" },
|
|
34
|
+
permissions: { type: "string" },
|
|
35
|
+
yes: { type: "boolean", short: "y", default: false },
|
|
36
|
+
pm: { type: "string" },
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
const auto = values.yes;
|
|
40
|
+
|
|
41
|
+
console.log("\n🧩 create-mutka-module\n");
|
|
42
|
+
|
|
43
|
+
// Target directory (positional, or asked, defaulting to a name-derived slug).
|
|
44
|
+
let dir = positionals[0];
|
|
45
|
+
if (!dir && !auto) dir = await ask("Project directory", "my-mutka-module");
|
|
46
|
+
dir = dir || "my-mutka-module";
|
|
47
|
+
const dirName = basename(resolve(dir));
|
|
48
|
+
|
|
49
|
+
const defaultName = values.name ?? (auto ? dirName : await ask("Display name", dirName));
|
|
50
|
+
const authorGithub =
|
|
51
|
+
values.author ?? (auto ? "" : await ask("Your GitHub username", ""));
|
|
52
|
+
const defaultId =
|
|
53
|
+
values.id ?? `${slug(authorGithub) || "you"}.${slug(defaultName) || dirName}`;
|
|
54
|
+
const id = auto ? defaultId : await ask("Module id (author.name)", defaultId);
|
|
55
|
+
if (!ID_RE.test(id)) {
|
|
56
|
+
throw new Error(`invalid module id "${id}" — use the form author.name (lowercase, e.g. you.my-module)`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const description =
|
|
60
|
+
values.description ?? (auto ? "A Mutka module." : await ask("Description", "A Mutka module."));
|
|
61
|
+
|
|
62
|
+
let permissions;
|
|
63
|
+
if (values.permissions !== undefined) {
|
|
64
|
+
permissions = values.permissions.split(",").map((p) => p.trim()).filter(Boolean);
|
|
65
|
+
} else if (auto) {
|
|
66
|
+
permissions = ["fs:read"];
|
|
67
|
+
} else {
|
|
68
|
+
console.log(`\n Available permissions: ${PERMISSIONS.join(", ")}`);
|
|
69
|
+
const raw = await ask("Permissions (comma-separated)", "fs:read");
|
|
70
|
+
permissions = raw.split(",").map((p) => p.trim()).filter(Boolean);
|
|
71
|
+
}
|
|
72
|
+
const unknown = permissions.filter((p) => !PERMISSIONS.includes(p));
|
|
73
|
+
if (unknown.length) throw new Error(`unknown permission(s): ${unknown.join(", ")}`);
|
|
74
|
+
|
|
75
|
+
const cfg = {
|
|
76
|
+
id,
|
|
77
|
+
name: defaultName,
|
|
78
|
+
description,
|
|
79
|
+
authorName: authorGithub || "",
|
|
80
|
+
authorGithub,
|
|
81
|
+
permissions,
|
|
82
|
+
pkgName: slug(dirName) || "mutka-module",
|
|
83
|
+
typesVersion: typesVersion(),
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const target = resolve(dir);
|
|
87
|
+
writeProject(target, cfg);
|
|
88
|
+
console.log(`\n✓ created ${dir}/ (id: ${id}, permissions: ${permissions.join(", ")})`);
|
|
89
|
+
|
|
90
|
+
const doInstall = skipInstall ? false : auto ? true : await confirm("Install dependencies now?", true);
|
|
91
|
+
if (doInstall) {
|
|
92
|
+
const pm = values.pm ?? detectPackageManager();
|
|
93
|
+
console.log(`\nInstalling with ${pm}…\n`);
|
|
94
|
+
try {
|
|
95
|
+
installDeps(target, pm);
|
|
96
|
+
} catch {
|
|
97
|
+
console.log(`\n⚠ install failed — run it yourself: cd ${dir} && ${pm} install`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
console.log(`
|
|
102
|
+
Next steps:
|
|
103
|
+
cd ${dir}${doInstall ? "" : "\n npm install"}
|
|
104
|
+
npm run install:local # build + load into Mutka, then reload the app
|
|
105
|
+
# edit src/index.ts — host is fully typed
|
|
106
|
+
|
|
107
|
+
Docs: https://github.com/ilianAZZ/mutka/blob/main/COMMUNITY_MODULES.md
|
|
108
|
+
`);
|
|
109
|
+
}
|
package/lib/prompt.mjs
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Minimal interactive prompts over Node's built-in readline — no dependencies,
|
|
2
|
+
// so the CLI runs instantly via npx with nothing to install.
|
|
3
|
+
import { createInterface } from "node:readline/promises";
|
|
4
|
+
import { stdin, stdout } from "node:process";
|
|
5
|
+
|
|
6
|
+
/** Ask a free-text question with an optional default shown in brackets. */
|
|
7
|
+
export async function ask(question, fallback = "") {
|
|
8
|
+
const rl = createInterface({ input: stdin, output: stdout });
|
|
9
|
+
try {
|
|
10
|
+
const hint = fallback ? ` (${fallback})` : "";
|
|
11
|
+
const answer = (await rl.question(`${question}${hint}: `)).trim();
|
|
12
|
+
return answer || fallback;
|
|
13
|
+
} finally {
|
|
14
|
+
rl.close();
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Ask a yes/no question. Returns a boolean; `def` is used on empty input. */
|
|
19
|
+
export async function confirm(question, def = true) {
|
|
20
|
+
const ans = (await ask(`${question} ${def ? "[Y/n]" : "[y/N]"}`)).toLowerCase();
|
|
21
|
+
if (!ans) return def;
|
|
22
|
+
return ans.startsWith("y");
|
|
23
|
+
}
|
package/lib/scaffold.mjs
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// Writes the project files to disk and (optionally) installs dependencies.
|
|
2
|
+
import { mkdirSync, writeFileSync, existsSync, readdirSync } from "node:fs";
|
|
3
|
+
import { join, dirname } from "node:path";
|
|
4
|
+
import { execSync } from "node:child_process";
|
|
5
|
+
import {
|
|
6
|
+
indexTs, packageJson, tsconfigJson, mutkaConfigJson, gitignore, devInstallMjs, readme,
|
|
7
|
+
} from "./templates.mjs";
|
|
8
|
+
|
|
9
|
+
/** Detect the package manager that invoked us (npm create / yarn create / pnpm). */
|
|
10
|
+
export function detectPackageManager() {
|
|
11
|
+
const ua = process.env.npm_config_user_agent ?? "";
|
|
12
|
+
if (ua.startsWith("pnpm")) return "pnpm";
|
|
13
|
+
if (ua.startsWith("yarn")) return "yarn";
|
|
14
|
+
return "npm";
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function writeFileEnsuring(path, contents) {
|
|
18
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
19
|
+
writeFileSync(path, contents);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Create the project directory tree. Throws if the target exists and is non-empty. */
|
|
23
|
+
export function writeProject(dir, cfg) {
|
|
24
|
+
if (existsSync(dir) && readdirSync(dir).length > 0) {
|
|
25
|
+
throw new Error(`target directory "${dir}" already exists and is not empty`);
|
|
26
|
+
}
|
|
27
|
+
const files = {
|
|
28
|
+
"package.json": packageJson(cfg),
|
|
29
|
+
"tsconfig.json": tsconfigJson(),
|
|
30
|
+
"mutka.config.json": mutkaConfigJson(),
|
|
31
|
+
".gitignore": gitignore(),
|
|
32
|
+
"README.md": readme(cfg),
|
|
33
|
+
"src/index.ts": indexTs(cfg),
|
|
34
|
+
"scripts/dev-install.mjs": devInstallMjs(),
|
|
35
|
+
};
|
|
36
|
+
for (const [rel, contents] of Object.entries(files)) {
|
|
37
|
+
writeFileEnsuring(join(dir, rel), contents);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Install dependencies in `dir` with the given package manager. Best-effort. */
|
|
42
|
+
export function installDeps(dir, pm) {
|
|
43
|
+
execSync(`${pm} install`, { cwd: dir, stdio: "inherit" });
|
|
44
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
// The files a scaffolded module project is made of. Each function takes the
|
|
2
|
+
// resolved config and returns file contents as a string. Kept here so `main`/
|
|
3
|
+
// `scaffold` stay about orchestration, not text.
|
|
4
|
+
|
|
5
|
+
/** Every permission a module may declare (mirrors ModulePermission in the app). */
|
|
6
|
+
export const PERMISSIONS = [
|
|
7
|
+
"fs:read", "fs:write", "fs:temp",
|
|
8
|
+
"clipboard:read", "clipboard:write",
|
|
9
|
+
"navigation", "view", "dialog",
|
|
10
|
+
"network:public", "network:local",
|
|
11
|
+
"storage", "secrets", "ui", "discovery", "shell",
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
const permsLiteral = (perms) => perms.map((p) => `"${p}"`).join(", ");
|
|
15
|
+
|
|
16
|
+
export function indexTs(cfg) {
|
|
17
|
+
return `import type { SandboxModuleDef } from "@mutka-explorer/module";
|
|
18
|
+
|
|
19
|
+
// A Mutka module is a single self-contained ESM file. It imports NOTHING at
|
|
20
|
+
// runtime — it reaches the system only through \`host\`, and every host call is
|
|
21
|
+
// checked against the permissions declared below. \`import type\` above is erased
|
|
22
|
+
// at compile time, so the built file stays self-contained.
|
|
23
|
+
const mod: SandboxModuleDef = {
|
|
24
|
+
id: "${cfg.id}",
|
|
25
|
+
name: "${cfg.name}",
|
|
26
|
+
version: "0.1.0",
|
|
27
|
+
description: "${cfg.description}",
|
|
28
|
+
author: { name: "${cfg.authorName}", github: "${cfg.authorGithub}" },
|
|
29
|
+
tags: [],
|
|
30
|
+
permissions: [${permsLiteral(cfg.permissions)}],
|
|
31
|
+
commands: [
|
|
32
|
+
{
|
|
33
|
+
id: "${cfg.id}.hello",
|
|
34
|
+
label: "${cfg.name}: count items here",
|
|
35
|
+
contextMenu: true,
|
|
36
|
+
contextMenuCategory: "View",
|
|
37
|
+
when: { selection: "any" },
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
setup(host) {
|
|
41
|
+
host.onCommand("${cfg.id}.hello", async (snap) => {
|
|
42
|
+
// host is fully typed — readDir resolves to FileItem[], no cast needed.
|
|
43
|
+
const items = await host.fs.readDir(snap.currentDirectory);
|
|
44
|
+
const dirs = items.filter((i) => i.isDir).length;
|
|
45
|
+
host.log(
|
|
46
|
+
\`\${snap.currentDirectory} → \${items.length} items (\${dirs} folders, \${items.length - dirs} files)\`,
|
|
47
|
+
);
|
|
48
|
+
});
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export default mod;
|
|
53
|
+
`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function packageJson(cfg) {
|
|
57
|
+
return JSON.stringify(
|
|
58
|
+
{
|
|
59
|
+
name: cfg.pkgName,
|
|
60
|
+
version: "0.1.0",
|
|
61
|
+
description: cfg.description,
|
|
62
|
+
type: "module",
|
|
63
|
+
private: true,
|
|
64
|
+
// Read by scripts/dev-install.mjs to place the built file under ~/.mutka.
|
|
65
|
+
mutka: { id: cfg.id },
|
|
66
|
+
scripts: {
|
|
67
|
+
build: "tsup src/index.ts --format esm",
|
|
68
|
+
dev: "tsup src/index.ts --format esm --watch",
|
|
69
|
+
"install:local": "npm run build && node scripts/dev-install.mjs",
|
|
70
|
+
},
|
|
71
|
+
devDependencies: {
|
|
72
|
+
"@mutka-explorer/module": cfg.typesVersion,
|
|
73
|
+
tsup: "^8.0.0",
|
|
74
|
+
typescript: "^5.5.0",
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
null,
|
|
78
|
+
2,
|
|
79
|
+
) + "\n";
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function tsconfigJson() {
|
|
83
|
+
return JSON.stringify(
|
|
84
|
+
{
|
|
85
|
+
compilerOptions: {
|
|
86
|
+
target: "ES2020",
|
|
87
|
+
module: "ESNext",
|
|
88
|
+
moduleResolution: "bundler",
|
|
89
|
+
lib: ["ES2020", "DOM", "DOM.Iterable"],
|
|
90
|
+
strict: true,
|
|
91
|
+
skipLibCheck: true,
|
|
92
|
+
noEmit: true,
|
|
93
|
+
types: [],
|
|
94
|
+
},
|
|
95
|
+
include: ["src"],
|
|
96
|
+
},
|
|
97
|
+
null,
|
|
98
|
+
2,
|
|
99
|
+
) + "\n";
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// GitHub discovery installs a repo named `mutka-module-*` by reading either a
|
|
103
|
+
// bare index.js at the root OR this file listing the built entry path(s).
|
|
104
|
+
export function mutkaConfigJson() {
|
|
105
|
+
return JSON.stringify({ projects: ["dist/index.js"] }, null, 2) + "\n";
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function gitignore() {
|
|
109
|
+
return ["node_modules/", "*.log", ".DS_Store", ""].join("\n");
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Builds, then copies the single bundled file into ~/.mutka/modules/<id>/ so the
|
|
113
|
+
// running app picks it up on reload — the fast local dev loop.
|
|
114
|
+
export function devInstallMjs() {
|
|
115
|
+
return `import { readFileSync, mkdirSync, copyFileSync, existsSync } from "node:fs";
|
|
116
|
+
import { homedir } from "node:os";
|
|
117
|
+
import { join } from "node:path";
|
|
118
|
+
|
|
119
|
+
const pkg = JSON.parse(readFileSync("package.json", "utf8"));
|
|
120
|
+
const id = pkg.mutka?.id;
|
|
121
|
+
if (!id) throw new Error("package.json is missing \\"mutka.id\\"");
|
|
122
|
+
if (!existsSync("dist/index.js")) throw new Error("dist/index.js not found — run \\"npm run build\\" first");
|
|
123
|
+
|
|
124
|
+
const dest = join(homedir(), ".mutka", "modules", id);
|
|
125
|
+
mkdirSync(dest, { recursive: true });
|
|
126
|
+
copyFileSync("dist/index.js", join(dest, "index.js"));
|
|
127
|
+
console.log(\`installed → \${join(dest, "index.js")}\\nReload Mutka (or toggle the module) to load it.\`);
|
|
128
|
+
`;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function readme(cfg) {
|
|
132
|
+
return `# ${cfg.name}
|
|
133
|
+
|
|
134
|
+
A [Mutka](https://github.com/ilianAZZ/mutka) module (\`${cfg.id}\`), written in
|
|
135
|
+
TypeScript against [\`@mutka-explorer/module\`](https://www.npmjs.com/package/@mutka-explorer/module).
|
|
136
|
+
|
|
137
|
+
## Develop
|
|
138
|
+
|
|
139
|
+
\`\`\`bash
|
|
140
|
+
npm install
|
|
141
|
+
npm run dev # rebuild dist/index.js on change
|
|
142
|
+
npm run install:local # build + copy into ~/.mutka/modules/${cfg.id}/ then reload Mutka
|
|
143
|
+
\`\`\`
|
|
144
|
+
|
|
145
|
+
Your module logic lives in \`src/index.ts\`. It runs ISOLATED in a Web Worker:
|
|
146
|
+
**no DOM, no native network** (\`fetch\`/\`XMLHttpRequest\`/\`WebSocket\` are blocked —
|
|
147
|
+
use \`host.net\`). Pure-logic npm libraries bundle fine; DOM/network ones do not.
|
|
148
|
+
|
|
149
|
+
## Build & publish
|
|
150
|
+
|
|
151
|
+
\`\`\`bash
|
|
152
|
+
npm run build # bundles src/index.ts → dist/index.js (one self-contained file)
|
|
153
|
+
\`\`\`
|
|
154
|
+
|
|
155
|
+
To distribute via Mutka's GitHub discovery, push this project to a repo named
|
|
156
|
+
\`mutka-module-*\` with \`dist/index.js\` committed; \`mutka.config.json\` points the
|
|
157
|
+
catalog at it. Users then find and install it from the Modules overlay.
|
|
158
|
+
`;
|
|
159
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mutka-explorer/create",
|
|
3
|
+
"version": "1.0.0-rc.1",
|
|
4
|
+
"description": "Scaffold a new Mutka module — a typed TypeScript project wired to @mutka-explorer/module that builds to a single ESM file.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Mutka contributors",
|
|
7
|
+
"homepage": "https://github.com/ilianAZZ/mutka",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/ilianAZZ/mutka.git",
|
|
11
|
+
"directory": "packages/create-module"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"mutka",
|
|
15
|
+
"module",
|
|
16
|
+
"create",
|
|
17
|
+
"scaffold",
|
|
18
|
+
"file-explorer"
|
|
19
|
+
],
|
|
20
|
+
"type": "module",
|
|
21
|
+
"bin": {
|
|
22
|
+
"create-mutka-module": "bin/cli.mjs"
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"bin",
|
|
26
|
+
"lib",
|
|
27
|
+
"README.md"
|
|
28
|
+
],
|
|
29
|
+
"engines": {
|
|
30
|
+
"node": ">=18"
|
|
31
|
+
},
|
|
32
|
+
"publishConfig": {
|
|
33
|
+
"access": "public"
|
|
34
|
+
}
|
|
35
|
+
}
|