create-stacki 0.1.61 → 0.1.64

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 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/validation.ts
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");