@neocompose/cli 0.24.0 → 0.24.1
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 +17 -0
- package/dist/neo.mjs +296 -143
- package/package.json +8 -2
- package/skills/neocompose-cli/SKILL.md +6 -2
- package/skills/neocompose-cli/references/animation-and-world-authoring.md +43 -18
- package/skills/neocompose-cli/references/cli-development.md +5 -3
- package/skills/neocompose-cli/references/values-identities-and-references.md +5 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.24.1] - 2026-08-07
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- Allow new idless rows and explicitly identified UUID rows inside existing
|
|
8
|
+
row-backed member-default lists. Changed ancestors and pending children now
|
|
9
|
+
reach prospective animation validation and trusted server materialization,
|
|
10
|
+
which preserves existing rows and creates only genuinely new children.
|
|
11
|
+
- Keep project constructor declarations in the `neo test` compiler context so
|
|
12
|
+
named calls to required constructors compile instead of falling back to a
|
|
13
|
+
schema-derived constructor.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- Update the bundled CLI skill for P60 selector targeting, project-owned stable
|
|
18
|
+
selector identity, and frugal `@id` authoring for new declarations and rows.
|
|
19
|
+
|
|
3
20
|
## [0.24.0] - 2026-08-06
|
|
4
21
|
|
|
5
22
|
### Added
|
package/dist/neo.mjs
CHANGED
|
@@ -685,12 +685,13 @@ var init_args = __esm({
|
|
|
685
685
|
}
|
|
686
686
|
});
|
|
687
687
|
|
|
688
|
-
//
|
|
688
|
+
// node_modules/ignore/index.js
|
|
689
689
|
var require_ignore = __commonJS({
|
|
690
|
-
"
|
|
690
|
+
"node_modules/ignore/index.js"(exports, module) {
|
|
691
691
|
function makeArray(subject) {
|
|
692
692
|
return Array.isArray(subject) ? subject : [subject];
|
|
693
693
|
}
|
|
694
|
+
var UNDEFINED = void 0;
|
|
694
695
|
var EMPTY = "";
|
|
695
696
|
var SPACE = " ";
|
|
696
697
|
var ESCAPE = "\\";
|
|
@@ -699,27 +700,32 @@ var require_ignore = __commonJS({
|
|
|
699
700
|
var REGEX_REPLACE_LEADING_EXCAPED_EXCLAMATION = /^\\!/;
|
|
700
701
|
var REGEX_REPLACE_LEADING_EXCAPED_HASH = /^\\#/;
|
|
701
702
|
var REGEX_SPLITALL_CRLF = /\r?\n/g;
|
|
702
|
-
var REGEX_TEST_INVALID_PATH =
|
|
703
|
+
var REGEX_TEST_INVALID_PATH = /^\.{0,2}\/|^\.{1,2}$/;
|
|
704
|
+
var REGEX_TEST_TRAILING_SLASH = /\/$/;
|
|
703
705
|
var SLASH = "/";
|
|
704
706
|
var TMP_KEY_IGNORE = "node-ignore";
|
|
705
707
|
if (typeof Symbol !== "undefined") {
|
|
706
708
|
TMP_KEY_IGNORE = /* @__PURE__ */ Symbol.for("node-ignore");
|
|
707
709
|
}
|
|
708
710
|
var KEY_IGNORE = TMP_KEY_IGNORE;
|
|
709
|
-
var define = (object2, key, value) =>
|
|
711
|
+
var define = (object2, key, value) => {
|
|
712
|
+
Object.defineProperty(object2, key, { value });
|
|
713
|
+
return value;
|
|
714
|
+
};
|
|
710
715
|
var REGEX_REGEXP_RANGE = /([0-z])-([0-z])/g;
|
|
711
716
|
var RETURN_FALSE = () => false;
|
|
712
717
|
var sanitizeRange = (range2) => range2.replace(
|
|
713
718
|
REGEX_REGEXP_RANGE,
|
|
714
719
|
(match, from, to) => from.charCodeAt(0) <= to.charCodeAt(0) ? match : EMPTY
|
|
715
720
|
);
|
|
721
|
+
var negateRange = (range2) => range2.startsWith("!") || range2.startsWith("\\^") ? `^${range2.slice(range2[0] === "!" ? 1 : 2)}` : range2;
|
|
716
722
|
var cleanRangeBackSlash = (slashes) => {
|
|
717
723
|
const { length } = slashes;
|
|
718
724
|
return slashes.slice(0, length - length % 2);
|
|
719
725
|
};
|
|
720
726
|
var REPLACERS = [
|
|
721
727
|
[
|
|
722
|
-
//
|
|
728
|
+
// Remove BOM
|
|
723
729
|
// TODO:
|
|
724
730
|
// Other similar zero-width characters?
|
|
725
731
|
/^\uFEFF/,
|
|
@@ -734,7 +740,7 @@ var require_ignore = __commonJS({
|
|
|
734
740
|
/((?:\\\\)*?)(\\?\s+)$/,
|
|
735
741
|
(_, m1, m2) => m1 + (m2.indexOf("\\") === 0 ? SPACE : EMPTY)
|
|
736
742
|
],
|
|
737
|
-
//
|
|
743
|
+
// Replace (\ ) with ' '
|
|
738
744
|
// (\ ) -> ' '
|
|
739
745
|
// (\\ ) -> '\\ '
|
|
740
746
|
// (\\\ ) -> '\\ '
|
|
@@ -790,7 +796,7 @@ var require_ignore = __commonJS({
|
|
|
790
796
|
// > "**/foo/bar" matches file or directory "bar" anywhere that is directly
|
|
791
797
|
// > under directory "foo".
|
|
792
798
|
// Notice that the '*'s have been replaced as '\\*'
|
|
793
|
-
|
|
799
|
+
/^\^*(?:\\\*\\\*\\\/)+/,
|
|
794
800
|
// '**/foo' <-> 'foo'
|
|
795
801
|
() => "^(?:.*\\/)?"
|
|
796
802
|
],
|
|
@@ -845,7 +851,7 @@ var require_ignore = __commonJS({
|
|
|
845
851
|
// > can be used to match one of the characters in a range.
|
|
846
852
|
// `\` is escaped by step 3
|
|
847
853
|
/(\\)?\[([^\]/]*?)(\\*)($|\])/g,
|
|
848
|
-
(match, leadEscape, range2, endEscape, close) => leadEscape === ESCAPE ? `\\[${range2}${cleanRangeBackSlash(endEscape)}${close}` : close === "]" ? endEscape.length % 2 === 0 ? `[${sanitizeRange(range2)}${endEscape}]` : "[]" : "[]"
|
|
854
|
+
(match, leadEscape, range2, endEscape, close) => leadEscape === ESCAPE ? `\\[${range2}${cleanRangeBackSlash(endEscape)}${close}` : close === "]" ? endEscape.length % 2 === 0 ? `[${negateRange(sanitizeRange(range2))}${endEscape}]` : "[]" : "[]"
|
|
849
855
|
],
|
|
850
856
|
// ending
|
|
851
857
|
[
|
|
@@ -863,55 +869,147 @@ var require_ignore = __commonJS({
|
|
|
863
869
|
// 'js/' will not match 'a.js'
|
|
864
870
|
// 'js' will match 'a.js' and 'a.js/'
|
|
865
871
|
(match) => /\/$/.test(match) ? `${match}$` : `${match}(?=$|\\/$)`
|
|
866
|
-
],
|
|
867
|
-
// trailing wildcard
|
|
868
|
-
[
|
|
869
|
-
/(\^|\\\/)?\\\*$/,
|
|
870
|
-
(_, p1) => {
|
|
871
|
-
const prefix = p1 ? `${p1}[^/]+` : "[^/]*";
|
|
872
|
-
return `${prefix}(?=$|\\/$)`;
|
|
873
|
-
}
|
|
874
872
|
]
|
|
875
873
|
];
|
|
876
|
-
var
|
|
877
|
-
var
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
)
|
|
884
|
-
|
|
874
|
+
var REGEX_REPLACE_TRAILING_WILDCARD = /(^|\\\/)?\\\*$/;
|
|
875
|
+
var MODE_IGNORE = "regex";
|
|
876
|
+
var MODE_CHECK_IGNORE = "checkRegex";
|
|
877
|
+
var UNDERSCORE = "_";
|
|
878
|
+
var TRAILING_WILD_CARD_REPLACERS = {
|
|
879
|
+
[MODE_IGNORE](_, p1) {
|
|
880
|
+
const prefix = p1 ? `${p1}[^/]+` : "[^/]*";
|
|
881
|
+
return `${prefix}(?=$|\\/$)`;
|
|
882
|
+
},
|
|
883
|
+
[MODE_CHECK_IGNORE](_, p1) {
|
|
884
|
+
const prefix = p1 ? `${p1}[^/]*` : "[^/]*";
|
|
885
|
+
return `${prefix}(?=$|\\/$)`;
|
|
885
886
|
}
|
|
886
|
-
return ignoreCase ? new RegExp(source, "i") : new RegExp(source);
|
|
887
887
|
};
|
|
888
|
+
var makeRegexPrefix = (pattern) => REPLACERS.reduce(
|
|
889
|
+
(prev, [matcher, replacer]) => prev.replace(matcher, replacer.bind(pattern)),
|
|
890
|
+
pattern
|
|
891
|
+
);
|
|
888
892
|
var isString2 = (subject) => typeof subject === "string";
|
|
889
893
|
var checkPattern = (pattern) => pattern && isString2(pattern) && !REGEX_TEST_BLANK_LINE.test(pattern) && !REGEX_INVALID_TRAILING_BACKSLASH.test(pattern) && pattern.indexOf("#") !== 0;
|
|
890
|
-
var splitPattern = (pattern) => pattern.split(REGEX_SPLITALL_CRLF);
|
|
894
|
+
var splitPattern = (pattern) => pattern.split(REGEX_SPLITALL_CRLF).filter(Boolean);
|
|
891
895
|
var IgnoreRule = class {
|
|
892
|
-
constructor(
|
|
893
|
-
this.origin = origin;
|
|
896
|
+
constructor(pattern, mark, body, ignoreCase, negative, prefix) {
|
|
894
897
|
this.pattern = pattern;
|
|
898
|
+
this.mark = mark;
|
|
895
899
|
this.negative = negative;
|
|
896
|
-
this
|
|
900
|
+
define(this, "body", body);
|
|
901
|
+
define(this, "ignoreCase", ignoreCase);
|
|
902
|
+
define(this, "regexPrefix", prefix);
|
|
903
|
+
}
|
|
904
|
+
get regex() {
|
|
905
|
+
const key = UNDERSCORE + MODE_IGNORE;
|
|
906
|
+
if (this[key]) {
|
|
907
|
+
return this[key];
|
|
908
|
+
}
|
|
909
|
+
return this._make(MODE_IGNORE, key);
|
|
910
|
+
}
|
|
911
|
+
get checkRegex() {
|
|
912
|
+
const key = UNDERSCORE + MODE_CHECK_IGNORE;
|
|
913
|
+
if (this[key]) {
|
|
914
|
+
return this[key];
|
|
915
|
+
}
|
|
916
|
+
return this._make(MODE_CHECK_IGNORE, key);
|
|
917
|
+
}
|
|
918
|
+
_make(mode, key) {
|
|
919
|
+
const str = this.regexPrefix.replace(
|
|
920
|
+
REGEX_REPLACE_TRAILING_WILDCARD,
|
|
921
|
+
// It does not need to bind pattern
|
|
922
|
+
TRAILING_WILD_CARD_REPLACERS[mode]
|
|
923
|
+
);
|
|
924
|
+
const regex = this.ignoreCase ? new RegExp(str, "i") : new RegExp(str);
|
|
925
|
+
return define(this, key, regex);
|
|
897
926
|
}
|
|
898
927
|
};
|
|
899
|
-
var createRule = (
|
|
900
|
-
|
|
928
|
+
var createRule = ({
|
|
929
|
+
pattern,
|
|
930
|
+
mark
|
|
931
|
+
}, ignoreCase) => {
|
|
901
932
|
let negative = false;
|
|
902
|
-
|
|
933
|
+
let body = pattern;
|
|
934
|
+
if (body.indexOf("!") === 0) {
|
|
903
935
|
negative = true;
|
|
904
|
-
|
|
936
|
+
body = body.substr(1);
|
|
905
937
|
}
|
|
906
|
-
|
|
907
|
-
const
|
|
938
|
+
body = body.replace(REGEX_REPLACE_LEADING_EXCAPED_EXCLAMATION, "!").replace(REGEX_REPLACE_LEADING_EXCAPED_HASH, "#");
|
|
939
|
+
const regexPrefix = makeRegexPrefix(body);
|
|
908
940
|
return new IgnoreRule(
|
|
909
|
-
origin,
|
|
910
941
|
pattern,
|
|
942
|
+
mark,
|
|
943
|
+
body,
|
|
944
|
+
ignoreCase,
|
|
911
945
|
negative,
|
|
912
|
-
|
|
946
|
+
regexPrefix
|
|
913
947
|
);
|
|
914
948
|
};
|
|
949
|
+
var RuleManager = class {
|
|
950
|
+
constructor(ignoreCase) {
|
|
951
|
+
this._ignoreCase = ignoreCase;
|
|
952
|
+
this._rules = [];
|
|
953
|
+
}
|
|
954
|
+
_add(pattern) {
|
|
955
|
+
if (pattern && pattern[KEY_IGNORE]) {
|
|
956
|
+
this._rules = this._rules.concat(pattern._rules._rules);
|
|
957
|
+
this._added = true;
|
|
958
|
+
return;
|
|
959
|
+
}
|
|
960
|
+
if (isString2(pattern)) {
|
|
961
|
+
pattern = {
|
|
962
|
+
pattern
|
|
963
|
+
};
|
|
964
|
+
}
|
|
965
|
+
if (checkPattern(pattern.pattern)) {
|
|
966
|
+
const rule = createRule(pattern, this._ignoreCase);
|
|
967
|
+
this._added = true;
|
|
968
|
+
this._rules.push(rule);
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
// @param {Array<string> | string | Ignore} pattern
|
|
972
|
+
add(pattern) {
|
|
973
|
+
this._added = false;
|
|
974
|
+
makeArray(
|
|
975
|
+
isString2(pattern) ? splitPattern(pattern) : pattern
|
|
976
|
+
).forEach(this._add, this);
|
|
977
|
+
return this._added;
|
|
978
|
+
}
|
|
979
|
+
// Test one single path without recursively checking parent directories
|
|
980
|
+
//
|
|
981
|
+
// - checkUnignored `boolean` whether should check if the path is unignored,
|
|
982
|
+
// setting `checkUnignored` to `false` could reduce additional
|
|
983
|
+
// path matching.
|
|
984
|
+
// - check `string` either `MODE_IGNORE` or `MODE_CHECK_IGNORE`
|
|
985
|
+
// @returns {TestResult} true if a file is ignored
|
|
986
|
+
test(path, checkUnignored, mode) {
|
|
987
|
+
let ignored = false;
|
|
988
|
+
let unignored = false;
|
|
989
|
+
let matchedRule;
|
|
990
|
+
this._rules.forEach((rule) => {
|
|
991
|
+
const { negative } = rule;
|
|
992
|
+
if (unignored === negative && ignored !== unignored || negative && !ignored && !unignored && !checkUnignored) {
|
|
993
|
+
return;
|
|
994
|
+
}
|
|
995
|
+
const matched = rule[mode].test(path);
|
|
996
|
+
if (!matched) {
|
|
997
|
+
return;
|
|
998
|
+
}
|
|
999
|
+
ignored = !negative;
|
|
1000
|
+
unignored = negative;
|
|
1001
|
+
matchedRule = negative ? UNDEFINED : rule;
|
|
1002
|
+
});
|
|
1003
|
+
const ret = {
|
|
1004
|
+
ignored,
|
|
1005
|
+
unignored
|
|
1006
|
+
};
|
|
1007
|
+
if (matchedRule) {
|
|
1008
|
+
ret.rule = matchedRule;
|
|
1009
|
+
}
|
|
1010
|
+
return ret;
|
|
1011
|
+
}
|
|
1012
|
+
};
|
|
915
1013
|
var throwError = (message, Ctor) => {
|
|
916
1014
|
throw new Ctor(message);
|
|
917
1015
|
};
|
|
@@ -944,34 +1042,16 @@ var require_ignore = __commonJS({
|
|
|
944
1042
|
allowRelativePaths = false
|
|
945
1043
|
} = {}) {
|
|
946
1044
|
define(this, KEY_IGNORE, true);
|
|
947
|
-
this._rules =
|
|
948
|
-
this.
|
|
949
|
-
this._allowRelativePaths = allowRelativePaths;
|
|
1045
|
+
this._rules = new RuleManager(ignoreCase);
|
|
1046
|
+
this._strictPathCheck = !allowRelativePaths;
|
|
950
1047
|
this._initCache();
|
|
951
1048
|
}
|
|
952
1049
|
_initCache() {
|
|
953
1050
|
this._ignoreCache = /* @__PURE__ */ Object.create(null);
|
|
954
1051
|
this._testCache = /* @__PURE__ */ Object.create(null);
|
|
955
1052
|
}
|
|
956
|
-
_addPattern(pattern) {
|
|
957
|
-
if (pattern && pattern[KEY_IGNORE]) {
|
|
958
|
-
this._rules = this._rules.concat(pattern._rules);
|
|
959
|
-
this._added = true;
|
|
960
|
-
return;
|
|
961
|
-
}
|
|
962
|
-
if (checkPattern(pattern)) {
|
|
963
|
-
const rule = createRule(pattern, this._ignoreCase);
|
|
964
|
-
this._added = true;
|
|
965
|
-
this._rules.push(rule);
|
|
966
|
-
}
|
|
967
|
-
}
|
|
968
|
-
// @param {Array<string> | string | Ignore} pattern
|
|
969
1053
|
add(pattern) {
|
|
970
|
-
this.
|
|
971
|
-
makeArray(
|
|
972
|
-
isString2(pattern) ? splitPattern(pattern) : pattern
|
|
973
|
-
).forEach(this._addPattern, this);
|
|
974
|
-
if (this._added) {
|
|
1054
|
+
if (this._rules.add(pattern)) {
|
|
975
1055
|
this._initCache();
|
|
976
1056
|
}
|
|
977
1057
|
return this;
|
|
@@ -980,58 +1060,45 @@ var require_ignore = __commonJS({
|
|
|
980
1060
|
addPattern(pattern) {
|
|
981
1061
|
return this.add(pattern);
|
|
982
1062
|
}
|
|
983
|
-
// | ignored : unignored
|
|
984
|
-
// negative | 0:0 | 0:1 | 1:0 | 1:1
|
|
985
|
-
// -------- | ------- | ------- | ------- | --------
|
|
986
|
-
// 0 | TEST | TEST | SKIP | X
|
|
987
|
-
// 1 | TESTIF | SKIP | TEST | X
|
|
988
|
-
// - SKIP: always skip
|
|
989
|
-
// - TEST: always test
|
|
990
|
-
// - TESTIF: only test if checkUnignored
|
|
991
|
-
// - X: that never happen
|
|
992
|
-
// @param {boolean} whether should check if the path is unignored,
|
|
993
|
-
// setting `checkUnignored` to `false` could reduce additional
|
|
994
|
-
// path matching.
|
|
995
|
-
// @returns {TestResult} true if a file is ignored
|
|
996
|
-
_testOne(path, checkUnignored) {
|
|
997
|
-
let ignored = false;
|
|
998
|
-
let unignored = false;
|
|
999
|
-
this._rules.forEach((rule) => {
|
|
1000
|
-
const { negative } = rule;
|
|
1001
|
-
if (unignored === negative && ignored !== unignored || negative && !ignored && !unignored && !checkUnignored) {
|
|
1002
|
-
return;
|
|
1003
|
-
}
|
|
1004
|
-
const matched = rule.regex.test(path);
|
|
1005
|
-
if (matched) {
|
|
1006
|
-
ignored = !negative;
|
|
1007
|
-
unignored = negative;
|
|
1008
|
-
}
|
|
1009
|
-
});
|
|
1010
|
-
return {
|
|
1011
|
-
ignored,
|
|
1012
|
-
unignored
|
|
1013
|
-
};
|
|
1014
|
-
}
|
|
1015
1063
|
// @returns {TestResult}
|
|
1016
1064
|
_test(originalPath, cache, checkUnignored, slices) {
|
|
1017
1065
|
const path = originalPath && checkPath.convert(originalPath);
|
|
1018
1066
|
checkPath(
|
|
1019
1067
|
path,
|
|
1020
1068
|
originalPath,
|
|
1021
|
-
this.
|
|
1069
|
+
this._strictPathCheck ? throwError : RETURN_FALSE
|
|
1022
1070
|
);
|
|
1023
1071
|
return this._t(path, cache, checkUnignored, slices);
|
|
1024
1072
|
}
|
|
1073
|
+
checkIgnore(path) {
|
|
1074
|
+
if (!REGEX_TEST_TRAILING_SLASH.test(path)) {
|
|
1075
|
+
return this.test(path);
|
|
1076
|
+
}
|
|
1077
|
+
const slices = path.split(SLASH).filter(Boolean);
|
|
1078
|
+
slices.pop();
|
|
1079
|
+
if (slices.length) {
|
|
1080
|
+
const parent = this._t(
|
|
1081
|
+
slices.join(SLASH) + SLASH,
|
|
1082
|
+
this._testCache,
|
|
1083
|
+
true,
|
|
1084
|
+
slices
|
|
1085
|
+
);
|
|
1086
|
+
if (parent.ignored) {
|
|
1087
|
+
return parent;
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
return this._rules.test(path, false, MODE_CHECK_IGNORE);
|
|
1091
|
+
}
|
|
1025
1092
|
_t(path, cache, checkUnignored, slices) {
|
|
1026
1093
|
if (path in cache) {
|
|
1027
1094
|
return cache[path];
|
|
1028
1095
|
}
|
|
1029
1096
|
if (!slices) {
|
|
1030
|
-
slices = path.split(SLASH);
|
|
1097
|
+
slices = path.split(SLASH).filter(Boolean);
|
|
1031
1098
|
}
|
|
1032
1099
|
slices.pop();
|
|
1033
1100
|
if (!slices.length) {
|
|
1034
|
-
return cache[path] = this.
|
|
1101
|
+
return cache[path] = this._rules.test(path, checkUnignored, MODE_IGNORE);
|
|
1035
1102
|
}
|
|
1036
1103
|
const parent = this._t(
|
|
1037
1104
|
slices.join(SLASH) + SLASH,
|
|
@@ -1039,7 +1106,7 @@ var require_ignore = __commonJS({
|
|
|
1039
1106
|
checkUnignored,
|
|
1040
1107
|
slices
|
|
1041
1108
|
);
|
|
1042
|
-
return cache[path] = parent.ignored ? parent : this.
|
|
1109
|
+
return cache[path] = parent.ignored ? parent : this._rules.test(path, checkUnignored, MODE_IGNORE);
|
|
1043
1110
|
}
|
|
1044
1111
|
ignores(path) {
|
|
1045
1112
|
return this._test(path, this._ignoreCache, false).ignored;
|
|
@@ -1057,18 +1124,22 @@ var require_ignore = __commonJS({
|
|
|
1057
1124
|
};
|
|
1058
1125
|
var factory = (options) => new Ignore(options);
|
|
1059
1126
|
var isPathValid = (path) => checkPath(path && checkPath.convert(path), path, RETURN_FALSE);
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1127
|
+
var setupWindows = () => {
|
|
1128
|
+
const makePosix = (str) => /^\\\\\?\\/.test(str) || /["<>|\u0000-\u001F]+/u.test(str) ? str : str.replace(/\\/g, "/");
|
|
1129
|
+
checkPath.convert = makePosix;
|
|
1130
|
+
const REGEX_TEST_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i;
|
|
1131
|
+
checkPath.isNotRelative = (path) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path) || isNotRelative(path);
|
|
1132
|
+
};
|
|
1063
1133
|
if (
|
|
1064
1134
|
// Detect `process` so that it can run in browsers.
|
|
1065
|
-
typeof process !== "undefined" &&
|
|
1135
|
+
typeof process !== "undefined" && process.platform === "win32"
|
|
1066
1136
|
) {
|
|
1067
|
-
|
|
1068
|
-
checkPath.convert = makePosix;
|
|
1069
|
-
const REGIX_IS_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i;
|
|
1070
|
-
checkPath.isNotRelative = (path) => REGIX_IS_WINDOWS_PATH_ABSOLUTE.test(path) || isNotRelative(path);
|
|
1137
|
+
setupWindows();
|
|
1071
1138
|
}
|
|
1139
|
+
module.exports = factory;
|
|
1140
|
+
factory.default = factory;
|
|
1141
|
+
module.exports.isPathValid = isPathValid;
|
|
1142
|
+
define(module.exports, /* @__PURE__ */ Symbol.for("setupWindows"), setupWindows);
|
|
1072
1143
|
}
|
|
1073
1144
|
});
|
|
1074
1145
|
|
|
@@ -35344,27 +35415,27 @@ var init_source_format = __esm({
|
|
|
35344
35415
|
}
|
|
35345
35416
|
});
|
|
35346
35417
|
|
|
35347
|
-
//
|
|
35418
|
+
// node_modules/uuid/dist-node/regex.js
|
|
35348
35419
|
var regex_default;
|
|
35349
35420
|
var init_regex = __esm({
|
|
35350
|
-
"
|
|
35421
|
+
"node_modules/uuid/dist-node/regex.js"() {
|
|
35351
35422
|
regex_default = /^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$/i;
|
|
35352
35423
|
}
|
|
35353
35424
|
});
|
|
35354
35425
|
|
|
35355
|
-
//
|
|
35426
|
+
// node_modules/uuid/dist-node/validate.js
|
|
35356
35427
|
function validate(uuid) {
|
|
35357
35428
|
return typeof uuid === "string" && regex_default.test(uuid);
|
|
35358
35429
|
}
|
|
35359
35430
|
var validate_default;
|
|
35360
35431
|
var init_validate2 = __esm({
|
|
35361
|
-
"
|
|
35432
|
+
"node_modules/uuid/dist-node/validate.js"() {
|
|
35362
35433
|
init_regex();
|
|
35363
35434
|
validate_default = validate;
|
|
35364
35435
|
}
|
|
35365
35436
|
});
|
|
35366
35437
|
|
|
35367
|
-
//
|
|
35438
|
+
// node_modules/uuid/dist-node/parse.js
|
|
35368
35439
|
function parse(uuid) {
|
|
35369
35440
|
if (!validate_default(uuid)) {
|
|
35370
35441
|
throw TypeError("Invalid UUID");
|
|
@@ -35374,19 +35445,19 @@ function parse(uuid) {
|
|
|
35374
35445
|
}
|
|
35375
35446
|
var parse_default;
|
|
35376
35447
|
var init_parse = __esm({
|
|
35377
|
-
"
|
|
35448
|
+
"node_modules/uuid/dist-node/parse.js"() {
|
|
35378
35449
|
init_validate2();
|
|
35379
35450
|
parse_default = parse;
|
|
35380
35451
|
}
|
|
35381
35452
|
});
|
|
35382
35453
|
|
|
35383
|
-
//
|
|
35454
|
+
// node_modules/uuid/dist-node/stringify.js
|
|
35384
35455
|
function unsafeStringify(arr, offset = 0) {
|
|
35385
35456
|
return (byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]).toLowerCase();
|
|
35386
35457
|
}
|
|
35387
35458
|
var byteToHex;
|
|
35388
35459
|
var init_stringify = __esm({
|
|
35389
|
-
"
|
|
35460
|
+
"node_modules/uuid/dist-node/stringify.js"() {
|
|
35390
35461
|
byteToHex = [];
|
|
35391
35462
|
for (let i = 0; i < 256; ++i) {
|
|
35392
35463
|
byteToHex.push((i + 256).toString(16).slice(1));
|
|
@@ -35394,18 +35465,18 @@ var init_stringify = __esm({
|
|
|
35394
35465
|
}
|
|
35395
35466
|
});
|
|
35396
35467
|
|
|
35397
|
-
//
|
|
35468
|
+
// node_modules/uuid/dist-node/rng.js
|
|
35398
35469
|
function rng() {
|
|
35399
35470
|
return crypto.getRandomValues(rnds8);
|
|
35400
35471
|
}
|
|
35401
35472
|
var rnds8;
|
|
35402
35473
|
var init_rng = __esm({
|
|
35403
|
-
"
|
|
35474
|
+
"node_modules/uuid/dist-node/rng.js"() {
|
|
35404
35475
|
rnds8 = new Uint8Array(16);
|
|
35405
35476
|
}
|
|
35406
35477
|
});
|
|
35407
35478
|
|
|
35408
|
-
//
|
|
35479
|
+
// node_modules/uuid/dist-node/v35.js
|
|
35409
35480
|
function stringToBytes(str) {
|
|
35410
35481
|
str = unescape(encodeURIComponent(str));
|
|
35411
35482
|
const bytes = new Uint8Array(str.length);
|
|
@@ -35443,7 +35514,7 @@ function v35(version, hash, value, namespace, buf, offset) {
|
|
|
35443
35514
|
}
|
|
35444
35515
|
var DNS, URL2;
|
|
35445
35516
|
var init_v35 = __esm({
|
|
35446
|
-
"
|
|
35517
|
+
"node_modules/uuid/dist-node/v35.js"() {
|
|
35447
35518
|
init_parse();
|
|
35448
35519
|
init_stringify();
|
|
35449
35520
|
DNS = "6ba7b810-9dad-11d1-80b4-00c04fd430c8";
|
|
@@ -35451,7 +35522,7 @@ var init_v35 = __esm({
|
|
|
35451
35522
|
}
|
|
35452
35523
|
});
|
|
35453
35524
|
|
|
35454
|
-
//
|
|
35525
|
+
// node_modules/uuid/dist-node/v4.js
|
|
35455
35526
|
function v4(options, buf, offset) {
|
|
35456
35527
|
if (!buf && !options && crypto.randomUUID) {
|
|
35457
35528
|
return crypto.randomUUID();
|
|
@@ -35480,14 +35551,14 @@ function _v4(options, buf, offset) {
|
|
|
35480
35551
|
}
|
|
35481
35552
|
var v4_default;
|
|
35482
35553
|
var init_v4 = __esm({
|
|
35483
|
-
"
|
|
35554
|
+
"node_modules/uuid/dist-node/v4.js"() {
|
|
35484
35555
|
init_rng();
|
|
35485
35556
|
init_stringify();
|
|
35486
35557
|
v4_default = v4;
|
|
35487
35558
|
}
|
|
35488
35559
|
});
|
|
35489
35560
|
|
|
35490
|
-
//
|
|
35561
|
+
// node_modules/uuid/dist-node/sha1.js
|
|
35491
35562
|
import { createHash } from "node:crypto";
|
|
35492
35563
|
function sha1(bytes) {
|
|
35493
35564
|
if (Array.isArray(bytes)) {
|
|
@@ -35499,18 +35570,18 @@ function sha1(bytes) {
|
|
|
35499
35570
|
}
|
|
35500
35571
|
var sha1_default;
|
|
35501
35572
|
var init_sha1 = __esm({
|
|
35502
|
-
"
|
|
35573
|
+
"node_modules/uuid/dist-node/sha1.js"() {
|
|
35503
35574
|
sha1_default = sha1;
|
|
35504
35575
|
}
|
|
35505
35576
|
});
|
|
35506
35577
|
|
|
35507
|
-
//
|
|
35578
|
+
// node_modules/uuid/dist-node/v5.js
|
|
35508
35579
|
function v5(value, namespace, buf, offset) {
|
|
35509
35580
|
return v35(80, sha1_default, value, namespace, buf, offset);
|
|
35510
35581
|
}
|
|
35511
35582
|
var v5_default;
|
|
35512
35583
|
var init_v5 = __esm({
|
|
35513
|
-
"
|
|
35584
|
+
"node_modules/uuid/dist-node/v5.js"() {
|
|
35514
35585
|
init_sha1();
|
|
35515
35586
|
init_v35();
|
|
35516
35587
|
v5.DNS = DNS;
|
|
@@ -35519,9 +35590,9 @@ var init_v5 = __esm({
|
|
|
35519
35590
|
}
|
|
35520
35591
|
});
|
|
35521
35592
|
|
|
35522
|
-
//
|
|
35593
|
+
// node_modules/uuid/dist-node/index.js
|
|
35523
35594
|
var init_dist_node = __esm({
|
|
35524
|
-
"
|
|
35595
|
+
"node_modules/uuid/dist-node/index.js"() {
|
|
35525
35596
|
init_v4();
|
|
35526
35597
|
init_v5();
|
|
35527
35598
|
}
|
|
@@ -78941,8 +79012,7 @@ function materializeAuthoredValueSeeds(args) {
|
|
|
78941
79012
|
document: args.document,
|
|
78942
79013
|
prepared: args.prepared,
|
|
78943
79014
|
pendingGraphValidations,
|
|
78944
|
-
|
|
78945
|
-
change,
|
|
79015
|
+
memberChange: { index, change },
|
|
78946
79016
|
member,
|
|
78947
79017
|
seed
|
|
78948
79018
|
});
|
|
@@ -79088,6 +79158,21 @@ function materializeAuthoredValueSeeds(args) {
|
|
|
79088
79158
|
}
|
|
79089
79159
|
for (const seed of args.authoredValueSeeds) {
|
|
79090
79160
|
if (consumedSeedMemberIds.has(seed.memberId)) continue;
|
|
79161
|
+
const member = args.document.members.find(
|
|
79162
|
+
(candidate) => candidate.id === seed.memberId
|
|
79163
|
+
);
|
|
79164
|
+
if (member !== void 0 && member.isStatic !== true && !isProjectRootMember(args.document.project, member.id)) {
|
|
79165
|
+
materializeInstanceDefaultSeed({
|
|
79166
|
+
document: args.document,
|
|
79167
|
+
prepared: args.prepared,
|
|
79168
|
+
pendingGraphValidations,
|
|
79169
|
+
memberChange: null,
|
|
79170
|
+
member,
|
|
79171
|
+
seed
|
|
79172
|
+
});
|
|
79173
|
+
consumedSeedMemberIds.add(seed.memberId);
|
|
79174
|
+
continue;
|
|
79175
|
+
}
|
|
79091
79176
|
materializeExistingAuthoredValueSeed({
|
|
79092
79177
|
document: args.document,
|
|
79093
79178
|
prepared: args.prepared,
|
|
@@ -79308,10 +79393,17 @@ function materializeInstanceDefaultSeed(args) {
|
|
|
79308
79393
|
...args.member,
|
|
79309
79394
|
defaultValue: seedValueContent(args.seed)
|
|
79310
79395
|
};
|
|
79311
|
-
const {
|
|
79396
|
+
const {
|
|
79397
|
+
createdValues,
|
|
79398
|
+
localizedTexts,
|
|
79399
|
+
storageKeyDeclarations,
|
|
79400
|
+
rootValue,
|
|
79401
|
+
existingValueIds
|
|
79402
|
+
} = materializeAuthoredStaticSeed({
|
|
79312
79403
|
document: args.document,
|
|
79313
79404
|
member: materializationMember,
|
|
79314
|
-
seed: { ...args.seed, values: args.seed.values }
|
|
79405
|
+
seed: { ...args.seed, values: args.seed.values },
|
|
79406
|
+
allowExistingRows: args.memberChange === null || args.memberChange.change.operation === "update"
|
|
79315
79407
|
});
|
|
79316
79408
|
stampCreatedValuesMapKey(createdValues, null);
|
|
79317
79409
|
applyDeclaredStorageKeyOverrides({
|
|
@@ -79346,12 +79438,21 @@ function materializeInstanceDefaultSeed(args) {
|
|
|
79346
79438
|
...typeof rootValue.classId === "string" ? { classId: rootValue.classId } : {}
|
|
79347
79439
|
}
|
|
79348
79440
|
};
|
|
79349
|
-
args.
|
|
79350
|
-
|
|
79351
|
-
|
|
79352
|
-
|
|
79441
|
+
if (args.memberChange === null) {
|
|
79442
|
+
if (canonicalJsonStringify(seedValueContent(args.seed)) !== canonicalJsonStringify(args.member.defaultValue)) {
|
|
79443
|
+
throw new Error(
|
|
79444
|
+
`Existing default value seed for member "${args.member.name}" (${args.member.id}) declares ${canonicalJsonStringify(seedValueContent(args.seed))}, but the unchanged member declares ${canonicalJsonStringify(args.member.defaultValue)}.`
|
|
79445
|
+
);
|
|
79446
|
+
}
|
|
79447
|
+
} else {
|
|
79448
|
+
args.prepared[args.memberChange.index] = {
|
|
79449
|
+
...args.memberChange.change,
|
|
79450
|
+
nextData: nextMember
|
|
79451
|
+
};
|
|
79452
|
+
}
|
|
79353
79453
|
for (const value of createdValues) {
|
|
79354
79454
|
if (value.id === rootValue.id) continue;
|
|
79455
|
+
if (existingValueIds.has(value.id)) continue;
|
|
79355
79456
|
args.prepared.push({
|
|
79356
79457
|
recordKind: "value",
|
|
79357
79458
|
recordId: value.id,
|
|
@@ -84972,10 +85073,52 @@ function lowerMemberDefaultSourcesV4(state, analysis, manifest, options = {}) {
|
|
|
84972
85073
|
const baseData3 = stateData(context, "member", memberId);
|
|
84973
85074
|
const backed = baseData3 === null || requiresEvaluation ? null : rowBackedDefaultBody(baseData3, (id2) => pulledValueIds.has(id2));
|
|
84974
85075
|
if (backed !== null && baseData3 !== null) {
|
|
84975
|
-
|
|
84976
|
-
|
|
84977
|
-
|
|
85076
|
+
const existingReconstructedKeys = new Set(context.reconstructed.keys());
|
|
85077
|
+
const existingPendingValueIds = new Set(context.pendingValues.keys());
|
|
85078
|
+
const existingPendingLocalizedTextIds = new Set(
|
|
85079
|
+
context.pendingLocalizedTexts.keys()
|
|
84978
85080
|
);
|
|
85081
|
+
const existingPendingBindingMemberIds = new Set(
|
|
85082
|
+
[...context.pendingBindingMembersByClassId.values()].map(
|
|
85083
|
+
(pending) => pending.id
|
|
85084
|
+
)
|
|
85085
|
+
);
|
|
85086
|
+
const lowered = lowerRowBackedDefault(
|
|
85087
|
+
context,
|
|
85088
|
+
member,
|
|
85089
|
+
backed,
|
|
85090
|
+
baseData3,
|
|
85091
|
+
binding
|
|
85092
|
+
);
|
|
85093
|
+
memberDefaultValues.set(memberId, lowered);
|
|
85094
|
+
const pendingRows = [...context.pendingValues.values()].filter(
|
|
85095
|
+
(row) => !existingPendingValueIds.has(row.id)
|
|
85096
|
+
);
|
|
85097
|
+
if (pendingRows.length > 0) {
|
|
85098
|
+
const existingRows = [...context.reconstructed].filter(
|
|
85099
|
+
([key, record3]) => !existingReconstructedKeys.has(key) && record3.recordKind === "value"
|
|
85100
|
+
).map(
|
|
85101
|
+
([, record3]) => staticValueSeedRow(
|
|
85102
|
+
record3.fileFields,
|
|
85103
|
+
context.loweredMemberIdByValueId.get(record3.recordId)
|
|
85104
|
+
)
|
|
85105
|
+
);
|
|
85106
|
+
const bindingMembers = [
|
|
85107
|
+
...context.pendingBindingMembersByClassId.values()
|
|
85108
|
+
].filter(
|
|
85109
|
+
(pending) => !existingPendingBindingMemberIds.has(pending.id)
|
|
85110
|
+
);
|
|
85111
|
+
const localizedTexts = [
|
|
85112
|
+
...context.pendingLocalizedTexts.values()
|
|
85113
|
+
].filter((text) => !existingPendingLocalizedTextIds.has(text.id));
|
|
85114
|
+
seeds.set(memberId, {
|
|
85115
|
+
...lowered,
|
|
85116
|
+
valueId: pendingValueId(binding, `${label}.default`),
|
|
85117
|
+
values: [...existingRows, ...pendingRows],
|
|
85118
|
+
...bindingMembers.length === 0 ? {} : { bindingMembers },
|
|
85119
|
+
...localizedTexts.length === 0 ? {} : { localizedTexts }
|
|
85120
|
+
});
|
|
85121
|
+
}
|
|
84979
85122
|
continue;
|
|
84980
85123
|
}
|
|
84981
85124
|
if (!defaultRequiresOwnedRows(context, member, expression, binding)) {
|
|
@@ -85582,9 +85725,22 @@ function lowerRowBackedDefault(context, member, backed, baseData3, binding) {
|
|
|
85582
85725
|
return symbolId;
|
|
85583
85726
|
}
|
|
85584
85727
|
const itemId = annotatedValue(element).id;
|
|
85585
|
-
if (itemId === null) {
|
|
85586
|
-
|
|
85587
|
-
|
|
85728
|
+
if (itemId === null || context.state[`value:${itemId}`] === void 0) {
|
|
85729
|
+
if (itemId !== null && !isPendingId(itemId) && !isUuidV4Id(itemId)) {
|
|
85730
|
+
context.loweringFailures.push({
|
|
85731
|
+
message: `Default list ${binding.label} entry @id("${itemId}") names no existing row, and a new row's durable identity must be a UUID v4. Fix the id if it meant an existing row, or drop the @id to mint a fresh identity.`,
|
|
85732
|
+
site: referenceSite(binding, element)
|
|
85733
|
+
});
|
|
85734
|
+
}
|
|
85735
|
+
return lowerPendingListItem(
|
|
85736
|
+
context,
|
|
85737
|
+
entryMember,
|
|
85738
|
+
element,
|
|
85739
|
+
binding,
|
|
85740
|
+
`${binding.label}[${index}]`,
|
|
85741
|
+
void 0,
|
|
85742
|
+
bindingEnvironment,
|
|
85743
|
+
entrySlices[index]
|
|
85588
85744
|
);
|
|
85589
85745
|
}
|
|
85590
85746
|
return lowerValueRow(
|
|
@@ -87114,11 +87270,6 @@ function lowerListValue(context, member, expression, base, source, inheritedEnvi
|
|
|
87114
87270
|
}
|
|
87115
87271
|
const annotatedItemId = annotatedValue(element).id;
|
|
87116
87272
|
if (annotatedItemId === null) {
|
|
87117
|
-
if (source.purpose === "memberDefault") {
|
|
87118
|
-
throw new Error(
|
|
87119
|
-
`List ${member.name} contains an idless default item. New default list item creation is not implemented by this slice.`
|
|
87120
|
-
);
|
|
87121
|
-
}
|
|
87122
87273
|
ids.push(
|
|
87123
87274
|
lowerPendingListItem(
|
|
87124
87275
|
context,
|
|
@@ -94410,6 +94561,7 @@ function createCliNeoScriptContext(options) {
|
|
|
94410
94561
|
};
|
|
94411
94562
|
const compilerContext = createNeoScriptDocumentContext({
|
|
94412
94563
|
vm,
|
|
94564
|
+
constructors: [...options.documents.constructors],
|
|
94413
94565
|
scriptKind: options.kind,
|
|
94414
94566
|
thisClass: options.thisClass,
|
|
94415
94567
|
...options.returnTypeInfo ? { returnTypeInfo: options.returnTypeInfo } : {},
|
|
@@ -96117,6 +96269,7 @@ function readDocumentArrays(raw) {
|
|
|
96117
96269
|
project: raw.project,
|
|
96118
96270
|
members: arrayOf2("members"),
|
|
96119
96271
|
classes: arrayOf2("classes"),
|
|
96272
|
+
constructors: arrayOf2("constructors"),
|
|
96120
96273
|
enums: arrayOf2("enums"),
|
|
96121
96274
|
interfaces: arrayOf2("interfaces"),
|
|
96122
96275
|
values: arrayOf2("values"),
|
|
@@ -101243,7 +101396,7 @@ var init_registry2 = __esm({
|
|
|
101243
101396
|
PROJECT_SCHEMA_CONTRACT = Object.freeze({
|
|
101244
101397
|
formatVersion: 3,
|
|
101245
101398
|
contractVersion: "3.9",
|
|
101246
|
-
cliVersion: "0.24.
|
|
101399
|
+
cliVersion: "0.24.1",
|
|
101247
101400
|
projectFileUploadBatchSize: 32,
|
|
101248
101401
|
documentRecords: {
|
|
101249
101402
|
member: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@neocompose/cli",
|
|
3
|
-
"version": "0.24.
|
|
3
|
+
"version": "0.24.1",
|
|
4
4
|
"description": "Neo Compose native project-source CLI with bidirectional sync.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -40,6 +40,12 @@
|
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"esbuild": "^0.28.1",
|
|
43
|
-
"prettier": "^3.9.5"
|
|
43
|
+
"prettier": "^3.9.5",
|
|
44
|
+
"uuid": "^14.0.1"
|
|
45
|
+
},
|
|
46
|
+
"repository": {
|
|
47
|
+
"type": "git",
|
|
48
|
+
"url": "git+https://github.com/ryanbliss/neo-compose.git",
|
|
49
|
+
"directory": "cli"
|
|
44
50
|
}
|
|
45
51
|
}
|
|
@@ -9,7 +9,7 @@ description: >-
|
|
|
9
9
|
`@neocompose/cli` or `node cli/bin/neo.mjs` in the neo-compose repository.
|
|
10
10
|
---
|
|
11
11
|
|
|
12
|
-
<!-- reviewed-through-cli: 0.24.
|
|
12
|
+
<!-- reviewed-through-cli: 0.24.1 -->
|
|
13
13
|
|
|
14
14
|
# Neo Compose CLI
|
|
15
15
|
|
|
@@ -50,8 +50,12 @@ opaque JSON or guessed syntax.
|
|
|
50
50
|
values, files, or other records. `@system` is reserved for platform-owned
|
|
51
51
|
system records; authoring it can make the record uneditable.
|
|
52
52
|
- Preserve existing `@id` annotations through rename, reorder, and file moves.
|
|
53
|
+
- Omit `@id` on new ordinary declarations and nested rows unless a same-push
|
|
54
|
+
structural reference genuinely requires an explicit UUID. A successful push
|
|
55
|
+
assigns the durable IDs and rewrites source.
|
|
53
56
|
- Do not add a second `@id` to a root or static member initializer. The member
|
|
54
|
-
ID owns that stored binding.
|
|
57
|
+
ID owns that stored binding. Existing nested class/list rows retain their
|
|
58
|
+
inline IDs; new rows normally omit them until push.
|
|
55
59
|
- Treat `.neo/` as private state and tracked Neo source as the authorable input.
|
|
56
60
|
- Never seek a force-CAS bypass. Pull, resolve, review, and retry.
|
|
57
61
|
|
|
@@ -109,9 +109,12 @@ override replaces the whole leaf.
|
|
|
109
109
|
Frame overrides may change `Enabled`, `FlipX`, and `SortingOrder`; they may
|
|
110
110
|
not add, remove, or reorder `Children`.
|
|
111
111
|
|
|
112
|
-
Address direct children
|
|
113
|
-
|
|
114
|
-
|
|
112
|
+
Address direct children through selector delegates. A selector should implement
|
|
113
|
+
the project's own stable identity contract, such as an immutable `Name`, slug,
|
|
114
|
+
or semantic position; do not turn a mutable object-row ID back into identity by
|
|
115
|
+
returning `Reference<T>(id: "...")`. Missing optional child slots are skipped
|
|
116
|
+
with diagnostics when the authored slot is absent, while stale pre-provenance
|
|
117
|
+
placements may still fail closed.
|
|
115
118
|
|
|
116
119
|
A placement row carries its `assetClassId` binding and optional `assetValueId`
|
|
117
120
|
override beside its schema keys. They are row provenance, not declared schema:
|
|
@@ -127,8 +130,8 @@ sprite specialization is `NeoSpriteAnimationSegment`, whose
|
|
|
127
130
|
|
|
128
131
|
Use one polymorphic `NeoAnimationClip.Tracks` list:
|
|
129
132
|
|
|
130
|
-
- `NeoAnimationChildTrack` schedules
|
|
131
|
-
`
|
|
133
|
+
- `NeoAnimationChildTrack` schedules `ClipKey` on the child returned by its
|
|
134
|
+
`Selector` delegate.
|
|
132
135
|
- A project subclass of
|
|
133
136
|
`NeoAnimationSegmentTrack<TChild, TValue>` schedules a segment onto one
|
|
134
137
|
target member. Implement its abstract `Segment` member as a stored value,
|
|
@@ -137,32 +140,54 @@ Use one polymorphic `NeoAnimationClip.Tracks` list:
|
|
|
137
140
|
`NeoSpriteAnimationSegmentTrack<TChild extends NeoSpriteObject>` pairing
|
|
138
141
|
targets `NeoSpriteObject.Sprite`.
|
|
139
142
|
|
|
140
|
-
Declare a concrete project track by implementing `Segment`,
|
|
141
|
-
|
|
143
|
+
Declare a concrete project track by implementing `Segment`, expose a compatible
|
|
144
|
+
selector function on the owning object, then pass that function to each row:
|
|
142
145
|
|
|
143
146
|
```neo
|
|
144
147
|
class StoredPantsTrack : NeoSpriteAnimationSegmentTrack<PantsSprite> {
|
|
145
148
|
public override NeoSpriteAnimationSegment Segment = new();
|
|
146
149
|
}
|
|
147
150
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
151
|
+
class LegPart : NeoObject {
|
|
152
|
+
public PantsSprite SelectPants() {
|
|
153
|
+
var child = this.Children.FirstOrDefault((candidate) => {
|
|
154
|
+
return candidate.Name == "Pants";
|
|
155
|
+
});
|
|
156
|
+
if (child is PantsSprite pants) {
|
|
157
|
+
return pants;
|
|
158
|
+
}
|
|
159
|
+
throw "Pants child is missing or has the wrong type";
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
public NeoAnimationClip<LegPart> Walk = new NeoAnimationClip<LegPart> {
|
|
163
|
+
Tracks = [
|
|
164
|
+
new StoredPantsTrack(selector: this.SelectPants) {
|
|
165
|
+
StartFrame = 0,
|
|
166
|
+
Direction = .Reverse,
|
|
167
|
+
OffsetEndIndex = 3,
|
|
168
|
+
},
|
|
169
|
+
]
|
|
170
|
+
};
|
|
171
|
+
}
|
|
156
172
|
```
|
|
157
173
|
|
|
174
|
+
Selectors may also be compatible inline closures. Prefer a named function when
|
|
175
|
+
the identity rule is reused or deserves an explicit failure message. Never use
|
|
176
|
+
a selector merely to wrap `Reference<T>(id: "...")`; that recreates the
|
|
177
|
+
row-ID coupling selectors were introduced to remove. For new track rows, omit
|
|
178
|
+
`@id` and let the successful push assign it. Preserve IDs already present on
|
|
179
|
+
pulled rows.
|
|
180
|
+
|
|
158
181
|
Each track has `StartFrame`, `Direction` (`.Forward` or `.Reverse`), and a
|
|
159
182
|
crop window `OffsetStartIndex`/`OffsetEndIndex`. Crop before reversing, then
|
|
160
183
|
schedule on the owning clip. Content beyond the owning clip truncates. Reject
|
|
161
184
|
empty/inverted crop windows and tracks that can never enter the clip.
|
|
162
185
|
|
|
163
|
-
Treat track target selection as
|
|
164
|
-
compatible with `TChild
|
|
165
|
-
|
|
186
|
+
Treat track target selection as project-authored identity logic, not row data.
|
|
187
|
+
The selector return type must be compatible with `TChild`; the segment value
|
|
188
|
+
must be compatible with `TValue`. `Refresh` controls selector reevaluation,
|
|
189
|
+
while track reads and segment getters are re-resolved for the next applied
|
|
190
|
+
frame after a watched dependency changes.
|
|
166
191
|
|
|
167
192
|
When multiple tracks write the same member in one frame, apply list order and
|
|
168
193
|
let the last write win.
|
|
@@ -41,10 +41,12 @@ Read the relevant specs in full:
|
|
|
41
41
|
- [P50 loops](https://github.com/ryanbliss/neo-compose-specs/blob/main/new-features/p50-neoscript-for-and-foreach-loops.md)
|
|
42
42
|
- [P51 switch](https://github.com/ryanbliss/neo-compose-specs/blob/main/new-features/p51-neoscript-switch-statements.md)
|
|
43
43
|
- [P52 try/catch](https://github.com/ryanbliss/neo-compose-specs/blob/main/new-features/p52-neoscript-try-catch-blocks.md)
|
|
44
|
+
- [P60 delegate parameters and selector targeting](https://github.com/ryanbliss/neo-compose-specs/blob/main/new-features/p60-closure-parameters-and-selector-targeting.md)
|
|
45
|
+
- [P61 initializer materialization](https://github.com/ryanbliss/neo-compose-specs/blob/main/new-features/complete/p61-initializer-materialization.md)
|
|
44
46
|
- [P64 NeoScript unit testing and push hooks](https://github.com/ryanbliss/neo-compose-specs/blob/main/new-features/p64-neoscript-unit-testing-and-push-hooks.md)
|
|
45
47
|
|
|
46
|
-
P38–P44, P47–P52, and the initial P64 test/push-hook vertical slice
|
|
47
|
-
implemented. P45 remains deliberately deferred; do not
|
|
48
|
+
P38–P44, P47–P52, P60–P61, and the initial P64 test/push-hook vertical slice
|
|
49
|
+
are implemented. P45 remains deliberately deferred; do not
|
|
48
50
|
build or teach runtime-child provenance. P46 is a hardening proposal, not an
|
|
49
51
|
authoring capability. P52's document header still says proposed, but try/catch
|
|
50
52
|
shipped in CLI 0.18; implementation and parity tests take precedence over that
|
|
@@ -80,7 +82,7 @@ wrappers.
|
|
|
80
82
|
The marker near the top of `SKILL.md` must exactly match the package version:
|
|
81
83
|
|
|
82
84
|
```html
|
|
83
|
-
<!-- reviewed-through-cli: 0.24.
|
|
85
|
+
<!-- reviewed-through-cli: 0.24.1 -->
|
|
84
86
|
```
|
|
85
87
|
|
|
86
88
|
The quoted version above is checked too, so this instruction cannot go stale
|
|
@@ -98,8 +98,11 @@ Reference<Dialogue>(id: "capitol-dialogue-id")
|
|
|
98
98
|
- Both forms are legal in a member's declaration default, not only inside a
|
|
99
99
|
value graph. A key written there resolves after every pass has run, so it may
|
|
100
100
|
name a row the same push creates.
|
|
101
|
-
- Keep structural references
|
|
102
|
-
|
|
101
|
+
- Keep genuine structural references ID-based when their collection has no
|
|
102
|
+
stable symbol, path, or key surface. Animation track and frame-override
|
|
103
|
+
targets are selectors, not structural references: encode a stable
|
|
104
|
+
project-owned identity such as `Name` or slug in the selector instead of
|
|
105
|
+
wrapping a row ID in `Reference<T>(id: "...")`.
|
|
103
106
|
|
|
104
107
|
Declaration and file order do not affect resolution within one push. Cyclic
|
|
105
108
|
identity references are legal because they resolve IDs rather than evaluate a
|