@folienbaukasten/extension-toolkit 1.0.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/README.md +109 -0
- package/abi/README.md +20 -0
- package/abi/abi.json +151 -0
- package/abi/pack.d.ts +196 -0
- package/abi/pack.js +126 -0
- package/abi/provenance.json +5 -0
- package/abi/shims/jsx-runtime.js +13 -0
- package/abi/shims/platform.js +48 -0
- package/abi/shims/react.js +18 -0
- package/abi/types/charts/geometry.d.ts +43 -0
- package/abi/types/charts/option.d.ts +15 -0
- package/abi/types/charts/scale.d.ts +14 -0
- package/abi/types/charts/theme.d.ts +16 -0
- package/abi/types/charts/waterfall.d.ts +35 -0
- package/abi/types/components/Block.d.ts +31 -0
- package/abi/types/components/BlockArrow.d.ts +16 -0
- package/abi/types/components/Card.d.ts +25 -0
- package/abi/types/components/ChartView.d.ts +4 -0
- package/abi/types/components/DriverTree.d.ts +9 -0
- package/abi/types/components/FindingsBanner.d.ts +21 -0
- package/abi/types/components/HarveyBall.d.ts +12 -0
- package/abi/types/components/HeaderTitle.d.ts +19 -0
- package/abi/types/components/InsightRail.d.ts +13 -0
- package/abi/types/components/KpiTile.d.ts +5 -0
- package/abi/types/components/Matrix.d.ts +5 -0
- package/abi/types/components/QuoteColumn.d.ts +5 -0
- package/abi/types/components/SourceLine.d.ts +6 -0
- package/abi/types/components/Table.d.ts +5 -0
- package/abi/types/components/Timeline.d.ts +5 -0
- package/abi/types/components/slide/ContentSlide.d.ts +29 -0
- package/abi/types/components/slide/MissingComponent.d.ts +26 -0
- package/abi/types/components/slide/SlideFrame.d.ts +20 -0
- package/abi/types/components/slide/SlotView.d.ts +30 -0
- package/abi/types/components/slide/registry.d.ts +27 -0
- package/abi/types/components/slide/views/AgendaSlideView.d.ts +8 -0
- package/abi/types/components/slide/views/CardsSlideView.d.ts +12 -0
- package/abi/types/components/slide/views/ChaptersSlideView.d.ts +11 -0
- package/abi/types/components/slide/views/ChartSlideView.d.ts +3 -0
- package/abi/types/components/slide/views/ComponentSlideView.d.ts +19 -0
- package/abi/types/components/slide/views/DriverTreeSlideView.d.ts +3 -0
- package/abi/types/components/slide/views/FreeformSlideView.d.ts +3 -0
- package/abi/types/components/slide/views/KpiSlideView.d.ts +3 -0
- package/abi/types/components/slide/views/MatrixSlideView.d.ts +3 -0
- package/abi/types/components/slide/views/QuoteSlideView.d.ts +3 -0
- package/abi/types/components/slide/views/QuotesSlideView.d.ts +5 -0
- package/abi/types/components/slide/views/SplitSlideView.d.ts +3 -0
- package/abi/types/components/slide/views/TableSlideView.d.ts +3 -0
- package/abi/types/components/slide/views/TimelineSlideView.d.ts +3 -0
- package/abi/types/components/slide/views/TitleSlideView.d.ts +7 -0
- package/abi/types/components/slide/views/TwoBlockSlideView.d.ts +3 -0
- package/abi/types/components/slide/views/WebSlideView.d.ts +23 -0
- package/abi/types/components/slide/views/WhileArrowSlideView.d.ts +14 -0
- package/abi/types/deck/data.d.ts +127 -0
- package/abi/types/deck/host.d.ts +9 -0
- package/abi/types/deck/layouts.d.ts +685 -0
- package/abi/types/deck/schema.d.ts +437 -0
- package/abi/types/deck/web-body.d.ts +47 -0
- package/abi/types/lib/format.d.ts +12 -0
- package/abi/types/sets/platform.d.ts +45 -0
- package/abi/types/sets/types.d.ts +128 -0
- package/dist/fbk-pack.js +756 -0
- package/package.json +49 -0
- package/tsconfig.extension.json +17 -0
package/dist/fbk-pack.js
ADDED
|
@@ -0,0 +1,756 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// toolkit/bin/fbk-pack.ts
|
|
4
|
+
import path6 from "node:path";
|
|
5
|
+
|
|
6
|
+
// toolkit/src/abi.ts
|
|
7
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
import { pathToFileURL } from "node:url";
|
|
10
|
+
var ABI_DIR = path.resolve(import.meta.dirname, "../abi");
|
|
11
|
+
var abi = JSON.parse(
|
|
12
|
+
readFileSync(path.join(ABI_DIR, "abi.json"), "utf8")
|
|
13
|
+
);
|
|
14
|
+
var provenance = existsSync(
|
|
15
|
+
path.join(ABI_DIR, "provenance.json")
|
|
16
|
+
) ? JSON.parse(readFileSync(path.join(ABI_DIR, "provenance.json"), "utf8")) : { app: "unknown", commit: "unknown", emittedAt: "unknown" };
|
|
17
|
+
var packFormat = await import(pathToFileURL(path.join(ABI_DIR, "pack.js")).href);
|
|
18
|
+
var SHIMS = path.join(ABI_DIR, "shims");
|
|
19
|
+
|
|
20
|
+
// toolkit/src/pack.ts
|
|
21
|
+
import { Ajv } from "ajv";
|
|
22
|
+
import { build } from "esbuild";
|
|
23
|
+
import JSZip from "jszip";
|
|
24
|
+
import { createGenerator } from "ts-json-schema-generator";
|
|
25
|
+
import { createHash, createPrivateKey, sign as signBytes } from "node:crypto";
|
|
26
|
+
import { existsSync as existsSync4, mkdirSync, readFileSync as readFileSync4, readdirSync, rmSync, writeFileSync } from "node:fs";
|
|
27
|
+
import path4 from "node:path";
|
|
28
|
+
|
|
29
|
+
// toolkit/src/config.ts
|
|
30
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
|
|
31
|
+
import path2 from "node:path";
|
|
32
|
+
function readExtension(target, overrides2 = {}) {
|
|
33
|
+
const dir = path2.resolve(target);
|
|
34
|
+
if (!existsSync2(path2.join(dir, "index.ts"))) {
|
|
35
|
+
throw new Error(
|
|
36
|
+
`no extension at ${dir}: an extension is a directory with an index.ts that default-exports a ComponentSet.`
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
const configFile = path2.join(dir, "pack.json");
|
|
40
|
+
const config = existsSync2(configFile) ? JSON.parse(readFileSync2(configFile, "utf8")) : {};
|
|
41
|
+
const merged = { ...config, ...overrides2 };
|
|
42
|
+
const id = merged.id ?? path2.basename(dir);
|
|
43
|
+
const provides = merged.provides ?? (merged.components?.length ? ["theme", "components"] : ["theme"]);
|
|
44
|
+
const cssFile = path2.join(dir, "theme.css");
|
|
45
|
+
return {
|
|
46
|
+
dir,
|
|
47
|
+
config: merged,
|
|
48
|
+
id,
|
|
49
|
+
version: merged.version ?? "1.0.0",
|
|
50
|
+
provides,
|
|
51
|
+
providesTheme: provides.includes("theme"),
|
|
52
|
+
themeCss: existsSync2(cssFile) ? readFileSync2(cssFile, "utf8") : null
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// toolkit/src/check.ts
|
|
57
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3, statSync } from "node:fs";
|
|
58
|
+
import path3 from "node:path";
|
|
59
|
+
import { pathToFileURL as pathToFileURL2 } from "node:url";
|
|
60
|
+
var MAX_GLYPH_BYTES = 16384;
|
|
61
|
+
function emptyFindings() {
|
|
62
|
+
return { errors: [], warnings: [] };
|
|
63
|
+
}
|
|
64
|
+
function merge(into, from) {
|
|
65
|
+
into.errors.push(...from.errors);
|
|
66
|
+
into.warnings.push(...from.warnings);
|
|
67
|
+
return into;
|
|
68
|
+
}
|
|
69
|
+
function definedTokens(css) {
|
|
70
|
+
return new Set([...css.matchAll(/(--[a-z0-9-]+)\s*:/gi)].map((m) => m[1]));
|
|
71
|
+
}
|
|
72
|
+
function definesBoxStyle(css, cls) {
|
|
73
|
+
return new RegExp(`\\.${cls}(?![-\\w])[^{]*\\{`).test(css);
|
|
74
|
+
}
|
|
75
|
+
function checkTheme(ext) {
|
|
76
|
+
const out = emptyFindings();
|
|
77
|
+
if (!ext.providesTheme) {
|
|
78
|
+
if (ext.themeCss) {
|
|
79
|
+
out.errors.push(
|
|
80
|
+
"this pack provides no theme but has a theme.css. The loader links a stylesheet only for a pack that provides a theme, so it would ship and never load. Style from the platform tokens instead \u2014 every theme defines them."
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
return out;
|
|
84
|
+
}
|
|
85
|
+
if (!ext.themeCss) {
|
|
86
|
+
out.errors.push("a theme needs a theme.css, imported from index.ts");
|
|
87
|
+
return out;
|
|
88
|
+
}
|
|
89
|
+
const defined = definedTokens(ext.themeCss);
|
|
90
|
+
const missing = abi.requiredTokens.filter((t) => !defined.has(t));
|
|
91
|
+
if (missing.length > 0) {
|
|
92
|
+
out.errors.push(
|
|
93
|
+
`theme.css is missing ${missing.length} platform token(s): ${missing.join(", ")}
|
|
94
|
+
\u2192 the platform reads these without a fallback. Copy a working theme.css and change its values rather than assembling one.`
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
const missingStyles = abi.requiredBoxStyles.filter((cls) => !definesBoxStyle(ext.themeCss, cls));
|
|
98
|
+
if (missingStyles.length > 0) {
|
|
99
|
+
out.errors.push(
|
|
100
|
+
`theme.css is missing required box styles: ${missingStyles.map((c) => `.${c}`).join(", ")}
|
|
101
|
+
\u2192 base.css owns the box structure; each theme owns the look, and an undefined style renders an unstyled box rather than failing.`
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
const missingSwatch = Object.entries(abi.swatchTokens).filter(([, token]) => !new RegExp(`${token}:\\s*[^;]+;`).test(ext.themeCss)).map(([, token]) => token);
|
|
105
|
+
if (missingSwatch.length > 0) {
|
|
106
|
+
out.warnings.push(
|
|
107
|
+
`no swatch could be read (${missingSwatch.join(", ")} not found), so the theme chooser will show it without its palette`
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
return out;
|
|
111
|
+
}
|
|
112
|
+
function checkGlyph(ext, kind, rel) {
|
|
113
|
+
const out = emptyFindings();
|
|
114
|
+
const file = path3.join(ext.dir, rel);
|
|
115
|
+
if (!rel.endsWith(".svg") || !existsSync3(file)) {
|
|
116
|
+
out.errors.push(`component '${kind}': glyph '${rel}' is not an .svg file here`);
|
|
117
|
+
return out;
|
|
118
|
+
}
|
|
119
|
+
const bytes = statSync(file).size;
|
|
120
|
+
if (bytes > MAX_GLYPH_BYTES) {
|
|
121
|
+
out.errors.push(
|
|
122
|
+
`component '${kind}': glyph is ${bytes} bytes; the limit is ${MAX_GLYPH_BYTES}. It is a diagram on a card, not an illustration.`
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
if (!/^\s*(<\?xml|<!--|<svg)/.test(readFileSync3(file, "utf8"))) {
|
|
126
|
+
out.errors.push(`component '${kind}': glyph is not SVG`);
|
|
127
|
+
}
|
|
128
|
+
return out;
|
|
129
|
+
}
|
|
130
|
+
function checkManifest(manifest) {
|
|
131
|
+
const out = emptyFindings();
|
|
132
|
+
for (const problem of packFormat.manifestProblems(manifest, abi.builtInSetIds)) {
|
|
133
|
+
out.errors.push(problem);
|
|
134
|
+
}
|
|
135
|
+
if (manifest.platformAbi !== abi.platformAbi) {
|
|
136
|
+
out.errors.push(
|
|
137
|
+
`this pack declares platform ABI ${manifest.platformAbi}, the bundle provides ${abi.platformAbi}`
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
return out;
|
|
141
|
+
}
|
|
142
|
+
async function inspectSet(bundle) {
|
|
143
|
+
const anything = new Proxy(function stub() {
|
|
144
|
+
}, {
|
|
145
|
+
get: (_t, prop) => prop === "default" ? anything : anything,
|
|
146
|
+
apply: () => anything,
|
|
147
|
+
construct: () => anything
|
|
148
|
+
});
|
|
149
|
+
globalThis.__FBK_SET_RUNTIME__ = {
|
|
150
|
+
react: anything,
|
|
151
|
+
jsxRuntime: anything,
|
|
152
|
+
platform: anything
|
|
153
|
+
};
|
|
154
|
+
try {
|
|
155
|
+
const mod = await import(`${pathToFileURL2(path3.resolve(bundle)).href}?t=${Date.now()}`);
|
|
156
|
+
const set = mod.default;
|
|
157
|
+
if (!set) return null;
|
|
158
|
+
const components = Object.entries(set.components ?? {});
|
|
159
|
+
return {
|
|
160
|
+
id: set.id,
|
|
161
|
+
components: components.map(([kind]) => kind),
|
|
162
|
+
webOnly: components.filter(([, def]) => def?.webOnly).map(([kind]) => kind),
|
|
163
|
+
hasChrome: typeof set.chrome === "object" && set.chrome !== null
|
|
164
|
+
};
|
|
165
|
+
} catch {
|
|
166
|
+
return null;
|
|
167
|
+
} finally {
|
|
168
|
+
delete globalThis.__FBK_SET_RUNTIME__;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
function checkRegistration(ext, set, declared) {
|
|
172
|
+
const out = emptyFindings();
|
|
173
|
+
if (!set) {
|
|
174
|
+
out.warnings.push(
|
|
175
|
+
"index.ts could not be inspected, so pack.json's component list was not checked against the set that will actually load"
|
|
176
|
+
);
|
|
177
|
+
return out;
|
|
178
|
+
}
|
|
179
|
+
if (set.id && set.id !== ext.id) {
|
|
180
|
+
out.errors.push(
|
|
181
|
+
`index.ts exports id '${set.id}' but this pack publishes as '${ext.id}'. The id is the namespace of every component kind and the value a deck records \u2014 they must match.`
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
const declaredKinds = declared.map((c) => c.kind);
|
|
185
|
+
const unregistered = declaredKinds.filter((k) => !set.components.includes(k));
|
|
186
|
+
if (unregistered.length > 0) {
|
|
187
|
+
out.errors.push(
|
|
188
|
+
`pack.json declares component(s) index.ts does not register: ${unregistered.join(", ")}
|
|
189
|
+
\u2192 the app would offer the slide type and then blank the slide's body.`
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
const undeclared = set.components.filter((k) => !declaredKinds.includes(k));
|
|
193
|
+
if (undeclared.length > 0) {
|
|
194
|
+
out.errors.push(
|
|
195
|
+
`index.ts registers component(s) pack.json does not declare: ${undeclared.join(", ")}
|
|
196
|
+
\u2192 undeclared components have no description, schema or glyph, so the model never writes them and the components view cannot show them.`
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
for (const c of declared) {
|
|
200
|
+
const codeSaysWebOnly = set.webOnly.includes(c.kind);
|
|
201
|
+
if (Boolean(c.webOnly) !== codeSaysWebOnly) {
|
|
202
|
+
out.errors.push(
|
|
203
|
+
`component '${c.kind}': webOnly is ${String(Boolean(c.webOnly))} in pack.json but ${String(codeSaysWebOnly)} in index.ts.
|
|
204
|
+
\u2192 the app reads pack.json. A web-only component declared PPTX-safe exports as a silently blank region.`
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
if (ext.providesTheme && !set.hasChrome) {
|
|
209
|
+
out.errors.push(
|
|
210
|
+
"this pack provides a theme but index.ts exports no chrome \u2014 a theme owns the header and footer of every content slide"
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
return out;
|
|
214
|
+
}
|
|
215
|
+
function checkIntent(ext) {
|
|
216
|
+
const out = emptyFindings();
|
|
217
|
+
if (ext.providesTheme && !ext.config.production) {
|
|
218
|
+
out.warnings.push(
|
|
219
|
+
`'${ext.id}' is not marked production: true \u2014 the installed theme appears in the Installed list but not in the theme chooser, so nobody can pick it`
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
if (!ext.config.publisher) {
|
|
223
|
+
out.warnings.push("no publisher \u2014 the install dialog will have no name to show");
|
|
224
|
+
}
|
|
225
|
+
return out;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// toolkit/src/pack.ts
|
|
229
|
+
function swatchOf(css) {
|
|
230
|
+
if (!css) return void 0;
|
|
231
|
+
const values = {};
|
|
232
|
+
for (const [key, token] of Object.entries(abi.swatchTokens)) {
|
|
233
|
+
const m = css.match(new RegExp(`${token}:\\s*([^;]+);`));
|
|
234
|
+
if (!m) return void 0;
|
|
235
|
+
values[key] = m[1].trim();
|
|
236
|
+
}
|
|
237
|
+
return values;
|
|
238
|
+
}
|
|
239
|
+
function componentMetas(ext, findings) {
|
|
240
|
+
return (ext.config.components ?? []).map((c) => {
|
|
241
|
+
const meta = { ...c };
|
|
242
|
+
delete meta.schemaType;
|
|
243
|
+
if (c.glyph) merge(findings, checkGlyph(ext, c.kind, c.glyph));
|
|
244
|
+
if (!c.schemaType) {
|
|
245
|
+
if (c.example !== void 0) {
|
|
246
|
+
findings.warnings.push(
|
|
247
|
+
`component '${c.kind}': an example without a schemaType is unchecked, and the model is shown props it cannot validate against a type`
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
return meta;
|
|
251
|
+
}
|
|
252
|
+
const componentsTs = path4.join(ext.dir, "components.ts");
|
|
253
|
+
if (!existsSync4(componentsTs)) {
|
|
254
|
+
findings.errors.push(
|
|
255
|
+
`component '${c.kind}': schemaType '${c.schemaType}' needs a components.ts to be generated from`
|
|
256
|
+
);
|
|
257
|
+
return meta;
|
|
258
|
+
}
|
|
259
|
+
const setTsconfig = path4.join(ext.dir, "tsconfig.json");
|
|
260
|
+
if (!existsSync4(setTsconfig)) {
|
|
261
|
+
findings.errors.push(
|
|
262
|
+
"an extension needs a tsconfig.json (extend the toolkit's tsconfig.extension.json) \u2014 the prop schemas are generated by compiling components.ts against it"
|
|
263
|
+
);
|
|
264
|
+
return meta;
|
|
265
|
+
}
|
|
266
|
+
try {
|
|
267
|
+
meta.schema = createGenerator({
|
|
268
|
+
path: componentsTs,
|
|
269
|
+
tsconfig: setTsconfig,
|
|
270
|
+
type: c.schemaType,
|
|
271
|
+
expose: "export",
|
|
272
|
+
jsDoc: "extended",
|
|
273
|
+
topRef: false,
|
|
274
|
+
skipTypeCheck: true
|
|
275
|
+
}).createSchema(c.schemaType);
|
|
276
|
+
} catch (error) {
|
|
277
|
+
findings.errors.push(
|
|
278
|
+
`component '${c.kind}': could not generate a schema for '${c.schemaType}' \u2014 ${error instanceof Error ? error.message : String(error)}`
|
|
279
|
+
);
|
|
280
|
+
return meta;
|
|
281
|
+
}
|
|
282
|
+
if (c.example !== void 0) {
|
|
283
|
+
const validate = new Ajv({ strict: false, allErrors: true }).compile(meta.schema);
|
|
284
|
+
if (!validate(c.example)) {
|
|
285
|
+
const why = (validate.errors ?? []).map((e) => `${e.instancePath || "(root)"} ${e.message}`).join("; ");
|
|
286
|
+
findings.errors.push(`component '${c.kind}': example does not match its schema: ${why}`);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return meta;
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
async function checkExtension(target, overrides2 = {}) {
|
|
293
|
+
const ext = readExtension(target, overrides2);
|
|
294
|
+
const findings = emptyFindings();
|
|
295
|
+
merge(findings, checkTheme(ext));
|
|
296
|
+
merge(findings, checkIntent(ext));
|
|
297
|
+
const metas = componentMetas(ext, findings);
|
|
298
|
+
const manifest = manifestOf(ext, metas, []);
|
|
299
|
+
merge(findings, checkManifest(manifest));
|
|
300
|
+
const tmp = path4.join(ext.dir, `.fbk-check-${process.pid}.mjs`);
|
|
301
|
+
try {
|
|
302
|
+
await bundleSet(ext, tmp);
|
|
303
|
+
merge(findings, checkRegistration(ext, await inspectSet(tmp), ext.config.components ?? []));
|
|
304
|
+
} catch (error) {
|
|
305
|
+
findings.errors.push(
|
|
306
|
+
`index.ts does not build: ${error instanceof Error ? error.message : String(error)}`
|
|
307
|
+
);
|
|
308
|
+
} finally {
|
|
309
|
+
rmSync(tmp, { force: true });
|
|
310
|
+
rmSync(`${tmp.replace(/\.mjs$/, "")}.css`, { force: true });
|
|
311
|
+
}
|
|
312
|
+
return { ext, findings, manifest };
|
|
313
|
+
}
|
|
314
|
+
async function bundleSet(ext, outfile) {
|
|
315
|
+
await build({
|
|
316
|
+
entryPoints: [path4.join(ext.dir, "index.ts")],
|
|
317
|
+
outfile,
|
|
318
|
+
bundle: true,
|
|
319
|
+
format: "esm",
|
|
320
|
+
target: "es2022",
|
|
321
|
+
jsx: "automatic",
|
|
322
|
+
minify: true,
|
|
323
|
+
plugins: [
|
|
324
|
+
{
|
|
325
|
+
/*
|
|
326
|
+
* A root-relative url() in a theme's CSS — /fonts/poppins-400.woff2,
|
|
327
|
+
* /logo.png — names a file the APP serves, not one to copy into the
|
|
328
|
+
* pack. Left as-is it resolves at render time against whatever
|
|
329
|
+
* origin the renderer is on.
|
|
330
|
+
*
|
|
331
|
+
* A plugin rather than `external: ['/*']`: esbuild tests external
|
|
332
|
+
* patterns against resolved absolute paths too, so that pattern also
|
|
333
|
+
* externalised `./theme.css` and the pack came out with no
|
|
334
|
+
* stylesheet. The existsSync guard is what keeps a real absolute
|
|
335
|
+
* path (the shims below) resolving normally.
|
|
336
|
+
*/
|
|
337
|
+
name: "app-served-urls",
|
|
338
|
+
setup(build2) {
|
|
339
|
+
build2.onResolve(
|
|
340
|
+
{ filter: /^\// },
|
|
341
|
+
(args) => existsSync4(args.path) ? null : { path: args.path, external: true }
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
],
|
|
346
|
+
alias: {
|
|
347
|
+
react: path4.join(SHIMS, "react.js"),
|
|
348
|
+
"react/jsx-runtime": path4.join(SHIMS, "jsx-runtime.js"),
|
|
349
|
+
"react/jsx-dev-runtime": path4.join(SHIMS, "jsx-runtime.js"),
|
|
350
|
+
"@folienbaukasten/platform": path4.join(SHIMS, "platform.js")
|
|
351
|
+
},
|
|
352
|
+
logLevel: "warning"
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
function manifestOf(ext, metas, templates) {
|
|
356
|
+
const swatch = swatchOf(ext.themeCss);
|
|
357
|
+
const c = ext.config;
|
|
358
|
+
return {
|
|
359
|
+
formatVersion: abi.packFormatVersion,
|
|
360
|
+
id: ext.id,
|
|
361
|
+
name: c.name ?? ext.id,
|
|
362
|
+
...c.description ? { description: c.description } : {},
|
|
363
|
+
version: ext.version,
|
|
364
|
+
platformAbi: abi.platformAbi,
|
|
365
|
+
...c.publisher ? { publisher: c.publisher } : {},
|
|
366
|
+
...c.production ? { production: true } : {},
|
|
367
|
+
...swatch ? { swatch } : {},
|
|
368
|
+
...templates.length ? { templates } : {},
|
|
369
|
+
...c.layoutDescriptions ? { layoutDescriptions: c.layoutDescriptions } : {},
|
|
370
|
+
...ext.provides.length === 1 && ext.provides[0] === "theme" ? {} : { provides: ext.provides },
|
|
371
|
+
...metas.length ? { components: metas } : {},
|
|
372
|
+
...c.pptxSafe === false ? { pptxSafe: false } : {}
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
async function packExtension(target, opts) {
|
|
376
|
+
const ext = readExtension(target, opts.overrides);
|
|
377
|
+
const outDir = path4.resolve(opts.outDir);
|
|
378
|
+
const findings = emptyFindings();
|
|
379
|
+
merge(findings, checkTheme(ext));
|
|
380
|
+
merge(findings, checkIntent(ext));
|
|
381
|
+
const metas = componentMetas(ext, findings);
|
|
382
|
+
const stage = path4.join(outDir, `.${ext.id}-stage`);
|
|
383
|
+
rmSync(stage, { recursive: true, force: true });
|
|
384
|
+
mkdirSync(stage, { recursive: true });
|
|
385
|
+
await bundleSet(ext, path4.join(stage, "set.js"));
|
|
386
|
+
const stylesheet = path4.join(stage, "set.css");
|
|
387
|
+
if (ext.providesTheme && !existsSync4(stylesheet)) {
|
|
388
|
+
findings.errors.push("this theme produced no stylesheet \u2014 index.ts must import its theme.css");
|
|
389
|
+
}
|
|
390
|
+
if (!ext.providesTheme && existsSync4(stylesheet)) {
|
|
391
|
+
findings.errors.push(
|
|
392
|
+
"this pack provides no theme but imported a stylesheet; a components-only pack's set.css would never be linked. Style from the platform tokens instead."
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
const probe = path4.join(stage, ".probe.mjs");
|
|
396
|
+
writeFileSync(probe, readFileSync4(path4.join(stage, "set.js")));
|
|
397
|
+
merge(findings, checkRegistration(ext, await inspectSet(probe), ext.config.components ?? []));
|
|
398
|
+
rmSync(probe, { force: true });
|
|
399
|
+
const templates = [];
|
|
400
|
+
const templateDir = path4.join(ext.dir, "templates");
|
|
401
|
+
if (existsSync4(templateDir)) {
|
|
402
|
+
mkdirSync(path4.join(stage, "templates"), { recursive: true });
|
|
403
|
+
for (const file2 of readdirSync(templateDir).filter((f) => f.endsWith(".json")).sort()) {
|
|
404
|
+
const id = path4.basename(file2, ".json");
|
|
405
|
+
const deck = JSON.parse(readFileSync4(path4.join(templateDir, file2), "utf8"));
|
|
406
|
+
if (deck.meta?.componentSet && deck.meta.componentSet !== ext.id && ext.providesTheme) {
|
|
407
|
+
findings.warnings.push(
|
|
408
|
+
`template '${id}' names componentSet '${String(deck.meta.componentSet)}', not '${ext.id}' \u2014 it will open in another theme`
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
writeFileSync(path4.join(stage, "templates", file2), JSON.stringify(deck, null, 2));
|
|
412
|
+
templates.push({
|
|
413
|
+
id,
|
|
414
|
+
name: deck.meta?.title ?? id,
|
|
415
|
+
...deck.meta?.description ? { description: deck.meta.description } : {}
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
for (const meta of metas) {
|
|
420
|
+
if (!meta.glyph) continue;
|
|
421
|
+
const from = path4.join(ext.dir, meta.glyph);
|
|
422
|
+
if (!existsSync4(from)) continue;
|
|
423
|
+
const name = `${meta.kind.replace(/[^a-z0-9]+/gi, "-")}.svg`;
|
|
424
|
+
mkdirSync(path4.join(stage, "glyphs"), { recursive: true });
|
|
425
|
+
writeFileSync(path4.join(stage, "glyphs", name), readFileSync4(from));
|
|
426
|
+
meta.glyph = `glyphs/${name}`;
|
|
427
|
+
}
|
|
428
|
+
for (const extra of ["guide.md", "preview.png"]) {
|
|
429
|
+
const from = path4.join(ext.dir, extra);
|
|
430
|
+
if (existsSync4(from)) writeFileSync(path4.join(stage, extra), readFileSync4(from));
|
|
431
|
+
}
|
|
432
|
+
const manifest = manifestOf(ext, metas, templates);
|
|
433
|
+
merge(findings, checkManifest(manifest));
|
|
434
|
+
if (findings.errors.length > 0) {
|
|
435
|
+
rmSync(stage, { recursive: true, force: true });
|
|
436
|
+
return { file: "", bytes: 0, manifest, findings, signed: false };
|
|
437
|
+
}
|
|
438
|
+
writeFileSync(path4.join(stage, "manifest.json"), JSON.stringify(manifest, null, 2));
|
|
439
|
+
const files = packFiles(stage).sort();
|
|
440
|
+
const digests = {};
|
|
441
|
+
for (const rel of files) {
|
|
442
|
+
digests[rel] = createHash("sha256").update(readFileSync4(path4.join(stage, rel))).digest("hex");
|
|
443
|
+
}
|
|
444
|
+
if (opts.key) {
|
|
445
|
+
const key = createPrivateKey(
|
|
446
|
+
readFileSync4(opts.key.file === "-" ? 0 : opts.key.file, "utf8")
|
|
447
|
+
);
|
|
448
|
+
const signature = {
|
|
449
|
+
algorithm: "ed25519",
|
|
450
|
+
keyId: opts.key.keyId,
|
|
451
|
+
signature: signBytes(
|
|
452
|
+
null,
|
|
453
|
+
Buffer.from(packFormat.signingPayload(digests), "utf8"),
|
|
454
|
+
key
|
|
455
|
+
).toString("base64")
|
|
456
|
+
};
|
|
457
|
+
writeFileSync(path4.join(stage, "signature.json"), JSON.stringify(signature, null, 2));
|
|
458
|
+
files.push("signature.json");
|
|
459
|
+
}
|
|
460
|
+
const zip = new JSZip();
|
|
461
|
+
for (const rel of files.sort(
|
|
462
|
+
(a, b) => a === "manifest.json" ? -1 : b === "manifest.json" ? 1 : a.localeCompare(b)
|
|
463
|
+
)) {
|
|
464
|
+
zip.file(rel, readFileSync4(path4.join(stage, rel)), {
|
|
465
|
+
compression: rel.endsWith(".png") ? "STORE" : "DEFLATE",
|
|
466
|
+
// A fixed date keeps two builds of an unchanged extension
|
|
467
|
+
// byte-identical.
|
|
468
|
+
date: /* @__PURE__ */ new Date(0),
|
|
469
|
+
/*
|
|
470
|
+
* No implicit directory entries. JSZip stamps those with the current
|
|
471
|
+
* time whatever `date` says, which is what stopped two builds of an
|
|
472
|
+
* unchanged extension from being byte-identical — the property the
|
|
473
|
+
* fixed date above exists to give. Install skips directory entries
|
|
474
|
+
* anyway (`if (entry.dir) continue`), and so does every unzip.
|
|
475
|
+
*/
|
|
476
|
+
createFolders: false
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
const file = path4.join(outDir, `${ext.id}-${ext.version}.fbkset`);
|
|
480
|
+
mkdirSync(outDir, { recursive: true });
|
|
481
|
+
const bytes = await zip.generateAsync({ type: "nodebuffer" });
|
|
482
|
+
writeFileSync(file, bytes);
|
|
483
|
+
rmSync(stage, { recursive: true, force: true });
|
|
484
|
+
return { file, bytes: bytes.length, manifest, findings, signed: Boolean(opts.key) };
|
|
485
|
+
}
|
|
486
|
+
function packFiles(dir, prefix = "") {
|
|
487
|
+
return readdirSync(dir, { withFileTypes: true }).flatMap((e) => {
|
|
488
|
+
const rel = prefix ? `${prefix}/${e.name}` : e.name;
|
|
489
|
+
return e.isDirectory() ? packFiles(path4.join(dir, e.name), rel) : [rel];
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// toolkit/src/publish.ts
|
|
494
|
+
import { copyFileSync, existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync5, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
495
|
+
import path5 from "node:path";
|
|
496
|
+
import JSZip2 from "jszip";
|
|
497
|
+
var EXTENSION_KEYWORD = "folienbaukasten-extension";
|
|
498
|
+
function abiKeyword(platformAbi) {
|
|
499
|
+
return `folienbaukasten-abi-${platformAbi}`;
|
|
500
|
+
}
|
|
501
|
+
function idFromPackageName(packageName) {
|
|
502
|
+
const scoped = /^@([^/]+)\/(.+)$/.exec(packageName);
|
|
503
|
+
const base = (scoped ? scoped[2] : packageName).replace(/-extension$/, "");
|
|
504
|
+
const clean = (s) => s.toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
505
|
+
return scoped ? `${clean(scoped[1])}.${clean(base)}` : clean(base);
|
|
506
|
+
}
|
|
507
|
+
function idIsClaimable(packageName, id) {
|
|
508
|
+
return id === idFromPackageName(packageName) || !id.includes(".");
|
|
509
|
+
}
|
|
510
|
+
async function extractGlyphs(packFile, manifest, dir) {
|
|
511
|
+
const wanted = /* @__PURE__ */ new Map();
|
|
512
|
+
for (const c of manifest.components ?? []) if (c.glyph) wanted.set(c.kind, c.glyph);
|
|
513
|
+
if (wanted.size === 0) return /* @__PURE__ */ new Map();
|
|
514
|
+
const zip = await JSZip2.loadAsync(readFileSync5(packFile));
|
|
515
|
+
const written = /* @__PURE__ */ new Map();
|
|
516
|
+
for (const [kind, rel] of wanted) {
|
|
517
|
+
const entry = zip.file(rel);
|
|
518
|
+
if (!entry) continue;
|
|
519
|
+
const target = path5.join(dir, rel);
|
|
520
|
+
mkdirSync2(path5.dirname(target), { recursive: true });
|
|
521
|
+
writeFileSync2(target, await entry.async("nodebuffer"));
|
|
522
|
+
written.set(kind, rel);
|
|
523
|
+
}
|
|
524
|
+
return written;
|
|
525
|
+
}
|
|
526
|
+
async function buildWrapper(opts) {
|
|
527
|
+
const { manifest, packageName } = opts;
|
|
528
|
+
const listing = !!opts.purchase;
|
|
529
|
+
const dir = path5.resolve(opts.outDir);
|
|
530
|
+
rmSync2(dir, { recursive: true, force: true });
|
|
531
|
+
mkdirSync2(dir, { recursive: true });
|
|
532
|
+
const packName = path5.basename(opts.packFile);
|
|
533
|
+
if (!existsSync5(opts.packFile)) throw new Error(`no pack at ${opts.packFile}`);
|
|
534
|
+
const glyphs = await extractGlyphs(opts.packFile, manifest, dir);
|
|
535
|
+
const meta = {
|
|
536
|
+
id: manifest.id,
|
|
537
|
+
provides: manifest.provides ?? ["theme"],
|
|
538
|
+
platformAbi: manifest.platformAbi,
|
|
539
|
+
// A listing ships no code — that is the half being sold. It still carries
|
|
540
|
+
// everything needed to *show* the extension, which is what makes a paid
|
|
541
|
+
// card as informative as a free one.
|
|
542
|
+
...listing ? { purchase: opts.purchase } : { pack: packName },
|
|
543
|
+
...manifest.swatch ? { swatch: manifest.swatch } : {},
|
|
544
|
+
...manifest.components?.length ? {
|
|
545
|
+
components: manifest.components.map((c) => ({
|
|
546
|
+
kind: c.kind,
|
|
547
|
+
...c.description ? { description: c.description } : {},
|
|
548
|
+
...c.webOnly ? { webOnly: true } : {},
|
|
549
|
+
...glyphs.has(c.kind) ? { glyph: glyphs.get(c.kind) } : {},
|
|
550
|
+
...c.schema ? { schema: c.schema } : {}
|
|
551
|
+
}))
|
|
552
|
+
} : {}
|
|
553
|
+
};
|
|
554
|
+
const pkg = {
|
|
555
|
+
name: packageName,
|
|
556
|
+
version: manifest.version,
|
|
557
|
+
description: manifest.description ?? `${manifest.name}, a Folienbaukasten extension.`,
|
|
558
|
+
keywords: [EXTENSION_KEYWORD, abiKeyword(manifest.platformAbi), "folienbaukasten", "slides"],
|
|
559
|
+
...manifest.publisher ? { author: manifest.publisher } : {},
|
|
560
|
+
...opts.license ? { license: opts.license } : {},
|
|
561
|
+
...opts.repository ? { repository: opts.repository } : {},
|
|
562
|
+
// Not a Node module and never imported: the app reads the tarball, it
|
|
563
|
+
// does not resolve the package. Saying so stops a bundler from trying.
|
|
564
|
+
private: false,
|
|
565
|
+
files: [...listing ? [] : [packName], ...glyphs.size ? ["glyphs"] : [], "README.md"],
|
|
566
|
+
folienbaukasten: meta,
|
|
567
|
+
$comment: "A Folienbaukasten extension, delivered through npm. The .fbkset beside this file is the extension; the app downloads this tarball, verifies it against the registry\u2019s own integrity digest, extracts that one file and installs it. Nothing here is imported as a Node module, and no install script runs \u2014 the app never invokes npm."
|
|
568
|
+
};
|
|
569
|
+
writeFileSync2(path5.join(dir, "package.json"), `${JSON.stringify(pkg, null, 2)}
|
|
570
|
+
`);
|
|
571
|
+
const files = ["package.json"];
|
|
572
|
+
if (!listing) {
|
|
573
|
+
copyFileSync(opts.packFile, path5.join(dir, packName));
|
|
574
|
+
files.push(packName);
|
|
575
|
+
}
|
|
576
|
+
for (const rel of new Set(glyphs.values())) files.push(rel);
|
|
577
|
+
writeFileSync2(path5.join(dir, "README.md"), readmeFor(manifest, packageName, opts));
|
|
578
|
+
files.push("README.md");
|
|
579
|
+
return { dir, packageName, version: manifest.version, listing, files };
|
|
580
|
+
}
|
|
581
|
+
function readmeFor(manifest, packageName, opts) {
|
|
582
|
+
const provides = manifest.provides ?? ["theme"];
|
|
583
|
+
const lines = [
|
|
584
|
+
`# ${manifest.name}`,
|
|
585
|
+
"",
|
|
586
|
+
manifest.description ?? "",
|
|
587
|
+
"",
|
|
588
|
+
`A **[Folienbaukasten](https://github.com/harlecin/folienbaukasten)** extension${manifest.publisher ? ` by ${manifest.publisher}` : ""}.`,
|
|
589
|
+
"",
|
|
590
|
+
"## What it provides",
|
|
591
|
+
""
|
|
592
|
+
];
|
|
593
|
+
if (provides.includes("theme")) {
|
|
594
|
+
lines.push("- **A theme** \u2014 the colours, type and the header and footer every slide wears.");
|
|
595
|
+
}
|
|
596
|
+
for (const c of manifest.components ?? []) {
|
|
597
|
+
lines.push(
|
|
598
|
+
`- **${c.kind.split("/").slice(1).join("/")}**${c.webOnly ? " *(web only)*" : ""}${c.description ? ` \u2014 ${c.description}` : ""}`
|
|
599
|
+
);
|
|
600
|
+
}
|
|
601
|
+
if (manifest.templates?.length) {
|
|
602
|
+
lines.push(`- **${manifest.templates.length} starter deck(s)** to begin from.`);
|
|
603
|
+
}
|
|
604
|
+
lines.push(
|
|
605
|
+
"",
|
|
606
|
+
"## Installing",
|
|
607
|
+
"",
|
|
608
|
+
opts.purchase ? `This is a paid extension. [Buy it](${opts.purchase}), then double-click the \`.fbkset\` you receive \u2014 the app installs it.` : "Open **Extensions \u2192 Browse** in Folienbaukasten and search for it. This package is the delivery vehicle: it carries a prebuilt `.fbkset`, and the app extracts and installs that. It is not a Node module \u2014 do not `npm install` it into a project.",
|
|
609
|
+
"",
|
|
610
|
+
"## Compatibility",
|
|
611
|
+
"",
|
|
612
|
+
`Built for **platform ABI ${manifest.platformAbi}**${manifest.platformAbi === abi.platformAbi ? "" : " (not this toolkit\u2019s)"}. An app that speaks a different ABI refuses it at install and says so; if that happens, update the app, or ask the publisher for a build that matches.`,
|
|
613
|
+
"",
|
|
614
|
+
"---",
|
|
615
|
+
"",
|
|
616
|
+
`\`${packageName}\` ${manifest.version}${manifest.publisher ? ` \xB7 ${manifest.publisher}` : ""}`,
|
|
617
|
+
""
|
|
618
|
+
);
|
|
619
|
+
return lines.join("\n");
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
// toolkit/bin/fbk-pack.ts
|
|
623
|
+
var argv = process.argv.slice(2);
|
|
624
|
+
var command = argv[0] && !argv[0].startsWith("-") ? argv[0] : "pack";
|
|
625
|
+
var positional = argv.slice(command === argv[0] ? 1 : 0).filter((a) => !a.startsWith("--"));
|
|
626
|
+
var flag = (name) => {
|
|
627
|
+
const at = argv.indexOf(`--${name}`);
|
|
628
|
+
return at > 0 ? argv[at + 1] : void 0;
|
|
629
|
+
};
|
|
630
|
+
function targetDir() {
|
|
631
|
+
const flagged = /* @__PURE__ */ new Set();
|
|
632
|
+
for (const [i, a] of argv.entries()) if (a.startsWith("--")) flagged.add(argv[i + 1] ?? "");
|
|
633
|
+
return positional.find((a) => !flagged.has(a)) ?? ".";
|
|
634
|
+
}
|
|
635
|
+
function report(findings) {
|
|
636
|
+
for (const w of findings.warnings) console.warn(`warning: ${w}`);
|
|
637
|
+
for (const e of findings.errors) console.error(`error: ${e}`);
|
|
638
|
+
}
|
|
639
|
+
var overrides = {
|
|
640
|
+
...flag("id") ? { id: flag("id") } : {},
|
|
641
|
+
...flag("name") ? { name: flag("name") } : {},
|
|
642
|
+
...flag("version") ? { version: flag("version") } : {},
|
|
643
|
+
...flag("publisher") ? { publisher: flag("publisher") } : {}
|
|
644
|
+
};
|
|
645
|
+
switch (command) {
|
|
646
|
+
case "abi": {
|
|
647
|
+
console.log(
|
|
648
|
+
`platform ABI ${abi.platformAbi}, pack format ${abi.packFormatVersion}
|
|
649
|
+
emitted from app ${provenance.app} (${provenance.commit}) on ${provenance.emittedAt}
|
|
650
|
+
${abi.requiredTokens.length} required tokens, ${abi.requiredBoxStyles.length} box styles, ${abi.platformExports.length} platform exports, ${abi.layouts.length} layouts
|
|
651
|
+
layouts: ${abi.layouts.join(", ")}`
|
|
652
|
+
);
|
|
653
|
+
break;
|
|
654
|
+
}
|
|
655
|
+
case "check": {
|
|
656
|
+
const { ext, findings } = await checkExtension(targetDir(), overrides);
|
|
657
|
+
report(findings);
|
|
658
|
+
if (findings.errors.length > 0) {
|
|
659
|
+
console.error(`
|
|
660
|
+
${ext.id}: ${findings.errors.length} error(s) \u2014 this pack would not build`);
|
|
661
|
+
process.exit(1);
|
|
662
|
+
}
|
|
663
|
+
console.log(
|
|
664
|
+
`${ext.id} ${ext.version}: ok \u2014 provides ${ext.provides.join(" + ")}, ${(ext.config.components ?? []).length} component(s), ABI ${abi.platformAbi}${findings.warnings.length ? `, ${findings.warnings.length} warning(s)` : ""}`
|
|
665
|
+
);
|
|
666
|
+
break;
|
|
667
|
+
}
|
|
668
|
+
case "pack": {
|
|
669
|
+
const keyFile = flag("key");
|
|
670
|
+
const keyId = flag("key-id");
|
|
671
|
+
if (keyFile && !keyId) {
|
|
672
|
+
console.error("--key needs --key-id (the name this key has in the app)");
|
|
673
|
+
process.exit(1);
|
|
674
|
+
}
|
|
675
|
+
const result = await packExtension(targetDir(), {
|
|
676
|
+
outDir: flag("out") ?? "dist",
|
|
677
|
+
overrides,
|
|
678
|
+
...keyFile ? { key: { file: keyFile, keyId } } : {}
|
|
679
|
+
});
|
|
680
|
+
report(result.findings);
|
|
681
|
+
if (result.findings.errors.length > 0) {
|
|
682
|
+
console.error("\nno pack was written.");
|
|
683
|
+
process.exit(1);
|
|
684
|
+
}
|
|
685
|
+
console.log(
|
|
686
|
+
`${result.file} (${(result.bytes / 1024).toFixed(0)} kB)${result.signed ? ", signed" : ", unsigned"}${result.manifest.templates?.length ? `, ${result.manifest.templates.length} template(s)` : ""}${result.manifest.components?.length ? `, ${result.manifest.components.length} component(s)` : ""}`
|
|
687
|
+
);
|
|
688
|
+
break;
|
|
689
|
+
}
|
|
690
|
+
/*
|
|
691
|
+
* Build the pack, then wrap it in a directory `npm publish` understands.
|
|
692
|
+
*
|
|
693
|
+
* It stops short of publishing. Sending a package to the registry is
|
|
694
|
+
* public and effectively irreversible — npm allows unpublish for 72 hours
|
|
695
|
+
* and never again — so the last step stays a thing a person types.
|
|
696
|
+
*/
|
|
697
|
+
case "publish": {
|
|
698
|
+
const packageName = flag("package");
|
|
699
|
+
if (!packageName) {
|
|
700
|
+
console.error(
|
|
701
|
+
"publish needs --package, the npm name to publish under\n e.g. --package @folienbaukasten/geo-extension"
|
|
702
|
+
);
|
|
703
|
+
process.exit(1);
|
|
704
|
+
}
|
|
705
|
+
const keyFile = flag("key");
|
|
706
|
+
const keyId = flag("key-id");
|
|
707
|
+
if (keyFile && !keyId) {
|
|
708
|
+
console.error("--key needs --key-id (the name this key has in the app)");
|
|
709
|
+
process.exit(1);
|
|
710
|
+
}
|
|
711
|
+
const outDir = flag("out") ?? "dist/npm";
|
|
712
|
+
const purchase = flag("purchase");
|
|
713
|
+
const result = await packExtension(targetDir(), {
|
|
714
|
+
outDir: path6.join(outDir, ".pack"),
|
|
715
|
+
overrides,
|
|
716
|
+
...keyFile ? { key: { file: keyFile, keyId } } : {}
|
|
717
|
+
});
|
|
718
|
+
report(result.findings);
|
|
719
|
+
if (result.findings.errors.length > 0) {
|
|
720
|
+
console.error("\nnothing was published \u2014 the pack itself does not build.");
|
|
721
|
+
process.exit(1);
|
|
722
|
+
}
|
|
723
|
+
if (!idIsClaimable(packageName, result.manifest.id)) {
|
|
724
|
+
console.error(
|
|
725
|
+
`error: ${packageName} derives the id '${idFromPackageName(packageName)}', but this extension's id is '${result.manifest.id}'.
|
|
726
|
+
Rename the package, or set the id in pack.json to the derived form.`
|
|
727
|
+
);
|
|
728
|
+
process.exit(1);
|
|
729
|
+
}
|
|
730
|
+
if (!result.manifest.id.includes(".")) {
|
|
731
|
+
console.warn(
|
|
732
|
+
`warning: '${result.manifest.id}' is a bare id in a first-come namespace. New extensions should use the '${idFromPackageName(packageName)}' form, where npm prevents collisions. Existing published ids keep theirs \u2014 a deck records the id forever and renaming one breaks it.`
|
|
733
|
+
);
|
|
734
|
+
}
|
|
735
|
+
const wrapper = await buildWrapper({
|
|
736
|
+
packFile: result.file,
|
|
737
|
+
manifest: result.manifest,
|
|
738
|
+
packageName,
|
|
739
|
+
outDir: path6.join(outDir, "package"),
|
|
740
|
+
...purchase ? { purchase } : {},
|
|
741
|
+
...flag("license") ? { license: flag("license") } : {},
|
|
742
|
+
...flag("repository") ? { repository: flag("repository") } : {}
|
|
743
|
+
});
|
|
744
|
+
console.log(
|
|
745
|
+
`${wrapper.packageName}@${wrapper.version} \u2192 ${wrapper.dir}
|
|
746
|
+
${wrapper.files.join(", ")}${wrapper.listing ? "\n listing only \u2014 no pack, the card will read Buy" : ""}${result.signed ? "\n the pack is signed" : "\n the pack is unsigned (installs as \u201Cunverified source\u201D)"}`
|
|
747
|
+
);
|
|
748
|
+
console.log(`
|
|
749
|
+
Publish it with:
|
|
750
|
+
npm publish ${wrapper.dir} --access public`);
|
|
751
|
+
break;
|
|
752
|
+
}
|
|
753
|
+
default:
|
|
754
|
+
console.error(`unknown command '${command}' \u2014 expected pack, check, publish or abi`);
|
|
755
|
+
process.exit(1);
|
|
756
|
+
}
|