@askrjs/cli 0.0.24 → 0.2.0
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/add.d.ts +1 -1
- package/dist/add.js +23 -11
- package/dist/analyze.d.ts +1 -1
- package/dist/analyze.js +1 -1
- package/dist/cli.js +1 -1
- package/dist/file-changes-CsrYAEYu.js +154 -0
- package/dist/{file-changes-cmFN-rF8.d.ts → file-changes-Va4jkhMm.d.ts} +2 -0
- package/dist/{guardrails-BVxzrcBX.js → guardrails-uLDOExsl.js} +1 -1
- package/dist/{runner-D2iNzz9g.js → runner-CtcqO3cF.js} +1 -1
- package/dist/{runner-N2qHCBar.js → runner-D_GIsgIj.js} +1 -1
- package/dist/templates/full-stack/package.json +10 -10
- package/dist/templates/spa/package.json +6 -6
- package/dist/templates/ssg/package.json +5 -5
- package/dist/templates/ssr/package.json +6 -6
- package/dist/templates/startkit/package.json +6 -6
- package/package.json +9 -9
- package/dist/file-changes-BAFLhEHZ.js +0 -66
package/dist/add.d.ts
CHANGED
package/dist/add.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { t as isDirectExecution } from "./is-direct-execution-Cdlr-ZUl.js";
|
|
3
|
-
import { t as writeFileChanges } from "./file-changes-
|
|
3
|
+
import { t as writeFileChanges } from "./file-changes-CsrYAEYu.js";
|
|
4
4
|
import fs from "node:fs/promises";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
//#region src/bin/add.ts
|
|
@@ -365,9 +365,11 @@ async function addPage(parsed, io, writeChanges) {
|
|
|
365
365
|
return 1;
|
|
366
366
|
}
|
|
367
367
|
const importSpecifier = toImportSpecifier(routesFile, pageFile);
|
|
368
|
+
let routeFileContent = "";
|
|
368
369
|
let updatedRoutes = "";
|
|
369
370
|
try {
|
|
370
|
-
|
|
371
|
+
routeFileContent = await fs.readFile(routesFile, "utf8");
|
|
372
|
+
updatedRoutes = createUpdatedRouteFile(routeFileContent, {
|
|
371
373
|
componentName,
|
|
372
374
|
importSpecifier,
|
|
373
375
|
routePath
|
|
@@ -387,7 +389,8 @@ async function addPage(parsed, io, writeChanges) {
|
|
|
387
389
|
})
|
|
388
390
|
}, {
|
|
389
391
|
filePath: routesFile,
|
|
390
|
-
content: updatedRoutes
|
|
392
|
+
content: updatedRoutes,
|
|
393
|
+
expectedContent: routeFileContent
|
|
391
394
|
}]);
|
|
392
395
|
} catch (error) {
|
|
393
396
|
io.error("Failed to write generated page artifacts.");
|
|
@@ -437,6 +440,7 @@ async function addAction(parsed, io, writeChanges) {
|
|
|
437
440
|
routePath: parsed.routePath,
|
|
438
441
|
slug
|
|
439
442
|
});
|
|
443
|
+
const [registryContent, authorizationContent] = await Promise.all([fs.readFile(registryFile, "utf8"), fs.readFile(authorizationFile, "utf8")]);
|
|
440
444
|
const actions = await discoverDeclaredActions(projectRoot, {
|
|
441
445
|
filePath: descriptorFile,
|
|
442
446
|
content: descriptor
|
|
@@ -463,11 +467,13 @@ async function addAction(parsed, io, writeChanges) {
|
|
|
463
467
|
},
|
|
464
468
|
{
|
|
465
469
|
filePath: registryFile,
|
|
466
|
-
content: renderServerActionRegistry(actions)
|
|
470
|
+
content: renderServerActionRegistry(actions),
|
|
471
|
+
expectedContent: registryContent
|
|
467
472
|
},
|
|
468
473
|
{
|
|
469
474
|
filePath: authorizationFile,
|
|
470
|
-
content: renderAuthorizationRegistry(actions)
|
|
475
|
+
content: renderAuthorizationRegistry(actions),
|
|
476
|
+
expectedContent: authorizationContent
|
|
471
477
|
}
|
|
472
478
|
]);
|
|
473
479
|
} catch (error) {
|
|
@@ -501,7 +507,8 @@ async function addDatabase(parsed, io, writeChanges) {
|
|
|
501
507
|
return 1;
|
|
502
508
|
}
|
|
503
509
|
try {
|
|
504
|
-
const
|
|
510
|
+
const manifestContent = await fs.readFile(manifestFile, "utf8");
|
|
511
|
+
const manifest = JSON.parse(manifestContent);
|
|
505
512
|
manifest.dependencies = {
|
|
506
513
|
...manifest.dependencies,
|
|
507
514
|
"@askrjs/orm": "0.0.0",
|
|
@@ -511,9 +518,12 @@ async function addDatabase(parsed, io, writeChanges) {
|
|
|
511
518
|
} : {}
|
|
512
519
|
};
|
|
513
520
|
manifest.dependencies = Object.fromEntries(Object.entries(manifest.dependencies).sort(([left], [right]) => left.localeCompare(right)));
|
|
514
|
-
const currentEnvironment = await fs.readFile(environmentFile, "utf8").catch(() =>
|
|
515
|
-
|
|
516
|
-
|
|
521
|
+
const currentEnvironment = await fs.readFile(environmentFile, "utf8").catch((error) => {
|
|
522
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") return null;
|
|
523
|
+
throw error;
|
|
524
|
+
});
|
|
525
|
+
const additions = (parsed.name === "postgres" ? ["DATABASE_URL=postgres://postgres:postgres@localhost:5432/app", "DATABASE_SHADOW_URL=postgres://postgres:postgres@localhost:5432/app_shadow"] : ["DATABASE_PATH=./data/app.sqlite"]).filter((line) => !currentEnvironment?.includes(`${line.split("=")[0]}=`));
|
|
526
|
+
const environment = `${currentEnvironment ?? ""}${currentEnvironment && !currentEnvironment.endsWith("\n") ? "\n" : ""}${additions.join("\n")}${additions.length ? "\n" : ""}`;
|
|
517
527
|
const driverImport = parsed.name;
|
|
518
528
|
const definition = [
|
|
519
529
|
"import { defineDatabase } from '@askrjs/orm';",
|
|
@@ -553,11 +563,13 @@ async function addDatabase(parsed, io, writeChanges) {
|
|
|
553
563
|
},
|
|
554
564
|
{
|
|
555
565
|
filePath: environmentFile,
|
|
556
|
-
content: environment
|
|
566
|
+
content: environment,
|
|
567
|
+
expectedContent: currentEnvironment
|
|
557
568
|
},
|
|
558
569
|
{
|
|
559
570
|
filePath: manifestFile,
|
|
560
|
-
content: `${JSON.stringify(manifest, null, 2)}\n
|
|
571
|
+
content: `${JSON.stringify(manifest, null, 2)}\n`,
|
|
572
|
+
expectedContent: manifestContent
|
|
561
573
|
}
|
|
562
574
|
]);
|
|
563
575
|
} catch (error) {
|
package/dist/analyze.d.ts
CHANGED
package/dist/analyze.js
CHANGED
|
@@ -71,7 +71,7 @@ async function runAnalyzeCli(args = process.argv.slice(2), io = console, runtime
|
|
|
71
71
|
io.log(helpText.trimEnd());
|
|
72
72
|
return 0;
|
|
73
73
|
}
|
|
74
|
-
const report = await (runtime.analyze ?? (await import("./runner-
|
|
74
|
+
const report = await (runtime.analyze ?? (await import("./runner-CtcqO3cF.js")).runAnalysis)({
|
|
75
75
|
cwd: parsed.cwd,
|
|
76
76
|
workspacePatterns: parsed.workspacePatterns,
|
|
77
77
|
check: parsed.check
|
package/dist/cli.js
CHANGED
|
@@ -101,7 +101,7 @@ async function runCli(args = process.argv.slice(2), io = console) {
|
|
|
101
101
|
return runAnalyzeCli(args.slice(1), io);
|
|
102
102
|
}
|
|
103
103
|
if (command === "check" || command === "doctor" || command === "repair") {
|
|
104
|
-
const { runGuardrailCli } = await import("./guardrails-
|
|
104
|
+
const { runGuardrailCli } = await import("./guardrails-uLDOExsl.js");
|
|
105
105
|
return runGuardrailCli(command, args.slice(1), io);
|
|
106
106
|
}
|
|
107
107
|
if (command === "generate") {
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { randomUUID } from "node:crypto";
|
|
4
|
+
//#region src/file-changes.ts
|
|
5
|
+
const LOCK_RETRY_MS = 10;
|
|
6
|
+
const LOCK_TIMEOUT_MS = 1e4;
|
|
7
|
+
const ORPHANED_LOCK_AGE_MS = 3e4;
|
|
8
|
+
function isNodeError(error, code) {
|
|
9
|
+
return error instanceof Error && "code" in error && error.code === code;
|
|
10
|
+
}
|
|
11
|
+
function delay(milliseconds) {
|
|
12
|
+
return new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
13
|
+
}
|
|
14
|
+
async function ownerIsAlive(lockPath) {
|
|
15
|
+
try {
|
|
16
|
+
const owner = JSON.parse(await fs.readFile(path.join(lockPath, "owner.json"), "utf8"));
|
|
17
|
+
if (!Number.isInteger(owner.pid) || owner.pid <= 0) return void 0;
|
|
18
|
+
try {
|
|
19
|
+
process.kill(owner.pid, 0);
|
|
20
|
+
return true;
|
|
21
|
+
} catch (error) {
|
|
22
|
+
if (isNodeError(error, "ESRCH")) return false;
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
} catch {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
async function removeOrphanedLock(lockPath) {
|
|
30
|
+
const ownerAlive = await ownerIsAlive(lockPath);
|
|
31
|
+
if (ownerAlive === true) return false;
|
|
32
|
+
if (ownerAlive === void 0) {
|
|
33
|
+
const stat = await fs.stat(lockPath).catch(() => null);
|
|
34
|
+
if (!stat || Date.now() - stat.mtimeMs < ORPHANED_LOCK_AGE_MS) return false;
|
|
35
|
+
}
|
|
36
|
+
await fs.rm(lockPath, {
|
|
37
|
+
recursive: true,
|
|
38
|
+
force: true
|
|
39
|
+
});
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
async function acquireFileLock(filePath) {
|
|
43
|
+
const lockPath = path.join(path.dirname(filePath), `.${path.basename(filePath)}.askr-lock`);
|
|
44
|
+
const deadline = Date.now() + LOCK_TIMEOUT_MS;
|
|
45
|
+
while (true) try {
|
|
46
|
+
await fs.mkdir(lockPath);
|
|
47
|
+
await fs.writeFile(path.join(lockPath, "owner.json"), `${JSON.stringify({ pid: process.pid })}\n`, { flag: "wx" });
|
|
48
|
+
return { lockPath };
|
|
49
|
+
} catch (error) {
|
|
50
|
+
if (!isNodeError(error, "EEXIST")) {
|
|
51
|
+
await fs.rm(lockPath, {
|
|
52
|
+
recursive: true,
|
|
53
|
+
force: true
|
|
54
|
+
}).catch(() => void 0);
|
|
55
|
+
throw error;
|
|
56
|
+
}
|
|
57
|
+
if (await removeOrphanedLock(lockPath)) continue;
|
|
58
|
+
if (Date.now() >= deadline) throw new Error(`Timed out waiting for file transaction lock: ${filePath}`);
|
|
59
|
+
await delay(LOCK_RETRY_MS);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
async function releaseFileLocks(locks) {
|
|
63
|
+
await Promise.all([...locks].reverse().map((lock) => fs.rm(lock.lockPath, {
|
|
64
|
+
recursive: true,
|
|
65
|
+
force: true
|
|
66
|
+
})));
|
|
67
|
+
}
|
|
68
|
+
async function readCurrentContent(filePath) {
|
|
69
|
+
try {
|
|
70
|
+
return await fs.readFile(filePath, "utf8");
|
|
71
|
+
} catch (error) {
|
|
72
|
+
if (isNodeError(error, "ENOENT")) return null;
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function hasExpectedContent(change) {
|
|
77
|
+
return Object.prototype.hasOwnProperty.call(change, "expectedContent");
|
|
78
|
+
}
|
|
79
|
+
async function remove(paths) {
|
|
80
|
+
await Promise.all(paths.map((filePath) => fs.rm(filePath, { force: true }).catch(() => void 0)));
|
|
81
|
+
}
|
|
82
|
+
async function restore(changes) {
|
|
83
|
+
let complete = true;
|
|
84
|
+
for (const change of [...changes].reverse()) try {
|
|
85
|
+
if (change.original === null) {
|
|
86
|
+
await fs.rm(change.filePath, { force: true });
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
const rollbackPath = path.join(path.dirname(change.filePath), `.${path.basename(change.filePath)}.askr-rollback-${randomUUID()}`);
|
|
90
|
+
await fs.writeFile(rollbackPath, change.original, {
|
|
91
|
+
flag: "wx",
|
|
92
|
+
mode: change.mode
|
|
93
|
+
});
|
|
94
|
+
await fs.rename(rollbackPath, change.filePath);
|
|
95
|
+
} catch {
|
|
96
|
+
complete = false;
|
|
97
|
+
}
|
|
98
|
+
return complete;
|
|
99
|
+
}
|
|
100
|
+
async function writeFileChanges(changes, options = {}) {
|
|
101
|
+
const ordered = [...changes].sort((left, right) => left.filePath.localeCompare(right.filePath));
|
|
102
|
+
if (new Set(ordered.map((change) => change.filePath)).size !== ordered.length) throw new Error("File changes contain duplicate target paths.");
|
|
103
|
+
const replace = options.replace ?? fs.rename;
|
|
104
|
+
const guarded = ordered.filter(hasExpectedContent);
|
|
105
|
+
const locks = [];
|
|
106
|
+
try {
|
|
107
|
+
for (const change of guarded) {
|
|
108
|
+
await fs.mkdir(path.dirname(change.filePath), { recursive: true });
|
|
109
|
+
locks.push(await acquireFileLock(change.filePath));
|
|
110
|
+
}
|
|
111
|
+
for (const change of guarded) if (await readCurrentContent(change.filePath) !== change.expectedContent) throw new Error(`File changed before writing: ${change.filePath}`);
|
|
112
|
+
await writeStagedChanges(ordered, replace);
|
|
113
|
+
} finally {
|
|
114
|
+
await releaseFileLocks(locks);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
async function writeStagedChanges(ordered, replace) {
|
|
118
|
+
const staged = [];
|
|
119
|
+
try {
|
|
120
|
+
for (const change of ordered) {
|
|
121
|
+
await fs.mkdir(path.dirname(change.filePath), { recursive: true });
|
|
122
|
+
const stat = await fs.stat(change.filePath).catch(() => null);
|
|
123
|
+
const original = stat ? await fs.readFile(change.filePath) : null;
|
|
124
|
+
const temporaryPath = path.join(path.dirname(change.filePath), `.${path.basename(change.filePath)}.askr-change-${randomUUID()}`);
|
|
125
|
+
const mode = stat?.mode ?? 420;
|
|
126
|
+
await fs.writeFile(temporaryPath, change.content, {
|
|
127
|
+
flag: "wx",
|
|
128
|
+
mode
|
|
129
|
+
});
|
|
130
|
+
staged.push({
|
|
131
|
+
...change,
|
|
132
|
+
original,
|
|
133
|
+
mode,
|
|
134
|
+
temporaryPath
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
} catch (error) {
|
|
138
|
+
await remove(staged.map((change) => change.temporaryPath));
|
|
139
|
+
throw error;
|
|
140
|
+
}
|
|
141
|
+
const replaced = [];
|
|
142
|
+
try {
|
|
143
|
+
for (const change of staged) {
|
|
144
|
+
await replace(change.temporaryPath, change.filePath);
|
|
145
|
+
replaced.push(change);
|
|
146
|
+
}
|
|
147
|
+
} catch {
|
|
148
|
+
const complete = await restore(replaced);
|
|
149
|
+
await remove(staged.map((change) => change.temporaryPath));
|
|
150
|
+
throw new Error(complete ? "File replacement failed; completed changes were rolled back." : "File replacement failed and rollback was incomplete.");
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
//#endregion
|
|
154
|
+
export { writeFileChanges as t };
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
interface FileChange {
|
|
3
3
|
readonly filePath: string;
|
|
4
4
|
readonly content: string;
|
|
5
|
+
/** Content observed while planning a shared-file edit; `null` means the file was absent. */
|
|
6
|
+
readonly expectedContent?: string | null;
|
|
5
7
|
}
|
|
6
8
|
interface FileChangeWriterOptions {
|
|
7
9
|
readonly replace?: (temporaryPath: string, filePath: string) => Promise<void>;
|
|
@@ -98,7 +98,7 @@ async function runGuardrailCli(command, args = process.argv.slice(2), io = conso
|
|
|
98
98
|
cwd: parsed.cwd,
|
|
99
99
|
workspacePatterns: parsed.workspacePatterns
|
|
100
100
|
};
|
|
101
|
-
const { runCheck, runDoctor, runRepair } = await import("./runner-
|
|
101
|
+
const { runCheck, runDoctor, runRepair } = await import("./runner-D_GIsgIj.js");
|
|
102
102
|
const report = command === "doctor" ? await runDoctor(options, runtime) : command === "repair" ? await runRepair(options) : await runCheck(options, runtime);
|
|
103
103
|
if (parsed.json) io.log(JSON.stringify(report));
|
|
104
104
|
else if (report.command === "doctor") printDoctor(report, io);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as writeFileChanges } from "./file-changes-
|
|
1
|
+
import { t as writeFileChanges } from "./file-changes-CsrYAEYu.js";
|
|
2
2
|
import { discoverWorkspaceProject } from "./discovery-DUDrZCIC.js";
|
|
3
3
|
import fs from "node:fs/promises";
|
|
4
4
|
import path from "node:path";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as inspectBundledSkills } from "./skills-C72Z3-Cr.js";
|
|
2
2
|
import { discoverWorkspaceProject } from "./discovery-DUDrZCIC.js";
|
|
3
|
-
import { analysisHasBlockingFindings, runAnalysis } from "./runner-
|
|
3
|
+
import { analysisHasBlockingFindings, runAnalysis } from "./runner-CtcqO3cF.js";
|
|
4
4
|
import fs from "node:fs/promises";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { spawn } from "node:child_process";
|
|
@@ -15,19 +15,19 @@
|
|
|
15
15
|
"check": "askr check"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@askrjs/askr": ">=0.0
|
|
19
|
-
"@askrjs/auth": ">=0.0
|
|
20
|
-
"@askrjs/i18n": ">=0.0
|
|
21
|
-
"@askrjs/node": ">=0.0
|
|
22
|
-
"@askrjs/otel": ">=0.0
|
|
23
|
-
"@askrjs/schema": ">=0.0
|
|
24
|
-
"@askrjs/server": ">=0.0
|
|
25
|
-
"@askrjs/themes": ">=0.0
|
|
26
|
-
"@askrjs/ui": ">=0.0
|
|
18
|
+
"@askrjs/askr": ">=0.2.0 <0.3.0",
|
|
19
|
+
"@askrjs/auth": ">=0.2.0 <0.3.0",
|
|
20
|
+
"@askrjs/i18n": ">=0.2.0 <0.3.0",
|
|
21
|
+
"@askrjs/node": ">=0.2.0 <0.3.0",
|
|
22
|
+
"@askrjs/otel": ">=0.2.0 <0.3.0",
|
|
23
|
+
"@askrjs/schema": ">=0.2.0 <0.3.0",
|
|
24
|
+
"@askrjs/server": ">=0.2.0 <0.3.0",
|
|
25
|
+
"@askrjs/themes": ">=0.2.0 <0.3.0",
|
|
26
|
+
"@askrjs/ui": ">=0.2.0 <0.3.0",
|
|
27
27
|
"@opentelemetry/api": "^1.9.0"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
|
-
"@askrjs/vite": ">=0.0
|
|
30
|
+
"@askrjs/vite": ">=0.2.0 <0.3.0",
|
|
31
31
|
"@types/node": "^20.19.0",
|
|
32
32
|
"jsdom": "^29.1.1",
|
|
33
33
|
"typescript": "^7.0.2",
|
|
@@ -16,14 +16,14 @@
|
|
|
16
16
|
"check": "askr check"
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@askrjs/askr": ">=0.0
|
|
20
|
-
"@askrjs/charts": ">=0.
|
|
21
|
-
"@askrjs/lucide": ">=0.0
|
|
22
|
-
"@askrjs/themes": ">=0.0
|
|
23
|
-
"@askrjs/ui": ">=0.0
|
|
19
|
+
"@askrjs/askr": ">=0.2.0 <0.3.0",
|
|
20
|
+
"@askrjs/charts": ">=0.2.0 <0.3.0",
|
|
21
|
+
"@askrjs/lucide": ">=0.2.0 <0.3.0",
|
|
22
|
+
"@askrjs/themes": ">=0.2.0 <0.3.0",
|
|
23
|
+
"@askrjs/ui": ">=0.2.0 <0.3.0"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
|
-
"@askrjs/vite": ">=0.0
|
|
26
|
+
"@askrjs/vite": ">=0.2.0 <0.3.0",
|
|
27
27
|
"@types/node": "^20.19.0",
|
|
28
28
|
"jsdom": "^29.1.1",
|
|
29
29
|
"typescript": "^7.0.2",
|
|
@@ -18,13 +18,13 @@
|
|
|
18
18
|
"check": "askr check"
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@askrjs/askr": ">=0.0
|
|
22
|
-
"@askrjs/themes": ">=0.0
|
|
23
|
-
"@askrjs/ui": ">=0.0
|
|
21
|
+
"@askrjs/askr": ">=0.2.0 <0.3.0",
|
|
22
|
+
"@askrjs/themes": ">=0.2.0 <0.3.0",
|
|
23
|
+
"@askrjs/ui": ">=0.2.0 <0.3.0"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
|
-
"@askrjs/cli": ">=0.0
|
|
27
|
-
"@askrjs/vite": ">=0.0
|
|
26
|
+
"@askrjs/cli": ">=0.2.0 <0.3.0",
|
|
27
|
+
"@askrjs/vite": ">=0.2.0 <0.3.0",
|
|
28
28
|
"@types/node": "^20.19.0",
|
|
29
29
|
"jsdom": "^29.1.1",
|
|
30
30
|
"typescript": "^7.0.2",
|
|
@@ -15,14 +15,14 @@
|
|
|
15
15
|
"check": "askr check"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@askrjs/askr": ">=0.0
|
|
19
|
-
"@askrjs/node": ">=0.0
|
|
20
|
-
"@askrjs/server": ">=0.0
|
|
21
|
-
"@askrjs/themes": ">=0.0
|
|
22
|
-
"@askrjs/ui": ">=0.0
|
|
18
|
+
"@askrjs/askr": ">=0.2.0 <0.3.0",
|
|
19
|
+
"@askrjs/node": ">=0.2.0 <0.3.0",
|
|
20
|
+
"@askrjs/server": ">=0.2.0 <0.3.0",
|
|
21
|
+
"@askrjs/themes": ">=0.2.0 <0.3.0",
|
|
22
|
+
"@askrjs/ui": ">=0.2.0 <0.3.0"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
|
-
"@askrjs/vite": ">=0.0
|
|
25
|
+
"@askrjs/vite": ">=0.2.0 <0.3.0",
|
|
26
26
|
"@types/node": "^20.19.0",
|
|
27
27
|
"jsdom": "^29.1.1",
|
|
28
28
|
"tsx": "^4.23.1",
|
|
@@ -17,14 +17,14 @@
|
|
|
17
17
|
"check": "askr check"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@askrjs/askr": ">=0.0
|
|
21
|
-
"@askrjs/auth": ">=0.0
|
|
22
|
-
"@askrjs/lucide": ">=0.0
|
|
23
|
-
"@askrjs/themes": ">=0.0
|
|
24
|
-
"@askrjs/ui": ">=0.0
|
|
20
|
+
"@askrjs/askr": ">=0.2.0 <0.3.0",
|
|
21
|
+
"@askrjs/auth": ">=0.2.0 <0.3.0",
|
|
22
|
+
"@askrjs/lucide": ">=0.2.0 <0.3.0",
|
|
23
|
+
"@askrjs/themes": ">=0.2.0 <0.3.0",
|
|
24
|
+
"@askrjs/ui": ">=0.2.0 <0.3.0"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
|
-
"@askrjs/vite": ">=0.0
|
|
27
|
+
"@askrjs/vite": ">=0.2.0 <0.3.0",
|
|
28
28
|
"@types/node": "^20.19.0",
|
|
29
29
|
"jsdom": "^29.1.1",
|
|
30
30
|
"typescript": "^7.0.2",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@askrjs/cli",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Unified CLI for the Askr platform",
|
|
5
5
|
"homepage": "https://github.com/askrjs/askr-cli#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -67,13 +67,13 @@
|
|
|
67
67
|
"typescript": "npm:@typescript/typescript6@^6.0.2"
|
|
68
68
|
},
|
|
69
69
|
"devDependencies": {
|
|
70
|
-
"@askrjs/askr": ">=0.0
|
|
71
|
-
"@askrjs/charts": ">=0.
|
|
72
|
-
"@askrjs/logos": ">=0.0
|
|
73
|
-
"@askrjs/lucide": ">=0.0
|
|
74
|
-
"@askrjs/themes": ">=0.0
|
|
75
|
-
"@askrjs/ui": ">=0.0
|
|
76
|
-
"@askrjs/vite": ">=0.0
|
|
70
|
+
"@askrjs/askr": ">=0.2.0 <0.3.0",
|
|
71
|
+
"@askrjs/charts": ">=0.2.0 <0.3.0",
|
|
72
|
+
"@askrjs/logos": ">=0.2.0 <0.3.0",
|
|
73
|
+
"@askrjs/lucide": ">=0.2.0 <0.3.0",
|
|
74
|
+
"@askrjs/themes": ">=0.2.0 <0.3.0",
|
|
75
|
+
"@askrjs/ui": ">=0.2.0 <0.3.0",
|
|
76
|
+
"@askrjs/vite": ">=0.2.0 <0.3.0",
|
|
77
77
|
"@types/js-yaml": "^4.0.9",
|
|
78
78
|
"@types/node": "^26.2.0",
|
|
79
79
|
"@types/semver": "^7.8.0",
|
|
@@ -84,7 +84,7 @@
|
|
|
84
84
|
"vitest": "^4.1.10"
|
|
85
85
|
},
|
|
86
86
|
"peerDependencies": {
|
|
87
|
-
"@askrjs/askr": ">=0.0
|
|
87
|
+
"@askrjs/askr": ">=0.2.0 <0.3.0"
|
|
88
88
|
},
|
|
89
89
|
"peerDependenciesMeta": {
|
|
90
90
|
"@askrjs/askr": {
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs/promises";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { randomUUID } from "node:crypto";
|
|
4
|
-
//#region src/file-changes.ts
|
|
5
|
-
async function remove(paths) {
|
|
6
|
-
await Promise.all(paths.map((filePath) => fs.rm(filePath, { force: true }).catch(() => void 0)));
|
|
7
|
-
}
|
|
8
|
-
async function restore(changes) {
|
|
9
|
-
let complete = true;
|
|
10
|
-
for (const change of [...changes].reverse()) try {
|
|
11
|
-
if (change.original === null) {
|
|
12
|
-
await fs.rm(change.filePath, { force: true });
|
|
13
|
-
continue;
|
|
14
|
-
}
|
|
15
|
-
const rollbackPath = path.join(path.dirname(change.filePath), `.${path.basename(change.filePath)}.askr-rollback-${randomUUID()}`);
|
|
16
|
-
await fs.writeFile(rollbackPath, change.original, {
|
|
17
|
-
flag: "wx",
|
|
18
|
-
mode: change.mode
|
|
19
|
-
});
|
|
20
|
-
await fs.rename(rollbackPath, change.filePath);
|
|
21
|
-
} catch {
|
|
22
|
-
complete = false;
|
|
23
|
-
}
|
|
24
|
-
return complete;
|
|
25
|
-
}
|
|
26
|
-
async function writeFileChanges(changes, options = {}) {
|
|
27
|
-
const ordered = [...changes].sort((left, right) => left.filePath.localeCompare(right.filePath));
|
|
28
|
-
if (new Set(ordered.map((change) => change.filePath)).size !== ordered.length) throw new Error("File changes contain duplicate target paths.");
|
|
29
|
-
const replace = options.replace ?? fs.rename;
|
|
30
|
-
const staged = [];
|
|
31
|
-
try {
|
|
32
|
-
for (const change of ordered) {
|
|
33
|
-
await fs.mkdir(path.dirname(change.filePath), { recursive: true });
|
|
34
|
-
const stat = await fs.stat(change.filePath).catch(() => null);
|
|
35
|
-
const original = stat ? await fs.readFile(change.filePath) : null;
|
|
36
|
-
const temporaryPath = path.join(path.dirname(change.filePath), `.${path.basename(change.filePath)}.askr-change-${randomUUID()}`);
|
|
37
|
-
const mode = stat?.mode ?? 420;
|
|
38
|
-
await fs.writeFile(temporaryPath, change.content, {
|
|
39
|
-
flag: "wx",
|
|
40
|
-
mode
|
|
41
|
-
});
|
|
42
|
-
staged.push({
|
|
43
|
-
...change,
|
|
44
|
-
original,
|
|
45
|
-
mode,
|
|
46
|
-
temporaryPath
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
} catch (error) {
|
|
50
|
-
await remove(staged.map((change) => change.temporaryPath));
|
|
51
|
-
throw error;
|
|
52
|
-
}
|
|
53
|
-
const replaced = [];
|
|
54
|
-
try {
|
|
55
|
-
for (const change of staged) {
|
|
56
|
-
await replace(change.temporaryPath, change.filePath);
|
|
57
|
-
replaced.push(change);
|
|
58
|
-
}
|
|
59
|
-
} catch {
|
|
60
|
-
const complete = await restore(replaced);
|
|
61
|
-
await remove(staged.map((change) => change.temporaryPath));
|
|
62
|
-
throw new Error(complete ? "File replacement failed; completed changes were rolled back." : "File replacement failed and rollback was incomplete.");
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
//#endregion
|
|
66
|
-
export { writeFileChanges as t };
|