@setzkasten/cli 0.1.0-rc.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/LICENSE +21 -0
- package/README.md +38 -0
- package/package.json +38 -0
- package/src/index.js +509 -0
- package/src/lib/core.js +129 -0
- package/src/lib/events.js +52 -0
- package/src/lib/manifest-lib.js +613 -0
- package/src/lib/policy.js +271 -0
- package/src/lib/quote.js +219 -0
- package/src/lib/scanner.js +176 -0
package/src/lib/core.js
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { appendFile, mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
|
|
6
|
+
export const MANIFEST_FILENAME = "LICENSE_MANIFEST.json";
|
|
7
|
+
export const EVENT_LOG_RELATIVE_PATH = ".setzkasten/events.log";
|
|
8
|
+
export const MANIFEST_VERSION = "1.0.0";
|
|
9
|
+
export const LICENSE_SPEC_VERSION = "1.0.0";
|
|
10
|
+
|
|
11
|
+
function isPlainObject(value) {
|
|
12
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function canonicalize(value) {
|
|
16
|
+
if (Array.isArray(value)) {
|
|
17
|
+
return value.map((entry) => canonicalize(entry));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (!isPlainObject(value)) {
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const sortedEntries = Object.keys(value)
|
|
25
|
+
.sort((a, b) => a.localeCompare(b))
|
|
26
|
+
.map((key) => [key, canonicalize(value[key])]);
|
|
27
|
+
|
|
28
|
+
return Object.fromEntries(sortedEntries);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function canonicalStringify(value) {
|
|
32
|
+
return JSON.stringify(canonicalize(value));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function sha256Hex(value) {
|
|
36
|
+
return createHash("sha256").update(value).digest("hex");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function nowIso() {
|
|
40
|
+
return new Date().toISOString();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function makeEventId() {
|
|
44
|
+
return randomUUID();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function slugifyId(input, fallback = "project") {
|
|
48
|
+
const normalized = input
|
|
49
|
+
.trim()
|
|
50
|
+
.toLowerCase()
|
|
51
|
+
.replace(/[^a-z0-9._:-]+/g, "-")
|
|
52
|
+
.replace(/^-+|-+$/g, "");
|
|
53
|
+
|
|
54
|
+
let result = normalized.length > 0 ? normalized : fallback;
|
|
55
|
+
|
|
56
|
+
if (!/^[a-z0-9]/.test(result)) {
|
|
57
|
+
result = `id-${result}`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return result.slice(0, 128);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function roundMoney(value) {
|
|
64
|
+
return Math.round((value + Number.EPSILON) * 100) / 100;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export async function pathExists(filePath) {
|
|
68
|
+
return existsSync(filePath);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export async function ensureDirectory(dirPath) {
|
|
72
|
+
await mkdir(dirPath, { recursive: true });
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export async function readJsonFile(filePath) {
|
|
76
|
+
const content = await readFile(filePath, "utf8");
|
|
77
|
+
return JSON.parse(content);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export async function writeJsonFileAtomic(filePath, value) {
|
|
81
|
+
const dirPath = path.dirname(filePath);
|
|
82
|
+
await ensureDirectory(dirPath);
|
|
83
|
+
|
|
84
|
+
const tmpFilePath = `${filePath}.tmp-${process.pid}-${Date.now()}`;
|
|
85
|
+
const json = `${JSON.stringify(value, null, 2)}\n`;
|
|
86
|
+
|
|
87
|
+
await writeFile(tmpFilePath, json, "utf8");
|
|
88
|
+
await rename(tmpFilePath, filePath);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export async function appendLine(filePath, value) {
|
|
92
|
+
const dirPath = path.dirname(filePath);
|
|
93
|
+
await ensureDirectory(dirPath);
|
|
94
|
+
await appendFile(filePath, `${value}\n`, "utf8");
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function findUp(fileName, startDir = process.cwd()) {
|
|
98
|
+
let currentDir = path.resolve(startDir);
|
|
99
|
+
|
|
100
|
+
while (true) {
|
|
101
|
+
const candidate = path.join(currentDir, fileName);
|
|
102
|
+
|
|
103
|
+
if (existsSync(candidate)) {
|
|
104
|
+
return candidate;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const parentDir = path.dirname(currentDir);
|
|
108
|
+
if (parentDir === currentDir) {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
currentDir = parentDir;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function parseListFlag(value) {
|
|
117
|
+
if (Array.isArray(value)) {
|
|
118
|
+
return value.flatMap((entry) => parseListFlag(entry));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (!value) {
|
|
122
|
+
return [];
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return String(value)
|
|
126
|
+
.split(",")
|
|
127
|
+
.map((entry) => entry.trim())
|
|
128
|
+
.filter((entry) => entry.length > 0);
|
|
129
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import {
|
|
3
|
+
EVENT_LOG_RELATIVE_PATH,
|
|
4
|
+
LICENSE_SPEC_VERSION,
|
|
5
|
+
MANIFEST_VERSION,
|
|
6
|
+
appendLine,
|
|
7
|
+
canonicalStringify,
|
|
8
|
+
makeEventId,
|
|
9
|
+
nowIso,
|
|
10
|
+
sha256Hex,
|
|
11
|
+
} from "./core.js";
|
|
12
|
+
|
|
13
|
+
export function createEvent(input) {
|
|
14
|
+
const payload = input.payload ?? {};
|
|
15
|
+
|
|
16
|
+
return {
|
|
17
|
+
event_id: makeEventId(),
|
|
18
|
+
event_type: input.eventType,
|
|
19
|
+
ts: nowIso(),
|
|
20
|
+
actor: input.actor ?? "local_user",
|
|
21
|
+
project_id: input.projectId,
|
|
22
|
+
schema_versions: {
|
|
23
|
+
manifest: input.schemaVersions?.manifest ?? MANIFEST_VERSION,
|
|
24
|
+
license_spec: input.schemaVersions?.license_spec ?? LICENSE_SPEC_VERSION,
|
|
25
|
+
},
|
|
26
|
+
payload,
|
|
27
|
+
payload_hash: sha256Hex(canonicalStringify(payload)),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function appendEvent(projectRoot, event) {
|
|
32
|
+
const eventLogPath = path.join(projectRoot, EVENT_LOG_RELATIVE_PATH);
|
|
33
|
+
await appendLine(eventLogPath, JSON.stringify(event));
|
|
34
|
+
return eventLogPath;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function appendProjectEvent(input) {
|
|
38
|
+
const event = createEvent({
|
|
39
|
+
eventType: input.eventType,
|
|
40
|
+
actor: input.actor,
|
|
41
|
+
projectId: input.projectId,
|
|
42
|
+
payload: input.payload,
|
|
43
|
+
schemaVersions: input.schemaVersions,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const eventLogPath = await appendEvent(input.projectRoot, event);
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
eventLogPath,
|
|
50
|
+
event,
|
|
51
|
+
};
|
|
52
|
+
}
|