@eunjae/il 0.0.1 → 1.0.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/scripts/il.d.mts +1 -0
- package/dist/scripts/il.mjs +977 -0
- package/package.json +10 -3
- package/dist/lib/__tests__/aliasRepo.test.js +0 -17
- package/dist/lib/__tests__/edit.test.js +0 -32
- package/dist/lib/__tests__/fsm.test.js +0 -22
- package/dist/lib/__tests__/id.test.js +0 -25
- package/dist/lib/__tests__/schema.test.js +0 -37
- package/dist/lib/__tests__/taskOperations.test.js +0 -33
- package/dist/lib/aliasReconcile.js +0 -28
- package/dist/lib/aliasRepo.js +0 -58
- package/dist/lib/config.js +0 -35
- package/dist/lib/constants.js +0 -6
- package/dist/lib/edit.js +0 -47
- package/dist/lib/format.js +0 -52
- package/dist/lib/fsm.js +0 -25
- package/dist/lib/gitignore.js +0 -21
- package/dist/lib/id.js +0 -30
- package/dist/lib/json.js +0 -10
- package/dist/lib/lock.js +0 -25
- package/dist/lib/pr.js +0 -64
- package/dist/lib/schema.js +0 -64
- package/dist/lib/search.js +0 -17
- package/dist/lib/taskFactory.js +0 -19
- package/dist/lib/taskOperations.js +0 -57
- package/dist/lib/taskRepo.js +0 -67
- package/dist/lib/taskResolver.js +0 -31
- package/dist/lib/taskStore.js +0 -9
- package/dist/lib/time.js +0 -18
- package/dist/lib/types.js +0 -3
- package/dist/lib/workspace.js +0 -58
- package/dist/scripts/il.js +0 -396
- package/dist/scripts/suivi.js +0 -396
package/dist/lib/schema.js
DELETED
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
import { taskStatuses, taskTypes } from './types';
|
|
3
|
-
export const logEntrySchema = z.object({
|
|
4
|
-
ts: z.string(),
|
|
5
|
-
msg: z.string(),
|
|
6
|
-
status: z.enum(taskStatuses).optional()
|
|
7
|
-
});
|
|
8
|
-
export const dayAssignmentSchema = z.object({
|
|
9
|
-
date: z.string(),
|
|
10
|
-
action: z.enum(['assign', 'unassign']),
|
|
11
|
-
ts: z.string(),
|
|
12
|
-
msg: z.string().optional()
|
|
13
|
-
});
|
|
14
|
-
const prRepoSchema = z.object({
|
|
15
|
-
host: z.string(),
|
|
16
|
-
owner: z.string(),
|
|
17
|
-
name: z.string()
|
|
18
|
-
});
|
|
19
|
-
const prFetchedSchema = z.object({
|
|
20
|
-
at: z.string(),
|
|
21
|
-
title: z.string(),
|
|
22
|
-
author: z.object({
|
|
23
|
-
login: z.string()
|
|
24
|
-
}),
|
|
25
|
-
state: z.enum(['open', 'closed', 'merged']),
|
|
26
|
-
draft: z.boolean(),
|
|
27
|
-
updated_at: z.string()
|
|
28
|
-
});
|
|
29
|
-
export const prAttachmentSchema = z.object({
|
|
30
|
-
url: z.string(),
|
|
31
|
-
provider: z.literal('github'),
|
|
32
|
-
repo: prRepoSchema.optional(),
|
|
33
|
-
number: z.number().int().positive().optional(),
|
|
34
|
-
fetched: prFetchedSchema.optional()
|
|
35
|
-
});
|
|
36
|
-
export const taskMetadataSchema = z.object({
|
|
37
|
-
url: z.string().optional(),
|
|
38
|
-
pr: prAttachmentSchema.optional()
|
|
39
|
-
});
|
|
40
|
-
export const taskSchema = z
|
|
41
|
-
.object({
|
|
42
|
-
id: z.string(),
|
|
43
|
-
ref: z.string(),
|
|
44
|
-
type: z.enum(taskTypes),
|
|
45
|
-
title: z.string(),
|
|
46
|
-
status: z.enum(taskStatuses),
|
|
47
|
-
created_at: z.string(),
|
|
48
|
-
updated_at: z.string(),
|
|
49
|
-
metadata: taskMetadataSchema,
|
|
50
|
-
logs: z.array(logEntrySchema),
|
|
51
|
-
day_assignments: z.array(dayAssignmentSchema)
|
|
52
|
-
})
|
|
53
|
-
.superRefine((task, ctx) => {
|
|
54
|
-
if (task.type === 'pr_review' && !task.metadata.pr?.url) {
|
|
55
|
-
ctx.addIssue({
|
|
56
|
-
code: z.ZodIssueCode.custom,
|
|
57
|
-
message: 'PR review tasks require metadata.pr.url',
|
|
58
|
-
path: ['metadata', 'pr', 'url']
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
});
|
|
62
|
-
export const validateTaskOrThrow = (task) => {
|
|
63
|
-
return taskSchema.parse(task);
|
|
64
|
-
};
|
package/dist/lib/search.js
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
export const taskMatchesQuery = (task, query) => {
|
|
2
|
-
const needle = query.toLowerCase();
|
|
3
|
-
const contains = (value) => value ? value.toLowerCase().includes(needle) : false;
|
|
4
|
-
if (contains(task.title)) {
|
|
5
|
-
return true;
|
|
6
|
-
}
|
|
7
|
-
if (task.logs.some((log) => contains(log.msg))) {
|
|
8
|
-
return true;
|
|
9
|
-
}
|
|
10
|
-
if (contains(task.metadata.pr?.url)) {
|
|
11
|
-
return true;
|
|
12
|
-
}
|
|
13
|
-
if (contains(task.metadata.pr?.fetched?.title)) {
|
|
14
|
-
return true;
|
|
15
|
-
}
|
|
16
|
-
return false;
|
|
17
|
-
};
|
package/dist/lib/taskFactory.js
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { generateStableRef, generateTaskId } from './id';
|
|
2
|
-
import { nowIso } from './time';
|
|
3
|
-
export const buildTask = (input) => {
|
|
4
|
-
const id = generateTaskId(input.type);
|
|
5
|
-
const ref = generateStableRef(input.type, id);
|
|
6
|
-
const timestamp = nowIso();
|
|
7
|
-
return {
|
|
8
|
-
id,
|
|
9
|
-
ref,
|
|
10
|
-
type: input.type,
|
|
11
|
-
title: input.title,
|
|
12
|
-
status: 'backlog',
|
|
13
|
-
created_at: timestamp,
|
|
14
|
-
updated_at: timestamp,
|
|
15
|
-
metadata: input.metadata ?? {},
|
|
16
|
-
logs: [],
|
|
17
|
-
day_assignments: []
|
|
18
|
-
};
|
|
19
|
-
};
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import { getNextStatus } from './fsm';
|
|
2
|
-
import { nowIso } from './time';
|
|
3
|
-
const defaultMessages = {
|
|
4
|
-
start: 'Started task',
|
|
5
|
-
pause: 'Paused task',
|
|
6
|
-
complete: 'Completed task',
|
|
7
|
-
cancel: 'Cancelled task'
|
|
8
|
-
};
|
|
9
|
-
export const appendLog = (task, message, status) => {
|
|
10
|
-
const log = {
|
|
11
|
-
ts: nowIso(),
|
|
12
|
-
msg: message,
|
|
13
|
-
status
|
|
14
|
-
};
|
|
15
|
-
return {
|
|
16
|
-
...task,
|
|
17
|
-
logs: [...task.logs, log],
|
|
18
|
-
updated_at: nowIso()
|
|
19
|
-
};
|
|
20
|
-
};
|
|
21
|
-
export const applyTransition = (task, action, message) => {
|
|
22
|
-
const nextStatus = getNextStatus(task.status, action);
|
|
23
|
-
const logMessage = message ?? defaultMessages[action];
|
|
24
|
-
const log = {
|
|
25
|
-
ts: nowIso(),
|
|
26
|
-
msg: logMessage,
|
|
27
|
-
status: nextStatus
|
|
28
|
-
};
|
|
29
|
-
const updated = {
|
|
30
|
-
...task,
|
|
31
|
-
status: nextStatus,
|
|
32
|
-
updated_at: nowIso(),
|
|
33
|
-
logs: [...task.logs, log]
|
|
34
|
-
};
|
|
35
|
-
return { task: updated, nextStatus };
|
|
36
|
-
};
|
|
37
|
-
export const isAssignedOnDate = (task, date) => {
|
|
38
|
-
const events = task.day_assignments.filter((entry) => entry.date === date);
|
|
39
|
-
if (events.length === 0) {
|
|
40
|
-
return false;
|
|
41
|
-
}
|
|
42
|
-
const lastEvent = events[events.length - 1];
|
|
43
|
-
return lastEvent.action === 'assign';
|
|
44
|
-
};
|
|
45
|
-
export const appendDayAssignment = (task, date, action, msg) => {
|
|
46
|
-
const event = {
|
|
47
|
-
date,
|
|
48
|
-
action,
|
|
49
|
-
ts: nowIso(),
|
|
50
|
-
msg
|
|
51
|
-
};
|
|
52
|
-
return {
|
|
53
|
-
...task,
|
|
54
|
-
day_assignments: [...task.day_assignments, event],
|
|
55
|
-
updated_at: nowIso()
|
|
56
|
-
};
|
|
57
|
-
};
|
package/dist/lib/taskRepo.js
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import { access, readdir, rename } from 'node:fs/promises';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { STATUS_ORDER, TASKS_DIR } from './constants';
|
|
4
|
-
import { readJsonFile, writeJsonAtomic } from './json';
|
|
5
|
-
import { taskSchema } from './schema';
|
|
6
|
-
const taskFileName = (taskId) => `${taskId}.json`;
|
|
7
|
-
const getStatusDir = (workspaceRoot, status) => path.join(workspaceRoot, TASKS_DIR, status);
|
|
8
|
-
export const getTaskPath = (workspaceRoot, status, taskId) => {
|
|
9
|
-
return path.join(getStatusDir(workspaceRoot, status), taskFileName(taskId));
|
|
10
|
-
};
|
|
11
|
-
const fileExists = async (filePath) => {
|
|
12
|
-
try {
|
|
13
|
-
await access(filePath);
|
|
14
|
-
return true;
|
|
15
|
-
}
|
|
16
|
-
catch {
|
|
17
|
-
return false;
|
|
18
|
-
}
|
|
19
|
-
};
|
|
20
|
-
export const findTaskPathById = async (workspaceRoot, taskId) => {
|
|
21
|
-
for (const status of STATUS_ORDER) {
|
|
22
|
-
const candidate = getTaskPath(workspaceRoot, status, taskId);
|
|
23
|
-
if (await fileExists(candidate)) {
|
|
24
|
-
return { filePath: candidate, status };
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
return null;
|
|
28
|
-
};
|
|
29
|
-
export const loadTaskFromPath = async (filePath) => {
|
|
30
|
-
const parsed = await readJsonFile(filePath);
|
|
31
|
-
return taskSchema.parse(parsed);
|
|
32
|
-
};
|
|
33
|
-
export const loadTaskById = async (workspaceRoot, taskId) => {
|
|
34
|
-
const found = await findTaskPathById(workspaceRoot, taskId);
|
|
35
|
-
if (!found) {
|
|
36
|
-
throw new Error(`Task not found: ${taskId}`);
|
|
37
|
-
}
|
|
38
|
-
const task = await loadTaskFromPath(found.filePath);
|
|
39
|
-
return { task, status: found.status, filePath: found.filePath };
|
|
40
|
-
};
|
|
41
|
-
export const listTasksByStatus = async (workspaceRoot, status) => {
|
|
42
|
-
const statusDir = getStatusDir(workspaceRoot, status);
|
|
43
|
-
const entries = await readdir(statusDir, { withFileTypes: true });
|
|
44
|
-
const tasks = await Promise.all(entries
|
|
45
|
-
.filter((entry) => entry.isFile() && entry.name.endsWith('.json'))
|
|
46
|
-
.map(async (entry) => {
|
|
47
|
-
const filePath = path.join(statusDir, entry.name);
|
|
48
|
-
const task = await loadTaskFromPath(filePath);
|
|
49
|
-
return { task, status, filePath };
|
|
50
|
-
}));
|
|
51
|
-
return tasks;
|
|
52
|
-
};
|
|
53
|
-
export const listAllTasks = async (workspaceRoot) => {
|
|
54
|
-
const all = await Promise.all(STATUS_ORDER.map((status) => listTasksByStatus(workspaceRoot, status)));
|
|
55
|
-
return all.flat();
|
|
56
|
-
};
|
|
57
|
-
export const saveTask = async (workspaceRoot, status, task) => {
|
|
58
|
-
const filePath = getTaskPath(workspaceRoot, status, task.id);
|
|
59
|
-
await writeJsonAtomic(filePath, task);
|
|
60
|
-
return filePath;
|
|
61
|
-
};
|
|
62
|
-
export const moveTaskFile = async (workspaceRoot, taskId, fromStatus, toStatus) => {
|
|
63
|
-
const fromPath = getTaskPath(workspaceRoot, fromStatus, taskId);
|
|
64
|
-
const toPath = getTaskPath(workspaceRoot, toStatus, taskId);
|
|
65
|
-
await rename(fromPath, toPath);
|
|
66
|
-
return toPath;
|
|
67
|
-
};
|
package/dist/lib/taskResolver.js
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { resolveAlias } from './aliasRepo';
|
|
2
|
-
import { findTaskPathById, listAllTasks, loadTaskFromPath } from './taskRepo';
|
|
3
|
-
const stableRefPattern = /^[TR]-[A-Z0-9]{6}$/;
|
|
4
|
-
export const resolveTaskId = async (workspaceRoot, identifier) => {
|
|
5
|
-
const aliasMatch = await resolveAlias(workspaceRoot, identifier);
|
|
6
|
-
if (aliasMatch) {
|
|
7
|
-
return aliasMatch;
|
|
8
|
-
}
|
|
9
|
-
if (stableRefPattern.test(identifier)) {
|
|
10
|
-
const tasks = await listAllTasks(workspaceRoot);
|
|
11
|
-
const match = tasks.find((stored) => stored.task.ref === identifier);
|
|
12
|
-
if (!match) {
|
|
13
|
-
throw new Error(`Task not found for ref: ${identifier}`);
|
|
14
|
-
}
|
|
15
|
-
return match.task.id;
|
|
16
|
-
}
|
|
17
|
-
const found = await findTaskPathById(workspaceRoot, identifier);
|
|
18
|
-
if (!found) {
|
|
19
|
-
throw new Error(`Task not found: ${identifier}`);
|
|
20
|
-
}
|
|
21
|
-
return identifier;
|
|
22
|
-
};
|
|
23
|
-
export const resolveTask = async (workspaceRoot, identifier) => {
|
|
24
|
-
const taskId = await resolveTaskId(workspaceRoot, identifier);
|
|
25
|
-
const found = await findTaskPathById(workspaceRoot, taskId);
|
|
26
|
-
if (!found) {
|
|
27
|
-
throw new Error(`Task not found: ${identifier}`);
|
|
28
|
-
}
|
|
29
|
-
const task = await loadTaskFromPath(found.filePath);
|
|
30
|
-
return { task, status: found.status, filePath: found.filePath };
|
|
31
|
-
};
|
package/dist/lib/taskStore.js
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { allocateAlias, writeAliases } from './aliasRepo';
|
|
2
|
-
import { saveTask } from './taskRepo';
|
|
3
|
-
export const createTaskInStore = async (workspaceRoot, task) => {
|
|
4
|
-
const prefix = task.type === 'pr_review' ? 'R' : 'T';
|
|
5
|
-
const { alias, aliases } = await allocateAlias(workspaceRoot, prefix, task.id);
|
|
6
|
-
await writeAliases(workspaceRoot, aliases);
|
|
7
|
-
await saveTask(workspaceRoot, task.status, task);
|
|
8
|
-
return { alias, task };
|
|
9
|
-
};
|
package/dist/lib/time.js
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { DateTime } from 'luxon';
|
|
2
|
-
const DEFAULT_ZONE = 'Europe/Paris';
|
|
3
|
-
export const nowIso = () => {
|
|
4
|
-
const value = DateTime.now().setZone(DEFAULT_ZONE).toISO();
|
|
5
|
-
if (!value) {
|
|
6
|
-
throw new Error('Failed to generate timestamp');
|
|
7
|
-
}
|
|
8
|
-
return value;
|
|
9
|
-
};
|
|
10
|
-
export const todayDate = (date) => {
|
|
11
|
-
const value = date
|
|
12
|
-
? DateTime.fromISO(date, { zone: DEFAULT_ZONE }).toISODate()
|
|
13
|
-
: DateTime.now().setZone(DEFAULT_ZONE).toISODate();
|
|
14
|
-
if (!value) {
|
|
15
|
-
throw new Error('Failed to generate date');
|
|
16
|
-
}
|
|
17
|
-
return value;
|
|
18
|
-
};
|
package/dist/lib/types.js
DELETED
package/dist/lib/workspace.js
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import { access, mkdir } from 'node:fs/promises';
|
|
2
|
-
import { homedir } from 'node:os';
|
|
3
|
-
import path from 'node:path';
|
|
4
|
-
import { APP_DIR, APP_NAME, LOCK_DIR, STATUS_ORDER, TASKS_DIR } from './constants';
|
|
5
|
-
const exists = async (filePath) => {
|
|
6
|
-
try {
|
|
7
|
-
await access(filePath);
|
|
8
|
-
return true;
|
|
9
|
-
}
|
|
10
|
-
catch {
|
|
11
|
-
return false;
|
|
12
|
-
}
|
|
13
|
-
};
|
|
14
|
-
export const findGitRoot = async (startDir) => {
|
|
15
|
-
let current = path.resolve(startDir);
|
|
16
|
-
while (true) {
|
|
17
|
-
if (await exists(path.join(current, '.git'))) {
|
|
18
|
-
return current;
|
|
19
|
-
}
|
|
20
|
-
const parent = path.dirname(current);
|
|
21
|
-
if (parent === current) {
|
|
22
|
-
return null;
|
|
23
|
-
}
|
|
24
|
-
current = parent;
|
|
25
|
-
}
|
|
26
|
-
};
|
|
27
|
-
export const resolveGlobalWorkspaceRoot = () => {
|
|
28
|
-
const dataHome = process.env.XDG_DATA_HOME ?? path.join(homedir(), '.local', 'share');
|
|
29
|
-
return path.join(dataHome, APP_NAME);
|
|
30
|
-
};
|
|
31
|
-
export const resolveWorkspace = async (options) => {
|
|
32
|
-
if (options.store) {
|
|
33
|
-
return { root: path.resolve(options.store), kind: 'explicit' };
|
|
34
|
-
}
|
|
35
|
-
const cwd = options.cwd ?? process.cwd();
|
|
36
|
-
const repoRoot = await findGitRoot(cwd);
|
|
37
|
-
if (options.repo) {
|
|
38
|
-
if (!repoRoot) {
|
|
39
|
-
throw new Error('Not inside a git repository');
|
|
40
|
-
}
|
|
41
|
-
return { root: path.join(repoRoot, APP_DIR), kind: 'repo' };
|
|
42
|
-
}
|
|
43
|
-
if (options.global) {
|
|
44
|
-
return { root: resolveGlobalWorkspaceRoot(), kind: 'global' };
|
|
45
|
-
}
|
|
46
|
-
if (repoRoot) {
|
|
47
|
-
const repoWorkspace = path.join(repoRoot, APP_DIR);
|
|
48
|
-
if (await exists(repoWorkspace)) {
|
|
49
|
-
return { root: repoWorkspace, kind: 'repo' };
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
return { root: resolveGlobalWorkspaceRoot(), kind: 'global' };
|
|
53
|
-
};
|
|
54
|
-
export const ensureWorkspaceLayout = async (workspaceRoot) => {
|
|
55
|
-
await mkdir(path.join(workspaceRoot, TASKS_DIR), { recursive: true });
|
|
56
|
-
await mkdir(path.join(workspaceRoot, LOCK_DIR), { recursive: true });
|
|
57
|
-
await Promise.all(STATUS_ORDER.map((status) => mkdir(path.join(workspaceRoot, TASKS_DIR, status), { recursive: true })));
|
|
58
|
-
};
|