tutuca 0.9.97 → 0.9.98
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/tutuca-cli.js +190 -59
- package/dist/tutuca-dev.ext.js +76 -29
- package/dist/tutuca-dev.js +76 -29
- package/dist/tutuca-dev.min.js +2 -2
- package/dist/tutuca-extra.ext.js +56 -28
- package/dist/tutuca-extra.js +56 -28
- package/dist/tutuca-extra.min.js +2 -2
- package/dist/tutuca-storybook.js +11 -4
- package/dist/tutuca.ext.js +56 -28
- package/dist/tutuca.js +56 -28
- package/dist/tutuca.min.js +2 -2
- package/package.json +3 -3
- package/skill/tutuca/advanced.md +14 -5
- package/skill/tutuca/core.md +78 -15
- package/skill/tutuca/margaui.md +25 -13
- package/skill/tutuca/patterns/README.md +1 -0
- package/skill/tutuca/patterns/filter-a-list.md +3 -1
- package/skill/tutuca/patterns/filter-and-paginate.md +116 -0
- package/skill/tutuca/patterns/paginate-a-list.md +3 -1
- package/skill/tutuca/storybook.md +7 -2
- package/skill/tutuca/testing.md +11 -0
- package/skill/tutuca-source/tutuca.ext.js +56 -28
package/dist/tutuca-cli.js
CHANGED
|
@@ -9799,37 +9799,47 @@ class Renderer {
|
|
|
9799
9799
|
renderEach(stack, iterInfo, node, viewName) {
|
|
9800
9800
|
const { seq, filter, loopWith } = iterInfo.eval(stack);
|
|
9801
9801
|
const r = [];
|
|
9802
|
-
const { iterData, start, end } = unpackLoopResult(loopWith.call(stack.it, seq), seq);
|
|
9803
|
-
|
|
9804
|
-
|
|
9805
|
-
|
|
9806
|
-
|
|
9807
|
-
|
|
9808
|
-
|
|
9802
|
+
const { iterData, start, end, keys } = unpackLoopResult(loopWith.call(stack.it, seq, makeLoopCtx(stack, filter)), seq);
|
|
9803
|
+
const renderOne = (key, value, attrName) => {
|
|
9804
|
+
const dom = this.renderIt(stack.enter(value, { key }, true), node, key, viewName);
|
|
9805
|
+
this.pushEachEntry(r, node.nodeId, attrName, key, dom);
|
|
9806
|
+
};
|
|
9807
|
+
if (keys)
|
|
9808
|
+
imKeysIter(seq, renderOne, keys);
|
|
9809
|
+
else
|
|
9810
|
+
getSeqInfo(seq)(seq, (key, value, attrName) => {
|
|
9811
|
+
if (filter.call(stack.it, key, value, iterData))
|
|
9812
|
+
renderOne(key, value, attrName);
|
|
9813
|
+
}, start, end);
|
|
9809
9814
|
return r;
|
|
9810
9815
|
}
|
|
9811
9816
|
renderEachWhen(stack, iterInfo, view, nid) {
|
|
9812
9817
|
const { seq, filter, loopWith, enricher } = iterInfo.eval(stack);
|
|
9813
9818
|
const r = [];
|
|
9814
9819
|
const it = stack.it;
|
|
9815
|
-
const { iterData, start, end } = unpackLoopResult(loopWith.call(it, seq), seq);
|
|
9816
|
-
|
|
9817
|
-
|
|
9818
|
-
|
|
9819
|
-
|
|
9820
|
-
|
|
9821
|
-
|
|
9822
|
-
|
|
9823
|
-
|
|
9824
|
-
|
|
9825
|
-
|
|
9826
|
-
|
|
9827
|
-
|
|
9828
|
-
|
|
9829
|
-
this.cache.set(cachePath, cacheKey, dom);
|
|
9830
|
-
}
|
|
9820
|
+
const { iterData, start, end, keys } = unpackLoopResult(loopWith.call(it, seq, makeLoopCtx(stack, filter)), seq);
|
|
9821
|
+
const renderOne = (key, value, attrName) => {
|
|
9822
|
+
const cachePath = enricher ? [view, it, value] : [view, value];
|
|
9823
|
+
const binds = { key, value };
|
|
9824
|
+
const cacheKey = `${nid}-${key}`;
|
|
9825
|
+
if (enricher)
|
|
9826
|
+
enricher.call(it, binds, key, value, iterData);
|
|
9827
|
+
const cachedNode = this.cache.get(cachePath, cacheKey);
|
|
9828
|
+
if (cachedNode)
|
|
9829
|
+
this.pushEachEntry(r, nid, attrName, key, cachedNode);
|
|
9830
|
+
else {
|
|
9831
|
+
const dom = this.renderView(view, stack.enter(value, binds, false));
|
|
9832
|
+
this.pushEachEntry(r, nid, attrName, key, dom);
|
|
9833
|
+
this.cache.set(cachePath, cacheKey, dom);
|
|
9831
9834
|
}
|
|
9832
|
-
}
|
|
9835
|
+
};
|
|
9836
|
+
if (keys)
|
|
9837
|
+
imKeysIter(seq, renderOne, keys);
|
|
9838
|
+
else
|
|
9839
|
+
getSeqInfo(seq)(seq, (key, value, attrName) => {
|
|
9840
|
+
if (filter.call(it, key, value, iterData))
|
|
9841
|
+
renderOne(key, value, attrName);
|
|
9842
|
+
}, start, end);
|
|
9833
9843
|
return r;
|
|
9834
9844
|
}
|
|
9835
9845
|
renderView(view, stack) {
|
|
@@ -9861,8 +9871,15 @@ var DATASET_ATTRS, getSeqInfo = (seq) => isIndexed(seq) ? imIndexedIter : isKeye
|
|
|
9861
9871
|
return [s, e < s ? s : e];
|
|
9862
9872
|
}, filterAlwaysTrue = (_v, _k, _seq) => true, nullLoopWith = (seq) => ({ iterData: { seq } }), unpackLoopResult = (result, seq) => {
|
|
9863
9873
|
const r = result ?? {};
|
|
9864
|
-
return { iterData: r.iterData ?? { seq }, start: r.start, end: r.end };
|
|
9865
|
-
},
|
|
9874
|
+
return { iterData: r.iterData ?? { seq }, start: r.start, end: r.end, keys: r.keys };
|
|
9875
|
+
}, imKeysIter = (seq, visit, keys) => {
|
|
9876
|
+
const attrName = isIndexed(seq) ? "si" : "sk";
|
|
9877
|
+
for (const key of keys)
|
|
9878
|
+
visit(key, seq.get(key), attrName);
|
|
9879
|
+
}, makeLoopCtx = (stack, filter) => ({
|
|
9880
|
+
lookup: (name) => stack.lookupBind(name),
|
|
9881
|
+
filter: (key, value, iterData) => filter.call(stack.it, key, value, iterData)
|
|
9882
|
+
}), imIndexedIter = (seq, visit, start, end) => {
|
|
9866
9883
|
const [s, e] = normalizeRange(start, end, seq.size);
|
|
9867
9884
|
for (let i = s;i < e; i++)
|
|
9868
9885
|
visit(i, seq.get(i), "si");
|
|
@@ -10049,11 +10066,11 @@ class IterInfo {
|
|
|
10049
10066
|
return { seq, filter, loopWith, enricher };
|
|
10050
10067
|
}
|
|
10051
10068
|
enrichBinds(stack, key) {
|
|
10052
|
-
const { seq, loopWith, enricher } = this.eval(stack);
|
|
10069
|
+
const { seq, filter, loopWith, enricher } = this.eval(stack);
|
|
10053
10070
|
const value = seq?.get ? seq.get(key, null) : null;
|
|
10054
10071
|
const binds = { key, value };
|
|
10055
10072
|
if (enricher) {
|
|
10056
|
-
const { iterData } = unpackLoopResult(loopWith.call(stack.it, seq), seq);
|
|
10073
|
+
const { iterData } = unpackLoopResult(loopWith.call(stack.it, seq, makeLoopCtx(stack, filter)), seq);
|
|
10057
10074
|
enricher.call(stack.it, binds, key, value, iterData);
|
|
10058
10075
|
}
|
|
10059
10076
|
return binds;
|
|
@@ -14760,6 +14777,13 @@ function phaseOps(phase) {
|
|
|
14760
14777
|
function resolveArgs(args, self) {
|
|
14761
14778
|
return typeof args === "function" ? args(self) ?? [] : args ?? [];
|
|
14762
14779
|
}
|
|
14780
|
+
function phaseHasBubble(phase) {
|
|
14781
|
+
if (!phase)
|
|
14782
|
+
return false;
|
|
14783
|
+
if (phase.bubble?.length)
|
|
14784
|
+
return true;
|
|
14785
|
+
return (phase.do ?? []).some((op) => op.type === "bubble");
|
|
14786
|
+
}
|
|
14763
14787
|
function dispatchPhase(dispatcher, targetPath, phase, self) {
|
|
14764
14788
|
if (!phase)
|
|
14765
14789
|
return;
|
|
@@ -14938,6 +14962,8 @@ async function driveStack(stack, value, phase, opts = {}) {
|
|
|
14938
14962
|
const t = info?.transaction;
|
|
14939
14963
|
opts.onMessage({ kind: t?.handlerProp ?? "input", name: t?.name, args: t?.args, path: t?.path }, old, val);
|
|
14940
14964
|
});
|
|
14965
|
+
if (phaseHasBubble(phase))
|
|
14966
|
+
console.warn("drive(): a `bubble` action is a no-op here — drive originates at the root and bubbles travel child→parent, so there is no ancestor to receive it (and the root's own bubble handler is skipped). To exercise a bubble handler, call it directly.");
|
|
14941
14967
|
dispatchPhase(rootDispatcher(transactor), new Path([]), phase, value);
|
|
14942
14968
|
await transactor.settle();
|
|
14943
14969
|
return transactor.state.val;
|
|
@@ -15113,13 +15139,17 @@ var init__registry = __esm(() => {
|
|
|
15113
15139
|
exitOn: (result) => result.hasErrors ? 3 : 0
|
|
15114
15140
|
},
|
|
15115
15141
|
test: {
|
|
15116
|
-
describe: "Run tests defined by getTests() (optional <name> to filter by component).",
|
|
15142
|
+
describe: "Run tests defined by getTests() (optional <name> to filter by component). Pass a directory to run every *.test.js / *.dev.js under it.",
|
|
15117
15143
|
defaultFormat: "cli",
|
|
15118
15144
|
needsEnv: true,
|
|
15119
15145
|
parseOptions: {
|
|
15120
15146
|
grep: { type: "string" },
|
|
15121
15147
|
bail: { type: "boolean" }
|
|
15122
15148
|
},
|
|
15149
|
+
acceptsDir: true,
|
|
15150
|
+
dirMatch: (name) => name.endsWith(".test.js") || name.endsWith(".dev.js"),
|
|
15151
|
+
dirFilter: (normalized) => typeof normalized.mod.getTests === "function",
|
|
15152
|
+
mergeResults: (reports) => new TestReport({ modules: reports.flatMap((r) => r.modules) }),
|
|
15123
15153
|
run: (normalized, { values, positionals }) => runTests({
|
|
15124
15154
|
getTests: normalized.mod.getTests,
|
|
15125
15155
|
components: normalized.components,
|
|
@@ -16011,6 +16041,23 @@ var init_env2 = __esm(() => {
|
|
|
16011
16041
|
init_lint_check();
|
|
16012
16042
|
});
|
|
16013
16043
|
|
|
16044
|
+
// tools/cli/walk.js
|
|
16045
|
+
import { readdirSync as readdirSync2 } from "node:fs";
|
|
16046
|
+
import { resolve as resolve4 } from "node:path";
|
|
16047
|
+
function walkFiles(dir, { match }, acc = []) {
|
|
16048
|
+
for (const e of readdirSync2(dir, { withFileTypes: true })) {
|
|
16049
|
+
if (e.name.startsWith(".") || e.name === "node_modules")
|
|
16050
|
+
continue;
|
|
16051
|
+
const full = resolve4(dir, e.name);
|
|
16052
|
+
if (e.isDirectory())
|
|
16053
|
+
walkFiles(full, { match }, acc);
|
|
16054
|
+
else if (e.isFile() && match(e.name))
|
|
16055
|
+
acc.push(full);
|
|
16056
|
+
}
|
|
16057
|
+
return acc;
|
|
16058
|
+
}
|
|
16059
|
+
var init_walk = () => {};
|
|
16060
|
+
|
|
16014
16061
|
// tools/cli/commands/storybook.js
|
|
16015
16062
|
var exports_storybook = {};
|
|
16016
16063
|
__export(exports_storybook, {
|
|
@@ -16021,45 +16068,34 @@ import {
|
|
|
16021
16068
|
createReadStream,
|
|
16022
16069
|
existsSync as existsSync4,
|
|
16023
16070
|
mkdirSync as mkdirSync3,
|
|
16024
|
-
readdirSync as readdirSync2,
|
|
16025
16071
|
readFileSync as readFileSync3,
|
|
16026
16072
|
statSync,
|
|
16027
16073
|
writeFileSync
|
|
16028
16074
|
} from "node:fs";
|
|
16029
16075
|
import { createServer } from "node:http";
|
|
16030
|
-
import { dirname as dirname4, join, normalize, relative as relative2, resolve as
|
|
16076
|
+
import { dirname as dirname4, join, normalize, relative as relative2, resolve as resolve5, sep } from "node:path";
|
|
16031
16077
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
16032
16078
|
import { parseArgs as parseArgs3 } from "node:util";
|
|
16033
|
-
function findDevModules(root
|
|
16034
|
-
|
|
16035
|
-
if (e.name.startsWith(".") || e.name === "node_modules")
|
|
16036
|
-
continue;
|
|
16037
|
-
const full = resolve4(dir, e.name);
|
|
16038
|
-
if (e.isDirectory()) {
|
|
16039
|
-
findDevModules(root, full, acc);
|
|
16040
|
-
} else if (e.isFile() && e.name.endsWith(".dev.js")) {
|
|
16041
|
-
acc.push(`/${relative2(root, full).split(sep).join("/")}`);
|
|
16042
|
-
}
|
|
16043
|
-
}
|
|
16044
|
-
return acc;
|
|
16079
|
+
function findDevModules(root) {
|
|
16080
|
+
return walkFiles(root, { match: (name) => name.endsWith(".dev.js") }).map((full) => `/${relative2(root, full).split(sep).join("/")}`);
|
|
16045
16081
|
}
|
|
16046
16082
|
function findSelf() {
|
|
16047
16083
|
const here = dirname4(fileURLToPath4(import.meta.url));
|
|
16048
16084
|
const pkgCandidates = [
|
|
16049
|
-
|
|
16050
|
-
|
|
16085
|
+
resolve5(here, "..", "..", "..", "package.json"),
|
|
16086
|
+
resolve5(here, "..", "package.json")
|
|
16051
16087
|
];
|
|
16052
16088
|
const pkgPath = pkgCandidates.find(existsSync4);
|
|
16053
16089
|
const version = pkgPath ? JSON.parse(readFileSync3(pkgPath, "utf8")).version : "latest";
|
|
16054
|
-
const distCandidates = [
|
|
16055
|
-
const distRoot = distCandidates.find((d) => existsSync4(
|
|
16090
|
+
const distCandidates = [resolve5(here, "..", "..", "..", "dist"), resolve5(here, ".")];
|
|
16091
|
+
const distRoot = distCandidates.find((d) => existsSync4(resolve5(d, "tutuca-storybook.js"))) ?? null;
|
|
16056
16092
|
return { version, distRoot };
|
|
16057
16093
|
}
|
|
16058
16094
|
function resolveTutucaBase(projectDir, self, forCdn) {
|
|
16059
16095
|
if (forCdn)
|
|
16060
16096
|
return { base: `https://cdn.jsdelivr.net/npm/tutuca@${self.version}/dist`, serveDist: null };
|
|
16061
|
-
const nm =
|
|
16062
|
-
if (existsSync4(
|
|
16097
|
+
const nm = resolve5(projectDir, "node_modules", "tutuca", "dist");
|
|
16098
|
+
if (existsSync4(resolve5(nm, "tutuca-dev.js"))) {
|
|
16063
16099
|
return { base: "/node_modules/tutuca/dist", serveDist: null };
|
|
16064
16100
|
}
|
|
16065
16101
|
if (self.distRoot)
|
|
@@ -16134,7 +16170,7 @@ async function runDevTests(projectDir, devModuleUrls) {
|
|
|
16134
16170
|
const failures = [];
|
|
16135
16171
|
const importErrors = [];
|
|
16136
16172
|
for (const url of devModuleUrls) {
|
|
16137
|
-
const abs =
|
|
16173
|
+
const abs = resolve5(projectDir, url.slice(1));
|
|
16138
16174
|
let mod;
|
|
16139
16175
|
try {
|
|
16140
16176
|
mod = await import(abs);
|
|
@@ -16166,7 +16202,7 @@ async function discoverModules(projectDir, devModuleUrls) {
|
|
|
16166
16202
|
await createNodeEnv();
|
|
16167
16203
|
const modules = [];
|
|
16168
16204
|
for (const url of devModuleUrls) {
|
|
16169
|
-
const abs =
|
|
16205
|
+
const abs = resolve5(projectDir, url.slice(1));
|
|
16170
16206
|
try {
|
|
16171
16207
|
const mod = await import(abs);
|
|
16172
16208
|
const { normalized, present } = normalizeModule(mod, { path: abs });
|
|
@@ -16258,7 +16294,7 @@ async function run4(argv, opts = {}) {
|
|
|
16258
16294
|
`);
|
|
16259
16295
|
return;
|
|
16260
16296
|
}
|
|
16261
|
-
const projectDir =
|
|
16297
|
+
const projectDir = resolve5(parsed.positionals[0] ?? process.cwd());
|
|
16262
16298
|
if (!existsSync4(projectDir) || !statSync(projectDir).isDirectory()) {
|
|
16263
16299
|
emitError(opts, {
|
|
16264
16300
|
code: CODES.USAGE_MISSING_ARGUMENT,
|
|
@@ -16266,7 +16302,7 @@ async function run4(argv, opts = {}) {
|
|
|
16266
16302
|
hint: "Pass a project directory to scan, or omit it to use the current directory."
|
|
16267
16303
|
});
|
|
16268
16304
|
}
|
|
16269
|
-
const devModuleUrls = findDevModules(projectDir
|
|
16305
|
+
const devModuleUrls = findDevModules(projectDir);
|
|
16270
16306
|
if (devModuleUrls.length === 0) {
|
|
16271
16307
|
emitError(opts, {
|
|
16272
16308
|
code: CODES.USAGE_MISSING_ARGUMENT,
|
|
@@ -16278,13 +16314,13 @@ async function run4(argv, opts = {}) {
|
|
|
16278
16314
|
const check = !parsed.values["no-check"];
|
|
16279
16315
|
const self = findSelf();
|
|
16280
16316
|
if (parsed.values.out) {
|
|
16281
|
-
const outDir =
|
|
16317
|
+
const outDir = resolve5(parsed.values.out);
|
|
16282
16318
|
mkdirSync3(outDir, { recursive: true });
|
|
16283
16319
|
const { base: base2 } = resolveTutucaBase(projectDir, self, true);
|
|
16284
16320
|
const imports2 = buildImports(base2, { margaui });
|
|
16285
16321
|
const bootstrapName = "tutuca-storybook.bootstrap.js";
|
|
16286
|
-
writeFileSync(
|
|
16287
|
-
writeFileSync(
|
|
16322
|
+
writeFileSync(resolve5(outDir, "index.html"), renderIndexHtml(imports2, { margaui, bootstrapUrl: `./${bootstrapName}` }));
|
|
16323
|
+
writeFileSync(resolve5(outDir, bootstrapName), renderBootstrap(devModuleUrls, { margaui, check }));
|
|
16288
16324
|
process.stdout.write(`wrote static storybook → ${relative2(process.cwd(), outDir) || "."}/
|
|
16289
16325
|
index.html + ${bootstrapName} (${devModuleUrls.length} dev modules, CDN import map)
|
|
16290
16326
|
Host it from the project root so /*.dev.js paths resolve.
|
|
@@ -16410,6 +16446,7 @@ var init_storybook = __esm(() => {
|
|
|
16410
16446
|
init_test();
|
|
16411
16447
|
init_env2();
|
|
16412
16448
|
init_errors();
|
|
16449
|
+
init_walk();
|
|
16413
16450
|
MIME = {
|
|
16414
16451
|
".js": "text/javascript",
|
|
16415
16452
|
".mjs": "text/javascript",
|
|
@@ -16943,17 +16980,60 @@ ${group}
|
|
|
16943
16980
|
// tools/tutuca.js
|
|
16944
16981
|
init_install_skill();
|
|
16945
16982
|
init_storybook();
|
|
16983
|
+
|
|
16984
|
+
// tools/cli/dev-build-hook.js
|
|
16985
|
+
import { register } from "node:module";
|
|
16986
|
+
function installDevBuildResolveHook() {
|
|
16987
|
+
let devUrl;
|
|
16988
|
+
try {
|
|
16989
|
+
devUrl = import.meta.resolve("tutuca/dev");
|
|
16990
|
+
} catch {
|
|
16991
|
+
return false;
|
|
16992
|
+
}
|
|
16993
|
+
const hookSource = `
|
|
16994
|
+
let devUrl;
|
|
16995
|
+
export async function initialize(data) { devUrl = data.devUrl; }
|
|
16996
|
+
export async function resolve(specifier, context, nextResolve) {
|
|
16997
|
+
return specifier === "tutuca"
|
|
16998
|
+
? { url: devUrl, shortCircuit: true }
|
|
16999
|
+
: nextResolve(specifier, context);
|
|
17000
|
+
}`;
|
|
17001
|
+
try {
|
|
17002
|
+
register(`data:text/javascript,${encodeURIComponent(hookSource)}`, import.meta.url, {
|
|
17003
|
+
data: { devUrl }
|
|
17004
|
+
});
|
|
17005
|
+
return true;
|
|
17006
|
+
} catch {
|
|
17007
|
+
return false;
|
|
17008
|
+
}
|
|
17009
|
+
}
|
|
17010
|
+
|
|
17011
|
+
// tools/tutuca.js
|
|
16946
17012
|
init_errors();
|
|
16947
17013
|
|
|
16948
17014
|
// tools/cli/with-module.js
|
|
16949
17015
|
init_env2();
|
|
17016
|
+
init_errors();
|
|
17017
|
+
import { statSync as statSync2 } from "node:fs";
|
|
16950
17018
|
import { parseArgs as parseArgs4 } from "node:util";
|
|
16951
17019
|
|
|
16952
17020
|
// tools/cli/load.js
|
|
16953
|
-
import {
|
|
17021
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
17022
|
+
import { resolve as resolve6 } from "node:path";
|
|
16954
17023
|
async function loadAndNormalize(modulePath) {
|
|
16955
|
-
const abs =
|
|
16956
|
-
|
|
17024
|
+
const abs = resolve6(modulePath);
|
|
17025
|
+
let mod;
|
|
17026
|
+
try {
|
|
17027
|
+
mod = await import(abs);
|
|
17028
|
+
} catch (e) {
|
|
17029
|
+
const entryProblem = e?.code === "ERR_UNSUPPORTED_DIR_IMPORT" || e?.code === "ERR_MODULE_NOT_FOUND" && !existsSync5(abs);
|
|
17030
|
+
if (entryProblem) {
|
|
17031
|
+
const err = new Error(e.code === "ERR_UNSUPPORTED_DIR_IMPORT" ? `expected a module file, got a directory: ${modulePath}` : `module not found: ${modulePath}`);
|
|
17032
|
+
err.code = "ERR_MODULE_LOAD_FAILED";
|
|
17033
|
+
throw err;
|
|
17034
|
+
}
|
|
17035
|
+
throw e;
|
|
17036
|
+
}
|
|
16957
17037
|
const { normalized } = normalizeModule(mod, { path: abs });
|
|
16958
17038
|
return normalized;
|
|
16959
17039
|
}
|
|
@@ -17456,15 +17536,57 @@ async function emit(result, { format: format5, pretty, output }) {
|
|
|
17456
17536
|
}
|
|
17457
17537
|
|
|
17458
17538
|
// tools/cli/with-module.js
|
|
17539
|
+
init_walk();
|
|
17459
17540
|
async function runCommand(cmd, argv, globalOpts) {
|
|
17460
17541
|
const parsed = parseArgs4({
|
|
17461
17542
|
args: argv,
|
|
17462
17543
|
options: cmd.parseOptions ?? {},
|
|
17463
17544
|
allowPositionals: true
|
|
17464
17545
|
});
|
|
17546
|
+
let stat = null;
|
|
17547
|
+
try {
|
|
17548
|
+
stat = statSync2(globalOpts.module);
|
|
17549
|
+
} catch {}
|
|
17550
|
+
if (stat?.isDirectory()) {
|
|
17551
|
+
await runOnDir(cmd, parsed, globalOpts);
|
|
17552
|
+
return;
|
|
17553
|
+
}
|
|
17465
17554
|
const normalized = await loadAndNormalize(globalOpts.module);
|
|
17466
17555
|
const env = cmd.needsEnv ? await createNodeEnv() : null;
|
|
17467
17556
|
const result = await cmd.run(normalized, parsed, env);
|
|
17557
|
+
await emitResult(cmd, result, globalOpts);
|
|
17558
|
+
}
|
|
17559
|
+
async function runOnDir(cmd, parsed, globalOpts) {
|
|
17560
|
+
if (!cmd.acceptsDir) {
|
|
17561
|
+
emitError(globalOpts, {
|
|
17562
|
+
code: CODES.MODULE_LOAD_FAILED,
|
|
17563
|
+
message: `expected a module file, got a directory: ${globalOpts.module}`,
|
|
17564
|
+
hint: "This command takes a single module file (.js). Pass a file path."
|
|
17565
|
+
});
|
|
17566
|
+
return;
|
|
17567
|
+
}
|
|
17568
|
+
const files = walkFiles(globalOpts.module, { match: cmd.dirMatch });
|
|
17569
|
+
const normalizedAll = [];
|
|
17570
|
+
for (const file of files) {
|
|
17571
|
+
const normalized = await loadAndNormalize(file);
|
|
17572
|
+
if (!cmd.dirFilter || cmd.dirFilter(normalized))
|
|
17573
|
+
normalizedAll.push(normalized);
|
|
17574
|
+
}
|
|
17575
|
+
if (normalizedAll.length === 0) {
|
|
17576
|
+
emitError(globalOpts, {
|
|
17577
|
+
code: CODES.MODULE_LOAD_FAILED,
|
|
17578
|
+
message: `no test modules found under ${globalOpts.module}`,
|
|
17579
|
+
hint: "Test modules are *.test.js / *.dev.js files that export getTests()."
|
|
17580
|
+
});
|
|
17581
|
+
return;
|
|
17582
|
+
}
|
|
17583
|
+
const env = cmd.needsEnv ? await createNodeEnv() : null;
|
|
17584
|
+
const results = [];
|
|
17585
|
+
for (const normalized of normalizedAll)
|
|
17586
|
+
results.push(await cmd.run(normalized, parsed, env));
|
|
17587
|
+
await emitResult(cmd, cmd.mergeResults(results), globalOpts);
|
|
17588
|
+
}
|
|
17589
|
+
async function emitResult(cmd, result, globalOpts) {
|
|
17468
17590
|
await emit(result, {
|
|
17469
17591
|
format: globalOpts.format ?? cmd.defaultFormat,
|
|
17470
17592
|
pretty: globalOpts.pretty,
|
|
@@ -17573,6 +17695,8 @@ async function main() {
|
|
|
17573
17695
|
opts.module = rest[1];
|
|
17574
17696
|
commandArgs = rest.slice(2);
|
|
17575
17697
|
}
|
|
17698
|
+
if (command === "test")
|
|
17699
|
+
installDevBuildResolveHook();
|
|
17576
17700
|
try {
|
|
17577
17701
|
await runCommand(cmd, commandArgs, opts);
|
|
17578
17702
|
} catch (e) {
|
|
@@ -17610,6 +17734,13 @@ async function main() {
|
|
|
17610
17734
|
hint: shape.hint ?? `Run \`tutuca help ${command}\` for valid flags.`
|
|
17611
17735
|
});
|
|
17612
17736
|
}
|
|
17737
|
+
if (e?.code === "ERR_MODULE_LOAD_FAILED") {
|
|
17738
|
+
emitError(opts, {
|
|
17739
|
+
code: CODES.MODULE_LOAD_FAILED,
|
|
17740
|
+
message: e.message,
|
|
17741
|
+
hint: "Pass a module file path. `tutuca test` also accepts a directory to run every test module under it."
|
|
17742
|
+
});
|
|
17743
|
+
}
|
|
17613
17744
|
throw e;
|
|
17614
17745
|
}
|
|
17615
17746
|
}
|
package/dist/tutuca-dev.ext.js
CHANGED
|
@@ -1842,37 +1842,47 @@ class Renderer {
|
|
|
1842
1842
|
renderEach(stack, iterInfo, node, viewName) {
|
|
1843
1843
|
const { seq, filter, loopWith } = iterInfo.eval(stack);
|
|
1844
1844
|
const r = [];
|
|
1845
|
-
const { iterData, start, end } = unpackLoopResult(loopWith.call(stack.it, seq), seq);
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1845
|
+
const { iterData, start, end, keys } = unpackLoopResult(loopWith.call(stack.it, seq, makeLoopCtx(stack, filter)), seq);
|
|
1846
|
+
const renderOne = (key, value, attrName) => {
|
|
1847
|
+
const dom = this.renderIt(stack.enter(value, { key }, true), node, key, viewName);
|
|
1848
|
+
this.pushEachEntry(r, node.nodeId, attrName, key, dom);
|
|
1849
|
+
};
|
|
1850
|
+
if (keys)
|
|
1851
|
+
imKeysIter(seq, renderOne, keys);
|
|
1852
|
+
else
|
|
1853
|
+
getSeqInfo(seq)(seq, (key, value, attrName) => {
|
|
1854
|
+
if (filter.call(stack.it, key, value, iterData))
|
|
1855
|
+
renderOne(key, value, attrName);
|
|
1856
|
+
}, start, end);
|
|
1852
1857
|
return r;
|
|
1853
1858
|
}
|
|
1854
1859
|
renderEachWhen(stack, iterInfo, view, nid) {
|
|
1855
1860
|
const { seq, filter, loopWith, enricher } = iterInfo.eval(stack);
|
|
1856
1861
|
const r = [];
|
|
1857
1862
|
const it = stack.it;
|
|
1858
|
-
const { iterData, start, end } = unpackLoopResult(loopWith.call(it, seq), seq);
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
this.cache.set(cachePath, cacheKey, dom);
|
|
1873
|
-
}
|
|
1863
|
+
const { iterData, start, end, keys } = unpackLoopResult(loopWith.call(it, seq, makeLoopCtx(stack, filter)), seq);
|
|
1864
|
+
const renderOne = (key, value, attrName) => {
|
|
1865
|
+
const cachePath = enricher ? [view, it, value] : [view, value];
|
|
1866
|
+
const binds = { key, value };
|
|
1867
|
+
const cacheKey = `${nid}-${key}`;
|
|
1868
|
+
if (enricher)
|
|
1869
|
+
enricher.call(it, binds, key, value, iterData);
|
|
1870
|
+
const cachedNode = this.cache.get(cachePath, cacheKey);
|
|
1871
|
+
if (cachedNode)
|
|
1872
|
+
this.pushEachEntry(r, nid, attrName, key, cachedNode);
|
|
1873
|
+
else {
|
|
1874
|
+
const dom = this.renderView(view, stack.enter(value, binds, false));
|
|
1875
|
+
this.pushEachEntry(r, nid, attrName, key, dom);
|
|
1876
|
+
this.cache.set(cachePath, cacheKey, dom);
|
|
1874
1877
|
}
|
|
1875
|
-
}
|
|
1878
|
+
};
|
|
1879
|
+
if (keys)
|
|
1880
|
+
imKeysIter(seq, renderOne, keys);
|
|
1881
|
+
else
|
|
1882
|
+
getSeqInfo(seq)(seq, (key, value, attrName) => {
|
|
1883
|
+
if (filter.call(it, key, value, iterData))
|
|
1884
|
+
renderOne(key, value, attrName);
|
|
1885
|
+
}, start, end);
|
|
1876
1886
|
return r;
|
|
1877
1887
|
}
|
|
1878
1888
|
renderView(view, stack) {
|
|
@@ -1908,8 +1918,17 @@ var filterAlwaysTrue = (_v, _k, _seq) => true;
|
|
|
1908
1918
|
var nullLoopWith = (seq) => ({ iterData: { seq } });
|
|
1909
1919
|
var unpackLoopResult = (result, seq) => {
|
|
1910
1920
|
const r = result ?? {};
|
|
1911
|
-
return { iterData: r.iterData ?? { seq }, start: r.start, end: r.end };
|
|
1921
|
+
return { iterData: r.iterData ?? { seq }, start: r.start, end: r.end, keys: r.keys };
|
|
1912
1922
|
};
|
|
1923
|
+
var imKeysIter = (seq, visit, keys) => {
|
|
1924
|
+
const attrName = isIndexed(seq) ? "si" : "sk";
|
|
1925
|
+
for (const key of keys)
|
|
1926
|
+
visit(key, seq.get(key), attrName);
|
|
1927
|
+
};
|
|
1928
|
+
var makeLoopCtx = (stack, filter) => ({
|
|
1929
|
+
lookup: (name) => stack.lookupBind(name),
|
|
1930
|
+
filter: (key, value, iterData) => filter.call(stack.it, key, value, iterData)
|
|
1931
|
+
});
|
|
1913
1932
|
var imIndexedIter = (seq, visit, start, end) => {
|
|
1914
1933
|
const [s, e] = normalizeRange(start, end, seq.size);
|
|
1915
1934
|
for (let i = s;i < e; i++)
|
|
@@ -2457,11 +2476,11 @@ class IterInfo {
|
|
|
2457
2476
|
return { seq, filter, loopWith, enricher };
|
|
2458
2477
|
}
|
|
2459
2478
|
enrichBinds(stack, key) {
|
|
2460
|
-
const { seq, loopWith, enricher } = this.eval(stack);
|
|
2479
|
+
const { seq, filter, loopWith, enricher } = this.eval(stack);
|
|
2461
2480
|
const value = seq?.get ? seq.get(key, null) : null;
|
|
2462
2481
|
const binds = { key, value };
|
|
2463
2482
|
if (enricher) {
|
|
2464
|
-
const { iterData } = unpackLoopResult(loopWith.call(stack.it, seq), seq);
|
|
2483
|
+
const { iterData } = unpackLoopResult(loopWith.call(stack.it, seq, makeLoopCtx(stack, filter)), seq);
|
|
2465
2484
|
enricher.call(stack.it, binds, key, value, iterData);
|
|
2466
2485
|
}
|
|
2467
2486
|
return binds;
|
|
@@ -6040,6 +6059,13 @@ function phaseOps(phase) {
|
|
|
6040
6059
|
function resolveArgs(args, self) {
|
|
6041
6060
|
return typeof args === "function" ? args(self) ?? [] : args ?? [];
|
|
6042
6061
|
}
|
|
6062
|
+
function phaseHasBubble(phase) {
|
|
6063
|
+
if (!phase)
|
|
6064
|
+
return false;
|
|
6065
|
+
if (phase.bubble?.length)
|
|
6066
|
+
return true;
|
|
6067
|
+
return (phase.do ?? []).some((op) => op.type === "bubble");
|
|
6068
|
+
}
|
|
6043
6069
|
function dispatchPhase(dispatcher, targetPath, phase, self) {
|
|
6044
6070
|
if (!phase)
|
|
6045
6071
|
return;
|
|
@@ -6976,6 +7002,8 @@ async function driveStack(stack, value, phase, opts = {}) {
|
|
|
6976
7002
|
const t = info?.transaction;
|
|
6977
7003
|
opts.onMessage({ kind: t?.handlerProp ?? "input", name: t?.name, args: t?.args, path: t?.path }, old, val);
|
|
6978
7004
|
});
|
|
7005
|
+
if (phaseHasBubble(phase))
|
|
7006
|
+
console.warn("drive(): a `bubble` action is a no-op here — drive originates at the root and bubbles travel child→parent, so there is no ancestor to receive it (and the root's own bubble handler is skipped). To exercise a bubble handler, call it directly.");
|
|
6979
7007
|
dispatchPhase(rootDispatcher(transactor), new Path([]), phase, value);
|
|
6980
7008
|
await transactor.settle();
|
|
6981
7009
|
return transactor.state.val;
|
|
@@ -7964,7 +7992,8 @@ class FieldSet extends Field {
|
|
|
7964
7992
|
}
|
|
7965
7993
|
function mkCompField(field, scope, args) {
|
|
7966
7994
|
const Comp = scope?.lookupComponent(field.type) ?? null;
|
|
7967
|
-
|
|
7995
|
+
if (Comp === null)
|
|
7996
|
+
console.warn(scope ? `component field "${field.name}": component "${field.type}" not found in scope` : `component field "${field.name}": cannot resolve component "${field.type}" — built without a registered scope (use ${field.type}.make({}) as the default, or build via a registered component)`);
|
|
7968
7997
|
return Comp?.make({ ...field.args, ...args }, { scope }) ?? null;
|
|
7969
7998
|
}
|
|
7970
7999
|
|
|
@@ -8138,13 +8167,30 @@ function resolveAlter(Comp, name) {
|
|
|
8138
8167
|
}
|
|
8139
8168
|
return fn;
|
|
8140
8169
|
}
|
|
8170
|
+
var seqGet = (seq, key) => Array.isArray(seq) ? seq[key] : seq.get ? seq.get(key) : seq[key];
|
|
8141
8171
|
function collectIterBindings(Comp, compInstance, seq, opts = {}) {
|
|
8142
8172
|
const whenFn = resolveAlter(Comp, opts.when) ?? filterAlwaysTrue;
|
|
8143
8173
|
const loopWithFn = resolveAlter(Comp, opts.loopWith) ?? nullLoopWith;
|
|
8144
8174
|
const enrichFn = resolveAlter(Comp, opts.enrichWith);
|
|
8175
|
+
const scopeEnrichFn = resolveAlter(Comp, opts.scopeEnrich);
|
|
8145
8176
|
const it = compInstance;
|
|
8146
|
-
const
|
|
8177
|
+
const scope = scopeEnrichFn ? scopeEnrichFn.call(it) ?? {} : opts.scope ?? {};
|
|
8178
|
+
const ctx = {
|
|
8179
|
+
lookup: (name) => scope[name],
|
|
8180
|
+
filter: (key, value, iterData2) => whenFn.call(it, key, value, iterData2)
|
|
8181
|
+
};
|
|
8182
|
+
const { iterData, start, end, keys } = unpackLoopResult(loopWithFn.call(it, seq, ctx), seq);
|
|
8147
8183
|
const out = [];
|
|
8184
|
+
if (keys) {
|
|
8185
|
+
for (const key of keys) {
|
|
8186
|
+
const value = seqGet(seq, key);
|
|
8187
|
+
const binds = { key, value };
|
|
8188
|
+
if (enrichFn)
|
|
8189
|
+
enrichFn.call(it, binds, key, value, iterData);
|
|
8190
|
+
out.push(binds);
|
|
8191
|
+
}
|
|
8192
|
+
return out;
|
|
8193
|
+
}
|
|
8148
8194
|
const iter = pickIter(seq);
|
|
8149
8195
|
iter(seq, (key, value) => {
|
|
8150
8196
|
if (!whenFn.call(it, key, value, iterData))
|
|
@@ -8408,6 +8454,7 @@ export {
|
|
|
8408
8454
|
removeIn,
|
|
8409
8455
|
remove,
|
|
8410
8456
|
phaseOps,
|
|
8457
|
+
phaseHasBubble,
|
|
8411
8458
|
mergeWith,
|
|
8412
8459
|
mergeDeepWith,
|
|
8413
8460
|
mergeDeep,
|