pattern-dump 0.3.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/CHANGELOG.md +61 -0
- package/README.md +7 -2
- package/dist/chunks/{chunk-5WFBNPXB.js → chunk-DIDJPCLS.js} +163 -15
- package/dist/index.js +1 -1
- package/dist/pd.js +1439 -725
- package/dist/types/config/index.d.ts +14 -5
- package/dist/types/config/own-manifest.d.ts +9 -0
- package/dist/types/config/validate.d.ts +8 -1
- package/dist/types/contracts/dev-protocol.d.ts +1 -1
- package/package.json +1 -1
- package/templates/storefront/.github/workflows/pages.yml +3 -2
- package/templates/storefront/pages/articles/hello.md +10 -0
- package/templates/storefront/pages/articles/index.md +10 -0
- package/templates/storefront/src/patterns/layouts/listing/listing.hbs +16 -0
- package/templates/storefront/src/patterns/layouts/listing/listing.json +13 -0
- package/templates/storefront/src/patterns/layouts/listing/listing.md +7 -0
- package/templates/storefront/src/patterns/layouts/listing/listing.schema.json +18 -0
- package/templates/storefront/src/patterns/layouts/page/page.hbs +1 -0
- package/templates/storefront/src/patterns/layouts/page/page.json +1 -1
- package/templates/storefront/src/patterns/layouts/page/page.md +2 -1
- package/templates/storefront/src/patterns/layouts/page/page.schema.json +3 -1
- package/templates/storefront/src/styles/layouts/listing.scss +15 -0
- package/templates/storefront/src/styles/layouts/page.scss +3 -0
- package/templates/storefront-fills/config.txt +4 -1
- package/templates/storefront-fills/readme.md +11 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,66 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.0
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- `pd build --browser` also checks a storefront's pages in Chromium, at a desktop and a phone width: colour contrast
|
|
8
|
+
and the other accessibility rules that need a rendered page (a critical or serious finding stops the build), and
|
|
9
|
+
each image's variant against the box it is laid out in (a warning when its slot's `sizes` doesn't match). It uses
|
|
10
|
+
the project's own Playwright: `npm install --save-dev playwright`, then `npx playwright install chromium`.
|
|
11
|
+
- Collections: a folder of storefront pages is a collection. A layout that declares `page.children` lists the
|
|
12
|
+
folder's pages on its `index.md` (route, title, description, date, image, tags), newest first with `sort: date`;
|
|
13
|
+
a page without a description is listed with the first paragraph of its body. `partials/base` can list folders
|
|
14
|
+
under `site.collections` to show their pages on every page, such as a home page's latest articles. Layouts can
|
|
15
|
+
show a page's own `page.date` and `page.tags`.
|
|
16
|
+
- A storefront can be published under a path, such as a GitHub project page: `storefront.url` takes the full
|
|
17
|
+
address (`https://example.github.io/project/`). Every page, stylesheet, script and image is then served under the
|
|
18
|
+
path, in `pd dev` too, with links still written from the site's root; canonical URLs, Open Graph, JSON-LD and the
|
|
19
|
+
sitemap carry it. Every page names its path in `<meta name="pd-base-path">` for a skin's own scripts, and
|
|
20
|
+
`pd build --browser` stops on a request outside the path. Under a path there is no `robots.txt` (the build says what
|
|
21
|
+
the host's own needs) and previews carry a robots `noindex` tag, as a workbench's do when it is built with `--base`.
|
|
22
|
+
- A project that is a pattern package itself (a style guide with its own workbench) describes its folders once: its
|
|
23
|
+
config takes each folder it doesn't name from its `package.json`'s `patternDump` field (patterns, data, helpers,
|
|
24
|
+
assets, styles with `entry` and `documentBase`, themes, scripts, `globalScripts`, `exclude`). A folder named in
|
|
25
|
+
both with different values is a config error naming both files.
|
|
26
|
+
- `storefront.head`: a site's own additions to every page's head (an analytics script, a verification tag), a
|
|
27
|
+
Handlebars file rendered with `site` after the skin's `partials/head` and checked like it. For a site whose skin
|
|
28
|
+
is a shared package.
|
|
29
|
+
- A `HELPER_SHADOW` warning on a component whose template has a bare `{{name}}` that calls a registered helper
|
|
30
|
+
while its data has a field of that name, at any depth: `{{id}}` inside `{{#each palettes}}` showed the `id`
|
|
31
|
+
helper's output instead of each palette's `id`, silently. Read the field as `{{this.id}}`, or rename it.
|
|
32
|
+
- `pd init --storefront` writes an `articles/` collection (a listing page and one dated, tagged article) with a
|
|
33
|
+
`listing` layout, shows a page's date and tags in its page layout, and its config shows a `storefront.url` for a
|
|
34
|
+
subfolder.
|
|
35
|
+
- `date`, `tags` and `draft` in a page's frontmatter. `date` (`YYYY-MM-DD`) is the page's date for listings and its
|
|
36
|
+
JSON-LD `datePublished`, which falls back to the page's first commit. `draft: true` shows a page in `pd dev` and
|
|
37
|
+
leaves it out of `pd build`, which names the drafts it left out.
|
|
38
|
+
|
|
39
|
+
### Fixed
|
|
40
|
+
|
|
41
|
+
- A storefront page loads the behaviour scripts its components bring (`requireScript` of its layout's components and
|
|
42
|
+
of the components it places, and `globalScripts`), as their workbench previews do. Before, only the markup and CSS
|
|
43
|
+
reached the page.
|
|
44
|
+
- A code block on a storefront page, or in a component's `<pre>`, keeps its lines as written when its partial is
|
|
45
|
+
included on an indented line (as a skin's `{{> @partial-block }}` usually is). Handlebars indented every line after
|
|
46
|
+
the first by the include's indentation.
|
|
47
|
+
- A schema field named like a registered helper (`id`, `eq`, …) is no longer an error only the workbench's Diagnostics
|
|
48
|
+
tab shows: `pd dev` and `pd build` say it in the terminal, a storefront page that places the component fails (as
|
|
49
|
+
one whose layout has such a field already did), and `pd audit` re-audits a component when its schema changes
|
|
50
|
+
instead of reusing a verdict cached before.
|
|
51
|
+
- An image in a site page's markdown from a pattern package's assets, when that package's folder is the site's only
|
|
52
|
+
asset folder, gets its variants. Before, the page failed with "is not an image in the assets" for a URL that was
|
|
53
|
+
served.
|
|
54
|
+
- `pd dev` no longer keeps base-layer rules that a component's stylesheet stopped writing (through a mixin) until it is
|
|
55
|
+
restarted.
|
|
56
|
+
|
|
57
|
+
### Changed
|
|
58
|
+
|
|
59
|
+
- **Breaking:** `date`, `tags`, `draft` and `sort` are now frontmatter keys of the build, like `layout` and
|
|
60
|
+
`image`. A layout whose schema declares one of them fails every page that uses it; rename the field in the
|
|
61
|
+
layout's schema (and show the page's date or tags through `page.date` and `page.tags`).
|
|
62
|
+
- A storefront page's JSON-LD carries `datePublished`.
|
|
63
|
+
|
|
3
64
|
## 0.3.0
|
|
4
65
|
|
|
5
66
|
From this release Pattern Dump is published as plain 0.x versions under npm's `latest` tag: install it with
|
package/README.md
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
# Pattern Dump
|
|
2
2
|
|
|
3
|
-
A workbench for HTML and CSS design systems, and a successor to
|
|
3
|
+
A workbench for HTML and CSS design systems, and a successor to Pattern Lab. Write components as
|
|
4
4
|
Handlebars templates with SCSS, data and Markdown docs; Pattern Dump shows them live, checks them, maps
|
|
5
5
|
how they relate, and publishes the result as a static site.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
**Demo:** a style guide and the website built from it, at https://lab.n8d.studio/pattern-dump-demo/
|
|
8
|
+
|
|
9
|
+
> **0.4.0.** Pattern Dump is at 0.x, so breaking changes are possible between minor versions (0.3 to 0.4);
|
|
8
10
|
> the [changelog](https://github.com/n8design/pattern-dump/blob/main/CHANGELOG.md) names them.
|
|
9
11
|
|
|
12
|
+
**Coming from Pattern Lab?** Point `patternsDir` at your patterns folder: Handlebars templates, `.json` data and
|
|
13
|
+
`~` variants work as they are, and no build step of your own is needed.
|
|
14
|
+
|
|
10
15
|
## Requirements
|
|
11
16
|
|
|
12
17
|
Node.js 22.22 or later, and npm.
|
|
@@ -54,7 +54,8 @@ var SHAPE = {
|
|
|
54
54
|
lang: "lang",
|
|
55
55
|
index: "boolean",
|
|
56
56
|
image: "asset-path",
|
|
57
|
-
images: IMAGES
|
|
57
|
+
images: IMAGES,
|
|
58
|
+
head: "hbs-file"
|
|
58
59
|
},
|
|
59
60
|
required: ["pagesDir", "outDir"]
|
|
60
61
|
}
|
|
@@ -90,25 +91,25 @@ function suggest(key, known) {
|
|
|
90
91
|
const sameStart = key.slice(0, 3).toLowerCase() === best.slice(0, 3).toLowerCase();
|
|
91
92
|
return bestD <= 2 || bestD <= 3 && sameStart ? best : void 0;
|
|
92
93
|
}
|
|
93
|
-
function checkObject(value, shape, required,
|
|
94
|
+
function checkObject(value, shape, required, at2, problems) {
|
|
94
95
|
const known = Object.keys(shape);
|
|
95
96
|
const unknown = Object.keys(value).filter((k) => !(k in shape));
|
|
96
97
|
for (const key of required) {
|
|
97
98
|
if (value[key] !== void 0) continue;
|
|
98
99
|
const near = unknown.find((u) => suggest(u, [key]) === key);
|
|
99
|
-
problems.push(near ? `missing required "${
|
|
100
|
+
problems.push(near ? `missing required "${at2}${key}" (found "${at2}${near}" \u2014 did you mean "${at2}${key}"?)` : `missing required "${at2}${key}"`);
|
|
100
101
|
}
|
|
101
102
|
for (const key of unknown) {
|
|
102
103
|
const near = suggest(key, known);
|
|
103
104
|
if (near && required.includes(near) && value[near] === void 0) continue;
|
|
104
|
-
problems.push(`unknown key "${
|
|
105
|
+
problems.push(`unknown key "${at2}${key}"${near ? ` \u2014 did you mean "${at2}${near}"?` : ""}`);
|
|
105
106
|
}
|
|
106
107
|
for (const key of known) {
|
|
107
|
-
if (value[key] !== void 0) checkValue(value[key], shape[key], `${
|
|
108
|
+
if (value[key] !== void 0) checkValue(value[key], shape[key], `${at2}${key}`, problems);
|
|
108
109
|
}
|
|
109
110
|
}
|
|
110
|
-
function checkValue(value, rule,
|
|
111
|
-
const fail = (expected) => problems.push(`"${
|
|
111
|
+
function checkValue(value, rule, at2, problems) {
|
|
112
|
+
const fail = (expected) => problems.push(`"${at2}" must be ${expected}`);
|
|
112
113
|
if (rule === "string") {
|
|
113
114
|
if (typeof value !== "string") fail("a string");
|
|
114
115
|
return;
|
|
@@ -154,12 +155,12 @@ function checkValue(value, rule, at, problems) {
|
|
|
154
155
|
if (typeof value === "string") {
|
|
155
156
|
try {
|
|
156
157
|
const url = new URL(value);
|
|
157
|
-
ok = /^https?:$/.test(url.protocol) && url.
|
|
158
|
+
ok = /^https?:$/.test(url.protocol) && url.search === "" && url.hash === "" && !/[?#]/.test(value) && !/\/\/./.test(url.pathname);
|
|
158
159
|
} catch {
|
|
159
160
|
ok = false;
|
|
160
161
|
}
|
|
161
162
|
}
|
|
162
|
-
if (!ok) fail(`the site's absolute http(s) address, with no
|
|
163
|
+
if (!ok) fail(`the site's absolute http(s) address, with no query or fragment, such as "https://example.com/", or "https://example.github.io/project/" for a site published under a path`);
|
|
163
164
|
return;
|
|
164
165
|
}
|
|
165
166
|
if (rule === "lang") {
|
|
@@ -168,6 +169,12 @@ function checkValue(value, rule, at, problems) {
|
|
|
168
169
|
}
|
|
169
170
|
return;
|
|
170
171
|
}
|
|
172
|
+
if (rule === "hbs-file") {
|
|
173
|
+
if (typeof value !== "string" || !/\.hbs$/.test(value) || /^(?:\/|[a-zA-Z]:)/.test(value)) {
|
|
174
|
+
fail(`a Handlebars file relative to the project's root, such as "./head.hbs"`);
|
|
175
|
+
}
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
171
178
|
if (rule === "asset-path") {
|
|
172
179
|
if (typeof value !== "string" || !/^\/assets\/[^?#]+[^/?#]$/.test(value)) {
|
|
173
180
|
fail(`a path to a file in the assets, such as "/assets/images/share.png"`);
|
|
@@ -194,7 +201,7 @@ function checkValue(value, rule, at, problems) {
|
|
|
194
201
|
}
|
|
195
202
|
if ("values" in rule) {
|
|
196
203
|
if (typeof value === "string" && rule.unsupported?.includes(value)) {
|
|
197
|
-
problems.push(`"${
|
|
204
|
+
problems.push(`"${at2}": "${value}" is not supported yet \u2014 use ${rule.values.map((v) => `"${v}"`).join(" or ")}, or leave it out`);
|
|
198
205
|
} else if (typeof value !== "string" || !rule.values.includes(value)) {
|
|
199
206
|
fail(rule.values.map((v) => `"${v}"`).join(" or "));
|
|
200
207
|
}
|
|
@@ -204,7 +211,7 @@ function checkValue(value, rule, at, problems) {
|
|
|
204
211
|
fail("an object");
|
|
205
212
|
return;
|
|
206
213
|
}
|
|
207
|
-
checkObject(value, rule.keys, rule.required ?? [], `${
|
|
214
|
+
checkObject(value, rule.keys, rule.required ?? [], `${at2}.`, problems);
|
|
208
215
|
}
|
|
209
216
|
var ConfigError = class extends Error {
|
|
210
217
|
name = "ConfigError";
|
|
@@ -218,8 +225,8 @@ function checkRelations(raw, problems) {
|
|
|
218
225
|
["storefront.outDir", sf.outDir]
|
|
219
226
|
];
|
|
220
227
|
const root = resolve(sep, "pd-root");
|
|
221
|
-
const inside = (
|
|
222
|
-
const rel = relative(parent,
|
|
228
|
+
const inside = (child2, parent) => {
|
|
229
|
+
const rel = relative(parent, child2);
|
|
223
230
|
return rel !== "" && !rel.startsWith("..") && !rel.startsWith(sep) && !/^[a-zA-Z]:/.test(rel);
|
|
224
231
|
};
|
|
225
232
|
for (let i = 0; i < folders.length; i++) {
|
|
@@ -263,7 +270,7 @@ ${problems.map((p) => ` - ${p}`).join("\n")}`);
|
|
|
263
270
|
const sf = config.storefront;
|
|
264
271
|
if (!sf) return config;
|
|
265
272
|
const workbenchUrl = sf.workbenchUrl !== void 0 ? withOneTrailingSlash(sf.workbenchUrl) : void 0;
|
|
266
|
-
const url = sf.url !== void 0 ?
|
|
273
|
+
const url = sf.url !== void 0 ? siteUrlForm(sf.url) : void 0;
|
|
267
274
|
if (workbenchUrl === sf.workbenchUrl && url === sf.url) return config;
|
|
268
275
|
return { ...config, storefront: {
|
|
269
276
|
...sf,
|
|
@@ -271,13 +278,152 @@ ${problems.map((p) => ` - ${p}`).join("\n")}`);
|
|
|
271
278
|
...url !== void 0 ? { url } : {}
|
|
272
279
|
} };
|
|
273
280
|
}
|
|
281
|
+
function siteUrlForm(value) {
|
|
282
|
+
const u = new URL(value);
|
|
283
|
+
return `${u.origin}${withOneTrailingSlash(u.pathname)}`;
|
|
284
|
+
}
|
|
285
|
+
function storefrontBasePath(storefront) {
|
|
286
|
+
return storefront?.url === void 0 ? "/" : new URL(storefront.url).pathname;
|
|
287
|
+
}
|
|
274
288
|
function sameHostWorkbenchPath(storefront) {
|
|
275
289
|
const w = storefront?.workbenchUrl;
|
|
276
290
|
if (w === void 0) return void 0;
|
|
277
291
|
if (w.startsWith("/")) return w;
|
|
278
292
|
if (storefront?.url === void 0) return void 0;
|
|
279
293
|
const u = new URL(w);
|
|
280
|
-
return
|
|
294
|
+
return u.origin === new URL(storefront.url).origin ? u.pathname : void 0;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// packages/config/src/own-manifest.ts
|
|
298
|
+
var isObject2 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
299
|
+
var child = (o, key) => {
|
|
300
|
+
if (!isObject2(o[key])) o[key] = {};
|
|
301
|
+
return o[key];
|
|
302
|
+
};
|
|
303
|
+
var at = (o, ...keys) => keys.reduce((v, k) => isObject2(v) ? v[k] : void 0, o);
|
|
304
|
+
var top = (from, to, paths = true) => ({
|
|
305
|
+
from,
|
|
306
|
+
get: (m) => m[from],
|
|
307
|
+
to,
|
|
308
|
+
read: (c) => c[to],
|
|
309
|
+
write: (c, v) => {
|
|
310
|
+
c[to] = v;
|
|
311
|
+
},
|
|
312
|
+
paths
|
|
313
|
+
});
|
|
314
|
+
var FIELDS = [
|
|
315
|
+
top("patterns", "patternsDir"),
|
|
316
|
+
top("data", "dataDir"),
|
|
317
|
+
top("helpers", "helpers"),
|
|
318
|
+
top("scripts", "scriptsDir"),
|
|
319
|
+
top("globalScripts", "globalScripts", false),
|
|
320
|
+
top("exclude", "exclude", false),
|
|
321
|
+
{
|
|
322
|
+
from: "assets",
|
|
323
|
+
get: (m) => m["assets"],
|
|
324
|
+
to: "assets.dir",
|
|
325
|
+
read: (c) => at(c, "assets", "dir"),
|
|
326
|
+
write: (c, v) => {
|
|
327
|
+
child(c, "assets")["dir"] = v;
|
|
328
|
+
},
|
|
329
|
+
paths: true
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
from: "styles.dir",
|
|
333
|
+
get: (m) => at(m, "styles", "dir"),
|
|
334
|
+
to: "styles.dir",
|
|
335
|
+
read: (c) => at(c, "styles", "dir"),
|
|
336
|
+
write: (c, v) => {
|
|
337
|
+
child(c, "styles")["dir"] = v;
|
|
338
|
+
},
|
|
339
|
+
paths: true
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
from: "styles.entry",
|
|
343
|
+
get: (m) => at(m, "styles", "entry"),
|
|
344
|
+
to: "styles.entry",
|
|
345
|
+
read: (c) => at(c, "styles", "entry"),
|
|
346
|
+
write: (c, v) => {
|
|
347
|
+
child(c, "styles")["entry"] = v;
|
|
348
|
+
},
|
|
349
|
+
paths: true
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
from: "styles.documentBase",
|
|
353
|
+
get: (m) => at(m, "styles", "documentBase"),
|
|
354
|
+
to: "documentBase",
|
|
355
|
+
read: (c) => c["documentBase"],
|
|
356
|
+
write: (c, v) => {
|
|
357
|
+
c["documentBase"] = v;
|
|
358
|
+
},
|
|
359
|
+
paths: true
|
|
360
|
+
},
|
|
361
|
+
{
|
|
362
|
+
from: "themes.dir",
|
|
363
|
+
get: (m) => at(m, "themes", "dir"),
|
|
364
|
+
to: "themes.dir",
|
|
365
|
+
read: (c) => at(c, "themes", "dir"),
|
|
366
|
+
write: (c, v) => {
|
|
367
|
+
child(c, "themes")["dir"] = v;
|
|
368
|
+
},
|
|
369
|
+
paths: true
|
|
370
|
+
},
|
|
371
|
+
{
|
|
372
|
+
from: "themes.default",
|
|
373
|
+
get: (m) => at(m, "themes", "default"),
|
|
374
|
+
to: "themes.default",
|
|
375
|
+
read: (c) => at(c, "themes", "default"),
|
|
376
|
+
write: (c, v) => {
|
|
377
|
+
child(c, "themes")["default"] = v;
|
|
378
|
+
},
|
|
379
|
+
paths: false
|
|
380
|
+
},
|
|
381
|
+
{
|
|
382
|
+
from: "themes.switch",
|
|
383
|
+
get: (m) => at(m, "themes", "switch"),
|
|
384
|
+
to: "themes.switch",
|
|
385
|
+
read: (c) => at(c, "themes", "switch"),
|
|
386
|
+
write: (c, v) => {
|
|
387
|
+
child(c, "themes")["switch"] = v;
|
|
388
|
+
},
|
|
389
|
+
paths: false
|
|
390
|
+
}
|
|
391
|
+
];
|
|
392
|
+
function samePath(p) {
|
|
393
|
+
const parts = [];
|
|
394
|
+
for (const seg of p.replace(/\\/g, "/").split("/")) {
|
|
395
|
+
if (seg === "" || seg === ".") continue;
|
|
396
|
+
if (seg === ".." && parts.length && parts[parts.length - 1] !== "..") parts.pop();
|
|
397
|
+
else parts.push(seg);
|
|
398
|
+
}
|
|
399
|
+
return parts.join("/") || ".";
|
|
400
|
+
}
|
|
401
|
+
function comparable(v, paths) {
|
|
402
|
+
const list = Array.isArray(v) ? v : [v];
|
|
403
|
+
return JSON.stringify(paths ? list.map((x) => typeof x === "string" ? samePath(x) : x) : Array.isArray(v) ? v : [v]);
|
|
404
|
+
}
|
|
405
|
+
var shown = (v) => JSON.stringify(v);
|
|
406
|
+
function withOwnManifest(raw, manifest, where) {
|
|
407
|
+
if (!isObject2(raw) || !isObject2(manifest)) return raw;
|
|
408
|
+
const config = structuredClone(raw);
|
|
409
|
+
const conflicts = [];
|
|
410
|
+
for (const f of FIELDS) {
|
|
411
|
+
const value = f.get(manifest);
|
|
412
|
+
if (value === void 0) continue;
|
|
413
|
+
const own = f.read(config);
|
|
414
|
+
if (own === void 0) {
|
|
415
|
+
f.write(config, structuredClone(value));
|
|
416
|
+
continue;
|
|
417
|
+
}
|
|
418
|
+
if (comparable(own, f.paths) !== comparable(value, f.paths)) {
|
|
419
|
+
conflicts.push(`"${f.to}" is ${shown(own)} in ${where.config}, but "patternDump.${f.from}" is ${shown(value)} in ${where.manifest}: name it in one of them, or give both the same value`);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
if (conflicts.length) {
|
|
423
|
+
throw new ConfigError(`${where.config}: the config and the project's own package manifest disagree
|
|
424
|
+
${conflicts.map((c) => ` - ${c}`).join("\n")}`);
|
|
425
|
+
}
|
|
426
|
+
return config;
|
|
281
427
|
}
|
|
282
428
|
|
|
283
429
|
// packages/config/src/index.ts
|
|
@@ -289,6 +435,8 @@ export {
|
|
|
289
435
|
suggest,
|
|
290
436
|
ConfigError,
|
|
291
437
|
validateConfig,
|
|
438
|
+
storefrontBasePath,
|
|
292
439
|
sameHostWorkbenchPath,
|
|
440
|
+
withOwnManifest,
|
|
293
441
|
defineConfig
|
|
294
442
|
};
|