@wcstack/typescript 1.32.0 → 2.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.ja.md +106 -106
- package/README.md +106 -106
- package/dist/index.d.ts +34 -16
- package/dist/index.esm.js +96 -28
- package/dist/index.esm.js.map +1 -1
- package/dist/schema-core.cjs +220 -187
- package/dist/tsc-core.cjs +2 -2
- package/dist/wcs-schema.mjs +149 -47
- package/dist/wcs-schema.mjs.map +1 -1
- package/dist/wcs-tsc.mjs +1 -1
- package/dist/wcs-tsc.mjs.map +1 -1
- package/package.json +84 -84
package/dist/tsc-core.cjs
CHANGED
|
@@ -44,7 +44,7 @@ function parseWcsScriptBlocks(html, stateTagName = "wcs-state") {
|
|
|
44
44
|
pos++;
|
|
45
45
|
continue;
|
|
46
46
|
}
|
|
47
|
-
const
|
|
47
|
+
const mountPath = extractAttribute(wcsMatch.tagContent, "mount");
|
|
48
48
|
pos = wcsMatch.end;
|
|
49
49
|
const wcsCloseIdx = findCloseTag(html, pos, stateTagName);
|
|
50
50
|
const wcsEnd = wcsCloseIdx === -1 ? len : wcsCloseIdx;
|
|
@@ -76,7 +76,7 @@ function parseWcsScriptBlocks(html, stateTagName = "wcs-state") {
|
|
|
76
76
|
contentStart,
|
|
77
77
|
contentEnd,
|
|
78
78
|
content: html.slice(contentStart, contentEnd),
|
|
79
|
-
|
|
79
|
+
mountPath
|
|
80
80
|
});
|
|
81
81
|
pos = html.indexOf(">", scriptCloseIdx) + 1;
|
|
82
82
|
if (pos === 0) break;
|
package/dist/wcs-schema.mjs
CHANGED
|
@@ -354,40 +354,77 @@ function generateStateSchema(file, options = {}) {
|
|
|
354
354
|
*
|
|
355
355
|
* The manifest is a **derived artifact** (D9): the TypeScript type is the source
|
|
356
356
|
* of truth, `wcs-schema emit` writes the manifest, and `wcs-schema check`
|
|
357
|
-
* detects drift between the two in CI.
|
|
358
|
-
*
|
|
359
|
-
*
|
|
360
|
-
*
|
|
357
|
+
* detects drift between the two in CI.
|
|
358
|
+
*
|
|
359
|
+
* v2 (schemaVersion 2): the application namespace carries a **single**
|
|
360
|
+
* `stateSchema` — one state tree per root, no name dimension
|
|
361
|
+
* (docs/state-mount-design.md D15). A volume (`<wcs-state mount="path">`)
|
|
362
|
+
* contributes a **subtree**: `--mount=<path>` merges the module's schema under
|
|
363
|
+
* that path inside the single `stateSchema`. `--merge` keeps everything else in
|
|
364
|
+
* an existing manifest (filters, listContexts); a hand-written schema for the
|
|
365
|
+
* same slot does not survive, by design: there is no implicit merge in the
|
|
366
|
+
* sidecar spec (§5).
|
|
361
367
|
*/
|
|
362
368
|
const APPLICATION_MANIFEST_FILENAME = "wcstack.manifest.json";
|
|
363
|
-
const SCHEMA_VERSION =
|
|
369
|
+
const SCHEMA_VERSION = 2;
|
|
364
370
|
const APPLICATION_NAMESPACE = "wcstack.application";
|
|
365
371
|
/**
|
|
366
|
-
* Create the manifest object for
|
|
367
|
-
* (a parsed manifest object; envelope fields are
|
|
372
|
+
* Create the manifest object for the state tree, or graft into `existing`
|
|
373
|
+
* (a parsed manifest object; envelope fields are normalized to v2 — a v1
|
|
374
|
+
* manifest's `states` map does not survive, its replacement is exactly this
|
|
375
|
+
* regeneration path).
|
|
376
|
+
*
|
|
377
|
+
* `mountPath === null` replaces the whole `stateSchema`; a mount path merges
|
|
378
|
+
* the schema as a subtree at that path (intermediate object nodes are created).
|
|
368
379
|
*/
|
|
369
|
-
function buildManifest(
|
|
380
|
+
function buildManifest(mountPath, schema, existing) {
|
|
370
381
|
const base = existing !== null && typeof existing === "object" && !Array.isArray(existing)
|
|
371
382
|
? { ...existing }
|
|
372
383
|
: {};
|
|
373
|
-
base.schemaVersion =
|
|
384
|
+
base.schemaVersion = SCHEMA_VERSION;
|
|
374
385
|
base.kind = "application";
|
|
375
386
|
const extensions = base.manifestExtensions !== null && typeof base.manifestExtensions === "object" && !Array.isArray(base.manifestExtensions)
|
|
376
387
|
? { ...base.manifestExtensions }
|
|
377
388
|
: {};
|
|
378
389
|
const nsRaw = extensions[APPLICATION_NAMESPACE];
|
|
379
390
|
const ns = nsRaw !== null && typeof nsRaw === "object" && !Array.isArray(nsRaw) ? { ...nsRaw } : {};
|
|
380
|
-
ns.version =
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
391
|
+
ns.version = SCHEMA_VERSION;
|
|
392
|
+
// v1 leftovers: the name dimension is gone; regeneration does not carry it
|
|
393
|
+
delete ns.states;
|
|
394
|
+
if (mountPath === null) {
|
|
395
|
+
ns.stateSchema = schema;
|
|
396
|
+
}
|
|
397
|
+
else {
|
|
398
|
+
const rootRaw = ns.stateSchema;
|
|
399
|
+
const root = rootRaw !== null && typeof rootRaw === "object" && !Array.isArray(rootRaw)
|
|
400
|
+
? { ...rootRaw }
|
|
401
|
+
: { type: "object" };
|
|
402
|
+
let node = root;
|
|
403
|
+
const segments = mountPath.split(".");
|
|
404
|
+
for (let i = 0; i < segments.length; i++) {
|
|
405
|
+
const properties = node.properties !== null && typeof node.properties === "object" && !Array.isArray(node.properties)
|
|
406
|
+
? { ...node.properties }
|
|
407
|
+
: {};
|
|
408
|
+
node.properties = properties;
|
|
409
|
+
if (i === segments.length - 1) {
|
|
410
|
+
properties[segments[i]] = schema;
|
|
411
|
+
break;
|
|
412
|
+
}
|
|
413
|
+
const childRaw = properties[segments[i]];
|
|
414
|
+
const child = childRaw !== null && typeof childRaw === "object" && !Array.isArray(childRaw)
|
|
415
|
+
? { ...childRaw }
|
|
416
|
+
: { type: "object" };
|
|
417
|
+
properties[segments[i]] = child;
|
|
418
|
+
node = child;
|
|
419
|
+
}
|
|
420
|
+
ns.stateSchema = root;
|
|
421
|
+
}
|
|
385
422
|
extensions[APPLICATION_NAMESPACE] = ns;
|
|
386
423
|
base.manifestExtensions = extensions;
|
|
387
424
|
return base;
|
|
388
425
|
}
|
|
389
|
-
/** Read `
|
|
390
|
-
function readStateSchema(manifest
|
|
426
|
+
/** Read the single `stateSchema` from a parsed manifest object, or undefined. */
|
|
427
|
+
function readStateSchema(manifest) {
|
|
391
428
|
if (manifest === null || typeof manifest !== "object")
|
|
392
429
|
return undefined;
|
|
393
430
|
const extensions = manifest.manifestExtensions;
|
|
@@ -396,13 +433,38 @@ function readStateSchema(manifest, stateName) {
|
|
|
396
433
|
const ns = extensions[APPLICATION_NAMESPACE];
|
|
397
434
|
if (ns === null || typeof ns !== "object")
|
|
398
435
|
return undefined;
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
436
|
+
return ns.stateSchema;
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* True for a v1-shaped manifest (`schemaVersion: 1` or a `states` map in the
|
|
440
|
+
* application namespace). `check` uses it to point at the regeneration path
|
|
441
|
+
* instead of reporting a confusing "missing stateSchema".
|
|
442
|
+
*/
|
|
443
|
+
function isV1Manifest(manifest) {
|
|
444
|
+
if (manifest === null || typeof manifest !== "object")
|
|
445
|
+
return false;
|
|
446
|
+
if (manifest.schemaVersion === 1)
|
|
447
|
+
return true;
|
|
448
|
+
const extensions = manifest.manifestExtensions;
|
|
449
|
+
if (extensions === null || typeof extensions !== "object")
|
|
450
|
+
return false;
|
|
451
|
+
const ns = extensions[APPLICATION_NAMESPACE];
|
|
452
|
+
if (ns === null || typeof ns !== "object")
|
|
453
|
+
return false;
|
|
454
|
+
return ns.states !== undefined;
|
|
455
|
+
}
|
|
456
|
+
/** Navigate `properties` by mount-path segments; undefined when the subtree is absent. */
|
|
457
|
+
function subtreeAt(schema, mountPath) {
|
|
458
|
+
let node = schema;
|
|
459
|
+
for (const segment of mountPath.split(".")) {
|
|
460
|
+
if (node === null || typeof node !== "object")
|
|
461
|
+
return undefined;
|
|
462
|
+
const properties = node.properties;
|
|
463
|
+
if (properties === null || typeof properties !== "object")
|
|
464
|
+
return undefined;
|
|
465
|
+
node = properties[segment];
|
|
466
|
+
}
|
|
467
|
+
return node;
|
|
406
468
|
}
|
|
407
469
|
/** JSON with object keys sorted at every level — the canonical form used for comparison. */
|
|
408
470
|
function stableStringify(value) {
|
|
@@ -421,11 +483,12 @@ function sortKeys(value) {
|
|
|
421
483
|
return value;
|
|
422
484
|
}
|
|
423
485
|
/**
|
|
424
|
-
* Compare the schema generated from the type with the one stored in `manifestText
|
|
486
|
+
* Compare the schema generated from the type with the one stored in `manifestText`
|
|
487
|
+
* (the whole tree, or the subtree at `mountPath`).
|
|
425
488
|
* `changes` lists JSON pointers: `+ ptr` (only in generated), `- ptr` (only in
|
|
426
489
|
* manifest), `~ ptr` (both, different value).
|
|
427
490
|
*/
|
|
428
|
-
function compareStateSchema(manifestText,
|
|
491
|
+
function compareStateSchema(manifestText, mountPath, generated) {
|
|
429
492
|
let parsed;
|
|
430
493
|
try {
|
|
431
494
|
parsed = JSON.parse(manifestText);
|
|
@@ -433,7 +496,12 @@ function compareStateSchema(manifestText, stateName, generated) {
|
|
|
433
496
|
catch (e) {
|
|
434
497
|
return { kind: "broken", message: e.message };
|
|
435
498
|
}
|
|
436
|
-
|
|
499
|
+
if (isV1Manifest(parsed))
|
|
500
|
+
return { kind: "v1-manifest" };
|
|
501
|
+
let stored = readStateSchema(parsed);
|
|
502
|
+
if (stored !== undefined && mountPath !== null) {
|
|
503
|
+
stored = subtreeAt(stored, mountPath);
|
|
504
|
+
}
|
|
437
505
|
if (stored === undefined)
|
|
438
506
|
return { kind: "missing-state" };
|
|
439
507
|
if (stableStringify(stored) === stableStringify(generated))
|
|
@@ -499,7 +567,7 @@ function loadSchemaCore() {
|
|
|
499
567
|
return cached;
|
|
500
568
|
}
|
|
501
569
|
|
|
502
|
-
var version = "
|
|
570
|
+
var version = "2.0.0";
|
|
503
571
|
var pkg = {
|
|
504
572
|
version: version};
|
|
505
573
|
|
|
@@ -508,26 +576,28 @@ const VERSION = pkg.version;
|
|
|
508
576
|
/**
|
|
509
577
|
* wcsSchema.ts — the `wcs-schema` command.
|
|
510
578
|
*
|
|
511
|
-
* wcs-schema emit <state.ts|state.js> [--
|
|
512
|
-
* wcs-schema check <state.ts|state.js> [--
|
|
579
|
+
* wcs-schema emit <state.ts|state.js> [--mount=<path>] [--out=wcstack.manifest.json] [--merge] [--tsconfig=<path>] [--max-depth=5]
|
|
580
|
+
* wcs-schema check <state.ts|state.js> [--mount=<path>] [--manifest=wcstack.manifest.json] [--tsconfig=<path>] [--max-depth=5]
|
|
513
581
|
*
|
|
514
582
|
* exit codes
|
|
515
583
|
* emit : 0 written / 2 usage, unreadable file, syntax error, or the generated manifest failed its self-check
|
|
516
584
|
* check: 0 manifest matches the type / 1 drift (changes listed on stderr) / 2 usage, unreadable file, broken or state-less manifest
|
|
517
585
|
*
|
|
518
586
|
* `--out=-` prints the manifest to stdout instead of writing a file. `--merge`
|
|
519
|
-
* keeps everything in an existing manifest except
|
|
587
|
+
* keeps everything in an existing manifest except the slot being written —
|
|
588
|
+
* the whole `stateSchema`, or the subtree at `--mount=<path>` (a volume's type
|
|
589
|
+
* merges as a subtree of the single tree, docs/state-mount-design.md D15).
|
|
520
590
|
*
|
|
521
591
|
* The generated artifact is always run through the validator core's
|
|
522
592
|
* `validateManifestArtifact` (dist/schema-core.cjs, built from vscode-wcs) so a
|
|
523
593
|
* generator bug can never write a manifest the validator would reject.
|
|
524
594
|
*/
|
|
525
|
-
const USAGE = "usage: wcs-schema emit <state.ts|state.js> [--
|
|
526
|
-
" wcs-schema check <state.ts|state.js> [--
|
|
595
|
+
const USAGE = "usage: wcs-schema emit <state.ts|state.js> [--mount=<path>] [--out=wcstack.manifest.json] [--merge] [--tsconfig=<path>] [--max-depth=5]\n" +
|
|
596
|
+
" wcs-schema check <state.ts|state.js> [--mount=<path>] [--manifest=wcstack.manifest.json] [--tsconfig=<path>] [--max-depth=5]\n";
|
|
527
597
|
function parseArgs(argv) {
|
|
528
598
|
let command;
|
|
529
599
|
let file;
|
|
530
|
-
let
|
|
600
|
+
let mount = null;
|
|
531
601
|
let out = APPLICATION_MANIFEST_FILENAME;
|
|
532
602
|
let manifest = APPLICATION_MANIFEST_FILENAME;
|
|
533
603
|
let merge = false;
|
|
@@ -539,8 +609,10 @@ function parseArgs(argv) {
|
|
|
539
609
|
command ??= "version";
|
|
540
610
|
else if (arg === "--help" || arg === "-h")
|
|
541
611
|
command ??= "help";
|
|
612
|
+
else if (arg.startsWith("--mount="))
|
|
613
|
+
mount = arg.slice("--mount=".length);
|
|
542
614
|
else if (arg.startsWith("--state="))
|
|
543
|
-
|
|
615
|
+
unknown.push(arg); // v2: 名前次元は撤去 — --mount=<path> へ
|
|
544
616
|
else if (arg.startsWith("--out="))
|
|
545
617
|
out = arg.slice("--out=".length);
|
|
546
618
|
else if (arg.startsWith("--manifest="))
|
|
@@ -560,7 +632,7 @@ function parseArgs(argv) {
|
|
|
560
632
|
else
|
|
561
633
|
unknown.push(arg);
|
|
562
634
|
}
|
|
563
|
-
return { command, file,
|
|
635
|
+
return { command, file, mount, out, manifest, merge, tsconfig, maxDepth, unknown };
|
|
564
636
|
}
|
|
565
637
|
function main(argv, io = defaultIo()) {
|
|
566
638
|
const args = parseArgs(argv);
|
|
@@ -573,8 +645,12 @@ function main(argv, io = defaultIo()) {
|
|
|
573
645
|
return 0;
|
|
574
646
|
}
|
|
575
647
|
if (args.command === undefined || args.file === undefined || args.unknown.length > 0) {
|
|
576
|
-
if (args.unknown.
|
|
648
|
+
if (args.unknown.some((arg) => arg.startsWith("--state="))) {
|
|
649
|
+
io.stderr('--state was removed in v2 — there is a single state tree. Use --mount=<path> for a volume, or no flag for the root tree.\n');
|
|
650
|
+
}
|
|
651
|
+
else if (args.unknown.length > 0) {
|
|
577
652
|
io.stderr(`unknown argument(s): ${args.unknown.join(" ")}\n`);
|
|
653
|
+
}
|
|
578
654
|
io.stderr(USAGE);
|
|
579
655
|
return 2;
|
|
580
656
|
}
|
|
@@ -582,9 +658,15 @@ function main(argv, io = defaultIo()) {
|
|
|
582
658
|
io.stderr("--max-depth must be a positive integer\n");
|
|
583
659
|
return 2;
|
|
584
660
|
}
|
|
585
|
-
if (
|
|
586
|
-
|
|
587
|
-
|
|
661
|
+
if (args.mount !== null) {
|
|
662
|
+
// runtime(state の validateVolumeMountPath)と同条件: 空セグメント・ワイルドカードと
|
|
663
|
+
// 予約文字($ # @ — 位置を問わず)を拒否し、数字先頭などはランタイム同様に受理する。
|
|
664
|
+
// 旧 regex は `$` 先頭を受理し数字先頭を拒否していて runtime と両方向にずれていた
|
|
665
|
+
const problem = findMountPathProblem(args.mount);
|
|
666
|
+
if (problem !== null) {
|
|
667
|
+
io.stderr(`--mount ${problem} (got "${args.mount}")\n`);
|
|
668
|
+
return 2;
|
|
669
|
+
}
|
|
588
670
|
}
|
|
589
671
|
let generated;
|
|
590
672
|
try {
|
|
@@ -598,11 +680,25 @@ function main(argv, io = defaultIo()) {
|
|
|
598
680
|
io.stderr(`warning: ${warning}\n`);
|
|
599
681
|
return args.command === "emit" ? emit(args, generated.schema, io) : check(args, generated.schema, io);
|
|
600
682
|
}
|
|
683
|
+
/** runtime(validateVolumeMountPath)が raise する形なら理由を返す(妥当なら null)。 */
|
|
684
|
+
function findMountPathProblem(mountPath) {
|
|
685
|
+
if (mountPath.length === 0)
|
|
686
|
+
return "requires a non-empty tree path";
|
|
687
|
+
for (const segment of mountPath.split(".")) {
|
|
688
|
+
if (segment.length === 0)
|
|
689
|
+
return "path has an empty segment";
|
|
690
|
+
if (segment === "*")
|
|
691
|
+
return "path must be static (wildcards are not allowed)";
|
|
692
|
+
if (/[$#@]/.test(segment))
|
|
693
|
+
return "path must not use reserved characters ($, #, @)";
|
|
694
|
+
}
|
|
695
|
+
return null;
|
|
696
|
+
}
|
|
601
697
|
function emit(args, schema, io) {
|
|
602
698
|
const toStdout = args.out === "-";
|
|
603
699
|
const outPath = toStdout ? undefined : resolve(io.cwd(), args.out);
|
|
604
700
|
let existing;
|
|
605
|
-
if (args.merge && outPath !== undefined && existsSync(outPath)) {
|
|
701
|
+
if ((args.merge || args.mount !== null) && outPath !== undefined && existsSync(outPath)) {
|
|
606
702
|
try {
|
|
607
703
|
existing = JSON.parse(readFileSync(outPath, "utf8"));
|
|
608
704
|
}
|
|
@@ -611,7 +707,7 @@ function emit(args, schema, io) {
|
|
|
611
707
|
return 2;
|
|
612
708
|
}
|
|
613
709
|
}
|
|
614
|
-
const manifest = buildManifest(args.
|
|
710
|
+
const manifest = buildManifest(args.mount, schema, existing);
|
|
615
711
|
const text = `${JSON.stringify(manifest, null, 2)}\n`;
|
|
616
712
|
// Self-check: the validator must accept what the generator wrote.
|
|
617
713
|
const core = loadSchemaCore();
|
|
@@ -631,7 +727,7 @@ function emit(args, schema, io) {
|
|
|
631
727
|
return 0;
|
|
632
728
|
}
|
|
633
729
|
writeFileSync(outPath, text, "utf8");
|
|
634
|
-
io.stderr(`wrote ${args.out} (state "${args.
|
|
730
|
+
io.stderr(`wrote ${args.out} (${args.mount === null ? "state tree" : `mount "${args.mount}"`})\n`);
|
|
635
731
|
return 0;
|
|
636
732
|
}
|
|
637
733
|
function check(args, schema, io) {
|
|
@@ -640,22 +736,28 @@ function check(args, schema, io) {
|
|
|
640
736
|
io.stderr(`cannot read ${args.manifest}: no such file (run \`wcs-schema emit\` first)\n`);
|
|
641
737
|
return 2;
|
|
642
738
|
}
|
|
643
|
-
const
|
|
739
|
+
const slot = args.mount === null ? "state tree" : `mount "${args.mount}"`;
|
|
740
|
+
// 誘導コマンドは check と同じスロットを指すこと(--mount 抜きだとルートを差し替えてしまう)
|
|
741
|
+
const mountFlag = args.mount === null ? "" : ` --mount=${args.mount}`;
|
|
742
|
+
const comparison = compareStateSchema(readFileSync(manifestPath, "utf8"), args.mount, schema);
|
|
644
743
|
switch (comparison.kind) {
|
|
645
744
|
case "same":
|
|
646
|
-
io.stderr(`${args.manifest}:
|
|
745
|
+
io.stderr(`${args.manifest}: ${slot} is up to date\n`);
|
|
647
746
|
return 0;
|
|
648
747
|
case "missing-state":
|
|
649
|
-
io.stderr(`${args.manifest}:
|
|
748
|
+
io.stderr(`${args.manifest}: ${slot} has no stateSchema (run \`wcs-schema emit --merge${mountFlag}\`)\n`);
|
|
749
|
+
return 2;
|
|
750
|
+
case "v1-manifest":
|
|
751
|
+
io.stderr(`${args.manifest}: schemaVersion 1 manifest (states[name]) — the name dimension was removed in v2. Regenerate with \`wcs-schema emit --out=${args.manifest}\` (single stateSchema, schemaVersion 2)\n`);
|
|
650
752
|
return 2;
|
|
651
753
|
case "broken":
|
|
652
754
|
io.stderr(`${args.manifest}: broken JSON: ${comparison.message}\n`);
|
|
653
755
|
return 2;
|
|
654
756
|
case "differs":
|
|
655
|
-
io.stderr(`${args.manifest}:
|
|
757
|
+
io.stderr(`${args.manifest}: ${slot} is out of date (${comparison.changes.length} change(s)):\n`);
|
|
656
758
|
for (const change of comparison.changes)
|
|
657
759
|
io.stderr(` ${change}\n`);
|
|
658
|
-
io.stderr(`run \`wcs-schema emit --merge --out=${args.manifest}\` to update it\n`);
|
|
760
|
+
io.stderr(`run \`wcs-schema emit --merge${mountFlag} --out=${args.manifest}\` to update it\n`);
|
|
659
761
|
return 1;
|
|
660
762
|
}
|
|
661
763
|
}
|