create-stacki 0.1.61 → 0.1.65
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/dist/cli.mjs +250 -1
- package/dist/stacki-studio.json +1 -1
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -133,6 +133,12 @@ function toRecord(input) {
|
|
|
133
133
|
}
|
|
134
134
|
return input;
|
|
135
135
|
}
|
|
136
|
+
function toArray(input) {
|
|
137
|
+
if (!Array.isArray(input)) {
|
|
138
|
+
return void 0;
|
|
139
|
+
}
|
|
140
|
+
return input;
|
|
141
|
+
}
|
|
136
142
|
|
|
137
143
|
// studio/vendor/stacki/shared/core/boundary.ts
|
|
138
144
|
var BOUNDARY_LIMITS = {
|
|
@@ -141,6 +147,36 @@ var BOUNDARY_LIMITS = {
|
|
|
141
147
|
itemsMax: LIMITS.ipcItemsMax,
|
|
142
148
|
depthMax: LIMITS.ipcDepthMax
|
|
143
149
|
};
|
|
150
|
+
function text(input) {
|
|
151
|
+
if (typeof input !== "string") {
|
|
152
|
+
throw new Error("Expected string");
|
|
153
|
+
}
|
|
154
|
+
if (input.length > BOUNDARY_LIMITS.textLengthMax) {
|
|
155
|
+
throw new Error("String exceeds limit");
|
|
156
|
+
}
|
|
157
|
+
return input;
|
|
158
|
+
}
|
|
159
|
+
function boolean(input) {
|
|
160
|
+
if (typeof input !== "boolean") {
|
|
161
|
+
throw new Error("Expected boolean");
|
|
162
|
+
}
|
|
163
|
+
return input;
|
|
164
|
+
}
|
|
165
|
+
function optional(parse) {
|
|
166
|
+
return (input) => input === void 0 ? void 0 : parse(input);
|
|
167
|
+
}
|
|
168
|
+
function list(parse) {
|
|
169
|
+
return (input) => {
|
|
170
|
+
const values = toArray(input);
|
|
171
|
+
if (!values) {
|
|
172
|
+
throw new Error("Expected array");
|
|
173
|
+
}
|
|
174
|
+
if (values.length > BOUNDARY_LIMITS.itemsMax) {
|
|
175
|
+
throw new Error("Array exceeds limit");
|
|
176
|
+
}
|
|
177
|
+
return values.map(parse);
|
|
178
|
+
};
|
|
179
|
+
}
|
|
144
180
|
function record(input) {
|
|
145
181
|
const value = toRecord(input);
|
|
146
182
|
if (!value) {
|
|
@@ -151,6 +187,19 @@ function record(input) {
|
|
|
151
187
|
}
|
|
152
188
|
return value;
|
|
153
189
|
}
|
|
190
|
+
function object(shape) {
|
|
191
|
+
return (input) => {
|
|
192
|
+
const source = record(input);
|
|
193
|
+
const result = {};
|
|
194
|
+
for (const [key, parse] of Object.entries(shape)) {
|
|
195
|
+
const value = parse(source[key]);
|
|
196
|
+
if (value !== void 0) {
|
|
197
|
+
result[key] = value;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return result;
|
|
201
|
+
};
|
|
202
|
+
}
|
|
154
203
|
|
|
155
204
|
// studio/vendor/stacki/shared/ipc/studioValidation.ts
|
|
156
205
|
function string(value, maximum = 1e6) {
|
|
@@ -384,7 +433,7 @@ function requireSupportedNode(version) {
|
|
|
384
433
|
import { lstat as lstat2, open as open2, realpath } from "node:fs/promises";
|
|
385
434
|
import path2 from "node:path";
|
|
386
435
|
|
|
387
|
-
// studio/src/shared/
|
|
436
|
+
// studio/src/shared/primitiveValidation.ts
|
|
388
437
|
var MAX_SOURCE = 1e6;
|
|
389
438
|
function record2(value) {
|
|
390
439
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
@@ -399,6 +448,206 @@ function string2(value, maximum = MAX_SOURCE) {
|
|
|
399
448
|
return value;
|
|
400
449
|
}
|
|
401
450
|
|
|
451
|
+
// studio/vendor/stacki/shared/core/assert.ts
|
|
452
|
+
function assert(condition, message) {
|
|
453
|
+
if (!condition) {
|
|
454
|
+
throw new Error(`Assertion failed: ${message}`);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// studio/vendor/stacki/shared/properties/componentDefaults.ts
|
|
459
|
+
var DEFAULT_LIMITS = {
|
|
460
|
+
componentsMax: 512,
|
|
461
|
+
childrenMax: 32,
|
|
462
|
+
depthMax: 12,
|
|
463
|
+
nodesMax: 256,
|
|
464
|
+
propsMax: 64,
|
|
465
|
+
textCharsMax: 16384,
|
|
466
|
+
sourceCharsMax: 262144,
|
|
467
|
+
storedNodesMax: 4096
|
|
468
|
+
};
|
|
469
|
+
function componentFile(input) {
|
|
470
|
+
const value = text(input);
|
|
471
|
+
if (value.length > 500 || !/^src\/[\w./ -]+\.astro$/.test(value)) {
|
|
472
|
+
throw new Error("Component defaults: expected a project-relative .astro path under src/");
|
|
473
|
+
}
|
|
474
|
+
if (value.split("/").some((part) => part.startsWith(".") || part === "")) {
|
|
475
|
+
throw new Error("Component defaults: invalid path segment");
|
|
476
|
+
}
|
|
477
|
+
return value;
|
|
478
|
+
}
|
|
479
|
+
function parseComponentDefaults(input) {
|
|
480
|
+
const value = record(input);
|
|
481
|
+
if (value["version"] !== 1) {
|
|
482
|
+
throw new Error("Component defaults: unsupported version");
|
|
483
|
+
}
|
|
484
|
+
const entries = Object.entries(record(value["components"]));
|
|
485
|
+
if (entries.length > DEFAULT_LIMITS.componentsMax) {
|
|
486
|
+
throw new Error("Component defaults: too many components");
|
|
487
|
+
}
|
|
488
|
+
let nodes = 0;
|
|
489
|
+
const takeNode = () => {
|
|
490
|
+
nodes += 1;
|
|
491
|
+
if (nodes > DEFAULT_LIMITS.storedNodesMax) {
|
|
492
|
+
throw new Error("Component defaults exceed the stored node limit");
|
|
493
|
+
}
|
|
494
|
+
};
|
|
495
|
+
const components = Object.fromEntries(
|
|
496
|
+
entries.map(([file, recipe]) => [componentFile(file), parseRecipe(recipe, 0, takeNode)])
|
|
497
|
+
);
|
|
498
|
+
assert(Object.keys(components).length === entries.length, "Recipe parsing preserves every owner");
|
|
499
|
+
assert(nodes <= DEFAULT_LIMITS.storedNodesMax, "Parsed recipes fit the total node budget");
|
|
500
|
+
return { version: 1, components };
|
|
501
|
+
}
|
|
502
|
+
var parseDefaultComponent = object({
|
|
503
|
+
file: componentFile,
|
|
504
|
+
name: text,
|
|
505
|
+
props: list(text),
|
|
506
|
+
slots: list(text),
|
|
507
|
+
slotText: boolean
|
|
508
|
+
});
|
|
509
|
+
var parseDefaultsSnapshot = object({
|
|
510
|
+
source: optional(text),
|
|
511
|
+
config: parseComponentDefaults,
|
|
512
|
+
catalog: list(parseDefaultComponent)
|
|
513
|
+
});
|
|
514
|
+
var parseDefaultsSave = object({
|
|
515
|
+
source: optional(text),
|
|
516
|
+
config: parseComponentDefaults
|
|
517
|
+
});
|
|
518
|
+
function defaultValue(input) {
|
|
519
|
+
if (typeof input === "boolean") {
|
|
520
|
+
return input;
|
|
521
|
+
}
|
|
522
|
+
if (typeof input === "number") {
|
|
523
|
+
if (!Number.isFinite(input)) {
|
|
524
|
+
throw new Error("Default number must be finite");
|
|
525
|
+
}
|
|
526
|
+
return input;
|
|
527
|
+
}
|
|
528
|
+
const value = text(input);
|
|
529
|
+
if (value.length > DEFAULT_LIMITS.textCharsMax) {
|
|
530
|
+
throw new Error("Default text exceeds 16 KiB");
|
|
531
|
+
}
|
|
532
|
+
return value;
|
|
533
|
+
}
|
|
534
|
+
function parseRecipe(input, depth, takeNode) {
|
|
535
|
+
takeNode();
|
|
536
|
+
if (depth > DEFAULT_LIMITS.depthMax) {
|
|
537
|
+
throw new Error("Component defaults exceed the nesting limit");
|
|
538
|
+
}
|
|
539
|
+
const value = record(input);
|
|
540
|
+
const props = value["props"];
|
|
541
|
+
const content = value["text"];
|
|
542
|
+
const children = value["children"];
|
|
543
|
+
if (content !== void 0 && children !== void 0) {
|
|
544
|
+
throw new Error("Choose text or children for a recipe, not both");
|
|
545
|
+
}
|
|
546
|
+
return {
|
|
547
|
+
...props === void 0 ? {} : { props: parseRecipeProps(props) },
|
|
548
|
+
...content === void 0 ? {} : { text: parseRecipeText(content) },
|
|
549
|
+
...children === void 0 ? {} : { children: parseRecipeChildren(children, depth, takeNode) }
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
function parseRecipeText(input) {
|
|
553
|
+
const value = defaultValue(input);
|
|
554
|
+
if (typeof value !== "string") {
|
|
555
|
+
throw new Error("Default text must be a string");
|
|
556
|
+
}
|
|
557
|
+
return value;
|
|
558
|
+
}
|
|
559
|
+
function parseRecipeProps(input) {
|
|
560
|
+
const entries = Object.entries(record(input));
|
|
561
|
+
if (entries.length > DEFAULT_LIMITS.propsMax) {
|
|
562
|
+
throw new Error("Too many default props");
|
|
563
|
+
}
|
|
564
|
+
return Object.fromEntries(
|
|
565
|
+
entries.map(([name, value]) => {
|
|
566
|
+
if (!/^[A-Za-z_][\w:-]{0,99}$/.test(name)) {
|
|
567
|
+
throw new Error("Invalid default prop name");
|
|
568
|
+
}
|
|
569
|
+
return [name, defaultValue(value)];
|
|
570
|
+
})
|
|
571
|
+
);
|
|
572
|
+
}
|
|
573
|
+
function parseRecipeChildren(input, depth, takeNode) {
|
|
574
|
+
const values = list(record)(input);
|
|
575
|
+
if (values.length > DEFAULT_LIMITS.childrenMax) {
|
|
576
|
+
throw new Error("Too many default children");
|
|
577
|
+
}
|
|
578
|
+
return values.map((value) => {
|
|
579
|
+
if (depth + 1 > DEFAULT_LIMITS.depthMax) {
|
|
580
|
+
throw new Error("Default children exceed the depth limit");
|
|
581
|
+
}
|
|
582
|
+
const kinds = ["literal", "element", "component"].filter((key) => value[key] !== void 0);
|
|
583
|
+
if (kinds.length !== 1) {
|
|
584
|
+
throw new Error("A default child must name exactly one component, element, or literal");
|
|
585
|
+
}
|
|
586
|
+
if (value["literal"] !== void 0) {
|
|
587
|
+
takeNode();
|
|
588
|
+
return { literal: parseRecipeText(value["literal"]) };
|
|
589
|
+
}
|
|
590
|
+
const recipe = parseRecipe(value, depth + 1, takeNode);
|
|
591
|
+
if (value["element"] !== void 0) {
|
|
592
|
+
const element = text(value["element"]);
|
|
593
|
+
if (!/^[a-z][a-z0-9-]{0,99}$/.test(element)) {
|
|
594
|
+
throw new Error("Invalid default HTML element");
|
|
595
|
+
}
|
|
596
|
+
return { element, ...recipe };
|
|
597
|
+
}
|
|
598
|
+
if (value["inherit"] !== void 0 && value["inherit"] !== false) {
|
|
599
|
+
throw new Error("Captured child defaults use inherit: false");
|
|
600
|
+
}
|
|
601
|
+
return {
|
|
602
|
+
component: componentFile(value["component"]),
|
|
603
|
+
...recipe,
|
|
604
|
+
...value["inherit"] === false ? { inherit: false } : {}
|
|
605
|
+
};
|
|
606
|
+
});
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
// studio/vendor/stacki/shared/properties/projectMetadata.ts
|
|
610
|
+
function metadataKind(input) {
|
|
611
|
+
if (input === "components" || input === "guide" || input === "publish") {
|
|
612
|
+
return input;
|
|
613
|
+
}
|
|
614
|
+
throw new Error("Unknown project metadata file");
|
|
615
|
+
}
|
|
616
|
+
function metadataSource(input) {
|
|
617
|
+
const source = text(input);
|
|
618
|
+
if (source.length > DEFAULT_LIMITS.sourceCharsMax) {
|
|
619
|
+
throw new Error("Project metadata exceeds 256 KiB");
|
|
620
|
+
}
|
|
621
|
+
return source;
|
|
622
|
+
}
|
|
623
|
+
var parseMetadataFile = object({ source: optional(metadataSource) });
|
|
624
|
+
var parseMetadataWrite = object({
|
|
625
|
+
kind: metadataKind,
|
|
626
|
+
source: metadataSource,
|
|
627
|
+
base: optional(metadataSource)
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
// studio/vendor/stacki/shared/properties/defaultsCanvas.ts
|
|
631
|
+
function isDefaultsCanvas(file) {
|
|
632
|
+
return /^\.stacki\/canvas\/[a-f0-9-]{36}\.astro$/.test(file);
|
|
633
|
+
}
|
|
634
|
+
function defaultsCanvasFile(input) {
|
|
635
|
+
const file = text(input);
|
|
636
|
+
if (!isDefaultsCanvas(file)) {
|
|
637
|
+
throw new Error("Invalid component defaults canvas path");
|
|
638
|
+
}
|
|
639
|
+
return file;
|
|
640
|
+
}
|
|
641
|
+
function defaultsCanvasSource(input) {
|
|
642
|
+
const source = text(input);
|
|
643
|
+
if (source.length > DEFAULT_LIMITS.sourceCharsMax) {
|
|
644
|
+
throw new Error("Component defaults canvas exceeds 256 KiB");
|
|
645
|
+
}
|
|
646
|
+
return source;
|
|
647
|
+
}
|
|
648
|
+
var parseCanvasCreate = object({ file: defaultsCanvasFile, source: defaultsCanvasSource });
|
|
649
|
+
var parseCanvasFile = object({ file: defaultsCanvasFile });
|
|
650
|
+
|
|
402
651
|
// studio/scripts/createStacki/project.ts
|
|
403
652
|
async function readJson(file, maximum) {
|
|
404
653
|
const handle = await open2(file, "r");
|