@nickchristensen/cliftin 1.1.1 → 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 +32 -18
- package/dist/commands/programs/show.d.ts +1 -4
- package/dist/commands/programs/show.js +7 -16
- package/dist/commands/workouts/next.d.ts +6 -0
- package/dist/commands/workouts/next.js +44 -0
- package/dist/commands/workouts/show.d.ts +1 -1
- package/dist/commands/workouts/show.js +12 -5
- 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/programs.js +1 -1
- package/dist/lib/repositories/workouts.d.ts +2 -1
- package/dist/lib/repositories/workouts.js +46 -1
- package/dist/lib/types.d.ts +8 -0
- package/oclif.manifest.json +33 -16
- 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,7 +47,8 @@ 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
|
|
50
|
+
* [`cliftin workouts next`](#cliftin-workouts-next)
|
|
51
|
+
* [`cliftin workouts show [WORKOUTID]`](#cliftin-workouts-show-workoutid)
|
|
51
52
|
|
|
52
53
|
## `cliftin exercises list`
|
|
53
54
|
|
|
@@ -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
|
|
|
@@ -151,14 +152,10 @@ Show one program hierarchy
|
|
|
151
152
|
|
|
152
153
|
```
|
|
153
154
|
USAGE
|
|
154
|
-
$ cliftin programs show [SELECTOR] [--json]
|
|
155
|
+
$ cliftin programs show [SELECTOR] [--json]
|
|
155
156
|
|
|
156
157
|
ARGUMENTS
|
|
157
|
-
[SELECTOR] program id or name
|
|
158
|
-
|
|
159
|
-
FLAGS
|
|
160
|
-
--active Show active program detail
|
|
161
|
-
--current Alias for --active
|
|
158
|
+
[SELECTOR] program id or name (default: active program)
|
|
162
159
|
|
|
163
160
|
GLOBAL FLAGS
|
|
164
161
|
--json Format output as json.
|
|
@@ -167,7 +164,7 @@ DESCRIPTION
|
|
|
167
164
|
Show one program hierarchy
|
|
168
165
|
```
|
|
169
166
|
|
|
170
|
-
_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)_
|
|
171
168
|
|
|
172
169
|
## `cliftin workouts list`
|
|
173
170
|
|
|
@@ -194,18 +191,35 @@ DESCRIPTION
|
|
|
194
191
|
List workouts
|
|
195
192
|
```
|
|
196
193
|
|
|
197
|
-
_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)_
|
|
198
212
|
|
|
199
|
-
## `cliftin workouts show WORKOUTID`
|
|
213
|
+
## `cliftin workouts show [WORKOUTID]`
|
|
200
214
|
|
|
201
215
|
Show one workout with exercises and sets
|
|
202
216
|
|
|
203
217
|
```
|
|
204
218
|
USAGE
|
|
205
|
-
$ cliftin workouts show WORKOUTID [--json]
|
|
219
|
+
$ cliftin workouts show [WORKOUTID] [--json]
|
|
206
220
|
|
|
207
221
|
ARGUMENTS
|
|
208
|
-
WORKOUTID workout id
|
|
222
|
+
[WORKOUTID] workout id (default: latest workout)
|
|
209
223
|
|
|
210
224
|
GLOBAL FLAGS
|
|
211
225
|
--json Format output as json.
|
|
@@ -214,5 +228,5 @@ DESCRIPTION
|
|
|
214
228
|
Show one workout with exercises and sets
|
|
215
229
|
```
|
|
216
230
|
|
|
217
|
-
_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)_
|
|
218
232
|
<!-- commandsstop -->
|
|
@@ -5,9 +5,6 @@ export default class ProgramsShow extends Command {
|
|
|
5
5
|
};
|
|
6
6
|
static description: string;
|
|
7
7
|
static enableJsonFlag: boolean;
|
|
8
|
-
static flags: {
|
|
9
|
-
active: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
10
|
-
current: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
|
-
};
|
|
8
|
+
static flags: {};
|
|
12
9
|
run(): Promise<unknown | void>;
|
|
13
10
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Args, Command
|
|
1
|
+
import { Args, Command } from '@oclif/core';
|
|
2
2
|
import { closeDb, openDb } from '../../lib/db.js';
|
|
3
3
|
import { serializeProgramDetailWithWeightUnits } from '../../lib/json-weight.js';
|
|
4
4
|
import { renderTable } from '../../lib/output.js';
|
|
@@ -30,26 +30,16 @@ function buildProgramRows(weeks, unitLabel) {
|
|
|
30
30
|
}
|
|
31
31
|
export default class ProgramsShow extends Command {
|
|
32
32
|
static args = {
|
|
33
|
-
selector: Args.string({ description: 'program id or name', ignoreStdin: true, required: false }),
|
|
33
|
+
selector: Args.string({ description: 'program id or name (default: active program)', ignoreStdin: true, required: false }),
|
|
34
34
|
};
|
|
35
35
|
static description = 'Show one program hierarchy';
|
|
36
36
|
static enableJsonFlag = true;
|
|
37
|
-
static flags = {
|
|
38
|
-
active: Flags.boolean({ description: 'Show active program detail' }),
|
|
39
|
-
current: Flags.boolean({ description: 'Alias for --active' }),
|
|
40
|
-
};
|
|
37
|
+
static flags = {};
|
|
41
38
|
async run() {
|
|
42
|
-
const { args
|
|
39
|
+
const { args } = await this.parse(ProgramsShow);
|
|
43
40
|
const context = openDb();
|
|
44
41
|
try {
|
|
45
|
-
const
|
|
46
|
-
if (args.selector && useActive) {
|
|
47
|
-
throw new Error('Use either a selector or --active/--current, not both.');
|
|
48
|
-
}
|
|
49
|
-
if (!args.selector && !useActive) {
|
|
50
|
-
throw new Error('Provide a selector or use --active/--current.');
|
|
51
|
-
}
|
|
52
|
-
const programId = await resolveProgramSelector(context.db, args.selector, Boolean(useActive));
|
|
42
|
+
const programId = await resolveProgramSelector(context.db, args.selector, !args.selector);
|
|
53
43
|
const detail = await getProgramDetail(context.db, programId);
|
|
54
44
|
const unitPreference = await resolveProgramWeightUnit(context.db, detail.program.id);
|
|
55
45
|
const unitLabel = weightUnitLabel(unitPreference);
|
|
@@ -57,7 +47,8 @@ export default class ProgramsShow extends Command {
|
|
|
57
47
|
return serializeProgramDetailWithWeightUnits(detail, unitPreference);
|
|
58
48
|
}
|
|
59
49
|
this.log(`[${detail.program.id}] ${detail.program.name}`);
|
|
60
|
-
this.log(`Active: ${detail.program.isActive}
|
|
50
|
+
this.log(`Active: ${detail.program.isActive}`);
|
|
51
|
+
this.log(`Template: ${detail.program.isTemplate}`);
|
|
61
52
|
this.log('');
|
|
62
53
|
const programRows = buildProgramRows(detail.weeks, unitLabel);
|
|
63
54
|
const renderedTable = renderTable(programRows).replace(/^\n+/, '');
|
|
@@ -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
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Command } from '@oclif/core';
|
|
2
2
|
export default class WorkoutsShow extends Command {
|
|
3
3
|
static args: {
|
|
4
|
-
workoutId: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
4
|
+
workoutId: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
5
5
|
};
|
|
6
6
|
static description: string;
|
|
7
7
|
static enableJsonFlag: boolean;
|
|
@@ -3,7 +3,7 @@ import { format, isValid, parseISO } from 'date-fns';
|
|
|
3
3
|
import { closeDb, openDb } from '../../lib/db.js';
|
|
4
4
|
import { serializeWorkoutDetailWithWeightUnits } from '../../lib/json-weight.js';
|
|
5
5
|
import { renderTable } from '../../lib/output.js';
|
|
6
|
-
import { getWorkoutDetail } from '../../lib/repositories/workouts.js';
|
|
6
|
+
import { getWorkoutDetail, listWorkouts } from '../../lib/repositories/workouts.js';
|
|
7
7
|
import { resolveGlobalWeightUnit, weightUnitLabel } from '../../lib/units.js';
|
|
8
8
|
function formatDurationMinutes(durationSeconds) {
|
|
9
9
|
if (durationSeconds === null)
|
|
@@ -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',
|
|
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;
|
|
@@ -28,10 +28,17 @@ export default class WorkoutsShow extends Command {
|
|
|
28
28
|
const { args } = await this.parse(WorkoutsShow);
|
|
29
29
|
const context = openDb();
|
|
30
30
|
try {
|
|
31
|
-
if (!/^\d+$/.test(args.workoutId)) {
|
|
32
|
-
throw new Error('Workout
|
|
31
|
+
if (args.workoutId !== undefined && !/^\d+$/.test(args.workoutId)) {
|
|
32
|
+
throw new Error('Workout id must be numeric.');
|
|
33
33
|
}
|
|
34
|
-
const
|
|
34
|
+
const workoutId = args.workoutId
|
|
35
|
+
? Number(args.workoutId)
|
|
36
|
+
: await listWorkouts(context.db, { limit: 1 }).then((rows) => {
|
|
37
|
+
if (rows.length === 0)
|
|
38
|
+
throw new Error('No workouts found.');
|
|
39
|
+
return rows[0].id;
|
|
40
|
+
});
|
|
41
|
+
const detail = await getWorkoutDetail(context.db, workoutId);
|
|
35
42
|
const unitPreference = await resolveGlobalWeightUnit(context.db);
|
|
36
43
|
const unitLabel = weightUnitLabel(unitPreference);
|
|
37
44
|
if (this.jsonEnabled()) {
|
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,
|
|
@@ -54,7 +54,7 @@ export async function resolveProgramSelector(db, selector, activeOnly) {
|
|
|
54
54
|
return fallbackRows[0].id;
|
|
55
55
|
}
|
|
56
56
|
if (!selector)
|
|
57
|
-
throw new Error('Program selector is required
|
|
57
|
+
throw new Error('Program selector is required.');
|
|
58
58
|
const programId = await resolveIdOrName(db, 'ZWORKOUTPLAN', selector);
|
|
59
59
|
const nonDeleted = await db
|
|
60
60
|
.selectFrom('ZWORKOUTPLAN')
|
|
@@ -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
|
|
@@ -73,7 +113,12 @@ export async function getWorkoutDetail(db, workoutId) {
|
|
|
73
113
|
.selectFrom('ZEXERCISERESULT as er')
|
|
74
114
|
.leftJoin('ZEXERCISECONFIGURATION as ec', 'ec.Z_PK', 'er.ZCONFIGURATION')
|
|
75
115
|
.leftJoin('ZEXERCISEINFORMATION as ei', 'ei.Z_PK', 'ec.ZINFORMATION')
|
|
76
|
-
.select([
|
|
116
|
+
.select([
|
|
117
|
+
'er.Z_PK as exerciseResultId',
|
|
118
|
+
'ei.Z_PK as exerciseId',
|
|
119
|
+
'ei.ZISUSERCREATED as isUserCreated',
|
|
120
|
+
'ei.ZNAME as exerciseName',
|
|
121
|
+
])
|
|
77
122
|
.where('er.ZWORKOUT', '=', workoutId)
|
|
78
123
|
.orderBy('er.Z_FOK_WORKOUT', 'asc')
|
|
79
124
|
.orderBy('er.Z_PK', 'asc')
|
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
|
@@ -210,7 +210,7 @@
|
|
|
210
210
|
"aliases": [],
|
|
211
211
|
"args": {
|
|
212
212
|
"selector": {
|
|
213
|
-
"description": "program id or name",
|
|
213
|
+
"description": "program id or name (default: active program)",
|
|
214
214
|
"name": "selector",
|
|
215
215
|
"required": false
|
|
216
216
|
}
|
|
@@ -223,18 +223,6 @@
|
|
|
223
223
|
"name": "json",
|
|
224
224
|
"allowNo": false,
|
|
225
225
|
"type": "boolean"
|
|
226
|
-
},
|
|
227
|
-
"active": {
|
|
228
|
-
"description": "Show active program detail",
|
|
229
|
-
"name": "active",
|
|
230
|
-
"allowNo": false,
|
|
231
|
-
"type": "boolean"
|
|
232
|
-
},
|
|
233
|
-
"current": {
|
|
234
|
-
"description": "Alias for --active",
|
|
235
|
-
"name": "current",
|
|
236
|
-
"allowNo": false,
|
|
237
|
-
"type": "boolean"
|
|
238
226
|
}
|
|
239
227
|
},
|
|
240
228
|
"hasDynamicHelp": false,
|
|
@@ -346,13 +334,42 @@
|
|
|
346
334
|
"list.js"
|
|
347
335
|
]
|
|
348
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
|
+
},
|
|
349
366
|
"workouts:show": {
|
|
350
367
|
"aliases": [],
|
|
351
368
|
"args": {
|
|
352
369
|
"workoutId": {
|
|
353
|
-
"description": "workout id",
|
|
370
|
+
"description": "workout id (default: latest workout)",
|
|
354
371
|
"name": "workoutId",
|
|
355
|
-
"required":
|
|
372
|
+
"required": false
|
|
356
373
|
}
|
|
357
374
|
},
|
|
358
375
|
"description": "Show one workout with exercises and sets",
|
|
@@ -382,5 +399,5 @@
|
|
|
382
399
|
]
|
|
383
400
|
}
|
|
384
401
|
},
|
|
385
|
-
"version": "1.
|
|
402
|
+
"version": "1.3.0"
|
|
386
403
|
}
|