@nickchristensen/cliftin 1.2.0 → 1.3.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/README.md +26 -8
- package/dist/commands/workouts/next.d.ts +6 -0
- package/dist/commands/workouts/next.js +44 -0
- package/dist/commands/workouts/show.js +1 -1
- package/dist/lib/db.d.ts +1 -0
- package/dist/lib/json-weight.d.ts +2 -1
- package/dist/lib/json-weight.js +16 -0
- package/dist/lib/repositories/workouts.d.ts +2 -1
- package/dist/lib/repositories/workouts.js +40 -0
- package/dist/lib/types.d.ts +8 -0
- package/oclif.manifest.json +30 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,8 +5,8 @@ CLIftin: A read-only CLI for Liftin'
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
[](https://oclif.io)
|
|
8
|
-
[](https://npmjs.org/package/cliftin)
|
|
9
|
-
[](https://npmjs.org/package/cliftin)
|
|
8
|
+
[](https://npmjs.org/package/@nickchristensen/cliftin)
|
|
9
|
+
[](https://npmjs.org/package/@nickchristensen/cliftin)
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
<!-- toc -->
|
|
@@ -47,6 +47,7 @@ USAGE
|
|
|
47
47
|
* [`cliftin programs list`](#cliftin-programs-list)
|
|
48
48
|
* [`cliftin programs show [SELECTOR]`](#cliftin-programs-show-selector)
|
|
49
49
|
* [`cliftin workouts list`](#cliftin-workouts-list)
|
|
50
|
+
* [`cliftin workouts next`](#cliftin-workouts-next)
|
|
50
51
|
* [`cliftin workouts show [WORKOUTID]`](#cliftin-workouts-show-workoutid)
|
|
51
52
|
|
|
52
53
|
## `cliftin exercises list`
|
|
@@ -72,7 +73,7 @@ DESCRIPTION
|
|
|
72
73
|
List exercises
|
|
73
74
|
```
|
|
74
75
|
|
|
75
|
-
_See code: [src/commands/exercises/list.ts](https://github.com/nickchristensen/cliftin/blob/v1.
|
|
76
|
+
_See code: [src/commands/exercises/list.ts](https://github.com/nickchristensen/cliftin/blob/v1.3.0/src/commands/exercises/list.ts)_
|
|
76
77
|
|
|
77
78
|
## `cliftin exercises show SELECTOR`
|
|
78
79
|
|
|
@@ -106,7 +107,7 @@ DESCRIPTION
|
|
|
106
107
|
Show one exercise detail and history
|
|
107
108
|
```
|
|
108
109
|
|
|
109
|
-
_See code: [src/commands/exercises/show.ts](https://github.com/nickchristensen/cliftin/blob/v1.
|
|
110
|
+
_See code: [src/commands/exercises/show.ts](https://github.com/nickchristensen/cliftin/blob/v1.3.0/src/commands/exercises/show.ts)_
|
|
110
111
|
|
|
111
112
|
## `cliftin help [COMMAND]`
|
|
112
113
|
|
|
@@ -143,7 +144,7 @@ DESCRIPTION
|
|
|
143
144
|
List programs
|
|
144
145
|
```
|
|
145
146
|
|
|
146
|
-
_See code: [src/commands/programs/list.ts](https://github.com/nickchristensen/cliftin/blob/v1.
|
|
147
|
+
_See code: [src/commands/programs/list.ts](https://github.com/nickchristensen/cliftin/blob/v1.3.0/src/commands/programs/list.ts)_
|
|
147
148
|
|
|
148
149
|
## `cliftin programs show [SELECTOR]`
|
|
149
150
|
|
|
@@ -163,7 +164,7 @@ DESCRIPTION
|
|
|
163
164
|
Show one program hierarchy
|
|
164
165
|
```
|
|
165
166
|
|
|
166
|
-
_See code: [src/commands/programs/show.ts](https://github.com/nickchristensen/cliftin/blob/v1.
|
|
167
|
+
_See code: [src/commands/programs/show.ts](https://github.com/nickchristensen/cliftin/blob/v1.3.0/src/commands/programs/show.ts)_
|
|
167
168
|
|
|
168
169
|
## `cliftin workouts list`
|
|
169
170
|
|
|
@@ -190,7 +191,24 @@ DESCRIPTION
|
|
|
190
191
|
List workouts
|
|
191
192
|
```
|
|
192
193
|
|
|
193
|
-
_See code: [src/commands/workouts/list.ts](https://github.com/nickchristensen/cliftin/blob/v1.
|
|
194
|
+
_See code: [src/commands/workouts/list.ts](https://github.com/nickchristensen/cliftin/blob/v1.3.0/src/commands/workouts/list.ts)_
|
|
195
|
+
|
|
196
|
+
## `cliftin workouts next`
|
|
197
|
+
|
|
198
|
+
Show the up-next routine from the active program
|
|
199
|
+
|
|
200
|
+
```
|
|
201
|
+
USAGE
|
|
202
|
+
$ cliftin workouts next [--json]
|
|
203
|
+
|
|
204
|
+
GLOBAL FLAGS
|
|
205
|
+
--json Format output as json.
|
|
206
|
+
|
|
207
|
+
DESCRIPTION
|
|
208
|
+
Show the up-next routine from the active program
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
_See code: [src/commands/workouts/next.ts](https://github.com/nickchristensen/cliftin/blob/v1.3.0/src/commands/workouts/next.ts)_
|
|
194
212
|
|
|
195
213
|
## `cliftin workouts show [WORKOUTID]`
|
|
196
214
|
|
|
@@ -210,5 +228,5 @@ DESCRIPTION
|
|
|
210
228
|
Show one workout with exercises and sets
|
|
211
229
|
```
|
|
212
230
|
|
|
213
|
-
_See code: [src/commands/workouts/show.ts](https://github.com/nickchristensen/cliftin/blob/v1.
|
|
231
|
+
_See code: [src/commands/workouts/show.ts](https://github.com/nickchristensen/cliftin/blob/v1.3.0/src/commands/workouts/show.ts)_
|
|
214
232
|
<!-- commandsstop -->
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
import { closeDb, openDb } from '../../lib/db.js';
|
|
3
|
+
import { toJsonErrorPayload } from '../../lib/json-error.js';
|
|
4
|
+
import { serializeNextWorkoutDetailWithWeightUnits } from '../../lib/json-weight.js';
|
|
5
|
+
import { renderTable } from '../../lib/output.js';
|
|
6
|
+
import { getNextWorkoutDetail } from '../../lib/repositories/workouts.js';
|
|
7
|
+
import { resolveProgramWeightUnit, weightUnitLabel } from '../../lib/units.js';
|
|
8
|
+
export default class WorkoutsNext extends Command {
|
|
9
|
+
static description = 'Show the up-next routine from the active program';
|
|
10
|
+
static enableJsonFlag = true;
|
|
11
|
+
async run() {
|
|
12
|
+
await this.parse(WorkoutsNext);
|
|
13
|
+
const context = openDb();
|
|
14
|
+
try {
|
|
15
|
+
const detail = await getNextWorkoutDetail(context.db);
|
|
16
|
+
const unitPreference = await resolveProgramWeightUnit(context.db, detail.program.id);
|
|
17
|
+
const unitLabel = weightUnitLabel(unitPreference);
|
|
18
|
+
if (this.jsonEnabled()) {
|
|
19
|
+
return serializeNextWorkoutDetailWithWeightUnits(detail, unitPreference);
|
|
20
|
+
}
|
|
21
|
+
this.log(`[${detail.routine.id}] ${detail.routine.name ?? 'Workout'}`);
|
|
22
|
+
this.log(`Program: ${detail.program.name}`);
|
|
23
|
+
this.log(`Week: ${detail.week.number}`);
|
|
24
|
+
for (const exercise of detail.routine.exercises) {
|
|
25
|
+
this.log('');
|
|
26
|
+
this.log(`[${exercise.id ?? exercise.exerciseConfigId}] ${exercise.name ?? '(unnamed)'}`);
|
|
27
|
+
this.log(renderTable(exercise.sets.map((set) => ({
|
|
28
|
+
reps: set.reps,
|
|
29
|
+
rpe: set.rpe,
|
|
30
|
+
timeSeconds: set.timeSeconds,
|
|
31
|
+
weight: set.weight === null ? null : `${set.weight} ${unitLabel}`,
|
|
32
|
+
}))));
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
if (this.jsonEnabled())
|
|
37
|
+
return toJsonErrorPayload(error);
|
|
38
|
+
throw error;
|
|
39
|
+
}
|
|
40
|
+
finally {
|
|
41
|
+
await closeDb(context);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -20,7 +20,7 @@ function formatWorkoutDate(dateIso) {
|
|
|
20
20
|
}
|
|
21
21
|
export default class WorkoutsShow extends Command {
|
|
22
22
|
static args = {
|
|
23
|
-
workoutId: Args.string({ description: 'workout id (default: latest workout)', required: false }),
|
|
23
|
+
workoutId: Args.string({ description: 'workout id (default: latest workout)', ignoreStdin: true, required: false }),
|
|
24
24
|
};
|
|
25
25
|
static description = 'Show one workout with exercises and sets';
|
|
26
26
|
static enableJsonFlag = true;
|
package/dist/lib/db.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { ExerciseHistoryRow, ProgramDetailTree, WorkoutDetail } from './types.js';
|
|
1
|
+
import { ExerciseHistoryRow, NextWorkoutDetail, ProgramDetailTree, WorkoutDetail } from './types.js';
|
|
2
2
|
import { UnitPreference, withWeightUnit } from './units.js';
|
|
3
3
|
export declare function serializeExerciseHistoryRowsWithWeightUnits(rows: ExerciseHistoryRow[], unitPreference: UnitPreference): Array<Omit<ExerciseHistoryRow, 'topWeight'> & {
|
|
4
4
|
topWeight: ReturnType<typeof withWeightUnit>;
|
|
5
5
|
}>;
|
|
6
6
|
export declare function serializeProgramDetailWithWeightUnits(detail: ProgramDetailTree, unitPreference: UnitPreference): unknown;
|
|
7
|
+
export declare function serializeNextWorkoutDetailWithWeightUnits(detail: NextWorkoutDetail, unitPreference: UnitPreference): unknown;
|
|
7
8
|
export declare function serializeWorkoutDetailWithWeightUnits(detail: WorkoutDetail, unitPreference: UnitPreference): unknown;
|
package/dist/lib/json-weight.js
CHANGED
|
@@ -24,6 +24,22 @@ export function serializeProgramDetailWithWeightUnits(detail, unitPreference) {
|
|
|
24
24
|
})),
|
|
25
25
|
};
|
|
26
26
|
}
|
|
27
|
+
export function serializeNextWorkoutDetailWithWeightUnits(detail, unitPreference) {
|
|
28
|
+
return {
|
|
29
|
+
...detail,
|
|
30
|
+
routine: {
|
|
31
|
+
...detail.routine,
|
|
32
|
+
exercises: detail.routine.exercises.map((exercise) => ({
|
|
33
|
+
...exercise,
|
|
34
|
+
plannedWeight: withWeightUnit(exercise.plannedWeight, unitPreference),
|
|
35
|
+
sets: exercise.sets.map((set) => ({
|
|
36
|
+
...set,
|
|
37
|
+
weight: withWeightUnit(set.weight, unitPreference),
|
|
38
|
+
})),
|
|
39
|
+
})),
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
}
|
|
27
43
|
export function serializeWorkoutDetailWithWeightUnits(detail, unitPreference) {
|
|
28
44
|
return {
|
|
29
45
|
...detail,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Kysely } from 'kysely';
|
|
2
2
|
import { DatabaseSchema } from '../db.js';
|
|
3
|
-
import { WorkoutDetail, WorkoutSummary } from '../types.js';
|
|
3
|
+
import { NextWorkoutDetail, WorkoutDetail, WorkoutSummary } from '../types.js';
|
|
4
4
|
export type WorkoutFilters = {
|
|
5
5
|
from?: string;
|
|
6
6
|
limit?: number;
|
|
@@ -9,5 +9,6 @@ export type WorkoutFilters = {
|
|
|
9
9
|
routine?: string;
|
|
10
10
|
to?: string;
|
|
11
11
|
};
|
|
12
|
+
export declare function getNextWorkoutDetail(db: Kysely<DatabaseSchema>): Promise<NextWorkoutDetail>;
|
|
12
13
|
export declare function listWorkouts(db: Kysely<DatabaseSchema>, filters: WorkoutFilters): Promise<WorkoutSummary[]>;
|
|
13
14
|
export declare function getWorkoutDetail(db: Kysely<DatabaseSchema>, workoutId: number): Promise<WorkoutDetail>;
|
|
@@ -2,10 +2,50 @@ import { formatExerciseDisplayName } from '../names.js';
|
|
|
2
2
|
import { normalizeRpe } from '../rpe.js';
|
|
3
3
|
import { appleSecondsToIso, dateRangeToAppleSeconds } from '../time.js';
|
|
4
4
|
import { convertKgToDisplayVolume, convertKgToDisplayWeight, resolveGlobalWeightUnit } from '../units.js';
|
|
5
|
+
import { getProgramDetail, resolveProgramSelector } from './programs.js';
|
|
5
6
|
import { resolveIdOrName } from './selectors.js';
|
|
6
7
|
function asBool(value) {
|
|
7
8
|
return value === 1;
|
|
8
9
|
}
|
|
10
|
+
export async function getNextWorkoutDetail(db) {
|
|
11
|
+
const programId = await resolveProgramSelector(db, undefined, true);
|
|
12
|
+
const programDetail = await getProgramDetail(db, programId);
|
|
13
|
+
const nextRoutines = await db
|
|
14
|
+
.selectFrom('ZROUTINE as r')
|
|
15
|
+
.leftJoin('ZPERIOD as p', 'p.Z_PK', 'r.ZPERIOD')
|
|
16
|
+
.select(['r.Z_PK as id', 'r.ZPERIOD as weekId'])
|
|
17
|
+
.where('r.ZUPNEXT', '=', 1)
|
|
18
|
+
.where('r.ZSOFTDELETED', 'is not', 1)
|
|
19
|
+
.where((eb) => eb.or([eb('p.ZWORKOUTPLAN', '=', programId), eb('r.ZWORKOUTPLAN', '=', programId)]))
|
|
20
|
+
.orderBy('p.Z_FOK_WORKOUTPLAN', 'asc')
|
|
21
|
+
.orderBy('r.Z_FOK_PERIOD', 'asc')
|
|
22
|
+
.orderBy('r.Z_PK', 'asc')
|
|
23
|
+
.execute();
|
|
24
|
+
if (nextRoutines.length === 0) {
|
|
25
|
+
throw new Error(`No up-next routine found for active program ${programDetail.program.name}.`);
|
|
26
|
+
}
|
|
27
|
+
if (nextRoutines.length > 1) {
|
|
28
|
+
throw new Error(`Expected exactly one up-next routine for active program ${programDetail.program.name}. Found ${nextRoutines.length}.`);
|
|
29
|
+
}
|
|
30
|
+
const nextRoutine = nextRoutines[0];
|
|
31
|
+
const weekIndex = programDetail.weeks.findIndex((week) => week.id === nextRoutine.weekId);
|
|
32
|
+
if (weekIndex === -1) {
|
|
33
|
+
throw new Error(`Up-next routine ${nextRoutine.id} is linked to unknown week ${nextRoutine.weekId}.`);
|
|
34
|
+
}
|
|
35
|
+
const week = programDetail.weeks[weekIndex];
|
|
36
|
+
const routine = week.routines.find((entry) => entry.id === nextRoutine.id);
|
|
37
|
+
if (!routine) {
|
|
38
|
+
throw new Error(`Up-next routine ${nextRoutine.id} was not found in active program detail.`);
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
program: programDetail.program,
|
|
42
|
+
routine,
|
|
43
|
+
week: {
|
|
44
|
+
id: week.id,
|
|
45
|
+
number: weekIndex + 1,
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
}
|
|
9
49
|
export async function listWorkouts(db, filters) {
|
|
10
50
|
const dateRange = dateRangeToAppleSeconds({ from: filters.from, on: filters.on, to: filters.to });
|
|
11
51
|
let query = db
|
package/dist/lib/types.d.ts
CHANGED
|
@@ -68,6 +68,14 @@ export type WorkoutDetail = {
|
|
|
68
68
|
program: null | string;
|
|
69
69
|
routine: null | string;
|
|
70
70
|
};
|
|
71
|
+
export type NextWorkoutDetail = {
|
|
72
|
+
program: ProgramSummary;
|
|
73
|
+
routine: ProgramRoutine;
|
|
74
|
+
week: {
|
|
75
|
+
id: number;
|
|
76
|
+
number: number;
|
|
77
|
+
};
|
|
78
|
+
};
|
|
71
79
|
export type ExerciseSummary = {
|
|
72
80
|
equipment: null | string;
|
|
73
81
|
id: number;
|
package/oclif.manifest.json
CHANGED
|
@@ -334,6 +334,35 @@
|
|
|
334
334
|
"list.js"
|
|
335
335
|
]
|
|
336
336
|
},
|
|
337
|
+
"workouts:next": {
|
|
338
|
+
"aliases": [],
|
|
339
|
+
"args": {},
|
|
340
|
+
"description": "Show the up-next routine from the active program",
|
|
341
|
+
"flags": {
|
|
342
|
+
"json": {
|
|
343
|
+
"description": "Format output as json.",
|
|
344
|
+
"helpGroup": "GLOBAL",
|
|
345
|
+
"name": "json",
|
|
346
|
+
"allowNo": false,
|
|
347
|
+
"type": "boolean"
|
|
348
|
+
}
|
|
349
|
+
},
|
|
350
|
+
"hasDynamicHelp": false,
|
|
351
|
+
"hiddenAliases": [],
|
|
352
|
+
"id": "workouts:next",
|
|
353
|
+
"pluginAlias": "@nickchristensen/cliftin",
|
|
354
|
+
"pluginName": "@nickchristensen/cliftin",
|
|
355
|
+
"pluginType": "core",
|
|
356
|
+
"strict": true,
|
|
357
|
+
"enableJsonFlag": true,
|
|
358
|
+
"isESM": true,
|
|
359
|
+
"relativePath": [
|
|
360
|
+
"dist",
|
|
361
|
+
"commands",
|
|
362
|
+
"workouts",
|
|
363
|
+
"next.js"
|
|
364
|
+
]
|
|
365
|
+
},
|
|
337
366
|
"workouts:show": {
|
|
338
367
|
"aliases": [],
|
|
339
368
|
"args": {
|
|
@@ -370,5 +399,5 @@
|
|
|
370
399
|
]
|
|
371
400
|
}
|
|
372
401
|
},
|
|
373
|
-
"version": "1.
|
|
402
|
+
"version": "1.3.0"
|
|
374
403
|
}
|