Skip to content

Commit

Permalink
fix env (#284)
Browse files Browse the repository at this point in the history
  • Loading branch information
ryanatkn authored Dec 10, 2021
1 parent 6d76c73 commit ab209ec
Show file tree
Hide file tree
Showing 14 changed files with 72 additions and 74 deletions.
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
> **Windows is not yet supported.** WSL works well enough
> _warning!_ You should not use Gro today
> _warning_: You should not use Gro today
> unless you're willing to take ownership of the code.
> For now, consider Gro's free software
> [free as in puppy](https://twitter.com/GalaxyKate/status/1371159136684105728),
Expand Down Expand Up @@ -40,13 +40,13 @@ It includes:
UIs along with Node servers, JS/TS/Svelte libraries, and other things
(see the [config docs](/src/docs/config.md), the [SvelteKit integration docs](/src/docs/sveltekit.md), and
[the default config](https://github.com/feltcoop/gro/blob/main/src/config/gro.config.default.ts))
- fully integrated [TypeScript](https://github.com/microsoft/typescript)
and [Svelte](https://github.com/sveltejs/svelte)
using [esbuild](https://github.com/evanw/esbuild) in dev mode for speed
- [configurable adapters](/src/docs/adapt.md) featuring e.g.
optional production bundling with [Rollup](https://github.com/rollup/rollup)
- [configurable plugins](/src/docs/plugin.md) to support SvelteKit,
auto-restarting API servers, and other external build processes
- fully integrated [TypeScript](https://github.com/microsoft/typescript)
and [Svelte](https://github.com/sveltejs/svelte)
using [esbuild](https://github.com/evanw/esbuild) in dev mode for speed
- [task runner](/src/docs/task.md) that uses the filesystem convention `*.task.ts`
- lots of [common default tasks](/src/docs/tasks.md) that projects can easily override and compose
- [testing](/src/docs/test.md) with [`uvu`](https://github.com/lukeed/uvu)
Expand Down
6 changes: 5 additions & 1 deletion changelog.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
# changelog

## 0.45.2
## 0.46.0

- **break**: change task `dev` property to `production`
([#284](https://github.com/feltcoop/gro/pull/284))
- fix `process.env.NODE_ENV` for production tasks
([#284](https://github.com/feltcoop/gro/pull/284))
- clean dist for production builds
([#285](https://github.com/feltcoop/gro/pull/285))

Expand Down
2 changes: 1 addition & 1 deletion src/build.task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export interface TaskEvents {

export const task: Task<TaskArgs, TaskEvents> = {
summary: 'build the project',
dev: false,
production: true,
run: async (ctx): Promise<void> => {
const {fs, dev, log, events, args} = ctx;

Expand Down
2 changes: 1 addition & 1 deletion src/cli/invoke.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const main = async () => {

// install sourcemaps for Gro development
if (process.env.NODE_ENV !== 'production') {
const sourcemapSupport = await import('source-map-support');
const sourcemapSupport = await import('source-map-support'); // is a peer dependency
sourcemapSupport.install({
handleUncaughtExceptions: false,
});
Expand Down
9 changes: 3 additions & 6 deletions src/config/gro.config.default.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,9 @@ This is the default config that's passed to `src/gro.config.ts`
if it exists in the current project, and if not, this is the final config.
It looks at the project and tries to do the right thing:
- if `src/routes` and `src/app.html`,
assumes a SvelteKit frontend
- if `src/lib/index.ts`,
assumes a Node library
- if `src/lib/server/server.ts`,
assumes a Node API server
- if `src/routes` and `src/app.html`, assumes a SvelteKit frontend
- if `src/lib/index.ts`, assumes a Node library
- if `src/lib/server/server.ts`, assumes a Node API server
*/

Expand Down
4 changes: 2 additions & 2 deletions src/deploy.task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ const EXCLUDED_BRANCHES = ['main', 'master'];

export const task: Task<TaskArgs> = {
summary: 'deploy to static hosting',
dev: false,
production: true,
run: async ({fs, invokeTask, args, log}): Promise<void> => {
const {dirname, branch, dry, clean: cleanAndExit, force} = args;

Expand Down Expand Up @@ -104,7 +104,7 @@ export const task: Task<TaskArgs> = {

try {
// Run the build.
await invokeTask('build', {...args, clean: false});
await invokeTask('build');

// After the build is ready, set the deployed directory, inferring as needed.
if (dirname !== undefined) {
Expand Down
14 changes: 2 additions & 12 deletions src/docs/sveltekit.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,11 @@ Gro has two important differences from SvelteKit:
- Gro ignores SvelteKit's [library packaging](https://kit.svelte.dev/docs#packaging)
capabilities for its own with [`gro publish`](./publish.md)

Beyond this, Gro mostly stays out of SvelteKit's way.
Beyond this, Gro mostly stays out of SvelteKit's way,
and the eventual goal is to defer to SvelteKit as much as possible.
You can still use `svelte-kit package`
but it doesn't currently integrate with Gro's other systems, checks, and conventions.

Gro's supplemental role is still a work in progress --
in the current implementation, user projects manage their own SvelteKit dependencies,
and commands like `gro dev` and `gro build` automatically detect SvelteKit projects.

Gro supports SvelteKit+Vite along with its own non-conflicting system for frontend development,
but Gro's frontend system does not currently support HMR, code splitting, routing,
and many other things provided by SvelteKit+Vite.
It's appropriate only for a small number of specific usecases
and can be wholly replaced with SvelteKit.
SvelteKit should be preferred by users today,
and Gro's frontend conventions are currently undocumented.
If you're curious, examples of Gro's frontend functionality include Gro's
[`src/client`](/src/client) and
[`ryanatkn/mirror-twin-gro`](https://github.com/ryanatkn/mirror-twin-gro).
6 changes: 3 additions & 3 deletions src/docs/task.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@

## what

A Gro task is just a function with some optional metadata.
A Gro `Task` is just an object with a `run` function and some optional metadata.
Gro prefers conventions and code over configuration,
and its task runner leverages the filesystem as the API
and defers composition to the user in regular TypeScript modules.

- Gro automatically discovers [all `*.task.ts` files](../docs/tasks.md)
in your source directory, so creating a new task is as simple as creating a new file -
no configuration or scaffolding commands needed!
no configuration needed
- task definitions are just objects with an async `run` function and some optional properties,
so composing tasks is explicit in your code, just like any other module
(but there's also the helper `invokeTask`: see more below)
Expand Down Expand Up @@ -294,7 +294,7 @@ export const task: Task = {
import type {Task} from '@feltcoop/gro';

export const task: Task = {
dev: false, // tell the task runner to set `dev` to false, updating `process.env.NODE_ENV`
production: true, // task runner will spawn a new process if `process.env.NODE_ENV` isn't 'production'
run: async ({dev, invokeTask}) => {
// `dev` is `false` because it's defined two lines up in the task definition,
// unless an ancestor task called `invokeTask` with a `true` value, like this:
Expand Down
3 changes: 3 additions & 0 deletions src/fs/mime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ export const removeMimeTypeExtension = (extension: FileExtension): boolean => {
['application/schema+json', ['json']],
['application/schema-instance+json', ['json']],
['application/ld+json', ['jsonld']],
// next 2 are equivalent in ActivityStreams: https://www.w3.org/TR/activitystreams-core/#media-type
['application/activity+json', ['json']],
['application/ld+json; profile="https://www.w3.org/ns/activitystreams"', ['json']],
['application/xml', ['xml']],
['application/xhtml+xml', ['xhtml']],
['application/pdf', ['pdf']],
Expand Down
6 changes: 3 additions & 3 deletions src/publish.task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export interface TaskArgs {

export const task: Task<TaskArgs> = {
summary: 'bump version, publish to npm, and sync to GitHub',
dev: false,
production: true,
run: async ({fs, args, log, invokeTask, dev}): Promise<void> => {
const {branch = GIT_DEPLOY_BRANCH, dry = false, restricted = false} = args;
if (dry) {
Expand Down Expand Up @@ -57,7 +57,7 @@ export const task: Task<TaskArgs> = {

// Check in dev mode before proceeding:
await buildSource(fs, config, true, log);
await invokeTask('check', {...args, _: []}, undefined, true);
await invokeTask('check', {...args, _: []}, undefined);

// Bump the version so the package.json is updated before building:
if (!dry) {
Expand All @@ -68,7 +68,7 @@ export const task: Task<TaskArgs> = {
}

// Build to create the final artifacts:
await invokeTask('build', {...args, clean: false});
await invokeTask('build');

if (dry) {
log.info({versionIncrement, publish: config.publish, branch});
Expand Down
44 changes: 26 additions & 18 deletions src/task/invokeTask.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import {EventEmitter} from 'events';
import {createStopwatch, Timings} from '@feltcoop/felt/util/timings.js';
import {printMs, printTimings} from '@feltcoop/felt/util/print.js';
import {plural} from '@feltcoop/felt/util/string.js';
import {spawn} from '@feltcoop/felt/util/process.js';

import type {Args} from 'src/task/task.js';
import {serializeArgs} from '../task/task.js';
import {runTask} from './runTask.js';
import {resolveRawInputPath, getPossibleSourceIds} from '../fs/inputPath.js';
import {TASK_FILE_SUFFIX, isTaskPath, toTaskName} from './task.js';
Expand Down Expand Up @@ -52,11 +54,7 @@ export const invokeTask = async (
taskName: string,
args: Args,
events = new EventEmitter(),
dev?: boolean,
): Promise<void> => {
// TODO not sure about this -- the idea is that imported modules need the updated `NODE_ENV`
process.env['NODE_ENV'] = dev || dev === undefined ? 'development' : 'production';

const log = new SystemLogger(printLogLabel(taskName || 'gro'));

// Check if the caller just wants to see the version.
Expand Down Expand Up @@ -93,13 +91,6 @@ export const invokeTask = async (
if (await shouldBuildProject(fs, pathData.id)) {
// Import these lazily to avoid importing their comparatively heavy transitive dependencies
// every time a task is invoked.
if (dev !== undefined) {
// TODO include this?
throw Error(
'Invalid `invokeTask` call with a `dev` argument and unbuilt project.' +
' This probably means Gro or a task made something weird happen.',
);
}
log.info('building project to run task');
const timingToLoadConfig = timings.start('load config');
// TODO probably do this as a separate process
Expand Down Expand Up @@ -129,14 +120,31 @@ export const invokeTask = async (
`→ ${cyan(task.name)} ${(task.mod.task.summary && gray(task.mod.task.summary)) || ''}`,
);
const timingToRunTask = timings.start('run task');
const result = await runTask(fs, task, args, events, invokeTask, dev);
timingToRunTask();
if (result.ok) {
log.info(`✓ ${cyan(task.name)}`);
const dev = process.env.NODE_ENV !== 'production'; // TODO should this use `fromEnv`? '$app/env'?
if (dev && task.mod.task.production) {
const result = await spawn('npx', ['gro', taskName, ...serializeArgs(args)], {
env: {...process.env, NODE_ENV: 'production'},
});
timingToRunTask();
if (result.ok) {
log.info(`✓ ${cyan(task.name)}`);
} else {
log.info(`${red('🞩')} ${cyan(task.name)}`);
logErrorReasons(log, [
`spawned task exited with code ${result.code}: ${result.signal}`,
]);
throw Error('Spawned task failed');
}
} else {
log.info(`${red('🞩')} ${cyan(task.name)}`);
logErrorReasons(log, [result.reason]);
throw result.error;
const result = await runTask(fs, task, args, events, invokeTask);
timingToRunTask();
if (result.ok) {
log.info(`✓ ${cyan(task.name)}`);
} else {
log.info(`${red('🞩')} ${cyan(task.name)}`);
logErrorReasons(log, [result.reason]);
throw result.error;
}
}
} else {
logErrorReasons(log, loadModulesResult.reasons);
Expand Down
3 changes: 0 additions & 3 deletions src/task/runTask.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ test__runTask('passes args and returns output', async () => {
args,
new EventEmitter(),
async () => {},
true,
);
assert.ok(result.ok);
assert.is(result.output, args);
Expand Down Expand Up @@ -54,7 +53,6 @@ test__runTask('invokes a sub task', async () => {
invokedTaskName = invokingTaskName;
invokedArgs = invokingArgs;
},
true,
);
assert.ok(result.ok);
assert.is(invokedTaskName, 'bar/testTask');
Expand All @@ -81,7 +79,6 @@ test__runTask('failing task', async () => {
{_: []},
new EventEmitter(),
async () => {},
true,
);
assert.not.ok(result.ok);
assert.ok(result.reason);
Expand Down
22 changes: 4 additions & 18 deletions src/task/runTask.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,10 @@ export const runTask = async (
args: Args,
events: EventEmitter,
invokeTask: typeof InvokeTaskFunction,
dev: boolean | undefined, // `undefined` on first task invocation, so it infers from the first task
): Promise<RunTaskResult> => {
const {task} = taskMeta.mod;
if (dev === undefined) {
if (task.dev !== undefined) {
dev = task.dev;
} else {
dev = process.env.NODE_ENV !== 'production';
}
}
// TODO the `=== false` is needed because we're not normalizing tasks, but we probably should,
// but not in this function, when the task is loaded
if (dev && task.dev === false) {
const dev = process.env.NODE_ENV !== 'production'; // TODO should this use `fromEnv`? '$app/env'?
if (dev && task.production) {
throw new TaskError(`The task "${taskMeta.name}" cannot be run in development`);
}
let output: unknown;
Expand All @@ -48,13 +39,8 @@ export const runTask = async (
args,
events,
log: new SystemLogger(printLogLabel(taskMeta.name)),
invokeTask: (
invokedTaskName,
invokedArgs = args,
invokedEvents = events,
invokedDev = dev,
invokedFs = fs,
) => invokeTask(invokedFs, invokedTaskName, invokedArgs, invokedEvents, invokedDev),
invokeTask: (invokedTaskName, invokedArgs = args, invokedEvents = events, invokedFs = fs) =>
invokeTask(invokedFs, invokedTaskName, invokedArgs, invokedEvents),
});
} catch (err) {
return {
Expand Down
17 changes: 15 additions & 2 deletions src/task/task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import type {Filesystem} from 'src/fs/filesystem.js';
export interface Task<TArgs = Args, TEvents = {}> {
run: (ctx: TaskContext<TArgs, TEvents>) => Promise<unknown>; // TODO return value (make generic, forward it..how?)
summary?: string;
dev?: boolean;
production?: boolean;
}

export interface TaskContext<TArgs = {}, TEvents = {}> {
Expand All @@ -21,7 +21,6 @@ export interface TaskContext<TArgs = {}, TEvents = {}> {
taskName: string,
args?: Args,
events?: StrictEventEmitter<EventEmitter, TEvents>,
dev?: boolean,
fs?: Filesystem,
) => Promise<void>;
}
Expand Down Expand Up @@ -51,3 +50,17 @@ export interface Args {
_: string[];
[key: string]: unknown; // can assign anything to `args` in tasks
}

export const serializeArgs = (args: Args): string[] => {
const result: string[] = [];
let _: string[] | null = null;
for (const [key, value] of Object.entries(args)) {
if (key === '_') {
_ = (value as any[]).map((v) => v.toString());
} else {
result.push(`--${key}`);
result.push((value as any).toString());
}
}
return _ ? [...result, ..._] : result;
};

0 comments on commit ab209ec

Please sign in to comment.