@ham2k/extension-tools 0.2.0 → 0.4.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.
- package/LICENSE +21 -0
- package/README.md +41 -2
- package/h2kext-build.mjs +91 -45
- package/h2kext-init.mjs +179 -81
- package/h2kext-pack.mjs +9 -9
- package/h2kext-publish.mjs +339 -0
- package/h2kext.mjs +164 -48
- package/package.json +13 -4
package/h2kext-init.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// Copyright ©️ 2026 Sebastian Delmont <sd@ham2k.com>
|
|
3
|
-
// SPDX-License-Identifier:
|
|
3
|
+
// SPDX-License-Identifier: MIT
|
|
4
4
|
//
|
|
5
5
|
// Scaffolds an extension project (HALO-452, docs/extensions/distribution.md).
|
|
6
6
|
//
|
|
@@ -16,17 +16,17 @@
|
|
|
16
16
|
// `n0call-my-extension` VALIDATES — N0CALL is a well-formed callsign — so it
|
|
17
17
|
// would ship, which is the one outcome the naming convention exists to stop.
|
|
18
18
|
|
|
19
|
-
import { readFileSync } from
|
|
20
|
-
import { access, mkdir, readFile, writeFile } from
|
|
21
|
-
import { dirname, join, relative, resolve } from
|
|
22
|
-
import { fileURLToPath } from
|
|
19
|
+
import { readFileSync } from "node:fs"
|
|
20
|
+
import { access, mkdir, readFile, writeFile } from "node:fs/promises"
|
|
21
|
+
import { dirname, join, relative, resolve } from "node:path"
|
|
22
|
+
import { fileURLToPath } from "node:url"
|
|
23
23
|
|
|
24
|
-
import { EXAMPLE_BUILD_SCRIPT } from
|
|
25
|
-
import { H2KEXT_API_VERSION, SCAFFOLD_SHARED_DEPENDENCIES, keyProblem } from
|
|
24
|
+
import { EXAMPLE_BUILD_SCRIPT } from "./h2kext-build.mjs"
|
|
25
|
+
import { H2KEXT_API_VERSION, SCAFFOLD_SHARED_DEPENDENCIES, keyProblem } from "./h2kext.mjs"
|
|
26
26
|
|
|
27
27
|
const args = process.argv.slice(2)
|
|
28
28
|
|
|
29
|
-
if (args.length === 0 || args.includes(
|
|
29
|
+
if (args.length === 0 || args.includes("--help") || args.includes("-h")) {
|
|
30
30
|
console.log(`Scaffold a Ham2K Logger extension.
|
|
31
31
|
|
|
32
32
|
h2kext-init <key> [--dir <path>] [--name "<display name>"]
|
|
@@ -35,10 +35,11 @@ The key is '<yourcallsign>-<name>' — e.g. ki2d-my-clock. The callsign is a
|
|
|
35
35
|
namespace you already have, globally unique, so two authors who never met
|
|
36
36
|
cannot collide. It also namespaces your extension's storage.
|
|
37
37
|
|
|
38
|
-
Writes manifest.json, src/index.ts, build.mjs, tsconfig.json, AGENTS.md
|
|
39
|
-
.gitignore into <path> (default: the working directory),
|
|
40
|
-
overwrite anything already there. An existing
|
|
41
|
-
scripts and dependencies this needs, nothing of
|
|
38
|
+
Writes manifest.json, src/index.ts, build.mjs, tsconfig.json, AGENTS.md,
|
|
39
|
+
.prettierrc.json and .gitignore into <path> (default: the working directory),
|
|
40
|
+
refusing rather than overwrite anything already there. An existing
|
|
41
|
+
package.json is added to — the scripts and dependencies this needs, nothing of
|
|
42
|
+
yours replaced.`)
|
|
42
43
|
process.exit(args.length === 0 ? 1 : 0)
|
|
43
44
|
}
|
|
44
45
|
|
|
@@ -48,9 +49,9 @@ let displayName
|
|
|
48
49
|
for (let i = 0; i < args.length; i++) {
|
|
49
50
|
// `resolve('')` is the working directory, so a bare `--dir` would scaffold
|
|
50
51
|
// wherever the author happened to be standing.
|
|
51
|
-
if (args[i] ===
|
|
52
|
-
else if (args[i] ===
|
|
53
|
-
else if (!args[i].startsWith(
|
|
52
|
+
if (args[i] === "--dir") dir = args[++i] ? resolve(args[i]) : undefined
|
|
53
|
+
else if (args[i] === "--name") displayName = args[++i]
|
|
54
|
+
else if (!args[i].startsWith("-") && key === undefined) key = args[i]
|
|
54
55
|
}
|
|
55
56
|
|
|
56
57
|
const problem = keyProblem(key)
|
|
@@ -60,16 +61,21 @@ if (problem) {
|
|
|
60
61
|
}
|
|
61
62
|
|
|
62
63
|
if (dir === undefined) {
|
|
63
|
-
console.error(
|
|
64
|
+
console.error("h2kext-init: --dir needs the directory to scaffold into")
|
|
64
65
|
process.exit(1)
|
|
65
66
|
}
|
|
66
67
|
|
|
67
|
-
const shortKey = key.slice(key.indexOf(
|
|
68
|
-
const name =
|
|
69
|
-
|
|
68
|
+
const shortKey = key.slice(key.indexOf("-") + 1)
|
|
69
|
+
const name =
|
|
70
|
+
displayName ??
|
|
71
|
+
shortKey
|
|
72
|
+
.split("-")
|
|
73
|
+
.filter(Boolean)
|
|
74
|
+
.map((w) => w[0].toUpperCase() + w.slice(1))
|
|
75
|
+
.join(" ")
|
|
70
76
|
|
|
71
77
|
const here = dirname(fileURLToPath(import.meta.url))
|
|
72
|
-
const { version: toolsVersion } = JSON.parse(await readFile(join(here,
|
|
78
|
+
const { version: toolsVersion } = JSON.parse(await readFile(join(here, "package.json"), "utf8"))
|
|
73
79
|
|
|
74
80
|
/// The SDK is a SEPARATE package on its own version line, so this cannot be
|
|
75
81
|
/// answered with the version of the tools that happen to be running: a
|
|
@@ -93,8 +99,8 @@ function sdkRange(projectDir) {
|
|
|
93
99
|
for (const start of [projectDir, here]) {
|
|
94
100
|
for (let dir = start; ; dir = dirname(dir)) {
|
|
95
101
|
try {
|
|
96
|
-
const path = join(dir,
|
|
97
|
-
const { version } = JSON.parse(readFileSync(path,
|
|
102
|
+
const path = join(dir, "node_modules/@ham2k/extension-sdk/package.json")
|
|
103
|
+
const { version } = JSON.parse(readFileSync(path, "utf8"))
|
|
98
104
|
if (version) return `^${version}`
|
|
99
105
|
} catch {
|
|
100
106
|
// Not here; walk up to the next node_modules.
|
|
@@ -102,63 +108,91 @@ function sdkRange(projectDir) {
|
|
|
102
108
|
if (dirname(dir) === dir) break
|
|
103
109
|
}
|
|
104
110
|
}
|
|
105
|
-
return
|
|
111
|
+
return "latest"
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/// One object of a JSON file, indented as Prettier indents it. Prettier keeps
|
|
115
|
+
/// an object expanded when it was written expanded, so nesting one of these
|
|
116
|
+
/// inside a template is stable at any width; an ARRAY it re-decides by width,
|
|
117
|
+
/// which is why every array below is written inline by hand rather than
|
|
118
|
+
/// stringified. Held by a test that formats everything the scaffold writes
|
|
119
|
+
/// and compares it with what Prettier would have produced.
|
|
120
|
+
function nestedJson(value, indent) {
|
|
121
|
+
return JSON.stringify(value, null, 2).replaceAll("\n", `\n${indent}`)
|
|
106
122
|
}
|
|
107
123
|
|
|
108
124
|
/// What the project's package.json has to say for the build, the packer and
|
|
109
125
|
/// the typecheck to run.
|
|
110
126
|
const projectPackage = {
|
|
111
127
|
name: key,
|
|
112
|
-
version:
|
|
128
|
+
version: "0.1.0",
|
|
113
129
|
private: true,
|
|
114
|
-
type:
|
|
130
|
+
type: "module",
|
|
115
131
|
scripts: {
|
|
116
|
-
build:
|
|
117
|
-
pack:
|
|
118
|
-
typecheck:
|
|
132
|
+
build: "node build.mjs",
|
|
133
|
+
pack: "h2kext-pack build",
|
|
134
|
+
typecheck: "tsc --noEmit",
|
|
135
|
+
format: "prettier --write .",
|
|
136
|
+
"format:check": "prettier --check .",
|
|
119
137
|
},
|
|
120
138
|
dependencies: {
|
|
121
|
-
|
|
139
|
+
"@ham2k/extension-sdk": sdkRange(dir),
|
|
122
140
|
},
|
|
123
141
|
devDependencies: {
|
|
124
|
-
|
|
125
|
-
esbuild:
|
|
126
|
-
|
|
142
|
+
"@ham2k/extension-tools": `^${toolsVersion}`,
|
|
143
|
+
esbuild: "^0.25.0",
|
|
144
|
+
// A patch range, not a caret: Prettier changes its output in a MINOR, and
|
|
145
|
+
// the templates below are checked against one version here. A caret would
|
|
146
|
+
// let an author install a 3.x that formats differently and fail the first
|
|
147
|
+
// `format:check` on files they never touched.
|
|
148
|
+
prettier: "~3.9.0",
|
|
149
|
+
typescript: "^5.6.0",
|
|
127
150
|
},
|
|
128
151
|
}
|
|
129
152
|
|
|
130
153
|
const files = {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
154
|
+
// `hooks` is what the Extensions list reads without evaluating any bundle,
|
|
155
|
+
// and the Add-Panel route shows only what declares `panel`. Undeclared, a
|
|
156
|
+
// panel extension installs and then cannot be found or switched on.
|
|
157
|
+
"manifest.json": `{
|
|
158
|
+
"key": ${JSON.stringify(key)},
|
|
159
|
+
"name": ${JSON.stringify(name)},
|
|
160
|
+
"version": "0.1.0",
|
|
161
|
+
"description": ${JSON.stringify(`${name}, an extension for the Ham2K Logger`)},
|
|
162
|
+
"category": "dashboard",
|
|
163
|
+
"api": ${JSON.stringify(H2KEXT_API_VERSION)},
|
|
164
|
+
"icon": "hand-wave-outline",
|
|
165
|
+
"hooks": ["panel"],
|
|
166
|
+
"sharedDependencies": ${nestedJson(SCAFFOLD_SHARED_DEPENDENCIES, " ")}
|
|
167
|
+
}
|
|
168
|
+
`,
|
|
169
|
+
|
|
170
|
+
"build.mjs": EXAMPLE_BUILD_SCRIPT,
|
|
171
|
+
|
|
172
|
+
"src/index.ts": `// ${name} — a panel that says hello.
|
|
149
173
|
//
|
|
150
174
|
// Everything here is the minimum: one hook, one descriptor, one render. See
|
|
151
175
|
// node_modules/@ham2k/extension-sdk/AGENTS.md for what to read next.
|
|
152
176
|
|
|
153
|
-
import { defineExtension } from
|
|
154
|
-
import type { HookContext, PanelContent, PanelDescriptor, PanelHook, PanelRenderArgs } from
|
|
177
|
+
import { defineExtension } from "@ham2k/extension-sdk"
|
|
178
|
+
import type { HookContext, PanelContent, PanelDescriptor, PanelHook, PanelRenderArgs } from "@ham2k/extension-sdk"
|
|
155
179
|
|
|
156
|
-
import manifest from
|
|
180
|
+
import manifest from "../manifest.json"
|
|
157
181
|
|
|
158
182
|
const HelloPanel: PanelHook = {
|
|
159
183
|
/// What the operator sees when choosing a panel to place.
|
|
160
184
|
async getPanels(_args: Record<string, never>, _ctx: HookContext): Promise<PanelDescriptor[]> {
|
|
161
|
-
|
|
185
|
+
// Written a field per line, and left that way by the formatter: on one
|
|
186
|
+
// line a long enough --name would push it past the width and the
|
|
187
|
+
// scaffold's own \`format:check\` would fail on untouched code.
|
|
188
|
+
return [
|
|
189
|
+
{
|
|
190
|
+
key: "hello",
|
|
191
|
+
title: ${JSON.stringify(name)},
|
|
192
|
+
description: "A panel that says hello",
|
|
193
|
+
icon: "hand-wave-outline",
|
|
194
|
+
},
|
|
195
|
+
]
|
|
162
196
|
},
|
|
163
197
|
|
|
164
198
|
/// What the pane shows. A panel returns CONTENT, never widgets — which is
|
|
@@ -168,37 +202,38 @@ const HelloPanel: PanelHook = {
|
|
|
168
202
|
/// its registered key and then calls this by name, so a near-miss is a panel
|
|
169
203
|
/// that installs, activates, and then renders nothing.
|
|
170
204
|
async render(_args: PanelRenderArgs, _ctx: HookContext): Promise<PanelContent> {
|
|
171
|
-
return { kind:
|
|
205
|
+
return { kind: "markdown", content: "# Hello, World!" }
|
|
172
206
|
},
|
|
173
207
|
}
|
|
174
208
|
|
|
175
209
|
defineExtension({
|
|
176
210
|
...manifest,
|
|
177
211
|
onActivation({ registerHook }) {
|
|
178
|
-
registerHook(
|
|
212
|
+
registerHook("panel", { key: "${key}", hook: HelloPanel })
|
|
179
213
|
},
|
|
180
214
|
})
|
|
181
215
|
`,
|
|
182
216
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
217
|
+
"tsconfig.json": `{
|
|
218
|
+
"compilerOptions": {
|
|
219
|
+
"target": "ES2020",
|
|
220
|
+
"module": "ESNext",
|
|
221
|
+
"moduleResolution": "bundler",
|
|
222
|
+
"lib": ["ES2020"],
|
|
223
|
+
"strict": true,
|
|
224
|
+
"noEmit": true,
|
|
225
|
+
"skipLibCheck": true,
|
|
226
|
+
"resolveJsonModule": true
|
|
227
|
+
},
|
|
228
|
+
"include": ["src/**/*.ts"]
|
|
229
|
+
}
|
|
230
|
+
`,
|
|
196
231
|
|
|
197
232
|
// The whole point of the file. Claude Code and its siblings read a project's
|
|
198
233
|
// AGENTS.md before they touch anything, so this is what carries the hook
|
|
199
234
|
// reference, the sandbox's limits and the manifest's shape into the session
|
|
200
235
|
// — none of which anyone would think to go looking for in node_modules.
|
|
201
|
-
|
|
236
|
+
"AGENTS.md": `# ${name}
|
|
202
237
|
|
|
203
238
|
An extension for the Ham2K Logger. How to write one, what the hooks are, and
|
|
204
239
|
what the sandbox will and will not let you do:
|
|
@@ -206,14 +241,24 @@ what the sandbox will and will not let you do:
|
|
|
206
241
|
@node_modules/@ham2k/extension-sdk/AGENTS.md
|
|
207
242
|
`,
|
|
208
243
|
|
|
209
|
-
|
|
244
|
+
// The style the scaffolded files are already in, so `npm run format:check`
|
|
245
|
+
// passes on an untouched project. Prettier reads the project's .gitignore
|
|
246
|
+
// from its working directory, so build/ and the packed .h2kext are already
|
|
247
|
+
// out of its way and no .prettierignore is needed.
|
|
248
|
+
".prettierrc.json": `{
|
|
249
|
+
"printWidth": 160,
|
|
250
|
+
"semi": false
|
|
251
|
+
}
|
|
252
|
+
`,
|
|
253
|
+
|
|
254
|
+
".gitignore": "node_modules/\nbuild/\n*.h2kext\n",
|
|
210
255
|
}
|
|
211
256
|
|
|
212
257
|
// Reported together rather than one per run: a scaffold that stops at the
|
|
213
258
|
// first collision has already written half a project, and the second attempt
|
|
214
259
|
// then collides with its own output.
|
|
215
|
-
const packagePath = join(dir,
|
|
216
|
-
const currentPackage = await readFile(packagePath,
|
|
260
|
+
const packagePath = join(dir, "package.json")
|
|
261
|
+
const currentPackage = await readFile(packagePath, "utf8").then(
|
|
217
262
|
(text) => {
|
|
218
263
|
try {
|
|
219
264
|
return JSON.parse(text)
|
|
@@ -225,27 +270,71 @@ const currentPackage = await readFile(packagePath, 'utf8').then(
|
|
|
225
270
|
process.exit(1)
|
|
226
271
|
}
|
|
227
272
|
},
|
|
228
|
-
() => ({})
|
|
273
|
+
() => ({}),
|
|
274
|
+
)
|
|
229
275
|
|
|
230
276
|
// `null`, `[]` and `"text"` all parse, and none of them is something to merge
|
|
231
277
|
// into — reached at the end, each is a raw TypeError over six written files.
|
|
232
|
-
if (currentPackage === null || typeof currentPackage !==
|
|
278
|
+
if (currentPackage === null || typeof currentPackage !== "object" || Array.isArray(currentPackage)) {
|
|
233
279
|
console.error(`h2kext-init: ${packagePath} is not a package description — nothing written`)
|
|
234
280
|
process.exit(1)
|
|
235
281
|
}
|
|
236
282
|
|
|
283
|
+
// Prettier reads the FIRST config it finds, and a `prettier` key in the
|
|
284
|
+
// package.json already here outranks a `.prettierrc.json` sitting beside it —
|
|
285
|
+
// so writing ours would leave a file that is never read, under templates
|
|
286
|
+
// formatted to a style the author's own config contradicts. Their config
|
|
287
|
+
// wins; this one is dropped, and said out loud, because the alternative is a
|
|
288
|
+
// project whose first `npm run format` rewrites every scaffolded file.
|
|
289
|
+
const PRETTIER_CONFIG_FILES = [
|
|
290
|
+
".prettierrc",
|
|
291
|
+
".prettierrc.json",
|
|
292
|
+
".prettierrc.json5",
|
|
293
|
+
".prettierrc.yaml",
|
|
294
|
+
".prettierrc.yml",
|
|
295
|
+
".prettierrc.js",
|
|
296
|
+
".prettierrc.mjs",
|
|
297
|
+
".prettierrc.cjs",
|
|
298
|
+
".prettierrc.toml",
|
|
299
|
+
"prettier.config.js",
|
|
300
|
+
"prettier.config.mjs",
|
|
301
|
+
"prettier.config.cjs",
|
|
302
|
+
]
|
|
303
|
+
const foundAt = async (path) =>
|
|
304
|
+
await access(join(dir, path)).then(
|
|
305
|
+
() => true,
|
|
306
|
+
() => false,
|
|
307
|
+
)
|
|
308
|
+
let foreignPrettierConfig
|
|
309
|
+
if (currentPackage.prettier !== undefined) foreignPrettierConfig = "the `prettier` key in package.json"
|
|
310
|
+
else
|
|
311
|
+
for (const path of PRETTIER_CONFIG_FILES) {
|
|
312
|
+
if (path !== ".prettierrc.json" && (await foundAt(path))) {
|
|
313
|
+
foreignPrettierConfig = path
|
|
314
|
+
break
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
if (foreignPrettierConfig) delete files[".prettierrc.json"]
|
|
318
|
+
|
|
237
319
|
// `access`, not a read: a directory where a file should go, or one this
|
|
238
320
|
// process cannot read, is something in the way — treated as absent, it becomes
|
|
239
321
|
// a write loop that fails half-way through.
|
|
240
322
|
const existing = []
|
|
241
323
|
for (const path of Object.keys(files)) {
|
|
242
|
-
if (
|
|
324
|
+
if (
|
|
325
|
+
await access(join(dir, path)).then(
|
|
326
|
+
() => true,
|
|
327
|
+
() => false,
|
|
328
|
+
)
|
|
329
|
+
)
|
|
330
|
+
existing.push(path)
|
|
243
331
|
}
|
|
244
332
|
if (existing.length) {
|
|
245
333
|
const where = relative(process.cwd(), dir)
|
|
246
334
|
console.error(
|
|
247
|
-
`h2kext-init: ${where.startsWith(
|
|
248
|
-
|
|
335
|
+
`h2kext-init: ${where.startsWith("..") ? dir : where || "."} already holds ${existing.join(", ")} — ` +
|
|
336
|
+
"nothing written. Scaffold into an empty directory with --dir.",
|
|
337
|
+
)
|
|
249
338
|
process.exit(1)
|
|
250
339
|
}
|
|
251
340
|
|
|
@@ -262,15 +351,24 @@ for (const [path, content] of Object.entries(files)) {
|
|
|
262
351
|
// Filled in key by key, never overwritten: what the author already declared —
|
|
263
352
|
// their own name for the project, a version of esbuild they chose — is theirs.
|
|
264
353
|
for (const [field, value] of Object.entries(projectPackage)) {
|
|
265
|
-
if (typeof value !==
|
|
354
|
+
if (typeof value !== "object") currentPackage[field] ??= value
|
|
266
355
|
else for (const [name, entry] of Object.entries(value)) (currentPackage[field] ??= {})[name] ??= entry
|
|
267
356
|
}
|
|
268
357
|
await writeFile(packagePath, `${JSON.stringify(currentPackage, null, 2)}\n`)
|
|
269
358
|
|
|
270
359
|
console.log(`${key} — ${name}
|
|
271
360
|
|
|
272
|
-
${[...Object.keys(files),
|
|
273
|
-
|
|
361
|
+
${[...Object.keys(files), "package.json"].join("\n ")}
|
|
362
|
+
${
|
|
363
|
+
foreignPrettierConfig
|
|
364
|
+
? `
|
|
365
|
+
No .prettierrc.json written — ${foreignPrettierConfig} already configures
|
|
366
|
+
Prettier here, and it is the one Prettier reads. The scaffolded files are in
|
|
367
|
+
the style this command's own config describes (print width 160, no
|
|
368
|
+
semicolons); \`npm run format\` will restyle them to yours.
|
|
369
|
+
`
|
|
370
|
+
: ""
|
|
371
|
+
}
|
|
274
372
|
Next:
|
|
275
373
|
npm install
|
|
276
374
|
npm run build && npm run pack`)
|
package/h2kext-pack.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// Copyright ©️ 2026 Sebastian Delmont <sd@ham2k.com>
|
|
3
|
-
// SPDX-License-Identifier:
|
|
3
|
+
// SPDX-License-Identifier: MIT
|
|
4
4
|
//
|
|
5
5
|
// Packages an extension into a `.h2kext` a user can install
|
|
6
6
|
// (HALO-422, docs/extensions/distribution.md).
|
|
@@ -10,11 +10,11 @@
|
|
|
10
10
|
// The directory holds a built `index.js`, a `manifest.json`, and optionally an
|
|
11
11
|
// `assets/` folder. Nothing here compiles anything — see h2kext.mjs.
|
|
12
12
|
|
|
13
|
-
import { pack, PackError, H2KEXT_API_VERSION, RESERVED_PREFIX } from
|
|
13
|
+
import { pack, PackError, H2KEXT_API_VERSION, RESERVED_PREFIX } from "./h2kext.mjs"
|
|
14
14
|
|
|
15
15
|
const args = process.argv.slice(2)
|
|
16
16
|
|
|
17
|
-
if (args.length === 0 || args.includes(
|
|
17
|
+
if (args.length === 0 || args.includes("--help") || args.includes("-h")) {
|
|
18
18
|
console.log(`Package an extension directory into a .h2kext bundle.
|
|
19
19
|
|
|
20
20
|
h2kext-pack <directory> [-o <out.h2kext>] [--force-name]
|
|
@@ -37,19 +37,19 @@ Defaults to <key>-<version>.h2kext in the working directory.`)
|
|
|
37
37
|
|
|
38
38
|
// Walked by index rather than matched by value: `args.indexOf(a)` finds the
|
|
39
39
|
// FIRST occurrence, so `-o foo foo` mistook the output path for the directory.
|
|
40
|
-
const forceName = args.includes(
|
|
40
|
+
const forceName = args.includes("--force-name")
|
|
41
41
|
let dir
|
|
42
42
|
let outPath
|
|
43
43
|
for (let i = 0; i < args.length; i++) {
|
|
44
|
-
if (args[i] ===
|
|
44
|
+
if (args[i] === "-o") {
|
|
45
45
|
outPath = args[++i]
|
|
46
|
-
} else if (!args[i].startsWith(
|
|
46
|
+
} else if (!args[i].startsWith("-") && dir === undefined) {
|
|
47
47
|
dir = args[i]
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
if (!dir) {
|
|
52
|
-
console.error(
|
|
52
|
+
console.error("h2kext-pack: name the directory to package")
|
|
53
53
|
process.exit(1)
|
|
54
54
|
}
|
|
55
55
|
|
|
@@ -58,11 +58,11 @@ try {
|
|
|
58
58
|
const assets = result.files.length - 2
|
|
59
59
|
console.log(`${result.outPath}`)
|
|
60
60
|
console.log(` ${result.manifest.key} ${result.manifest.version} — ${result.manifest.name}`)
|
|
61
|
-
console.log(` ${assets} asset${assets === 1 ?
|
|
61
|
+
console.log(` ${assets} asset${assets === 1 ? "" : "s"}`)
|
|
62
62
|
if (result.manifest.domains?.length) {
|
|
63
63
|
// Named on the way out, because this is the one part of the manifest the
|
|
64
64
|
// person installing it will be asked to approve.
|
|
65
|
-
console.log(` may reach: ${result.manifest.domains.join(
|
|
65
|
+
console.log(` may reach: ${result.manifest.domains.join(", ")}`)
|
|
66
66
|
}
|
|
67
67
|
} catch (e) {
|
|
68
68
|
if (e instanceof PackError) {
|