feat: add top-level ocas tag/untag commands, remove var tag
Adds `ocas tag <target> <tag>...` and `ocas untag <target> <tag>...` top-level CLI commands operating on store.tag.* (TagStore). Targets may be hashes or @scope/name variables (resolved via resolveHash). The redundant `ocas var tag` subcommand is removed; `var tag` now falls through to "Unknown var subcommand: tag". Registers `@ocas/output/tag` and `@ocas/output/untag` schemas and templates in bootstrap; removes `@ocas/output/var-tag`. Closes #52 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+53
-61
@@ -3,13 +3,12 @@
|
|||||||
import { existsSync, readFileSync } from "node:fs";
|
import { existsSync, readFileSync } from "node:fs";
|
||||||
import { homedir } from "node:os";
|
import { homedir } from "node:os";
|
||||||
import { join, resolve } from "node:path";
|
import { join, resolve } from "node:path";
|
||||||
import type { Hash, ListOptions, Store } from "@ocas/core";
|
import type { Hash, ListOptions, Store, TagOp } from "@ocas/core";
|
||||||
import {
|
import {
|
||||||
CasNodeNotFoundError,
|
CasNodeNotFoundError,
|
||||||
computeHash,
|
computeHash,
|
||||||
gc,
|
gc,
|
||||||
getSchema,
|
getSchema,
|
||||||
InvalidTagFormatError,
|
|
||||||
InvalidVariableNameError,
|
InvalidVariableNameError,
|
||||||
putSchema,
|
putSchema,
|
||||||
refs,
|
refs,
|
||||||
@@ -685,64 +684,51 @@ async function cmdVarDelete(args: string[]): Promise<void> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function cmdVarTag(args: string[]): Promise<void> {
|
async function cmdTag(args: string[]): Promise<void> {
|
||||||
const name = args[0];
|
const targetInput = args[0];
|
||||||
const schemaInput = flags.schema as string | undefined;
|
|
||||||
|
|
||||||
if (!name || !schemaInput) {
|
|
||||||
die("Usage: ocas var tag <name> --schema <hash-or-name> <operations...>");
|
|
||||||
}
|
|
||||||
|
|
||||||
const tagArgs = args.slice(1);
|
const tagArgs = args.slice(1);
|
||||||
if (tagArgs.length === 0) {
|
if (!targetInput || tagArgs.length === 0) {
|
||||||
die("Usage: ocas var tag <name> --schema <hash-or-name> <operations...>");
|
die("Usage: ocas tag <target> <tag>...");
|
||||||
}
|
}
|
||||||
|
|
||||||
const store = await openStore();
|
const store = await openStore();
|
||||||
|
const target = resolveHash(targetInput, store);
|
||||||
try {
|
const { tags, labels, deleteNames } = parseTagsLabels(tagArgs);
|
||||||
const schema = resolveHash(schemaInput, store);
|
if (deleteNames.length > 0) {
|
||||||
const { tags, labels, deleteNames } = parseTagsLabels(tagArgs);
|
die("Error: Cannot use deletion syntax (:name) in tag (use untag)");
|
||||||
|
|
||||||
// VarStore.set with options replaces all tags/labels — to express
|
|
||||||
// "add some / delete some / preserve the rest", merge against the current.
|
|
||||||
const existing = store.var.get(name, schema);
|
|
||||||
if (existing === null) {
|
|
||||||
throw new VariableNotFoundError(name, schema);
|
|
||||||
}
|
|
||||||
const newTags: Record<string, string> = { ...existing.tags };
|
|
||||||
const newLabels: string[] = [...existing.labels];
|
|
||||||
for (const k of deleteNames) {
|
|
||||||
delete newTags[k];
|
|
||||||
const idx = newLabels.indexOf(k);
|
|
||||||
if (idx !== -1) newLabels.splice(idx, 1);
|
|
||||||
}
|
|
||||||
for (const [k, v] of Object.entries(tags)) {
|
|
||||||
newTags[k] = v;
|
|
||||||
}
|
|
||||||
for (const lb of labels) {
|
|
||||||
if (!newLabels.includes(lb)) newLabels.push(lb);
|
|
||||||
}
|
|
||||||
|
|
||||||
const variable = store.var.set(name, existing.value, {
|
|
||||||
tags: newTags,
|
|
||||||
labels: newLabels,
|
|
||||||
});
|
|
||||||
|
|
||||||
await out(
|
|
||||||
await wrapEnvelope(store, "@ocas/output/var-tag", variable),
|
|
||||||
store,
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
if (
|
|
||||||
e instanceof VariableNotFoundError ||
|
|
||||||
e instanceof TagLabelConflictError ||
|
|
||||||
e instanceof InvalidTagFormatError
|
|
||||||
) {
|
|
||||||
die(`Error: ${e.message}`);
|
|
||||||
}
|
|
||||||
throw e;
|
|
||||||
}
|
}
|
||||||
|
const ops: TagOp[] = [
|
||||||
|
...Object.entries(tags).map(
|
||||||
|
([key, value]) => ({ op: "set", key, value }) as TagOp,
|
||||||
|
),
|
||||||
|
...labels.map((key) => ({ op: "set", key }) as TagOp),
|
||||||
|
];
|
||||||
|
store.tag.tag(target, ops);
|
||||||
|
await out(
|
||||||
|
await wrapEnvelope(store, "@ocas/output/tag", store.tag.tags(target)),
|
||||||
|
store,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function cmdUntag(args: string[]): Promise<void> {
|
||||||
|
const targetInput = args[0];
|
||||||
|
const tagArgs = args.slice(1);
|
||||||
|
if (!targetInput || tagArgs.length === 0) {
|
||||||
|
die("Usage: ocas untag <target> <tag>...");
|
||||||
|
}
|
||||||
|
const store = await openStore();
|
||||||
|
const target = resolveHash(targetInput, store);
|
||||||
|
const keys = tagArgs.map((a) =>
|
||||||
|
a.startsWith(":")
|
||||||
|
? a.slice(1)
|
||||||
|
: a.includes(":")
|
||||||
|
? a.slice(0, a.indexOf(":"))
|
||||||
|
: a,
|
||||||
|
);
|
||||||
|
store.tag.untag(target, keys);
|
||||||
|
await out(
|
||||||
|
await wrapEnvelope(store, "@ocas/output/untag", store.tag.tags(target)),
|
||||||
|
store,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function cmdVarHistory(args: string[]): Promise<void> {
|
async function cmdVarHistory(args: string[]): Promise<void> {
|
||||||
@@ -1043,12 +1029,13 @@ Commands:
|
|||||||
render --pipe/-p [options] Render { type, value } from stdin (raw output)
|
render --pipe/-p [options] Render { type, value } from stdin (raw output)
|
||||||
list --type <hash-or-name> List hashes for a type (value=string[]) (@ocas/output/list)
|
list --type <hash-or-name> List hashes for a type (value=string[]) (@ocas/output/list)
|
||||||
list-meta List meta-schema hashes (value=string[]) (@ocas/output/list-meta)
|
list-meta List meta-schema hashes (value=string[]) (@ocas/output/list-meta)
|
||||||
list-schema List all schema hashes (value=string[]) (@ocas/output/list-schema)
|
list-schema List all schema hashes (value=string[]) (@ocas/output/list-schema)
|
||||||
|
tag <target> <tag>... Apply tags/labels to a target (@ocas/output/tag)
|
||||||
|
untag <target> <tag>... Remove tags/labels from a target (@ocas/output/untag)
|
||||||
var set <name> <hash> [--tag <tag>...] Create/update a variable (@ocas/output/var-set)
|
var set <name> <hash> [--tag <tag>...] Create/update a variable (@ocas/output/var-set)
|
||||||
var get <name> --schema <hash> Get a variable by name + schema (@ocas/output/var-get)
|
var get <name> --schema <hash> Get a variable by name + schema (@ocas/output/var-get)
|
||||||
var delete <name> [--schema <hash>] Delete variable(s) (@ocas/output/var-delete)
|
var delete <name> [--schema <hash>] Delete variable(s) (@ocas/output/var-delete)
|
||||||
var list [prefix] [--schema <hash>] [--tag <tag>...] List variables (@ocas/output/var-list)
|
var list [prefix] [--schema <hash>] [--tag <tag>...] List variables (@ocas/output/var-list)
|
||||||
var tag <name> --schema <hash> <operations...> Modify tags/labels (@ocas/output/var-tag)
|
|
||||||
var history <name> [--schema <hash>] Show value history (LRU) (@ocas/output/var-history)
|
var history <name> [--schema <hash>] Show value history (LRU) (@ocas/output/var-history)
|
||||||
template set <schema-hash> <file> | --inline <text> Set template for schema (@ocas/output/template-set)
|
template set <schema-hash> <file> | --inline <text> Set template for schema (@ocas/output/template-set)
|
||||||
template get <schema-hash> Get template content (value=string) (@ocas/output/template-get)
|
template get <schema-hash> Get template content (value=string) (@ocas/output/template-get)
|
||||||
@@ -1125,6 +1112,14 @@ switch (cmd) {
|
|||||||
await cmdListSchema(rest);
|
await cmdListSchema(rest);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case "tag":
|
||||||
|
await cmdTag(rest);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "untag":
|
||||||
|
await cmdUntag(rest);
|
||||||
|
break;
|
||||||
|
|
||||||
case "var": {
|
case "var": {
|
||||||
const [sub, ...subRest] = rest;
|
const [sub, ...subRest] = rest;
|
||||||
switch (sub) {
|
switch (sub) {
|
||||||
@@ -1137,9 +1132,6 @@ switch (cmd) {
|
|||||||
case "delete":
|
case "delete":
|
||||||
await cmdVarDelete(subRest);
|
await cmdVarDelete(subRest);
|
||||||
break;
|
break;
|
||||||
case "tag":
|
|
||||||
await cmdVarTag(subRest);
|
|
||||||
break;
|
|
||||||
case "list":
|
case "list":
|
||||||
await cmdVarList(subRest);
|
await cmdVarList(subRest);
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -103,8 +103,8 @@ ocas var delete @myapp/config # remove
|
|||||||
ocas var list [prefix] # list (prefix filter)
|
ocas var list [prefix] # list (prefix filter)
|
||||||
ocas var list @myapp/ --tag env:prod # filter by scope + tag
|
ocas var list @myapp/ --tag env:prod # filter by scope + tag
|
||||||
ocas var history @myapp/config # last 10 values (LRU)
|
ocas var history @myapp/config # last 10 values (LRU)
|
||||||
ocas var tag @myapp/config --schema <h> status:active # add tag
|
ocas tag @myapp/config status:active # add tag/label to a target
|
||||||
ocas var tag @myapp/config --schema <h> :status # remove tag
|
ocas untag @myapp/config status # remove tag/label by key
|
||||||
```
|
```
|
||||||
|
|
||||||
**Naming rules:**
|
**Naming rules:**
|
||||||
|
|||||||
@@ -25,12 +25,13 @@ Commands:
|
|||||||
render --pipe/-p [options] Render { type, value } from stdin (raw output)
|
render --pipe/-p [options] Render { type, value } from stdin (raw output)
|
||||||
list --type <hash-or-name> List hashes for a type (value=string[]) (@ocas/output/list)
|
list --type <hash-or-name> List hashes for a type (value=string[]) (@ocas/output/list)
|
||||||
list-meta List meta-schema hashes (value=string[]) (@ocas/output/list-meta)
|
list-meta List meta-schema hashes (value=string[]) (@ocas/output/list-meta)
|
||||||
list-schema List all schema hashes (value=string[]) (@ocas/output/list-schema)
|
list-schema List all schema hashes (value=string[]) (@ocas/output/list-schema)
|
||||||
|
tag <target> <tag>... Apply tags/labels to a target (@ocas/output/tag)
|
||||||
|
untag <target> <tag>... Remove tags/labels from a target (@ocas/output/untag)
|
||||||
var set <name> <hash> [--tag <tag>...] Create/update a variable (@ocas/output/var-set)
|
var set <name> <hash> [--tag <tag>...] Create/update a variable (@ocas/output/var-set)
|
||||||
var get <name> --schema <hash> Get a variable by name + schema (@ocas/output/var-get)
|
var get <name> --schema <hash> Get a variable by name + schema (@ocas/output/var-get)
|
||||||
var delete <name> [--schema <hash>] Delete variable(s) (@ocas/output/var-delete)
|
var delete <name> [--schema <hash>] Delete variable(s) (@ocas/output/var-delete)
|
||||||
var list [prefix] [--schema <hash>] [--tag <tag>...] List variables (@ocas/output/var-list)
|
var list [prefix] [--schema <hash>] [--tag <tag>...] List variables (@ocas/output/var-list)
|
||||||
var tag <name> --schema <hash> <operations...> Modify tags/labels (@ocas/output/var-tag)
|
|
||||||
var history <name> [--schema <hash>] Show value history (LRU) (@ocas/output/var-history)
|
var history <name> [--schema <hash>] Show value history (LRU) (@ocas/output/var-history)
|
||||||
template set <schema-hash> <file> | --inline <text> Set template for schema (@ocas/output/template-set)
|
template set <schema-hash> <file> | --inline <text> Set template for schema (@ocas/output/template-set)
|
||||||
template get <schema-hash> Get template content (value=string) (@ocas/output/template-get)
|
template get <schema-hash> Get template content (value=string) (@ocas/output/template-get)
|
||||||
@@ -202,6 +203,13 @@ exports[`Phase 3: Variable System 3.3 var list shows all variables 1`] = `
|
|||||||
"tags": {},
|
"tags": {},
|
||||||
"value": "2TKP4RGBJ4V43",
|
"value": "2TKP4RGBJ4V43",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"labels": [],
|
||||||
|
"name": "@ocas/output/tag",
|
||||||
|
"schema": "CTS5P6RD8HMCS",
|
||||||
|
"tags": {},
|
||||||
|
"value": "CPSWA9TB2JMWP",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"labels": [],
|
"labels": [],
|
||||||
"name": "@ocas/output/template-delete",
|
"name": "@ocas/output/template-delete",
|
||||||
@@ -230,6 +238,13 @@ exports[`Phase 3: Variable System 3.3 var list shows all variables 1`] = `
|
|||||||
"tags": {},
|
"tags": {},
|
||||||
"value": "BJDHPAE4Q8TXM",
|
"value": "BJDHPAE4Q8TXM",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"labels": [],
|
||||||
|
"name": "@ocas/output/untag",
|
||||||
|
"schema": "CTS5P6RD8HMCS",
|
||||||
|
"tags": {},
|
||||||
|
"value": "BPEQMRQNJK80Z",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"labels": [],
|
"labels": [],
|
||||||
"name": "@ocas/output/var-delete",
|
"name": "@ocas/output/var-delete",
|
||||||
@@ -265,13 +280,6 @@ exports[`Phase 3: Variable System 3.3 var list shows all variables 1`] = `
|
|||||||
"tags": {},
|
"tags": {},
|
||||||
"value": "0Q5EMYK4SYSS9",
|
"value": "0Q5EMYK4SYSS9",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"labels": [],
|
|
||||||
"name": "@ocas/output/var-tag",
|
|
||||||
"schema": "CTS5P6RD8HMCS",
|
|
||||||
"tags": {},
|
|
||||||
"value": "9103EYRMM949A",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"labels": [],
|
"labels": [],
|
||||||
"name": "@ocas/output/verify",
|
"name": "@ocas/output/verify",
|
||||||
@@ -332,9 +340,9 @@ exports[`Phase 3: Variable System 3.5 var set upsert updates existing variable 1
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`Phase 3: Variable System 3.6 var tag adds kv tag and label 1`] = `
|
exports[`Phase 3: Variable System 3.6 var set with tag and label adds them 1`] = `
|
||||||
{
|
{
|
||||||
"type": "9103EYRMM949A",
|
"type": "0Q5EMYK4SYSS9",
|
||||||
"value": {
|
"value": {
|
||||||
"labels": [
|
"labels": [
|
||||||
"important",
|
"important",
|
||||||
@@ -387,9 +395,9 @@ exports[`Phase 3: Variable System 3.8 var list --tag important filters by label
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`Phase 3: Variable System 3.9 var tag remove deletes label 1`] = `
|
exports[`Phase 3: Variable System 3.9 var set without label removes it 1`] = `
|
||||||
{
|
{
|
||||||
"type": "9103EYRMM949A",
|
"type": "0Q5EMYK4SYSS9",
|
||||||
"value": {
|
"value": {
|
||||||
"labels": [],
|
"labels": [],
|
||||||
"name": "@myapp/config",
|
"name": "@myapp/config",
|
||||||
|
|||||||
@@ -246,14 +246,15 @@ describe("Phase 3: Variable System", () => {
|
|||||||
await runCli(["var", "set", "@myapp/config", nodeHash]);
|
await runCli(["var", "set", "@myapp/config", nodeHash]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("3.6 var tag adds kv tag and label", async () => {
|
test("3.6 var set with tag and label adds them", async () => {
|
||||||
const { exitCode, stdout } = await runCli([
|
const { exitCode, stdout } = await runCli([
|
||||||
"var",
|
"var",
|
||||||
"tag",
|
"set",
|
||||||
"@myapp/config",
|
"@myapp/config",
|
||||||
"--schema",
|
nodeHash,
|
||||||
typeHash,
|
"--tag",
|
||||||
"env:prod",
|
"env:prod",
|
||||||
|
"--tag",
|
||||||
"important",
|
"important",
|
||||||
]);
|
]);
|
||||||
expect(exitCode).toBe(0);
|
expect(exitCode).toBe(0);
|
||||||
@@ -284,14 +285,14 @@ describe("Phase 3: Variable System", () => {
|
|||||||
expect(stripVolatile(stdout)).toMatchSnapshot();
|
expect(stripVolatile(stdout)).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("3.9 var tag remove deletes label", async () => {
|
test("3.9 var set without label removes it", async () => {
|
||||||
const { exitCode, stdout } = await runCli([
|
const { exitCode, stdout } = await runCli([
|
||||||
"var",
|
"var",
|
||||||
"tag",
|
"set",
|
||||||
"@myapp/config",
|
"@myapp/config",
|
||||||
"--schema",
|
nodeHash,
|
||||||
typeHash,
|
"--tag",
|
||||||
":important",
|
"env:prod",
|
||||||
]);
|
]);
|
||||||
expect(exitCode).toBe(0);
|
expect(exitCode).toBe(0);
|
||||||
expect(stripVolatile(stdout)).toMatchSnapshot();
|
expect(stripVolatile(stdout)).toMatchSnapshot();
|
||||||
|
|||||||
@@ -0,0 +1,235 @@
|
|||||||
|
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
||||||
|
import { mkdirSync, rmSync } from "node:fs";
|
||||||
|
import { tmpdir } from "node:os";
|
||||||
|
import { join } from "node:path";
|
||||||
|
import type { Hash } from "@ocas/core";
|
||||||
|
import { bootstrap } from "@ocas/core";
|
||||||
|
import { openStore as openFsStore } from "@ocas/fs";
|
||||||
|
|
||||||
|
let testDir: string;
|
||||||
|
let storePath: string;
|
||||||
|
let cliPath: string;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
testDir = join(
|
||||||
|
tmpdir(),
|
||||||
|
`ocas-tag-test-${Date.now()}-${Math.random().toString(36).slice(2)}`,
|
||||||
|
);
|
||||||
|
storePath = join(testDir, "store");
|
||||||
|
cliPath = join(import.meta.dir, "../src/index.ts");
|
||||||
|
mkdirSync(testDir, { recursive: true });
|
||||||
|
mkdirSync(storePath, { recursive: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
try {
|
||||||
|
rmSync(testDir, { recursive: true, force: true });
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
async function runCli(...args: string[]): Promise<{
|
||||||
|
stdout: string;
|
||||||
|
stderr: string;
|
||||||
|
exitCode: number;
|
||||||
|
}> {
|
||||||
|
const proc = Bun.spawn(
|
||||||
|
["bun", "run", cliPath, "--home", storePath, ...args],
|
||||||
|
{ stdout: "pipe", stderr: "pipe" },
|
||||||
|
);
|
||||||
|
const [stdout, stderr] = await Promise.all([
|
||||||
|
new Response(proc.stdout).text(),
|
||||||
|
new Response(proc.stderr).text(),
|
||||||
|
]);
|
||||||
|
await proc.exited;
|
||||||
|
return {
|
||||||
|
stdout: stdout.trim(),
|
||||||
|
stderr: stderr.trim(),
|
||||||
|
exitCode: proc.exitCode ?? 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createTestNode(): Promise<Hash> {
|
||||||
|
const store = await openFsStore(storePath);
|
||||||
|
const aliases = bootstrap(store);
|
||||||
|
const typeHash = aliases["@ocas/string"];
|
||||||
|
if (!typeHash) throw new Error("@ocas/string not found");
|
||||||
|
const hash = store.cas.put(typeHash, `test-value-${Math.random()}`);
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function readTags(target: Hash) {
|
||||||
|
const store = await openFsStore(storePath);
|
||||||
|
return store.tag.tags(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("ocas tag", () => {
|
||||||
|
test("Test 1: tag <hash> applies key:value tags and labels", async () => {
|
||||||
|
const hash = await createTestNode();
|
||||||
|
|
||||||
|
const { stdout, exitCode } = await runCli(
|
||||||
|
"tag",
|
||||||
|
hash,
|
||||||
|
"env:prod",
|
||||||
|
"stable",
|
||||||
|
);
|
||||||
|
expect(exitCode).toBe(0);
|
||||||
|
|
||||||
|
const envelope = JSON.parse(stdout);
|
||||||
|
expect(Array.isArray(envelope.value)).toBe(true);
|
||||||
|
const value = envelope.value as Array<{
|
||||||
|
key: string;
|
||||||
|
value: string | null;
|
||||||
|
target: string;
|
||||||
|
}>;
|
||||||
|
const byKey = (k: string) => value.find((t) => t.key === k);
|
||||||
|
expect(byKey("env")).toMatchObject({
|
||||||
|
key: "env",
|
||||||
|
value: "prod",
|
||||||
|
target: hash,
|
||||||
|
});
|
||||||
|
expect(byKey("stable")).toMatchObject({
|
||||||
|
key: "stable",
|
||||||
|
value: null,
|
||||||
|
target: hash,
|
||||||
|
});
|
||||||
|
|
||||||
|
const tags = await readTags(hash);
|
||||||
|
expect(tags.find((t) => t.key === "env")?.value).toBe("prod");
|
||||||
|
expect(tags.find((t) => t.key === "stable")?.value).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Test 2: tag @scope/name resolves variable to its value hash", async () => {
|
||||||
|
const hash = await createTestNode();
|
||||||
|
|
||||||
|
await runCli("var", "set", "@user/foo", hash);
|
||||||
|
const { exitCode } = await runCli("tag", "@user/foo", "reviewed");
|
||||||
|
expect(exitCode).toBe(0);
|
||||||
|
|
||||||
|
const tagsOnHash = await readTags(hash);
|
||||||
|
expect(tagsOnHash.find((t) => t.key === "reviewed")).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Test 3: untag <hash> env removes tag by key", async () => {
|
||||||
|
const hash = await createTestNode();
|
||||||
|
|
||||||
|
await runCli("tag", hash, "env:prod", "stable");
|
||||||
|
const { stdout, exitCode } = await runCli("untag", hash, "env");
|
||||||
|
expect(exitCode).toBe(0);
|
||||||
|
|
||||||
|
const envelope = JSON.parse(stdout);
|
||||||
|
const keys = (envelope.value as Array<{ key: string }>).map((t) => t.key);
|
||||||
|
expect(keys).toContain("stable");
|
||||||
|
expect(keys).not.toContain("env");
|
||||||
|
|
||||||
|
const remaining = await readTags(hash);
|
||||||
|
expect(remaining.map((t) => t.key)).toEqual(["stable"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Test 4: untag accepts key:value form (uses key only)", async () => {
|
||||||
|
const hash = await createTestNode();
|
||||||
|
|
||||||
|
await runCli("tag", hash, "env:prod");
|
||||||
|
const { exitCode } = await runCli("untag", hash, "env:prod");
|
||||||
|
expect(exitCode).toBe(0);
|
||||||
|
|
||||||
|
expect(await readTags(hash)).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Test 5: untag removes labels", async () => {
|
||||||
|
const hash = await createTestNode();
|
||||||
|
|
||||||
|
await runCli("tag", hash, "pinned");
|
||||||
|
const { exitCode } = await runCli("untag", hash, "pinned");
|
||||||
|
expect(exitCode).toBe(0);
|
||||||
|
|
||||||
|
expect(await readTags(hash)).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Test 6: tag without tag args errors", async () => {
|
||||||
|
const hash = await createTestNode();
|
||||||
|
const { stderr, exitCode } = await runCli("tag", hash);
|
||||||
|
expect(exitCode).not.toBe(0);
|
||||||
|
expect(stderr).toContain("Usage: ocas tag <target> <tag>...");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Test 7: tag with no args errors", async () => {
|
||||||
|
const { stderr, exitCode } = await runCli("tag");
|
||||||
|
expect(exitCode).not.toBe(0);
|
||||||
|
expect(stderr).toContain("Usage:");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Test 8: untag missing args errors", async () => {
|
||||||
|
const hash = await createTestNode();
|
||||||
|
|
||||||
|
const r1 = await runCli("untag");
|
||||||
|
expect(r1.exitCode).not.toBe(0);
|
||||||
|
|
||||||
|
const r2 = await runCli("untag", hash);
|
||||||
|
expect(r2.exitCode).not.toBe(0);
|
||||||
|
expect(r2.stderr).toContain("Usage: ocas untag <target> <tag>...");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Test 9: tag with unknown variable name errors", async () => {
|
||||||
|
const { stderr, exitCode } = await runCli("tag", "@user/missing", "label");
|
||||||
|
expect(exitCode).not.toBe(0);
|
||||||
|
expect(stderr).toContain("Schema not found: @user/missing");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Test 10: var tag is removed", async () => {
|
||||||
|
const { stderr, exitCode } = await runCli(
|
||||||
|
"var",
|
||||||
|
"tag",
|
||||||
|
"@any/name",
|
||||||
|
"--schema",
|
||||||
|
"@ocas/string",
|
||||||
|
"foo",
|
||||||
|
);
|
||||||
|
expect(exitCode).not.toBe(0);
|
||||||
|
expect(stderr).toContain("Unknown var subcommand: tag");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Test 11: envelope schema name is @ocas/output/tag and @ocas/output/untag", async () => {
|
||||||
|
const hash = await createTestNode();
|
||||||
|
const store = await openFsStore(storePath);
|
||||||
|
const aliases = bootstrap(store);
|
||||||
|
const tagSchemaHash = aliases["@ocas/output/tag"];
|
||||||
|
const untagSchemaHash = aliases["@ocas/output/untag"];
|
||||||
|
expect(tagSchemaHash).toBeDefined();
|
||||||
|
expect(untagSchemaHash).toBeDefined();
|
||||||
|
|
||||||
|
const r1 = await runCli("tag", hash, "stable");
|
||||||
|
const env1 = JSON.parse(r1.stdout);
|
||||||
|
expect(env1.type).toBe(tagSchemaHash);
|
||||||
|
|
||||||
|
const r2 = await runCli("untag", hash, "stable");
|
||||||
|
const env2 = JSON.parse(r2.stdout);
|
||||||
|
expect(env2.type).toBe(untagSchemaHash);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Test 12: idempotent re-tag updates existing key value", async () => {
|
||||||
|
const hash = await createTestNode();
|
||||||
|
|
||||||
|
await runCli("tag", hash, "env:dev");
|
||||||
|
await runCli("tag", hash, "env:prod");
|
||||||
|
|
||||||
|
const tags = await readTags(hash);
|
||||||
|
const envTags = tags.filter((t) => t.key === "env");
|
||||||
|
expect(envTags).toHaveLength(1);
|
||||||
|
expect(envTags[0]?.value).toBe("prod");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Test 15: bootstrap registers @ocas/output/tag and @ocas/output/untag", async () => {
|
||||||
|
const store = await openFsStore(storePath);
|
||||||
|
const aliases = bootstrap(store);
|
||||||
|
expect(aliases["@ocas/output/tag"]).toBeDefined();
|
||||||
|
expect(aliases["@ocas/output/untag"]).toBeDefined();
|
||||||
|
|
||||||
|
const tagVar = store.var.list({ exactName: "@ocas/output/tag" });
|
||||||
|
expect(tagVar.length).toBeGreaterThan(0);
|
||||||
|
|
||||||
|
const untagVar = store.var.list({ exactName: "@ocas/output/untag" });
|
||||||
|
expect(untagVar.length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -752,246 +752,6 @@ describe("var list", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("var tag", () => {
|
|
||||||
test("add new tag", async () => {
|
|
||||||
const store = await openFsStore(storePath);
|
|
||||||
const typeHash = await getBootstrapHash(store);
|
|
||||||
const hash = await createTestNode(store, typeHash, { test: "data" });
|
|
||||||
|
|
||||||
// Create variable without tags
|
|
||||||
await runCli("var", "set", "@test/x", hash);
|
|
||||||
|
|
||||||
// Add tag
|
|
||||||
const { stdout, exitCode } = await runCli(
|
|
||||||
"var",
|
|
||||||
"tag",
|
|
||||||
"@test/x",
|
|
||||||
"--schema",
|
|
||||||
typeHash,
|
|
||||||
"env:prod",
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(exitCode).toBe(0);
|
|
||||||
|
|
||||||
const envelope = JSON.parse(stdout);
|
|
||||||
expect(envelope.value.tags).toEqual({ env: "prod" });
|
|
||||||
});
|
|
||||||
|
|
||||||
test("update existing tag value", async () => {
|
|
||||||
const store = await openFsStore(storePath);
|
|
||||||
const typeHash = await getBootstrapHash(store);
|
|
||||||
const hash = await createTestNode(store, typeHash, { test: "data" });
|
|
||||||
|
|
||||||
// Create variable with tag
|
|
||||||
await runCli("var", "set", "@test/x", hash, "--tag", "env:dev");
|
|
||||||
|
|
||||||
// Update tag
|
|
||||||
const { stdout, exitCode } = await runCli(
|
|
||||||
"var",
|
|
||||||
"tag",
|
|
||||||
"@test/x",
|
|
||||||
"--schema",
|
|
||||||
typeHash,
|
|
||||||
"env:prod",
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(exitCode).toBe(0);
|
|
||||||
|
|
||||||
const envelope = JSON.parse(stdout);
|
|
||||||
expect(envelope.value.tags).toEqual({ env: "prod" });
|
|
||||||
});
|
|
||||||
|
|
||||||
test("add label", async () => {
|
|
||||||
const store = await openFsStore(storePath);
|
|
||||||
const typeHash = await getBootstrapHash(store);
|
|
||||||
const hash = await createTestNode(store, typeHash, { test: "data" });
|
|
||||||
|
|
||||||
// Create variable without labels
|
|
||||||
await runCli("var", "set", "@test/x", hash);
|
|
||||||
|
|
||||||
// Add label
|
|
||||||
const { stdout, exitCode } = await runCli(
|
|
||||||
"var",
|
|
||||||
"tag",
|
|
||||||
"@test/x",
|
|
||||||
"--schema",
|
|
||||||
typeHash,
|
|
||||||
"stable",
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(exitCode).toBe(0);
|
|
||||||
|
|
||||||
const envelope = JSON.parse(stdout);
|
|
||||||
expect(envelope.value.labels).toEqual(["stable"]);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("delete tag", async () => {
|
|
||||||
const store = await openFsStore(storePath);
|
|
||||||
const typeHash = await getBootstrapHash(store);
|
|
||||||
const hash = await createTestNode(store, typeHash, { test: "data" });
|
|
||||||
|
|
||||||
// Create variable with tags
|
|
||||||
await runCli(
|
|
||||||
"var",
|
|
||||||
"set",
|
|
||||||
"@test/x",
|
|
||||||
hash,
|
|
||||||
"--tag",
|
|
||||||
"env:prod",
|
|
||||||
"--tag",
|
|
||||||
"version:1.0",
|
|
||||||
);
|
|
||||||
|
|
||||||
// Delete tag
|
|
||||||
const { stdout, exitCode } = await runCli(
|
|
||||||
"var",
|
|
||||||
"tag",
|
|
||||||
"@test/x",
|
|
||||||
"--schema",
|
|
||||||
typeHash,
|
|
||||||
":env",
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(exitCode).toBe(0);
|
|
||||||
|
|
||||||
const envelope = JSON.parse(stdout);
|
|
||||||
expect(envelope.value.tags).toEqual({ version: "1.0" });
|
|
||||||
});
|
|
||||||
|
|
||||||
test("delete label", async () => {
|
|
||||||
const store = await openFsStore(storePath);
|
|
||||||
const typeHash = await getBootstrapHash(store);
|
|
||||||
const hash = await createTestNode(store, typeHash, { test: "data" });
|
|
||||||
|
|
||||||
// Create variable with labels
|
|
||||||
await runCli(
|
|
||||||
"var",
|
|
||||||
"set",
|
|
||||||
"@test/x",
|
|
||||||
hash,
|
|
||||||
"--tag",
|
|
||||||
"stable",
|
|
||||||
"--tag",
|
|
||||||
"beta",
|
|
||||||
);
|
|
||||||
|
|
||||||
// Delete label
|
|
||||||
const { stdout, exitCode } = await runCli(
|
|
||||||
"var",
|
|
||||||
"tag",
|
|
||||||
"@test/x",
|
|
||||||
"--schema",
|
|
||||||
typeHash,
|
|
||||||
":stable",
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(exitCode).toBe(0);
|
|
||||||
|
|
||||||
const envelope = JSON.parse(stdout);
|
|
||||||
expect(envelope.value.labels).toEqual(["beta"]);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("mixed add and delete operations", async () => {
|
|
||||||
const store = await openFsStore(storePath);
|
|
||||||
const typeHash = await getBootstrapHash(store);
|
|
||||||
const hash = await createTestNode(store, typeHash, { test: "data" });
|
|
||||||
|
|
||||||
// Create variable with tags and labels
|
|
||||||
await runCli("var", "set", "@test/x", hash, "--tag", "a:1", "--tag", "b");
|
|
||||||
|
|
||||||
// Mixed operations
|
|
||||||
const { stdout, exitCode } = await runCli(
|
|
||||||
"var",
|
|
||||||
"tag",
|
|
||||||
"@test/x",
|
|
||||||
"--schema",
|
|
||||||
typeHash,
|
|
||||||
"c:3",
|
|
||||||
":a",
|
|
||||||
"d",
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(exitCode).toBe(0);
|
|
||||||
|
|
||||||
const envelope = JSON.parse(stdout);
|
|
||||||
expect(envelope.value.tags).toEqual({ c: "3" });
|
|
||||||
expect(envelope.value.labels.sort()).toEqual(["b", "d"]);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("error on tag/label conflict", async () => {
|
|
||||||
const store = await openFsStore(storePath);
|
|
||||||
const typeHash = await getBootstrapHash(store);
|
|
||||||
const hash = await createTestNode(store, typeHash, { test: "data" });
|
|
||||||
|
|
||||||
// Create variable with tag
|
|
||||||
await runCli("var", "set", "@test/x", hash, "--tag", "env:prod");
|
|
||||||
|
|
||||||
// Try to add same name as label
|
|
||||||
const { stderr, exitCode } = await runCli(
|
|
||||||
"var",
|
|
||||||
"tag",
|
|
||||||
"@test/x",
|
|
||||||
"--schema",
|
|
||||||
typeHash,
|
|
||||||
"env",
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(exitCode).toBe(1);
|
|
||||||
expect(stderr).toContain("Error: Conflict: 'env' already exists as a");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("error when variable not found", async () => {
|
|
||||||
const store = await openFsStore(storePath);
|
|
||||||
const typeHash = await getBootstrapHash(store);
|
|
||||||
|
|
||||||
const { stderr, exitCode } = await runCli(
|
|
||||||
"var",
|
|
||||||
"tag",
|
|
||||||
"@test/nonexistent",
|
|
||||||
"--schema",
|
|
||||||
typeHash,
|
|
||||||
"env:prod",
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(exitCode).toBe(1);
|
|
||||||
expect(stderr).toContain(
|
|
||||||
`Error: Variable not found: name=@test/nonexistent, schema=${typeHash}`,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("error when --schema missing", async () => {
|
|
||||||
const { stderr, exitCode } = await runCli(
|
|
||||||
"var",
|
|
||||||
"tag",
|
|
||||||
"@test/x",
|
|
||||||
"env:prod",
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(exitCode).toBe(1);
|
|
||||||
expect(stderr).toContain(
|
|
||||||
"Usage: ocas var tag <name> --schema <hash-or-name> <operations...>",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("error when no operations provided", async () => {
|
|
||||||
const store = await openFsStore(storePath);
|
|
||||||
const typeHash = await getBootstrapHash(store);
|
|
||||||
|
|
||||||
const { stderr, exitCode } = await runCli(
|
|
||||||
"var",
|
|
||||||
"tag",
|
|
||||||
"@test/x",
|
|
||||||
"--schema",
|
|
||||||
typeHash,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(exitCode).toBe(1);
|
|
||||||
expect(stderr).toContain(
|
|
||||||
"Usage: ocas var tag <name> --schema <hash-or-name> <operations...>",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("global options", () => {
|
describe("global options", () => {
|
||||||
test("--json flag for compact output", async () => {
|
test("--json flag for compact output", async () => {
|
||||||
const store = await openFsStore(storePath);
|
const store = await openFsStore(storePath);
|
||||||
@@ -1067,4 +827,18 @@ describe("old commands removed", () => {
|
|||||||
expect(exitCode).toBe(1);
|
expect(exitCode).toBe(1);
|
||||||
expect(stderr).toContain("Unknown var subcommand: update");
|
expect(stderr).toContain("Unknown var subcommand: update");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("var tag subcommand removed", async () => {
|
||||||
|
const { stderr, exitCode } = await runCli(
|
||||||
|
"var",
|
||||||
|
"tag",
|
||||||
|
"@any/name",
|
||||||
|
"--schema",
|
||||||
|
"@ocas/string",
|
||||||
|
"foo",
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(exitCode).toBe(1);
|
||||||
|
expect(stderr).toContain("Unknown var subcommand: tag");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -18,9 +18,10 @@ const OUTPUT_ALIASES = [
|
|||||||
"@ocas/output/var-set",
|
"@ocas/output/var-set",
|
||||||
"@ocas/output/var-get",
|
"@ocas/output/var-get",
|
||||||
"@ocas/output/var-delete",
|
"@ocas/output/var-delete",
|
||||||
"@ocas/output/var-tag",
|
|
||||||
"@ocas/output/var-list",
|
"@ocas/output/var-list",
|
||||||
"@ocas/output/var-history",
|
"@ocas/output/var-history",
|
||||||
|
"@ocas/output/tag",
|
||||||
|
"@ocas/output/untag",
|
||||||
"@ocas/output/template-set",
|
"@ocas/output/template-set",
|
||||||
"@ocas/output/template-get",
|
"@ocas/output/template-get",
|
||||||
"@ocas/output/template-list",
|
"@ocas/output/template-list",
|
||||||
@@ -33,11 +34,11 @@ const OUTPUT_ALIASES = [
|
|||||||
// ──────────────────────────────────────────────────────────────────────────────
|
// ──────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
describe("bootstrap - Built-in Schemas", () => {
|
describe("bootstrap - Built-in Schemas", () => {
|
||||||
test("should return map of 30 built-in schema aliases to hashes", async () => {
|
test("should return map of 31 built-in schema aliases to hashes", async () => {
|
||||||
const store = createMemoryStore();
|
const store = createMemoryStore();
|
||||||
const builtinSchemas = bootstrap(store);
|
const builtinSchemas = bootstrap(store);
|
||||||
|
|
||||||
// Should return object with 9 primitive + 21 output aliases = 30
|
// Should return object with 9 primitive + 22 output aliases = 31
|
||||||
expect(builtinSchemas).toHaveProperty("@ocas/schema");
|
expect(builtinSchemas).toHaveProperty("@ocas/schema");
|
||||||
expect(builtinSchemas).toHaveProperty("@ocas/string");
|
expect(builtinSchemas).toHaveProperty("@ocas/string");
|
||||||
expect(builtinSchemas).toHaveProperty("@ocas/number");
|
expect(builtinSchemas).toHaveProperty("@ocas/number");
|
||||||
@@ -52,7 +53,7 @@ describe("bootstrap - Built-in Schemas", () => {
|
|||||||
expect(builtinSchemas).toHaveProperty(alias);
|
expect(builtinSchemas).toHaveProperty(alias);
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(Object.keys(builtinSchemas)).toHaveLength(30);
|
expect(Object.keys(builtinSchemas)).toHaveLength(31);
|
||||||
|
|
||||||
// All values should be valid hashes
|
// All values should be valid hashes
|
||||||
for (const [_alias, hash] of Object.entries(builtinSchemas)) {
|
for (const [_alias, hash] of Object.entries(builtinSchemas)) {
|
||||||
|
|||||||
@@ -236,14 +236,6 @@ const OUTPUT_SCHEMAS: ReadonlyArray<
|
|||||||
title: "ocas var delete result",
|
title: "ocas var delete result",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[
|
|
||||||
"@ocas/output/var-tag",
|
|
||||||
{
|
|
||||||
type: "object",
|
|
||||||
properties: { ...VARIABLE_PROPERTIES },
|
|
||||||
title: "ocas var tag result",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
[
|
[
|
||||||
"@ocas/output/var-list",
|
"@ocas/output/var-list",
|
||||||
{
|
{
|
||||||
@@ -267,6 +259,38 @@ const OUTPUT_SCHEMAS: ReadonlyArray<
|
|||||||
title: "ocas var history result",
|
title: "ocas var history result",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
"@ocas/output/tag",
|
||||||
|
{
|
||||||
|
type: "array",
|
||||||
|
items: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
key: { type: "string" },
|
||||||
|
value: { type: ["string", "null"] },
|
||||||
|
target: { type: "string", format: "ocas_ref" },
|
||||||
|
created: { type: "number" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
title: "ocas tag result",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"@ocas/output/untag",
|
||||||
|
{
|
||||||
|
type: "array",
|
||||||
|
items: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
key: { type: "string" },
|
||||||
|
value: { type: ["string", "null"] },
|
||||||
|
target: { type: "string", format: "ocas_ref" },
|
||||||
|
created: { type: "number" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
title: "ocas untag result",
|
||||||
|
},
|
||||||
|
],
|
||||||
[
|
[
|
||||||
"@ocas/output/template-set",
|
"@ocas/output/template-set",
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -289,7 +289,7 @@ describe("bootstrap", () => {
|
|||||||
expect(hash).toMatch(/^[0-9A-HJKMNP-TV-Z]{13}$/);
|
expect(hash).toMatch(/^[0-9A-HJKMNP-TV-Z]{13}$/);
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(Object.keys(builtinSchemas)).toHaveLength(30);
|
expect(Object.keys(builtinSchemas)).toHaveLength(31);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("meta-schema node is stored and retrievable", async () => {
|
test("meta-schema node is stored and retrievable", async () => {
|
||||||
@@ -326,7 +326,7 @@ describe("bootstrap", () => {
|
|||||||
const h2 = bootstrap(store);
|
const h2 = bootstrap(store);
|
||||||
|
|
||||||
expect(h1).toEqual(h2);
|
expect(h1).toEqual(h2);
|
||||||
// All built-in schemas typed by the meta-schema (1 self + 7 unique primitives + 21 outputs)
|
// All built-in schemas typed by the meta-schema (1 self + 7 unique primitives + 22 outputs)
|
||||||
expect(store.cas.listByType(h1["@ocas/schema"] ?? "")).toHaveLength(29);
|
expect(store.cas.listByType(h1["@ocas/schema"] ?? "")).toHaveLength(30);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -15,9 +15,10 @@ const OUTPUT_ALIASES = [
|
|||||||
"@ocas/output/var-set",
|
"@ocas/output/var-set",
|
||||||
"@ocas/output/var-get",
|
"@ocas/output/var-get",
|
||||||
"@ocas/output/var-delete",
|
"@ocas/output/var-delete",
|
||||||
"@ocas/output/var-tag",
|
|
||||||
"@ocas/output/var-list",
|
"@ocas/output/var-list",
|
||||||
"@ocas/output/var-history",
|
"@ocas/output/var-history",
|
||||||
|
"@ocas/output/tag",
|
||||||
|
"@ocas/output/untag",
|
||||||
"@ocas/output/template-set",
|
"@ocas/output/template-set",
|
||||||
"@ocas/output/template-get",
|
"@ocas/output/template-get",
|
||||||
"@ocas/output/template-list",
|
"@ocas/output/template-list",
|
||||||
@@ -32,7 +33,7 @@ describe("registerOutputTemplates", () => {
|
|||||||
|
|
||||||
const registered = await registerOutputTemplates(store);
|
const registered = await registerOutputTemplates(store);
|
||||||
|
|
||||||
expect(Object.keys(registered)).toHaveLength(19);
|
expect(Object.keys(registered)).toHaveLength(20);
|
||||||
|
|
||||||
for (const alias of OUTPUT_ALIASES) {
|
for (const alias of OUTPUT_ALIASES) {
|
||||||
expect(registered).toHaveProperty(alias);
|
expect(registered).toHaveProperty(alias);
|
||||||
|
|||||||
@@ -27,14 +27,18 @@ const DEFAULT_TEMPLATES: ReadonlyArray<
|
|||||||
"@ocas/output/var-delete",
|
"@ocas/output/var-delete",
|
||||||
"name: {{ payload.name }}\nschema: {{ payload.schema }}\nvalue: {{ payload.value }}",
|
"name: {{ payload.name }}\nschema: {{ payload.schema }}\nvalue: {{ payload.value }}",
|
||||||
],
|
],
|
||||||
[
|
|
||||||
"@ocas/output/var-tag",
|
|
||||||
"name: {{ payload.name }}\nschema: {{ payload.schema }}\nvalue: {{ payload.value }}",
|
|
||||||
],
|
|
||||||
[
|
[
|
||||||
"@ocas/output/var-list",
|
"@ocas/output/var-list",
|
||||||
"{% for v in payload %}name: {{ v.name }}\nschema: {{ v.schema }}\nvalue: {{ v.value }}\n{% endfor %}",
|
"{% for v in payload %}name: {{ v.name }}\nschema: {{ v.schema }}\nvalue: {{ v.value }}\n{% endfor %}",
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
"@ocas/output/tag",
|
||||||
|
"{% for t in payload %}{{ t.key }}{% if t.value %}:{{ t.value }}{% endif %}\n{% endfor %}",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"@ocas/output/untag",
|
||||||
|
"{% for t in payload %}{{ t.key }}{% if t.value %}:{{ t.value }}{% endif %}\n{% endfor %}",
|
||||||
|
],
|
||||||
[
|
[
|
||||||
"@ocas/output/var-history",
|
"@ocas/output/var-history",
|
||||||
"name: {{ payload.name }}\nschema: {{ payload.schema }}\n{% for v in payload.values %}{{ forloop.index0 }}: {{ v }}\n{% endfor %}",
|
"name: {{ payload.name }}\nschema: {{ payload.schema }}\n{% for v in payload.values %}{{ forloop.index0 }}: {{ v }}\n{% endfor %}",
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ describe("createFsStore – init and bootstrap", () => {
|
|||||||
const h2 = bootstrap(store);
|
const h2 = bootstrap(store);
|
||||||
|
|
||||||
expect(h1).toEqual(h2);
|
expect(h1).toEqual(h2);
|
||||||
expect(store.cas.listByType(h1["@ocas/schema"] ?? "")).toHaveLength(29);
|
expect(store.cas.listByType(h1["@ocas/schema"] ?? "")).toHaveLength(30);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user