@adbayb/stack 2.32.1 → 2.33.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/configs/eslint/constants.js +2 -0
- package/configs/eslint/index.js +14 -14
- package/configs/eslint/presets/dependencies.js +14 -0
- package/configs/eslint/presets/eslint.js +3 -4
- package/configs/eslint/presets/import.js +9 -21
- package/configs/eslint/presets/jsdoc.js +1 -1
- package/configs/eslint/presets/node.js +4 -3
- package/configs/eslint/presets/react.js +27 -37
- package/configs/eslint/presets/sonar.js +1 -3
- package/configs/eslint/presets/stylistic.js +74 -71
- package/configs/eslint/presets/test.js +30 -5
- package/configs/eslint/presets/typescript.js +1 -2
- package/configs/eslint/presets/unicorn.js +8 -1
- package/dist/index.js +401 -401
- package/package.json +26 -28
- package/templates/multi-projects/.github/workflows/continuous_delivery.yml +6 -13
- package/templates/multi-projects/.github/workflows/conventional_commit.yml +1 -2
- package/templates/multi-projects/.github/workflows/dependency_changelog.yml +1 -1
- package/templates/multi-projects/.github/workflows/workflow.yml +3 -13
- package/templates/single-project/.github/workflows/continuous_delivery.yml +6 -13
- package/templates/single-project/.github/workflows/conventional_commit.yml +1 -2
- package/templates/single-project/.github/workflows/dependency_changelog.yml +1 -1
- package/templates/single-project/.github/workflows/workflow.yml +3 -13
- package/configs/eslint/presets/uncategorized.js +0 -42
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { helpers, termost } from 'termost';
|
|
2
|
-
import { resolve, join } from 'node:path';
|
|
3
2
|
import { createRequire } from 'node:module';
|
|
4
|
-
import {
|
|
5
|
-
import { existsSync, cpSync, readFileSync, renameSync, writeFileSync
|
|
3
|
+
import { resolve, join } from 'node:path';
|
|
4
|
+
import { existsSync, readdirSync, cpSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
5
|
+
import { rm, mkdir, symlink, writeFile, chmod } from 'node:fs/promises';
|
|
6
6
|
import { fdir } from 'fdir';
|
|
7
7
|
|
|
8
8
|
const require$1 = createRequire(import.meta.url);
|
|
@@ -41,9 +41,9 @@ function assert(expectedCondition, createError) {
|
|
|
41
41
|
│ ◠ ◠ ${input.title}
|
|
42
42
|
│ ${type === "error" ? "◠" : "◡"} │ ${input.description}
|
|
43
43
|
╰─────╯
|
|
44
|
-
${
|
|
44
|
+
${input.body ? `
|
|
45
45
|
${input.body}
|
|
46
|
-
`}`, {
|
|
46
|
+
` : ""}`, {
|
|
47
47
|
color: colorByType[type]
|
|
48
48
|
}));
|
|
49
49
|
};
|
|
@@ -197,215 +197,316 @@ const ESLINT_EXTENSIONS = [
|
|
|
197
197
|
"mdx"
|
|
198
198
|
];
|
|
199
199
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
const createWatchCommand = (program)=>{
|
|
200
|
+
const createBuildCommand = (program)=>{
|
|
203
201
|
program.command({
|
|
204
|
-
|
|
205
|
-
|
|
202
|
+
description: "Build the project in production mode",
|
|
203
|
+
name: "build"
|
|
206
204
|
}).task({
|
|
207
205
|
async handler () {
|
|
208
|
-
await turbo("
|
|
206
|
+
await turbo("build");
|
|
209
207
|
}
|
|
210
208
|
});
|
|
211
209
|
};
|
|
212
210
|
|
|
213
|
-
const
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
description: "Test the code execution"
|
|
217
|
-
}).task({
|
|
218
|
-
async handler () {
|
|
219
|
-
await turbo("test");
|
|
220
|
-
}
|
|
221
|
-
});
|
|
222
|
-
};
|
|
211
|
+
const checkCode = eslint({
|
|
212
|
+
isFixMode: false
|
|
213
|
+
});
|
|
223
214
|
|
|
224
|
-
const
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
await turbo("start");
|
|
231
|
-
}
|
|
232
|
-
});
|
|
215
|
+
const checkCommit = async ()=>{
|
|
216
|
+
try {
|
|
217
|
+
return await helpers.exec('commitlint --extends "@commitlint/config-conventional" --edit');
|
|
218
|
+
} catch (error) {
|
|
219
|
+
throw createError("commitlint", error);
|
|
220
|
+
}
|
|
233
221
|
};
|
|
234
222
|
|
|
235
|
-
const
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
}).task({
|
|
252
|
-
async handler () {
|
|
253
|
-
helpers.message("New changelog entry\n");
|
|
254
|
-
await changeset("changeset");
|
|
255
|
-
},
|
|
256
|
-
skip: ifNotEqualTo("log")
|
|
257
|
-
}).task({
|
|
258
|
-
async handler () {
|
|
259
|
-
helpers.message("Bumping the package(s) version\n");
|
|
260
|
-
await changeset("changeset version && pnpm install --no-frozen-lockfile");
|
|
261
|
-
},
|
|
262
|
-
skip: ifNotEqualTo("tag")
|
|
263
|
-
}).task({
|
|
264
|
-
async handler () {
|
|
265
|
-
helpers.message("Publishing package(s) to the registry\n");
|
|
266
|
-
await changeset("stack build && pnpm changeset publish");
|
|
267
|
-
},
|
|
268
|
-
skip: ifNotEqualTo("publish")
|
|
223
|
+
const checkDependency = async ()=>{
|
|
224
|
+
const stdout = await helpers.exec("pnpm recursive ls --json");
|
|
225
|
+
const checkDependencyVersionMismatch = createPackagesVersionMismatchChecker();
|
|
226
|
+
const packages = JSON.parse(stdout).map((package_)=>{
|
|
227
|
+
const packagePath = join(package_.path, "package.json");
|
|
228
|
+
assert(package_.name, ()=>createPackageError(`\`${packagePath}\` must have a name field.`));
|
|
229
|
+
const packageContent = require$1(packagePath);
|
|
230
|
+
const peerDependencies = packageContent.peerDependencies ?? {};
|
|
231
|
+
const devDependencies = packageContent.devDependencies ?? {};
|
|
232
|
+
const dependencies = packageContent.dependencies ?? {};
|
|
233
|
+
return {
|
|
234
|
+
dependencies,
|
|
235
|
+
devDependencies,
|
|
236
|
+
name: package_.name,
|
|
237
|
+
peerDependencies
|
|
238
|
+
};
|
|
269
239
|
});
|
|
240
|
+
for (const package_ of packages){
|
|
241
|
+
// Check version mismatches to guarantee a single copy for a given package in the monorepo (use case: prevent singleton-like issues with React contexts)
|
|
242
|
+
checkDependencyVersionMismatch(package_);
|
|
243
|
+
// Check version range accordingly to our dependency guidelines (ie. dev dependencies must be pinned and dependencies must have caret)
|
|
244
|
+
checkDependencyVersionRange(package_);
|
|
245
|
+
}
|
|
270
246
|
};
|
|
271
|
-
const
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
247
|
+
const checkDependencyVersionRange = ({ dependencies, devDependencies, name, peerDependencies })=>{
|
|
248
|
+
for (const dependencyName of Object.keys(devDependencies)){
|
|
249
|
+
const version = devDependencies[dependencyName];
|
|
250
|
+
assertVersion(version, {
|
|
251
|
+
consumedBy: name,
|
|
252
|
+
name: dependencyName
|
|
253
|
+
});
|
|
254
|
+
if (version !== "workspace:*" && !isExcluded(version) && !/^\d/.test(version)) throw createPackageError(`As a dev dependency, \`${dependencyName}\` version must be fixed (or set as "workspace:*" for local packages) to reduce accidental breaking change risks due to an implicit semver upgrade.`, {
|
|
255
|
+
consumedBy: name,
|
|
256
|
+
name: dependencyName
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
for (const dependencyName of Object.keys(dependencies)){
|
|
260
|
+
const version = dependencies[dependencyName];
|
|
261
|
+
assertVersion(version, {
|
|
262
|
+
consumedBy: name,
|
|
263
|
+
name: dependencyName
|
|
264
|
+
});
|
|
265
|
+
if (version !== "workspace:^" && !hasCaret(version) && !isExcluded(version)) throw createPackageError(`As a dependency, \`${dependencyName}\` version must be prefixed with a caret (or set as "workspace:^" for local packages) to optimize the size (whether of installation or bundle output) on the consumer side.`, {
|
|
266
|
+
consumedBy: name,
|
|
267
|
+
name: dependencyName
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
for (const dependencyName of Object.keys(peerDependencies)){
|
|
271
|
+
const version = peerDependencies[dependencyName];
|
|
272
|
+
assertVersion(version, {
|
|
273
|
+
consumedBy: name,
|
|
274
|
+
name: dependencyName
|
|
275
|
+
});
|
|
276
|
+
if (!hasCaret(version) && !isExcluded(version)) /*
|
|
277
|
+
* Why disallowing workspace protocol as a version resolver?
|
|
278
|
+
* To reduce the update frequency needs consumer-side and guarantee on our side the minimum compatible version,
|
|
279
|
+
* the best practice should be to keeping an explicit number version which represents the lowest compatible version from an API perspective.
|
|
280
|
+
*/ throw createPackageError(`As a peer dependency, \`${dependencyName}\` version must be explicit (i.e. the "workspace:^" protocol a version resolver is not allowed) and prefixed with a caret to optimize the size (whether of installation or bundle output) on the consumer side.`, {
|
|
281
|
+
consumedBy: name,
|
|
282
|
+
name: dependencyName
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
const createPackagesVersionMismatchChecker = ()=>{
|
|
287
|
+
const monorepoDependencies = new Map();
|
|
288
|
+
const monorepoDevelopmentDependencies = new Map();
|
|
289
|
+
const lint = (package_, type)=>{
|
|
290
|
+
const packageName = package_.name;
|
|
291
|
+
const isDevelopment = type === "development";
|
|
292
|
+
const store = isDevelopment ? monorepoDevelopmentDependencies : monorepoDependencies;
|
|
293
|
+
const dependencies = isDevelopment ? package_.devDependencies : package_.dependencies;
|
|
294
|
+
for (const dependencyName of Object.keys(dependencies)){
|
|
295
|
+
const depVersion = dependencies[dependencyName];
|
|
296
|
+
if (!depVersion) continue;
|
|
297
|
+
const storedVersion = store.get(dependencyName);
|
|
298
|
+
if (!storedVersion) {
|
|
299
|
+
store.set(dependencyName, depVersion);
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
const isSameMonorepoVersion = depVersion === storedVersion;
|
|
303
|
+
if (!isSameMonorepoVersion) {
|
|
304
|
+
throw createPackageError(`Mismatched versions: received version \`${depVersion}\` while others use \`${storedVersion}\`. To prevent issues with singleton-like code (React contexts, …), please make sure to update all packages to use the same \`${dependencyName}\` version (either \`${storedVersion}\` or \`${depVersion}\`).`, {
|
|
305
|
+
consumedBy: packageName,
|
|
306
|
+
name: dependencyName
|
|
307
|
+
});
|
|
308
|
+
}
|
|
295
309
|
}
|
|
296
|
-
}
|
|
310
|
+
};
|
|
311
|
+
return (package_)=>{
|
|
312
|
+
lint(package_, "development");
|
|
313
|
+
lint(package_, "production");
|
|
314
|
+
};
|
|
297
315
|
};
|
|
298
|
-
const
|
|
299
|
-
|
|
300
|
-
const filename = resolveFromProjectDirectory(`.git/hooks/${hook}`);
|
|
301
|
-
await writeFile(filename, content);
|
|
302
|
-
return chmod(filename, "0755");
|
|
316
|
+
const createPackageError = (message, context)=>{
|
|
317
|
+
return createError("stack check", context ? `\`${context.name}\` consumed by \`${context.consumedBy}\` doesn't conform to package guidelines.\n${message}` : message);
|
|
303
318
|
};
|
|
319
|
+
function assertVersion(version, { consumedBy, name }) {
|
|
320
|
+
assert(version, ()=>createPackageError(`\`${name}\` must have a valid version specified (current version equals to \`${String(version)}\`).`, {
|
|
321
|
+
consumedBy,
|
|
322
|
+
name
|
|
323
|
+
}));
|
|
324
|
+
}
|
|
325
|
+
const isExcluded = (version)=>{
|
|
326
|
+
const isPreReleaseVersion = /\d+\.\d+\.\d+-(alpha|beta|experimental|next|rc).*/.exec(version);
|
|
327
|
+
const isNpmProtocol = version.startsWith("npm:");
|
|
328
|
+
return isNpmProtocol || isPreReleaseVersion;
|
|
329
|
+
};
|
|
330
|
+
const hasCaret = (version)=>version.startsWith("^");
|
|
304
331
|
|
|
305
|
-
const
|
|
306
|
-
isFixMode: true
|
|
307
|
-
});
|
|
308
|
-
|
|
309
|
-
const PRETTIER_IGNORE_FILES = [
|
|
310
|
-
"pnpm-lock.yaml"
|
|
311
|
-
];
|
|
312
|
-
const fixFormatting = async (files)=>{
|
|
313
|
-
let prettierFiles = [];
|
|
314
|
-
if (files.length === 0) {
|
|
315
|
-
prettierFiles.push(`"**/!(${PRETTIER_IGNORE_FILES.join("|")})"`);
|
|
316
|
-
} else {
|
|
317
|
-
prettierFiles = files.filter((file)=>{
|
|
318
|
-
return !PRETTIER_IGNORE_FILES.some((filename)=>file.endsWith(filename));
|
|
319
|
-
});
|
|
320
|
-
if (prettierFiles.length === 0) return;
|
|
321
|
-
}
|
|
322
|
-
const arguments_ = [
|
|
323
|
-
...prettierFiles
|
|
324
|
-
];
|
|
325
|
-
if (existsSync(resolveFromProjectDirectory(".gitignore"))) {
|
|
326
|
-
arguments_.push("--ignore-path .gitignore");
|
|
327
|
-
}
|
|
328
|
-
arguments_.push("--write", "--ignore-unknown", "--no-error-on-unmatched-pattern");
|
|
332
|
+
const checkType = async ()=>{
|
|
329
333
|
try {
|
|
330
|
-
return await helpers.exec(
|
|
334
|
+
return await helpers.exec("pnpm --parallel exec tsc --noEmit");
|
|
331
335
|
} catch (error) {
|
|
332
|
-
throw createError("
|
|
336
|
+
throw createError("tsc", error);
|
|
333
337
|
}
|
|
334
338
|
};
|
|
335
339
|
|
|
336
|
-
const
|
|
340
|
+
const ONLY_VALUES = [
|
|
341
|
+
"commit",
|
|
342
|
+
"code",
|
|
343
|
+
"dependency",
|
|
344
|
+
"type"
|
|
345
|
+
];
|
|
346
|
+
const createCheckCommand = (program)=>{
|
|
337
347
|
program.command({
|
|
338
|
-
|
|
339
|
-
|
|
348
|
+
description: "Check code health (static analysis)",
|
|
349
|
+
name: "check"
|
|
350
|
+
}).option({
|
|
351
|
+
defaultValue: undefined,
|
|
352
|
+
description: `Filter the compliance check to run (accepted value: ${ONLY_VALUES.join(", ")})`,
|
|
353
|
+
key: "filter",
|
|
354
|
+
name: "filter"
|
|
340
355
|
}).task({
|
|
341
356
|
handler (_, argv) {
|
|
342
357
|
logCheckableFiles(argv.operands);
|
|
343
|
-
}
|
|
358
|
+
},
|
|
359
|
+
skip: ifFilterDefinedAndNotEqualTo("code")
|
|
344
360
|
}).task({
|
|
345
|
-
label: label$3("Prepare the project"),
|
|
346
361
|
async handler () {
|
|
347
362
|
await turbo("build", {
|
|
348
363
|
excludeExamples: true,
|
|
349
364
|
hasLiveOutput: false
|
|
350
365
|
});
|
|
366
|
+
},
|
|
367
|
+
label: label$4("Prepare the project"),
|
|
368
|
+
skip ({ filter }) {
|
|
369
|
+
return filter === "commit"; // No need to build if only commit is checked
|
|
351
370
|
}
|
|
352
371
|
}).task({
|
|
353
|
-
|
|
372
|
+
async handler () {
|
|
373
|
+
await checkDependency();
|
|
374
|
+
},
|
|
375
|
+
label: label$4("Check dependency compliance"),
|
|
376
|
+
skip: ifFilterDefinedAndNotEqualTo("dependency")
|
|
377
|
+
}).task({
|
|
354
378
|
async handler (_, argv) {
|
|
355
|
-
|
|
379
|
+
const filenames = argv.operands;
|
|
380
|
+
await checkCode(filenames);
|
|
381
|
+
},
|
|
382
|
+
label: label$4("Check code compliance"),
|
|
383
|
+
skip: ifFilterDefinedAndNotEqualTo("code")
|
|
384
|
+
}).task({
|
|
385
|
+
async handler () {
|
|
386
|
+
await checkType();
|
|
387
|
+
},
|
|
388
|
+
label: label$4("Check type compliance"),
|
|
389
|
+
skip (context, argv) {
|
|
390
|
+
return ifFilterDefinedAndNotEqualTo("type")(context) || !hasDependency("typescript") || /**
|
|
391
|
+
* For now, skip type-checking if some files are passed down.
|
|
392
|
+
* @see https://github.com/microsoft/TypeScript/issues/27379
|
|
393
|
+
*/ argv.operands.length > 0;
|
|
356
394
|
}
|
|
357
395
|
}).task({
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
396
|
+
async handler () {
|
|
397
|
+
await checkCommit();
|
|
398
|
+
},
|
|
399
|
+
label: label$4("Check commit compliance"),
|
|
400
|
+
skip (context) {
|
|
401
|
+
return context.filter !== "commit";
|
|
361
402
|
}
|
|
362
403
|
});
|
|
363
404
|
};
|
|
364
|
-
const label$
|
|
405
|
+
const label$4 = (message)=>`${message} 🧐`;
|
|
406
|
+
const ifFilterDefinedAndNotEqualTo = (filter)=>(context)=>{
|
|
407
|
+
return context.filter !== undefined && context.filter !== filter;
|
|
408
|
+
};
|
|
365
409
|
|
|
366
|
-
const
|
|
410
|
+
const createCleanCommand = (program)=>{
|
|
367
411
|
program.command({
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
}).task({
|
|
371
|
-
handler () {
|
|
372
|
-
botMessage({
|
|
373
|
-
title: `I'm Stack v${version}, your bot assistant`,
|
|
374
|
-
description: "I can guarantee you a project creation in under 1 minute 🚀",
|
|
375
|
-
type: "information"
|
|
376
|
-
});
|
|
377
|
-
}
|
|
412
|
+
description: "Clean the project",
|
|
413
|
+
name: "clean"
|
|
378
414
|
}).task({
|
|
379
|
-
label: label$2("Check pre-requisites"),
|
|
380
415
|
async handler () {
|
|
381
|
-
|
|
382
|
-
await
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
416
|
+
const cachePath = "node_modules/.cache";
|
|
417
|
+
const files = await retrieveIgnoredFiles();
|
|
418
|
+
if (isDirectoryExistAndNotEmpty(resolveFromProjectDirectory(cachePath))) {
|
|
419
|
+
files.push(cachePath);
|
|
420
|
+
}
|
|
421
|
+
return files;
|
|
422
|
+
},
|
|
423
|
+
key: "files",
|
|
424
|
+
label: label$3("Retrieve removable files")
|
|
425
|
+
}).task({
|
|
426
|
+
async handler ({ files }) {
|
|
427
|
+
if (files.length === 0) return;
|
|
428
|
+
await cleanFiles(files);
|
|
429
|
+
},
|
|
430
|
+
label ({ files }) {
|
|
431
|
+
return files.length > 0 ? label$3("Clean assets") : "Already clean ✨";
|
|
432
|
+
}
|
|
433
|
+
}).task({
|
|
434
|
+
handler ({ files }) {
|
|
435
|
+
helpers.message(files.join("\n "), {
|
|
436
|
+
label: "Removed assets",
|
|
437
|
+
lineBreak: {
|
|
438
|
+
end: false,
|
|
439
|
+
start: true
|
|
440
|
+
},
|
|
441
|
+
type: "information"
|
|
442
|
+
});
|
|
443
|
+
},
|
|
444
|
+
skip ({ files }) {
|
|
445
|
+
return files.length === 0;
|
|
446
|
+
}
|
|
447
|
+
});
|
|
448
|
+
};
|
|
449
|
+
const label$3 = (message)=>`${message} 🧹`;
|
|
450
|
+
const cleanFiles = async (files)=>{
|
|
451
|
+
return Promise.all(files.map(async (file)=>rm(file, {
|
|
452
|
+
force: true,
|
|
453
|
+
recursive: true
|
|
454
|
+
})));
|
|
455
|
+
};
|
|
456
|
+
const isDirectoryExistAndNotEmpty = (path)=>{
|
|
457
|
+
return existsSync(path) && readdirSync(path).length > 0;
|
|
458
|
+
};
|
|
459
|
+
const retrieveIgnoredFiles = async ()=>{
|
|
460
|
+
const rawFiles = await helpers.exec("git clean -fdXn");
|
|
461
|
+
return rawFiles.split(/\n|\r\n/).filter((cleanOutput)=>!PRESERVE_FILES.some((excludedFile)=>cleanOutput.includes(excludedFile))).map((cleanOutput)=>cleanOutput.split(" ").at(-1));
|
|
462
|
+
};
|
|
463
|
+
const PRESERVE_FILES = [
|
|
464
|
+
"node_modules"
|
|
465
|
+
];
|
|
466
|
+
|
|
467
|
+
var version = "2.33.0";
|
|
468
|
+
|
|
469
|
+
const createCreateCommand = (program)=>{
|
|
470
|
+
program.command({
|
|
471
|
+
description: "Scaffold a new project",
|
|
472
|
+
name: "create"
|
|
473
|
+
}).task({
|
|
474
|
+
handler () {
|
|
475
|
+
botMessage({
|
|
476
|
+
description: "I can guarantee you a project creation in under 1 minute 🚀",
|
|
477
|
+
title: `I'm Stack v${version}, your bot assistant`,
|
|
478
|
+
type: "information"
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
}).task({
|
|
482
|
+
async handler () {
|
|
483
|
+
// Check pnpm availability by verifying its version
|
|
484
|
+
await getNpmVersion();
|
|
485
|
+
},
|
|
486
|
+
label: label$2("Check pre-requisites")
|
|
487
|
+
}).input({
|
|
488
|
+
key: "inputName",
|
|
489
|
+
label: "What's your project name?",
|
|
387
490
|
type: "text"
|
|
388
491
|
}).input({
|
|
389
492
|
key: "inputDescription",
|
|
390
493
|
label: "How would you describe it?",
|
|
391
494
|
type: "text"
|
|
392
495
|
}).input({
|
|
496
|
+
defaultValue: "git@github.com:adbayb/xxx.git",
|
|
393
497
|
key: "inputUrl",
|
|
394
498
|
label: "Where will it be stored? (Git remote URL)",
|
|
395
|
-
defaultValue: "git@github.com:adbayb/xxx.git",
|
|
396
499
|
type: "text"
|
|
397
500
|
}).input({
|
|
501
|
+
defaultValue: "single-project",
|
|
398
502
|
key: "inputTemplate",
|
|
399
503
|
label: "Which template you would like to apply?",
|
|
400
|
-
defaultValue: "single-project",
|
|
401
504
|
options: [
|
|
402
505
|
"single-project",
|
|
403
506
|
"multi-projects"
|
|
404
507
|
],
|
|
405
508
|
type: "select"
|
|
406
509
|
}).task({
|
|
407
|
-
key: "data",
|
|
408
|
-
label: label$2("Check and format input"),
|
|
409
510
|
async handler ({ inputDescription, inputName, inputUrl }) {
|
|
410
511
|
if (!inputName) {
|
|
411
512
|
throw createError("stack create", "The project name is not optional. Make sure to provide a valid value (non-empty string).");
|
|
@@ -414,50 +515,51 @@ const createCreateCommand = (program)=>{
|
|
|
414
515
|
if (!repoOwner || !repoName) {
|
|
415
516
|
throw createError("git", "The owner and repository name can not be extracted. Please make sure to follow either `/^git@.*:(?<repoOwner>.*)/(?<repoName>.*).git$/` or `/^https?://.*/(?<repoOwner>.*)/(?<repoName>.*).git$/` pattern.");
|
|
416
517
|
}
|
|
417
|
-
const nodeVersion =
|
|
418
|
-
const npmVersion =
|
|
518
|
+
const nodeVersion = await request.get("https://resolve-node.vercel.app/lts", "text");
|
|
519
|
+
const { version: npmVersion } = await request.get("https://registry.npmjs.org/pnpm/latest", "json");
|
|
419
520
|
return {
|
|
420
521
|
licenseYear: new Date().getFullYear().toString(),
|
|
421
|
-
nodeVersion,
|
|
422
|
-
npmVersion,
|
|
522
|
+
nodeVersion: nodeVersion.replace("v", ""),
|
|
523
|
+
npmVersion: String(npmVersion),
|
|
423
524
|
projectDescription: inputDescription.charAt(0).toUpperCase() + inputDescription.slice(1),
|
|
424
525
|
projectName: inputName.toLowerCase(),
|
|
425
526
|
projectUrl: inputUrl,
|
|
426
527
|
repoId: `${repoOwner}/${repoName}`
|
|
427
528
|
};
|
|
428
|
-
}
|
|
429
|
-
}).task({
|
|
430
|
-
label ({ data }) {
|
|
431
|
-
return label$2(`Create \`${data.projectName}\` folder`);
|
|
432
529
|
},
|
|
530
|
+
key: "data",
|
|
531
|
+
label: label$2("Check and format input")
|
|
532
|
+
}).task({
|
|
433
533
|
async handler ({ data }) {
|
|
434
534
|
const projectPath = resolve(process.cwd(), data.projectName);
|
|
435
535
|
await mkdir(projectPath);
|
|
436
536
|
process.chdir(projectPath);
|
|
537
|
+
},
|
|
538
|
+
label ({ data }) {
|
|
539
|
+
return label$2(`Create \`${data.projectName}\` folder`);
|
|
437
540
|
}
|
|
438
541
|
}).task({
|
|
439
|
-
label: label$2("Initialize `git`"),
|
|
440
542
|
async handler ({ data }) {
|
|
441
543
|
await helpers.exec("git init");
|
|
442
544
|
await helpers.exec(`git remote add origin ${data.projectUrl}`);
|
|
443
|
-
}
|
|
545
|
+
},
|
|
546
|
+
label: label$2("Initialize `git`")
|
|
444
547
|
}).task({
|
|
445
|
-
label: label$2("Apply template"),
|
|
446
548
|
handler ({ data, inputTemplate }) {
|
|
447
549
|
applyTemplate(inputTemplate, data);
|
|
448
|
-
}
|
|
550
|
+
},
|
|
551
|
+
label: label$2("Apply template")
|
|
449
552
|
}).task({
|
|
450
|
-
label: label$2("Create a symlink to `README.md` file"),
|
|
451
553
|
async handler ({ data: { projectName }, inputTemplate }) {
|
|
452
554
|
await symlink(join(inputTemplate === "single-project" ? projectName : join("libraries", projectName), "README.md"), "./README.md");
|
|
453
|
-
}
|
|
555
|
+
},
|
|
556
|
+
label: label$2("Create a symlink to `README.md` file")
|
|
454
557
|
}).task({
|
|
455
|
-
label: label$2("Set up the package manager"),
|
|
456
558
|
async handler () {
|
|
457
559
|
await setPackageManager();
|
|
458
|
-
}
|
|
560
|
+
},
|
|
561
|
+
label: label$2("Set up the package manager")
|
|
459
562
|
}).task({
|
|
460
|
-
label: label$2("Install dependencies"),
|
|
461
563
|
async handler ({ data }) {
|
|
462
564
|
const localDevelopmentDependencies = [
|
|
463
565
|
"quickbundle",
|
|
@@ -473,23 +575,24 @@ const createCreateCommand = (program)=>{
|
|
|
473
575
|
} catch (error) {
|
|
474
576
|
throw createError("pnpm", error);
|
|
475
577
|
}
|
|
476
|
-
}
|
|
578
|
+
},
|
|
579
|
+
label: label$2("Install dependencies")
|
|
477
580
|
}).task({
|
|
478
|
-
label: label$2("Run `stack install`"),
|
|
479
581
|
async handler () {
|
|
480
582
|
await helpers.exec("stack install");
|
|
481
|
-
}
|
|
583
|
+
},
|
|
584
|
+
label: label$2("Run `stack install`")
|
|
482
585
|
}).task({
|
|
483
|
-
label: label$2("Commit"),
|
|
484
586
|
async handler () {
|
|
485
587
|
await helpers.exec("git add -A");
|
|
486
588
|
await helpers.exec('git commit -m "chore: initial commit"');
|
|
487
|
-
}
|
|
589
|
+
},
|
|
590
|
+
label: label$2("Commit")
|
|
488
591
|
}).task({
|
|
489
592
|
handler ({ data }) {
|
|
490
593
|
botMessage({
|
|
491
|
-
title: "The project was successfully created",
|
|
492
594
|
description: `Run \`cd ./${data.projectName}\` and Enjoy 🚀`,
|
|
595
|
+
title: "The project was successfully created",
|
|
493
596
|
type: "success"
|
|
494
597
|
});
|
|
495
598
|
}
|
|
@@ -533,281 +636,178 @@ const label$2 = (message)=>`${message} 🔨`;
|
|
|
533
636
|
});
|
|
534
637
|
};
|
|
535
638
|
|
|
536
|
-
const
|
|
537
|
-
|
|
538
|
-
name: "clean",
|
|
539
|
-
description: "Clean the project"
|
|
540
|
-
}).task({
|
|
541
|
-
key: "files",
|
|
542
|
-
label: label$1("Retrieve removable files"),
|
|
543
|
-
async handler () {
|
|
544
|
-
const cachePath = "node_modules/.cache";
|
|
545
|
-
const files = await retrieveIgnoredFiles();
|
|
546
|
-
if (isDirectoryExistAndNotEmpty(resolveFromProjectDirectory(cachePath))) {
|
|
547
|
-
files.push(cachePath);
|
|
548
|
-
}
|
|
549
|
-
return files;
|
|
550
|
-
}
|
|
551
|
-
}).task({
|
|
552
|
-
label ({ files }) {
|
|
553
|
-
return files.length > 0 ? label$1("Clean assets") : "Already clean ✨";
|
|
554
|
-
},
|
|
555
|
-
async handler ({ files }) {
|
|
556
|
-
if (files.length === 0) return;
|
|
557
|
-
await cleanFiles(files);
|
|
558
|
-
}
|
|
559
|
-
}).task({
|
|
560
|
-
handler ({ files }) {
|
|
561
|
-
helpers.message(files.join("\n "), {
|
|
562
|
-
label: "Removed assets",
|
|
563
|
-
lineBreak: {
|
|
564
|
-
end: false,
|
|
565
|
-
start: true
|
|
566
|
-
},
|
|
567
|
-
type: "information"
|
|
568
|
-
});
|
|
569
|
-
},
|
|
570
|
-
skip ({ files }) {
|
|
571
|
-
return files.length === 0;
|
|
572
|
-
}
|
|
573
|
-
});
|
|
574
|
-
};
|
|
575
|
-
const label$1 = (message)=>`${message} 🧹`;
|
|
576
|
-
const cleanFiles = async (files)=>{
|
|
577
|
-
return Promise.all(files.map(async (file)=>rm(file, {
|
|
578
|
-
force: true,
|
|
579
|
-
recursive: true
|
|
580
|
-
})));
|
|
581
|
-
};
|
|
582
|
-
const isDirectoryExistAndNotEmpty = (path)=>{
|
|
583
|
-
return existsSync(path) && readdirSync(path).length > 0;
|
|
584
|
-
};
|
|
585
|
-
const retrieveIgnoredFiles = async ()=>{
|
|
586
|
-
const rawFiles = await helpers.exec("git clean -fdXn");
|
|
587
|
-
return rawFiles.split(/\n|\r\n/).filter((cleanOutput)=>!PRESERVE_FILES.some((excludedFile)=>cleanOutput.includes(excludedFile))).map((cleanOutput)=>cleanOutput.split(" ").at(-1));
|
|
588
|
-
};
|
|
589
|
-
const PRESERVE_FILES = [
|
|
590
|
-
"node_modules"
|
|
639
|
+
const PRETTIER_IGNORE_FILES = [
|
|
640
|
+
"pnpm-lock.yaml"
|
|
591
641
|
];
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
};
|
|
600
|
-
|
|
601
|
-
const checkDependency = async ()=>{
|
|
602
|
-
const stdout = await helpers.exec("pnpm recursive ls --json");
|
|
603
|
-
const checkDependencyVersionMismatch = createPackagesVersionMismatchChecker();
|
|
604
|
-
const packages = JSON.parse(stdout).map((package_)=>{
|
|
605
|
-
const packagePath = join(package_.path, "package.json");
|
|
606
|
-
assert(package_.name, ()=>createPackageError(`\`${packagePath}\` must have a name field.`));
|
|
607
|
-
const packageContent = require$1(packagePath);
|
|
608
|
-
const peerDependencies = packageContent.peerDependencies ?? {};
|
|
609
|
-
const devDependencies = packageContent.devDependencies ?? {};
|
|
610
|
-
const dependencies = packageContent.dependencies ?? {};
|
|
611
|
-
return {
|
|
612
|
-
name: package_.name,
|
|
613
|
-
dependencies,
|
|
614
|
-
devDependencies,
|
|
615
|
-
peerDependencies
|
|
616
|
-
};
|
|
617
|
-
});
|
|
618
|
-
for (const package_ of packages){
|
|
619
|
-
// Check version mismatches to guarantee a single copy for a given package in the monorepo (use case: prevent singleton-like issues with React contexts)
|
|
620
|
-
checkDependencyVersionMismatch(package_);
|
|
621
|
-
// Check version range accordingly to our dependency guidelines (ie. dev dependencies must be pinned and dependencies must have caret)
|
|
622
|
-
checkDependencyVersionRange(package_);
|
|
623
|
-
}
|
|
624
|
-
};
|
|
625
|
-
const checkDependencyVersionRange = ({ name, dependencies, devDependencies, peerDependencies })=>{
|
|
626
|
-
for (const dependencyName of Object.keys(devDependencies)){
|
|
627
|
-
const version = devDependencies[dependencyName];
|
|
628
|
-
assertVersion(version, {
|
|
629
|
-
name: dependencyName,
|
|
630
|
-
consumedBy: name
|
|
631
|
-
});
|
|
632
|
-
if (version !== "workspace:*" && !isExcluded(version) && !/^\d/.test(version)) throw createPackageError(`As a dev dependency, \`${dependencyName}\` version must be fixed (or set as "workspace:*" for local packages) to reduce accidental breaking change risks due to an implicit semver upgrade.`, {
|
|
633
|
-
name: dependencyName,
|
|
634
|
-
consumedBy: name
|
|
635
|
-
});
|
|
636
|
-
}
|
|
637
|
-
for (const dependencyName of Object.keys(dependencies)){
|
|
638
|
-
const version = dependencies[dependencyName];
|
|
639
|
-
assertVersion(version, {
|
|
640
|
-
name: dependencyName,
|
|
641
|
-
consumedBy: name
|
|
642
|
-
});
|
|
643
|
-
if (version !== "workspace:^" && !hasCaret(version) && !isExcluded(version)) throw createPackageError(`As a dependency, \`${dependencyName}\` version must be prefixed with a caret (or set as "workspace:^" for local packages) to optimize the size (whether of installation or bundle output) on the consumer side.`, {
|
|
644
|
-
name: dependencyName,
|
|
645
|
-
consumedBy: name
|
|
642
|
+
const fixFormatting = async (files)=>{
|
|
643
|
+
let prettierFiles = [];
|
|
644
|
+
if (files.length === 0) {
|
|
645
|
+
prettierFiles.push(`"**/!(${PRETTIER_IGNORE_FILES.join("|")})"`);
|
|
646
|
+
} else {
|
|
647
|
+
prettierFiles = files.filter((file)=>{
|
|
648
|
+
return !PRETTIER_IGNORE_FILES.some((filename)=>file.endsWith(filename));
|
|
646
649
|
});
|
|
650
|
+
if (prettierFiles.length === 0) return;
|
|
647
651
|
}
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
});
|
|
654
|
-
if (!hasCaret(version) && !isExcluded(version)) /*
|
|
655
|
-
* Why disallowing workspace protocol as a version resolver?
|
|
656
|
-
* To reduce the update frequency needs consumer-side and guarantee on our side the minimum compatible version,
|
|
657
|
-
* the best practice should be to keeping an explicit number version which represents the lowest compatible version from an API perspective.
|
|
658
|
-
*/ throw createPackageError(`As a peer dependency, \`${dependencyName}\` version must be explicit (i.e. the "workspace:^" protocol a version resolver is not allowed) and prefixed with a caret to optimize the size (whether of installation or bundle output) on the consumer side.`, {
|
|
659
|
-
name: dependencyName,
|
|
660
|
-
consumedBy: name
|
|
661
|
-
});
|
|
652
|
+
const arguments_ = [
|
|
653
|
+
...prettierFiles
|
|
654
|
+
];
|
|
655
|
+
if (existsSync(resolveFromProjectDirectory(".gitignore"))) {
|
|
656
|
+
arguments_.push("--ignore-path .gitignore");
|
|
662
657
|
}
|
|
663
|
-
|
|
664
|
-
const createPackagesVersionMismatchChecker = ()=>{
|
|
665
|
-
const monorepoDependencies = new Map();
|
|
666
|
-
const monorepoDevelopmentDependencies = new Map();
|
|
667
|
-
const lint = (package_, type)=>{
|
|
668
|
-
const packageName = package_.name;
|
|
669
|
-
const isDevelopment = type === "development";
|
|
670
|
-
const store = isDevelopment ? monorepoDevelopmentDependencies : monorepoDependencies;
|
|
671
|
-
const dependencies = isDevelopment ? package_.devDependencies : package_.dependencies;
|
|
672
|
-
for (const dependencyName of Object.keys(dependencies)){
|
|
673
|
-
const depVersion = dependencies[dependencyName];
|
|
674
|
-
if (!depVersion) continue;
|
|
675
|
-
const storedVersion = store.get(dependencyName);
|
|
676
|
-
if (!storedVersion) {
|
|
677
|
-
store.set(dependencyName, depVersion);
|
|
678
|
-
continue;
|
|
679
|
-
}
|
|
680
|
-
const isSameMonorepoVersion = depVersion === storedVersion;
|
|
681
|
-
if (!isSameMonorepoVersion) {
|
|
682
|
-
throw createPackageError(`Mismatched versions: received version \`${depVersion}\` while others use \`${storedVersion}\`. To prevent issues with singleton-like code (React contexts, …), please make sure to update all packages to use the same \`${dependencyName}\` version (either \`${storedVersion}\` or \`${depVersion}\`).`, {
|
|
683
|
-
name: dependencyName,
|
|
684
|
-
consumedBy: packageName
|
|
685
|
-
});
|
|
686
|
-
}
|
|
687
|
-
}
|
|
688
|
-
};
|
|
689
|
-
return (package_)=>{
|
|
690
|
-
lint(package_, "development");
|
|
691
|
-
lint(package_, "production");
|
|
692
|
-
};
|
|
693
|
-
};
|
|
694
|
-
const createPackageError = (message, context)=>{
|
|
695
|
-
return createError("stack check", !context ? message : `\`${context.name}\` consumed by \`${context.consumedBy}\` doesn't conform to package guidelines.\n${message}`);
|
|
696
|
-
};
|
|
697
|
-
function assertVersion(version, { name, consumedBy }) {
|
|
698
|
-
assert(version, ()=>createPackageError(`\`${name}\` must have a valid version specified (current version equals to \`${String(version)}\`).`, {
|
|
699
|
-
name,
|
|
700
|
-
consumedBy
|
|
701
|
-
}));
|
|
702
|
-
}
|
|
703
|
-
const isExcluded = (version)=>{
|
|
704
|
-
const isPreReleaseVersion = /\d+\.\d+\.\d+-(alpha|beta|experimental|next|rc).*/.exec(version);
|
|
705
|
-
const isNpmProtocol = version.startsWith("npm:");
|
|
706
|
-
return isNpmProtocol || isPreReleaseVersion;
|
|
707
|
-
};
|
|
708
|
-
const hasCaret = (version)=>version.startsWith("^");
|
|
709
|
-
|
|
710
|
-
const checkCommit = async ()=>{
|
|
658
|
+
arguments_.push("--write", "--ignore-unknown", "--no-error-on-unmatched-pattern");
|
|
711
659
|
try {
|
|
712
|
-
return await helpers.exec(
|
|
660
|
+
return await helpers.exec(`prettier ${arguments_.join(" ")}`);
|
|
713
661
|
} catch (error) {
|
|
714
|
-
throw createError("
|
|
662
|
+
throw createError("prettier", error);
|
|
715
663
|
}
|
|
716
664
|
};
|
|
717
665
|
|
|
718
|
-
const
|
|
719
|
-
isFixMode:
|
|
666
|
+
const fixLinter = eslint({
|
|
667
|
+
isFixMode: true
|
|
720
668
|
});
|
|
721
669
|
|
|
722
|
-
const
|
|
723
|
-
"commit",
|
|
724
|
-
"code",
|
|
725
|
-
"dependency",
|
|
726
|
-
"type"
|
|
727
|
-
];
|
|
728
|
-
const createCheckCommand = (program)=>{
|
|
670
|
+
const createFixCommand = (program)=>{
|
|
729
671
|
program.command({
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
}).option({
|
|
733
|
-
key: "filter",
|
|
734
|
-
name: "filter",
|
|
735
|
-
description: `Filter the compliance check to run (accepted value: ${ONLY_VALUES.join(", ")})`,
|
|
736
|
-
defaultValue: undefined
|
|
672
|
+
description: "Fix auto-fixable issues",
|
|
673
|
+
name: "fix"
|
|
737
674
|
}).task({
|
|
738
675
|
handler (_, argv) {
|
|
739
676
|
logCheckableFiles(argv.operands);
|
|
740
|
-
}
|
|
741
|
-
skip: ifFilterDefinedAndNotEqualTo("code")
|
|
677
|
+
}
|
|
742
678
|
}).task({
|
|
743
|
-
label: label("Prepare the project"),
|
|
744
679
|
async handler () {
|
|
745
680
|
await turbo("build", {
|
|
746
681
|
excludeExamples: true,
|
|
747
682
|
hasLiveOutput: false
|
|
748
683
|
});
|
|
749
684
|
},
|
|
750
|
-
|
|
751
|
-
return filter === "commit"; // No need to build if only commit is checked
|
|
752
|
-
}
|
|
685
|
+
label: label$1("Prepare the project")
|
|
753
686
|
}).task({
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
await checkDependency();
|
|
687
|
+
async handler (_, argv) {
|
|
688
|
+
await fixLinter(argv.operands);
|
|
757
689
|
},
|
|
758
|
-
|
|
690
|
+
label: label$1("Fix linter issues")
|
|
759
691
|
}).task({
|
|
760
|
-
label: label("Check code compliance"),
|
|
761
692
|
async handler (_, argv) {
|
|
762
|
-
|
|
763
|
-
await checkCode(filenames);
|
|
693
|
+
await fixFormatting(argv.operands);
|
|
764
694
|
},
|
|
765
|
-
|
|
695
|
+
label: label$1("Fix formatting issues")
|
|
696
|
+
});
|
|
697
|
+
};
|
|
698
|
+
const label$1 = (message)=>`${message} 🚑`;
|
|
699
|
+
|
|
700
|
+
const createInstallCommand = (program)=>{
|
|
701
|
+
program.command({
|
|
702
|
+
description: "Install required setup",
|
|
703
|
+
name: "install"
|
|
766
704
|
}).task({
|
|
767
|
-
label: label("Check type compliance"),
|
|
768
705
|
async handler () {
|
|
769
|
-
|
|
706
|
+
const lineBreakMatcher = String.raw`\n|\r\n`;
|
|
707
|
+
const modifiedFilesCommand = `node -e 'console.log(require("child_process").execSync("git status --porcelain", {encoding: "utf8"}).split(/${lineBreakMatcher}/).filter(Boolean).map(item => item.split(" ").at(-1)).join(" "))'`;
|
|
708
|
+
await installGitHook("pre-commit", `${getStackCommand(`fix $(${modifiedFilesCommand})`, false)} && git add -A`);
|
|
770
709
|
},
|
|
771
|
-
|
|
772
|
-
return ifFilterDefinedAndNotEqualTo("type")(context) || !hasDependency("typescript") || /**
|
|
773
|
-
* For now, skip type-checking if some files are passed down.
|
|
774
|
-
* @see https://github.com/microsoft/TypeScript/issues/27379
|
|
775
|
-
*/ argv.operands.length > 0;
|
|
776
|
-
}
|
|
710
|
+
label: label("Install `git.pre-commit` hook")
|
|
777
711
|
}).task({
|
|
778
|
-
label: label("Check commit compliance"),
|
|
779
712
|
async handler () {
|
|
780
|
-
await
|
|
713
|
+
await installGitHook("commit-msg", getStackCommand("check --filter commit", false));
|
|
781
714
|
},
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
715
|
+
label: label("Install `git.commit-msg` hook")
|
|
716
|
+
}).task({
|
|
717
|
+
async handler () {
|
|
718
|
+
await checkPackageManager();
|
|
719
|
+
},
|
|
720
|
+
label: label("Check the package manager")
|
|
785
721
|
});
|
|
786
722
|
};
|
|
787
|
-
const label = (message)=>`${message}
|
|
788
|
-
const
|
|
789
|
-
|
|
723
|
+
const label = (message)=>`${message} 📲`;
|
|
724
|
+
const installGitHook = async (hook, content)=>{
|
|
725
|
+
const filename = resolveFromProjectDirectory(`.git/hooks/${hook}`);
|
|
726
|
+
await writeFile(filename, content);
|
|
727
|
+
return chmod(filename, "0755");
|
|
728
|
+
};
|
|
729
|
+
|
|
730
|
+
const createReleaseCommand = (program)=>{
|
|
731
|
+
program.command({
|
|
732
|
+
description: "Log, version, and publish package(s)",
|
|
733
|
+
name: "release"
|
|
734
|
+
}).option({
|
|
735
|
+
description: "Add a new changelog entry",
|
|
736
|
+
key: "log",
|
|
737
|
+
name: "log"
|
|
738
|
+
}).option({
|
|
739
|
+
description: "Bump the package(s) version",
|
|
740
|
+
key: "tag",
|
|
741
|
+
name: "tag"
|
|
742
|
+
}).option({
|
|
743
|
+
description: "Publish package(s) to the registry",
|
|
744
|
+
key: "publish",
|
|
745
|
+
name: "publish"
|
|
746
|
+
}).task({
|
|
747
|
+
async handler () {
|
|
748
|
+
helpers.message("New changelog entry\n");
|
|
749
|
+
await changeset("changeset");
|
|
750
|
+
},
|
|
751
|
+
skip: ifNotEqualTo("log")
|
|
752
|
+
}).task({
|
|
753
|
+
async handler () {
|
|
754
|
+
helpers.message("Bumping the package(s) version\n");
|
|
755
|
+
await changeset("changeset version && pnpm install --no-frozen-lockfile");
|
|
756
|
+
},
|
|
757
|
+
skip: ifNotEqualTo("tag")
|
|
758
|
+
}).task({
|
|
759
|
+
async handler () {
|
|
760
|
+
helpers.message("Publishing package(s) to the registry\n");
|
|
761
|
+
await changeset("stack build && pnpm changeset publish");
|
|
762
|
+
},
|
|
763
|
+
skip: ifNotEqualTo("publish")
|
|
764
|
+
});
|
|
765
|
+
};
|
|
766
|
+
const ifNotEqualTo = (validOption)=>(context)=>{
|
|
767
|
+
return !context[validOption];
|
|
790
768
|
};
|
|
791
769
|
|
|
792
|
-
const
|
|
770
|
+
const createStartCommand = (program)=>{
|
|
793
771
|
program.command({
|
|
794
|
-
|
|
795
|
-
|
|
772
|
+
description: "Start the project in production mode",
|
|
773
|
+
name: "start"
|
|
796
774
|
}).task({
|
|
797
775
|
async handler () {
|
|
798
|
-
await turbo("
|
|
776
|
+
await turbo("start");
|
|
777
|
+
}
|
|
778
|
+
});
|
|
779
|
+
};
|
|
780
|
+
|
|
781
|
+
const createTestCommand = (program)=>{
|
|
782
|
+
program.command({
|
|
783
|
+
description: "Test the code execution",
|
|
784
|
+
name: "test"
|
|
785
|
+
}).task({
|
|
786
|
+
async handler () {
|
|
787
|
+
await turbo("test");
|
|
788
|
+
}
|
|
789
|
+
});
|
|
790
|
+
};
|
|
791
|
+
|
|
792
|
+
const createWatchCommand = (program)=>{
|
|
793
|
+
program.command({
|
|
794
|
+
description: "Build and start the project in development mode",
|
|
795
|
+
name: "watch"
|
|
796
|
+
}).task({
|
|
797
|
+
async handler () {
|
|
798
|
+
await turbo("watch");
|
|
799
799
|
}
|
|
800
800
|
});
|
|
801
801
|
};
|
|
802
802
|
|
|
803
803
|
const createProgram = (...commandFactories)=>{
|
|
804
804
|
const program = termost({
|
|
805
|
-
name: "stack",
|
|
806
805
|
description: "Toolbox to easily scaffold and maintain a project",
|
|
806
|
+
name: "stack",
|
|
807
807
|
onException () {
|
|
808
808
|
botMessage({
|
|
809
|
-
title: "Oops, an error occurred",
|
|
810
809
|
description: "Keep calm and carry on with some coffee ☕️",
|
|
810
|
+
title: "Oops, an error occurred",
|
|
811
811
|
type: "error"
|
|
812
812
|
});
|
|
813
813
|
},
|