@hatchingpoint/point 0.0.13 → 0.0.14
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/package.json +6 -2
- package/src/core/cli.ts +45 -10
- package/src/core/packages.ts +103 -11
- package/src/core/run-bridge.ts +93 -0
- package/src/std/env.ts +4 -0
- package/src/std/fs.ts +19 -0
- package/src/std/text.ts +15 -0
- package/src/std/time.ts +15 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hatchingpoint/point",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.14",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Point language compiler and CLI.",
|
|
@@ -26,7 +26,11 @@
|
|
|
26
26
|
"./cli": "./src/cli.ts",
|
|
27
27
|
"./core": "./src/core/index.ts",
|
|
28
28
|
"./std/json": "./src/std/json.ts",
|
|
29
|
-
"./std/http": "./src/std/http.ts"
|
|
29
|
+
"./std/http": "./src/std/http.ts",
|
|
30
|
+
"./std/text": "./src/std/text.ts",
|
|
31
|
+
"./std/time": "./src/std/time.ts",
|
|
32
|
+
"./std/fs": "./src/std/fs.ts",
|
|
33
|
+
"./std/env": "./src/std/env.ts"
|
|
30
34
|
},
|
|
31
35
|
"publishConfig": {
|
|
32
36
|
"access": "public",
|
package/src/core/cli.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { createSemanticIndex, explainSemanticRef, mapPublicDiagnostics } from ".
|
|
|
7
7
|
import { emitPointCoreTypeScript } from "./emit-typescript.ts";
|
|
8
8
|
import { emitPointCoreJavaScript } from "./emit-javascript.ts";
|
|
9
9
|
import { emitPointCorePython, isPureLogicProgram } from "./emit-python.ts";
|
|
10
|
+
import { canBundleRunInMemory, executeBundledEntry } from "./run-bridge.ts";
|
|
10
11
|
import { formatPointSource } from "./format.ts";
|
|
11
12
|
import { isCacheHit, isIncrementalEnabled, readBuildCache, recordCacheEntry, writeBuildCache } from "./incremental.ts";
|
|
12
13
|
import { parsePointSource } from "./parser.ts";
|
|
@@ -23,7 +24,17 @@ const DEFAULT_PATTERNS = ["examples/**/*.point", "std/**/*.point", "compiler/**/
|
|
|
23
24
|
const GENERATED_DIR = "generated";
|
|
24
25
|
|
|
25
26
|
export async function main() {
|
|
26
|
-
const
|
|
27
|
+
const command = Bun.argv[2] ?? "check";
|
|
28
|
+
const tail = Bun.argv.slice(3);
|
|
29
|
+
let input = tail[0] ?? DEFAULT_INPUT;
|
|
30
|
+
let output = tail[1] ?? DEFAULT_OUTPUT;
|
|
31
|
+
let runFlags: Record<string, boolean | undefined> | undefined;
|
|
32
|
+
if (command === "run") {
|
|
33
|
+
const parsed = parseCliFlags(tail);
|
|
34
|
+
runFlags = parsed.flags;
|
|
35
|
+
input = parsed.positional[0] ?? DEFAULT_INPUT;
|
|
36
|
+
output = parsed.positional[1] ?? DEFAULT_OUTPUT;
|
|
37
|
+
}
|
|
27
38
|
if (command.endsWith("-all")) {
|
|
28
39
|
await runProjectCommand(command);
|
|
29
40
|
return;
|
|
@@ -176,16 +187,17 @@ export async function main() {
|
|
|
176
187
|
console.error(JSON.stringify({ ok: false, diagnostics }, null, 2));
|
|
177
188
|
process.exit(1);
|
|
178
189
|
}
|
|
179
|
-
const runOutput = resolve(tmpdir(), `point-run-${Date.now()}.js`);
|
|
180
|
-
await Bun.write(runOutput, emitPointCoreJavaScript(program));
|
|
181
190
|
let entryName: string | null = null;
|
|
182
191
|
try {
|
|
183
|
-
const mod = await import(pathToFileUrl(runOutput));
|
|
184
192
|
entryName = findRunEntryName(program);
|
|
185
193
|
if (!entryName) throw new Error("No zero-argument entrypoint found. Define an action or calculation with no inputs.");
|
|
186
|
-
const
|
|
187
|
-
if (
|
|
188
|
-
|
|
194
|
+
const useBundle = runFlags?.bundle === true || (runFlags?.bundle !== false && canBundleRunInMemory(program));
|
|
195
|
+
if (runFlags?.bundle === true && !canBundleRunInMemory(program)) {
|
|
196
|
+
throw new Error("Cannot use --bundle: module has imports, externals, or non-pure logic (views, routes, workflows, commands).");
|
|
197
|
+
}
|
|
198
|
+
const value = useBundle
|
|
199
|
+
? await executeBundledEntry(program, entryName)
|
|
200
|
+
: await executeTempModuleRun(program, entryName);
|
|
189
201
|
if (value !== undefined) console.log(typeof value === "string" ? value : JSON.stringify(value));
|
|
190
202
|
} catch (error) {
|
|
191
203
|
console.error(`Runtime error in ${runtimeSourceLocation(program, input, entryName)}: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -434,6 +446,26 @@ function pathToFileUrl(path: string): string {
|
|
|
434
446
|
return `file://${path.replaceAll("\\", "/")}`;
|
|
435
447
|
}
|
|
436
448
|
|
|
449
|
+
async function executeTempModuleRun(program: PointCoreProgram, entryName: string): Promise<unknown> {
|
|
450
|
+
const runOutput = resolve(tmpdir(), `point-run-${Date.now()}.js`);
|
|
451
|
+
await Bun.write(runOutput, emitPointCoreJavaScript(program));
|
|
452
|
+
const mod = await import(pathToFileUrl(runOutput));
|
|
453
|
+
const entry = mod[entryName];
|
|
454
|
+
if (typeof entry !== "function") throw new Error(`Entrypoint ${entryName} was not exported.`);
|
|
455
|
+
return await entry();
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
function parseCliFlags(args: string[]): { flags: Record<string, boolean | undefined>; positional: string[] } {
|
|
459
|
+
const flags: Record<string, boolean | undefined> = {};
|
|
460
|
+
const positional: string[] = [];
|
|
461
|
+
for (const arg of args) {
|
|
462
|
+
if (arg === "--bundle") flags.bundle = true;
|
|
463
|
+
else if (arg === "--no-bundle") flags.bundle = false;
|
|
464
|
+
else positional.push(arg);
|
|
465
|
+
}
|
|
466
|
+
return { flags, positional };
|
|
467
|
+
}
|
|
468
|
+
|
|
437
469
|
export function findRunEntryName(program: PointCoreProgram): string | null {
|
|
438
470
|
const zeroArgFunctions = program.declarations.filter((declaration) => declaration.kind === "function" && declaration.params.length === 0);
|
|
439
471
|
const preferred =
|
|
@@ -524,9 +556,12 @@ function publicDeclarations(program: PointCoreProgram): Array<Extract<PointCoreD
|
|
|
524
556
|
}
|
|
525
557
|
|
|
526
558
|
function resolveDependencyInput(input: string, from: string): string {
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
559
|
+
const normalized = from.replaceAll("\\", "/");
|
|
560
|
+
if (normalized.startsWith("./") || normalized.startsWith("../")) {
|
|
561
|
+
const base = dirname(resolve(process.cwd(), input));
|
|
562
|
+
return resolve(base, from).replace(resolve(process.cwd()), "").replace(/^[/\\]/, "");
|
|
563
|
+
}
|
|
564
|
+
return normalized;
|
|
530
565
|
}
|
|
531
566
|
|
|
532
567
|
function normalizeInput(input: string): string {
|
package/src/core/packages.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import { existsSync, readdirSync } from "node:fs";
|
|
2
3
|
import { join, relative, resolve } from "node:path";
|
|
3
4
|
|
|
4
5
|
export const POINT_MANIFEST = "point.json";
|
|
@@ -29,15 +30,36 @@ export interface ParsedDependencySpec {
|
|
|
29
30
|
locator: string;
|
|
30
31
|
}
|
|
31
32
|
|
|
33
|
+
export interface ParsedNpmLocator {
|
|
34
|
+
name: string;
|
|
35
|
+
version?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function parseNpmLocator(locator: string): ParsedNpmLocator {
|
|
39
|
+
if (locator.startsWith("@")) {
|
|
40
|
+
const slash = locator.indexOf("/");
|
|
41
|
+
if (slash < 0) {
|
|
42
|
+
throw new Error(`Invalid npm package name "${locator}". Scoped packages use @scope/name.`);
|
|
43
|
+
}
|
|
44
|
+
const rest = locator.slice(slash + 1);
|
|
45
|
+
const versionAt = rest.indexOf("@");
|
|
46
|
+
if (versionAt >= 0) {
|
|
47
|
+
return { name: `${locator.slice(0, slash + 1 + versionAt)}`, version: rest.slice(versionAt + 1) };
|
|
48
|
+
}
|
|
49
|
+
return { name: locator };
|
|
50
|
+
}
|
|
51
|
+
const versionAt = locator.lastIndexOf("@");
|
|
52
|
+
if (versionAt > 0) {
|
|
53
|
+
return { name: locator.slice(0, versionAt), version: locator.slice(versionAt + 1) };
|
|
54
|
+
}
|
|
55
|
+
return { name: locator };
|
|
56
|
+
}
|
|
57
|
+
|
|
32
58
|
export function parseDependencySpec(spec: string): ParsedDependencySpec {
|
|
33
59
|
if (spec.startsWith("workspace:")) return { kind: "workspace", locator: spec.slice("workspace:".length) };
|
|
34
60
|
if (spec.startsWith("file:")) return { kind: "file", locator: spec.slice("file:".length) };
|
|
35
61
|
if (spec.startsWith("npm:")) return { kind: "npm", locator: spec.slice("npm:".length) };
|
|
36
|
-
throw new Error(`Invalid dependency spec "${spec}". Use workspace:<path>, file:<path>, or npm:<package>
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export function npmDependencyNotSupportedMessage(spec: string): string {
|
|
40
|
-
return `npm: registry dependencies are not supported yet (${spec}). Use workspace:<path> or file:<path> for local Point packages.`;
|
|
62
|
+
throw new Error(`Invalid dependency spec "${spec}". Use workspace:<path>, file:<path>, or npm:<package>[@version].`);
|
|
41
63
|
}
|
|
42
64
|
|
|
43
65
|
export async function readPointManifest(cwd = process.cwd()): Promise<PointManifest> {
|
|
@@ -74,9 +96,71 @@ export function normalizePackagePath(cwd: string, rawPath: string): string {
|
|
|
74
96
|
return relative(cwd, absolute).split("\\").join("/") || ".";
|
|
75
97
|
}
|
|
76
98
|
|
|
77
|
-
export function
|
|
99
|
+
export function locatePointPackageRoot(pkgDir: string): string {
|
|
100
|
+
if (existsSync(join(pkgDir, POINT_MANIFEST))) return pkgDir;
|
|
101
|
+
const srcDir = join(pkgDir, "src");
|
|
102
|
+
if (existsSync(srcDir) && readdirSync(srcDir).some((name) => name.endsWith(".point"))) return pkgDir;
|
|
103
|
+
if (readdirSync(pkgDir).some((name) => name.endsWith(".point"))) return pkgDir;
|
|
104
|
+
throw new Error(`Point package at ${pkgDir} has no ${POINT_MANIFEST} or src/*.point modules`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function resolveNpmPackagePath(cwd: string, packageName: string): string | null {
|
|
108
|
+
const direct = join(cwd, "node_modules", ...packageName.split("/"));
|
|
109
|
+
if (existsSync(join(direct, "package.json"))) return direct;
|
|
110
|
+
try {
|
|
111
|
+
const req = createRequire(join(cwd, "package.json"));
|
|
112
|
+
const pkgJson = req.resolve(`${packageName}/package.json`);
|
|
113
|
+
return resolve(pkgJson, "..");
|
|
114
|
+
} catch {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async function readInstalledNpmVersion(pkgDir: string): Promise<string | null> {
|
|
120
|
+
const pkgJsonPath = join(pkgDir, "package.json");
|
|
121
|
+
if (!existsSync(pkgJsonPath)) return null;
|
|
122
|
+
const pkg = (await Bun.file(pkgJsonPath).json()) as { version?: string };
|
|
123
|
+
return pkg.version ?? null;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export async function ensureNpmPackage(cwd: string, packageName: string, version?: string): Promise<string> {
|
|
127
|
+
const installed = resolveNpmPackagePath(cwd, packageName);
|
|
128
|
+
if (installed) {
|
|
129
|
+
const installedVersion = await readInstalledNpmVersion(installed);
|
|
130
|
+
if (!version || installedVersion === version) return installed;
|
|
131
|
+
}
|
|
132
|
+
const installSpec = version ? `${packageName}@${version}` : packageName;
|
|
133
|
+
const result = await Bun.$`npm install ${installSpec} --prefix ${cwd} --no-save --no-package-lock`.quiet().nothrow();
|
|
134
|
+
if (result.exitCode !== 0) {
|
|
135
|
+
const detail = result.stderr.toString().trim() || result.stdout.toString().trim();
|
|
136
|
+
throw new Error(`npm install failed for ${installSpec}: ${detail || "unknown error"}`);
|
|
137
|
+
}
|
|
138
|
+
const resolved = resolveNpmPackagePath(cwd, packageName);
|
|
139
|
+
if (!resolved) {
|
|
140
|
+
throw new Error(`npm package "${packageName}" was not found under node_modules/ after install`);
|
|
141
|
+
}
|
|
142
|
+
if (version) {
|
|
143
|
+
const installedVersion = await readInstalledNpmVersion(resolved);
|
|
144
|
+
if (installedVersion && installedVersion !== version) {
|
|
145
|
+
throw new Error(`npm package "${packageName}" resolved to ${installedVersion}, expected ${version}`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return resolved;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export async function resolveNpmDependencySpec(spec: string, cwd = process.cwd()): Promise<PointLockPackage> {
|
|
78
152
|
const parsed = parseDependencySpec(spec);
|
|
79
|
-
if (parsed.kind
|
|
153
|
+
if (parsed.kind !== "npm") throw new Error(`Expected npm: spec, got ${spec}`);
|
|
154
|
+
const { name, version } = parseNpmLocator(parsed.locator);
|
|
155
|
+
const pkgDir = await ensureNpmPackage(cwd, name, version);
|
|
156
|
+
locatePointPackageRoot(pkgDir);
|
|
157
|
+
const npmVersion = (await readInstalledNpmVersion(pkgDir)) ?? version ?? "npm";
|
|
158
|
+
return { version: npmVersion, path: normalizePackagePath(cwd, pkgDir) };
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export async function resolveDependencySpec(spec: string, cwd = process.cwd()): Promise<PointLockPackage> {
|
|
162
|
+
const parsed = parseDependencySpec(spec);
|
|
163
|
+
if (parsed.kind === "npm") return resolveNpmDependencySpec(spec, cwd);
|
|
80
164
|
const path = normalizePackagePath(cwd, parsed.locator);
|
|
81
165
|
return { version: parsed.kind, path };
|
|
82
166
|
}
|
|
@@ -89,7 +173,7 @@ export async function resolveLockFromManifest(manifest: PointManifest, cwd = pro
|
|
|
89
173
|
},
|
|
90
174
|
};
|
|
91
175
|
for (const [name, spec] of Object.entries(manifest.dependencies ?? {})) {
|
|
92
|
-
packages[name] = resolveDependencySpec(spec, cwd);
|
|
176
|
+
packages[name] = await resolveDependencySpec(spec, cwd);
|
|
93
177
|
const entry = packages[name];
|
|
94
178
|
if (!entry.path) continue;
|
|
95
179
|
const nestedManifestPath = join(cwd, entry.path, POINT_MANIFEST);
|
|
@@ -123,7 +207,12 @@ export function packageRootFromLock(lock: PointLock | null, packageName: string)
|
|
|
123
207
|
return null;
|
|
124
208
|
}
|
|
125
209
|
|
|
126
|
-
|
|
210
|
+
function modulePathCandidates(root: string, modulePath: string): string[] {
|
|
211
|
+
const segments = modulePath.replaceAll(".", "/");
|
|
212
|
+
return [`${root}/${segments}.point`, `${root}/src/${segments}.point`];
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export function modulePathFromLock(lock: PointLock | null, moduleName: string, cwd = process.cwd()): string {
|
|
127
216
|
const dot = moduleName.indexOf(".");
|
|
128
217
|
if (dot < 0) {
|
|
129
218
|
throw new Error(`Use declarations without from must target package modules: ${moduleName}`);
|
|
@@ -134,5 +223,8 @@ export function modulePathFromLock(lock: PointLock | null, moduleName: string):
|
|
|
134
223
|
if (!root) {
|
|
135
224
|
throw new Error(`Unknown package "${packageName}" in ${moduleName}. Add it with: point add ${packageName} <spec>`);
|
|
136
225
|
}
|
|
137
|
-
|
|
226
|
+
for (const candidate of modulePathCandidates(root, modulePath)) {
|
|
227
|
+
if (existsSync(join(cwd, candidate))) return candidate;
|
|
228
|
+
}
|
|
229
|
+
return modulePathCandidates(root, modulePath)[0]!;
|
|
138
230
|
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import type { PointCoreProgram } from "./ast.ts";
|
|
2
|
+
import { emitPointCoreJavaScript } from "./emit-javascript.ts";
|
|
3
|
+
import { isPureLogicProgram } from "./emit-python.ts";
|
|
4
|
+
|
|
5
|
+
/** Pure logic with no imports or externals can run in-memory without a temp module file. */
|
|
6
|
+
export function canBundleRunInMemory(program: PointCoreProgram): boolean {
|
|
7
|
+
if (!isPureLogicProgram(program)) return false;
|
|
8
|
+
if (program.declarations.some((declaration) => declaration.kind === "import" || declaration.kind === "external")) return false;
|
|
9
|
+
if (program.semanticSource?.uses?.length) return false;
|
|
10
|
+
const emitted = emitPointCoreJavaScript(program);
|
|
11
|
+
if (/^\s*import\s/m.test(emitted)) return false;
|
|
12
|
+
return !hasUnresolvedCallTargets(emitted);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const BUILTIN_CALL_TARGETS = new Set([
|
|
16
|
+
"Array",
|
|
17
|
+
"Boolean",
|
|
18
|
+
"JSON",
|
|
19
|
+
"Math",
|
|
20
|
+
"Number",
|
|
21
|
+
"Object",
|
|
22
|
+
"Promise",
|
|
23
|
+
"String",
|
|
24
|
+
"console",
|
|
25
|
+
"fetch",
|
|
26
|
+
"parseInt",
|
|
27
|
+
"parseFloat",
|
|
28
|
+
]);
|
|
29
|
+
|
|
30
|
+
const NON_CALL_KEYWORDS = new Set([
|
|
31
|
+
"async",
|
|
32
|
+
"await",
|
|
33
|
+
"catch",
|
|
34
|
+
"delete",
|
|
35
|
+
"export",
|
|
36
|
+
"for",
|
|
37
|
+
"function",
|
|
38
|
+
"if",
|
|
39
|
+
"import",
|
|
40
|
+
"instanceof",
|
|
41
|
+
"new",
|
|
42
|
+
"return",
|
|
43
|
+
"switch",
|
|
44
|
+
"throw",
|
|
45
|
+
"typeof",
|
|
46
|
+
"void",
|
|
47
|
+
"while",
|
|
48
|
+
"with",
|
|
49
|
+
"yield",
|
|
50
|
+
]);
|
|
51
|
+
|
|
52
|
+
function hasUnresolvedCallTargets(source: string): boolean {
|
|
53
|
+
const defined = new Set<string>();
|
|
54
|
+
for (const match of source.matchAll(/(?:export\s+)?(?:async\s+)?function\s+([A-Za-z_$][\w$]*)/g)) defined.add(match[1]!);
|
|
55
|
+
for (const match of source.matchAll(/\b([A-Za-z_$][\w$]*)\s*\(/g)) {
|
|
56
|
+
const name = match[1]!;
|
|
57
|
+
if (NON_CALL_KEYWORDS.has(name) || BUILTIN_CALL_TARGETS.has(name) || defined.has(name)) continue;
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function bundleJavaScriptForEval(source: string): { body: string; exports: string[] } {
|
|
64
|
+
const exports: string[] = [];
|
|
65
|
+
const bodyLines: string[] = [];
|
|
66
|
+
for (const line of source.split(/\r?\n/)) {
|
|
67
|
+
if (/^\s*import\s/.test(line)) continue;
|
|
68
|
+
const fnMatch = line.match(/^export\s+(async\s+)?function\s+([A-Za-z_$][\w$]*)/);
|
|
69
|
+
if (fnMatch) {
|
|
70
|
+
exports.push(fnMatch[2]!);
|
|
71
|
+
bodyLines.push(line.replace(/^export\s+/, ""));
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
const bindingMatch = line.match(/^export\s+(const|let|var)\s+([A-Za-z_$][\w$]*)/);
|
|
75
|
+
if (bindingMatch) {
|
|
76
|
+
exports.push(bindingMatch[2]!);
|
|
77
|
+
bodyLines.push(line.replace(/^export\s+/, ""));
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
bodyLines.push(line);
|
|
81
|
+
}
|
|
82
|
+
return { body: bodyLines.join("\n"), exports };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export async function executeBundledEntry(program: PointCoreProgram, entryName: string): Promise<unknown> {
|
|
86
|
+
const { body, exports } = bundleJavaScriptForEval(emitPointCoreJavaScript(program));
|
|
87
|
+
if (!exports.includes(entryName)) throw new Error(`Entrypoint ${entryName} was not emitted.`);
|
|
88
|
+
const factory = new Function(`"use strict";\n${body}\nreturn { ${exports.join(", ")} };`);
|
|
89
|
+
const mod = factory() as Record<string, unknown>;
|
|
90
|
+
const entry = mod[entryName];
|
|
91
|
+
if (typeof entry !== "function") throw new Error(`Entrypoint ${entryName} was not a function.`);
|
|
92
|
+
return await (entry as () => unknown)();
|
|
93
|
+
}
|
package/src/std/env.ts
ADDED
package/src/std/fs.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
|
|
3
|
+
type PointStdError = { message: string };
|
|
4
|
+
|
|
5
|
+
export function readFile(path: string): string | PointStdError {
|
|
6
|
+
try {
|
|
7
|
+
return readFileSync(path, "utf8");
|
|
8
|
+
} catch (error) {
|
|
9
|
+
return { message: error instanceof Error ? error.message : String(error) };
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function writeFile(path: string, contents: string): void | PointStdError {
|
|
14
|
+
try {
|
|
15
|
+
writeFileSync(path, contents, "utf8");
|
|
16
|
+
} catch (error) {
|
|
17
|
+
return { message: error instanceof Error ? error.message : String(error) };
|
|
18
|
+
}
|
|
19
|
+
}
|
package/src/std/text.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export function textLength(value: string): number {
|
|
2
|
+
return value.length;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export function textContains(value: string, search: string): boolean {
|
|
6
|
+
return value.includes(search);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function textSplit(value: string, separator: string): string[] {
|
|
10
|
+
return value.split(separator);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function textTrim(value: string): string {
|
|
14
|
+
return value.trim();
|
|
15
|
+
}
|
package/src/std/time.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export function now(): string {
|
|
2
|
+
return new Date().toISOString();
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export async function sleep(ms: number): Promise<void> {
|
|
6
|
+
await Bun.sleep(ms);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function formatTime(value: string): string {
|
|
10
|
+
const date = new Date(value);
|
|
11
|
+
if (Number.isNaN(date.getTime())) {
|
|
12
|
+
return value;
|
|
13
|
+
}
|
|
14
|
+
return date.toUTCString();
|
|
15
|
+
}
|